blendjob 0.1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
blendjob-0.1.5/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OmooLab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.4
2
+ Name: blendjob
3
+ Version: 0.1.5
4
+ Summary: Reusable local Job Server runtime for Blender add-ons
5
+ Keywords: blender,addon,job,server,runtime
6
+ Author: OmooLab
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Dist: fastapi==0.139.2 ; extra == 'server'
17
+ Requires-Dist: uvicorn==0.51.0 ; extra == 'server'
18
+ Requires-Python: >=3.10
19
+ Project-URL: Documentation, https://docs.omoolab.xyz/blendjob/
20
+ Project-URL: Issues, https://github.com/OmooLab/BlendJob/issues
21
+ Project-URL: Repository, https://github.com/OmooLab/BlendJob
22
+ Provides-Extra: server
23
+ Description-Content-Type: text/markdown
24
+
25
+ # BlendJob
26
+
27
+ BlendJob 是面向 Blender Add-on 的本地 Job Server 运行时。它把 Blender Operator、独立 Python Environment、HTTP Job Server、FIFO Queue、进度、取消和 Resource 生命周期组合成一套可复用 API。
28
+
29
+ BlendJob 适合需要在 Blender 界面之外运行 AI 推理、模型下载、媒体处理或其它重型 Python 任务的 Add-on。Blender 主进程只保留轻量 Client 和 UI 逻辑,业务依赖安装在独立 Environment 中。
30
+
31
+ ## 要求
32
+
33
+ - Python 3.10 或更高版本
34
+ - 支持 Python 3.10+ 的 Blender 版本
35
+ - Windows x64、macOS arm64 或 Linux x64;内建 uv 安装器目前只提供这些平台
36
+
37
+ ## 安装
38
+
39
+ 普通 Python 项目可以使用 pip 或 uv:
40
+
41
+ ```bash
42
+ python -m pip install blendjob
43
+ ```
44
+
45
+ ```bash
46
+ uv add blendjob
47
+ ```
48
+
49
+ 默认安装保持零依赖,适合 Blender 主进程。需要在普通 Python 环境中直接创建 FastAPI App 或运行 Runner 时,安装 `server` extra:
50
+
51
+ ```bash
52
+ python -m pip install "blendjob[server]"
53
+ ```
54
+
55
+ Blender Extension 需要把 BlendJob wheel 随扩展分发,并在 `blender_manifest.toml` 中声明:
56
+
57
+ ```toml
58
+ wheels = ["./wheels/blendjob-0.1.5-py3-none-any.whl"]
59
+ ```
60
+
61
+ 同一个 wheel 还需要加入 Runtime 的独立 Environment,使 Server Python 可以执行 `python -m blendjob.runner`:
62
+
63
+ ```python
64
+ from pathlib import Path
65
+
66
+
67
+ blendjob_wheel = Path(__file__).parent / "wheels" / "blendjob-0.1.5-py3-none-any.whl"
68
+
69
+ environment = {
70
+ "python": "3.10",
71
+ "packages": [str(blendjob_wheel)],
72
+ "platform_packages": {},
73
+ }
74
+ ```
75
+
76
+ ## 最小示例
77
+
78
+ Server package 导出一个 `JobServer`:
79
+
80
+ ```python
81
+ from blendjob import JobServer
82
+
83
+
84
+ server = JobServer("Example Job Server")
85
+
86
+
87
+ @server.job("double")
88
+ def double(context, parameters):
89
+ context.progress(0.5, "Calculating")
90
+ return {"value": parameters["value"] * 2}
91
+ ```
92
+
93
+ Blender 侧创建一个 Runtime,并让业务 Operator 继承绑定后的基类:
94
+
95
+ ```python
96
+ from pathlib import Path
97
+
98
+ from blendjob import JobRuntime
99
+
100
+
101
+ runtime = JobRuntime(
102
+ "server:server",
103
+ entrypoint_root=Path(__file__).parent,
104
+ storage_root=Path.home() / ".example-addon",
105
+ environment=environment,
106
+ namespace="example_addon",
107
+ )
108
+
109
+ JobOperatorBase = runtime.JobOperatorBase
110
+ ```
111
+
112
+ 完整的 Runtime、Operator、Server Handler、Resource 和存储结构说明见 [开发者指南](https://docs.omoolab.xyz/blendjob/)。
113
+
114
+ ## 开发
115
+
116
+ ```bash
117
+ uv sync
118
+ uv run pytest
119
+ uv build
120
+ uv run mkdocs build --strict
121
+ ```
122
+
123
+ 版本化文档使用 Mike:
124
+
125
+ ```bash
126
+ uv run mike deploy --update-aliases 0.1 latest
127
+ uv run mike set-default latest
128
+ ```
129
+
130
+ ## License
131
+
132
+ BlendJob 使用 [MIT License](https://github.com/OmooLab/BlendJob/blob/main/LICENSE)。
@@ -0,0 +1,108 @@
1
+ # BlendJob
2
+
3
+ BlendJob 是面向 Blender Add-on 的本地 Job Server 运行时。它把 Blender Operator、独立 Python Environment、HTTP Job Server、FIFO Queue、进度、取消和 Resource 生命周期组合成一套可复用 API。
4
+
5
+ BlendJob 适合需要在 Blender 界面之外运行 AI 推理、模型下载、媒体处理或其它重型 Python 任务的 Add-on。Blender 主进程只保留轻量 Client 和 UI 逻辑,业务依赖安装在独立 Environment 中。
6
+
7
+ ## 要求
8
+
9
+ - Python 3.10 或更高版本
10
+ - 支持 Python 3.10+ 的 Blender 版本
11
+ - Windows x64、macOS arm64 或 Linux x64;内建 uv 安装器目前只提供这些平台
12
+
13
+ ## 安装
14
+
15
+ 普通 Python 项目可以使用 pip 或 uv:
16
+
17
+ ```bash
18
+ python -m pip install blendjob
19
+ ```
20
+
21
+ ```bash
22
+ uv add blendjob
23
+ ```
24
+
25
+ 默认安装保持零依赖,适合 Blender 主进程。需要在普通 Python 环境中直接创建 FastAPI App 或运行 Runner 时,安装 `server` extra:
26
+
27
+ ```bash
28
+ python -m pip install "blendjob[server]"
29
+ ```
30
+
31
+ Blender Extension 需要把 BlendJob wheel 随扩展分发,并在 `blender_manifest.toml` 中声明:
32
+
33
+ ```toml
34
+ wheels = ["./wheels/blendjob-0.1.5-py3-none-any.whl"]
35
+ ```
36
+
37
+ 同一个 wheel 还需要加入 Runtime 的独立 Environment,使 Server Python 可以执行 `python -m blendjob.runner`:
38
+
39
+ ```python
40
+ from pathlib import Path
41
+
42
+
43
+ blendjob_wheel = Path(__file__).parent / "wheels" / "blendjob-0.1.5-py3-none-any.whl"
44
+
45
+ environment = {
46
+ "python": "3.10",
47
+ "packages": [str(blendjob_wheel)],
48
+ "platform_packages": {},
49
+ }
50
+ ```
51
+
52
+ ## 最小示例
53
+
54
+ Server package 导出一个 `JobServer`:
55
+
56
+ ```python
57
+ from blendjob import JobServer
58
+
59
+
60
+ server = JobServer("Example Job Server")
61
+
62
+
63
+ @server.job("double")
64
+ def double(context, parameters):
65
+ context.progress(0.5, "Calculating")
66
+ return {"value": parameters["value"] * 2}
67
+ ```
68
+
69
+ Blender 侧创建一个 Runtime,并让业务 Operator 继承绑定后的基类:
70
+
71
+ ```python
72
+ from pathlib import Path
73
+
74
+ from blendjob import JobRuntime
75
+
76
+
77
+ runtime = JobRuntime(
78
+ "server:server",
79
+ entrypoint_root=Path(__file__).parent,
80
+ storage_root=Path.home() / ".example-addon",
81
+ environment=environment,
82
+ namespace="example_addon",
83
+ )
84
+
85
+ JobOperatorBase = runtime.JobOperatorBase
86
+ ```
87
+
88
+ 完整的 Runtime、Operator、Server Handler、Resource 和存储结构说明见 [开发者指南](https://docs.omoolab.xyz/blendjob/)。
89
+
90
+ ## 开发
91
+
92
+ ```bash
93
+ uv sync
94
+ uv run pytest
95
+ uv build
96
+ uv run mkdocs build --strict
97
+ ```
98
+
99
+ 版本化文档使用 Mike:
100
+
101
+ ```bash
102
+ uv run mike deploy --update-aliases 0.1 latest
103
+ uv run mike set-default latest
104
+ ```
105
+
106
+ ## License
107
+
108
+ BlendJob 使用 [MIT License](https://github.com/OmooLab/BlendJob/blob/main/LICENSE)。
@@ -0,0 +1,53 @@
1
+ [project]
2
+ name = "blendjob"
3
+ version = "0.1.5"
4
+ description = "Reusable local Job Server runtime for Blender add-ons"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ keywords = [
10
+ "blender",
11
+ "addon",
12
+ "job",
13
+ "server",
14
+ "runtime",
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ ]
25
+ dependencies = []
26
+
27
+ [[project.authors]]
28
+ name = "OmooLab"
29
+
30
+ [project.urls]
31
+ Documentation = "https://docs.omoolab.xyz/blendjob/"
32
+ Issues = "https://github.com/OmooLab/BlendJob/issues"
33
+ Repository = "https://github.com/OmooLab/BlendJob"
34
+
35
+ [project.optional-dependencies]
36
+ server = [
37
+ "fastapi==0.139.2",
38
+ "uvicorn==0.51.0",
39
+ ]
40
+
41
+ [dependency-groups]
42
+ dev = [
43
+ "fastapi==0.139.2",
44
+ "mike>=2.1,<3",
45
+ "mkdocs-material>=9.6,<10",
46
+ "mkdocstrings[python]>=0.30,<1",
47
+ "pytest>=8.4,<9",
48
+ "uvicorn==0.51.0",
49
+ ]
50
+
51
+ [build-system]
52
+ requires = ["uv_build>=0.11.3,<0.13.0"]
53
+ build-backend = "uv_build"
@@ -0,0 +1,45 @@
1
+ [project]
2
+ name = "blendjob"
3
+ version = "0.1.5"
4
+ description = "Reusable local Job Server runtime for Blender add-ons"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [{ name = "OmooLab" }]
10
+ keywords = ["blender", "addon", "job", "server", "runtime"]
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Intended Audience :: Developers",
14
+ "Operating System :: OS Independent",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ ]
20
+ dependencies = []
21
+
22
+ [project.urls]
23
+ Documentation = "https://docs.omoolab.xyz/blendjob/"
24
+ Issues = "https://github.com/OmooLab/BlendJob/issues"
25
+ Repository = "https://github.com/OmooLab/BlendJob"
26
+
27
+ [project.optional-dependencies]
28
+ server = [
29
+ "fastapi==0.139.2",
30
+ "uvicorn==0.51.0",
31
+ ]
32
+
33
+ [dependency-groups]
34
+ dev = [
35
+ "fastapi==0.139.2",
36
+ "mike>=2.1,<3",
37
+ "mkdocs-material>=9.6,<10",
38
+ "mkdocstrings[python]>=0.30,<1",
39
+ "pytest>=8.4,<9",
40
+ "uvicorn==0.51.0",
41
+ ]
42
+
43
+ [build-system]
44
+ requires = ["uv_build>=0.11.3,<0.13.0"]
45
+ build-backend = "uv_build"
@@ -0,0 +1,21 @@
1
+ """Reusable local HTTP Job Server for Blender add-ons."""
2
+
3
+ from .client import JobClient, JobResult
4
+ from .controller import ServerConnection, ServerController
5
+ from .operator import JobOperatorBase, JobOperatorState
6
+ from .runtime import JobRuntime
7
+ from .server import JobCancelled, JobContext, JobServer
8
+
9
+
10
+ __all__ = (
11
+ "JobCancelled",
12
+ "JobClient",
13
+ "JobContext",
14
+ "JobResult",
15
+ "JobServer",
16
+ "JobRuntime",
17
+ "JobOperatorBase",
18
+ "JobOperatorState",
19
+ "ServerConnection",
20
+ "ServerController",
21
+ )
@@ -0,0 +1,101 @@
1
+ import json
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+ from urllib.error import HTTPError, URLError
5
+ from urllib.request import ProxyHandler, Request, build_opener
6
+
7
+
8
+ class JobClient:
9
+ """Dependency-free HTTP client for a local :class:`JobServer`."""
10
+
11
+ def __init__(self, host, port, *, timeout=2.0):
12
+ self.base_url = f"http://{host}:{int(port)}"
13
+ self.timeout = timeout
14
+ self.opener = build_opener(ProxyHandler({}))
15
+
16
+ def request(self, method, path, payload=None, timeout=None):
17
+ data = None
18
+ headers = {}
19
+ if payload is not None:
20
+ data = json.dumps(payload).encode("utf-8")
21
+ headers["Content-Type"] = "application/json"
22
+ request = Request(
23
+ f"{self.base_url}{path}",
24
+ data=data,
25
+ headers=headers,
26
+ method=method,
27
+ )
28
+ try:
29
+ with self.opener.open(
30
+ request,
31
+ timeout=self.timeout if timeout is None else timeout,
32
+ ) as response:
33
+ return json.loads(response.read().decode("utf-8"))
34
+ except HTTPError as error:
35
+ try:
36
+ detail = json.loads(error.read().decode("utf-8")).get("detail")
37
+ except (json.JSONDecodeError, UnicodeDecodeError):
38
+ detail = None
39
+ raise RuntimeError(detail or f"Job Server returned HTTP {error.code}")
40
+ except (OSError, URLError) as error:
41
+ raise RuntimeError(f"Unable to reach Job Server: {error}")
42
+
43
+ def health(self):
44
+ return self.request("GET", "/health")
45
+
46
+ def submit(self, job_type, parameters):
47
+ return self.request(
48
+ "POST",
49
+ "/jobs",
50
+ {"job_type": str(job_type), "parameters": dict(parameters)},
51
+ )
52
+
53
+ def status(self, job_id):
54
+ return self.request("GET", f"/jobs/{job_id}")
55
+
56
+ def cancel(self, job_id):
57
+ return self.request("DELETE", f"/jobs/{job_id}")
58
+
59
+ def resources(self):
60
+ return self.request("GET", "/resources")
61
+
62
+ def resource(self, name):
63
+ return self.request("GET", f"/resources/{name}")
64
+
65
+ def clear_resource(self, name):
66
+ return self.request("POST", f"/resources/{name}/clear")
67
+
68
+ def shutdown(self):
69
+ return self.request("POST", "/shutdown")
70
+ @dataclass(frozen=True)
71
+ class JobResult:
72
+ """Successful Job value and files returned to a Blender Operator."""
73
+
74
+ job_id: str
75
+ directory: Path
76
+ value: object
77
+ status: dict
78
+
79
+ @classmethod
80
+ def from_status(cls, status):
81
+ return cls(
82
+ job_id=str(status.get("job_id", "")),
83
+ directory=Path(status["directory"]),
84
+ value=status.get("result"),
85
+ status=dict(status),
86
+ )
87
+
88
+ def file(self, name):
89
+ if not isinstance(self.value, dict):
90
+ raise RuntimeError("Job result does not contain named files")
91
+ try:
92
+ relative = self.value[name]
93
+ except KeyError:
94
+ raise RuntimeError(f"Job result does not contain file: {name}") from None
95
+ root = self.directory.resolve()
96
+ path = (root / str(relative)).resolve()
97
+ if path != root and root not in path.parents:
98
+ raise RuntimeError(f"Job result file escapes its directory: {relative}")
99
+ if not path.is_file():
100
+ raise RuntimeError(f"Job result file does not exist: {path}")
101
+ return path