pulze-renderflow 1.2.7__tar.gz → 2.0.7__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.
@@ -1,24 +1,25 @@
1
- __pycache__/
2
- *.py[cod]
3
- *$py.class
4
- *.so
5
- .Python
6
- build/
7
- develop-eggs/
8
- dist/
9
- downloads/
10
- eggs/
11
- .eggs/
12
- lib/
13
- lib64/
14
- parts/
15
- sdist/
16
- var/
17
- wheels/
18
- *.egg-info/
19
- .installed.cfg
20
- *.egg
21
- .env
22
- .venv
23
- venv/
24
- ENV/
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ .Python
6
+ build/
7
+ develop-eggs/
8
+ dist/
9
+ downloads/
10
+ eggs/
11
+ .eggs/
12
+ lib/
13
+ lib64/
14
+ parts/
15
+ sdist/
16
+ var/
17
+ wheels/
18
+ *.egg-info/
19
+ .installed.cfg
20
+ *.egg
21
+ .env
22
+ .venv
23
+ venv/
24
+ ENV/
25
+
@@ -0,0 +1,30 @@
1
+ # packages/sdks/python — CLAUDE.md
2
+
3
+ The published Python SDK. **Standard library only — no runtime dependencies, ever.** The client
4
+ imports inside a DCC's bundled Python with nothing installed; adding a dependency breaks every
5
+ plugin that embeds it.
6
+
7
+ ## How it works
8
+
9
+ - `renderflow/wire.py` binds operations off the OpenAPI document **at runtime** — there is no
10
+ generated per-operation code. `farm.jobs.get_jobs()` exists because the document says so.
11
+ - `renderflow/openapi.json` is a **committed copy** of the service's `openapi.public.json`;
12
+ `sdk-surface.test.ts` (service) holds it byte-equal. A route change that skips regeneration fails
13
+ the suite.
14
+ - `renderflow/models.pyi` = TypedDicts for editors/mypy, generated by `datamodel-code-generator`
15
+ (dev-only tool); `models.py` is a tiny runtime shim. A stub file never executes, so it may use
16
+ 3.11 typing on a 3.10 interpreter.
17
+
18
+ ## Commands
19
+
20
+ ```bash
21
+ python scripts/generate.py # refresh openapi.json + models.pyi after a public route change
22
+ python test/test_runtime.py # stdlib unittest, no pytest
23
+ ```
24
+
25
+ Both generated files are **committed** — review them like code. Publishing is manual
26
+ (`python -m build` + `twine upload`), on its own cadence; this package's version field is real.
27
+
28
+ `examples/` holds the example **custom plugins** (staged into installs as `sdk/examples`,
29
+ excluded from the wheel — `packages = ["renderflow"]`). If a work-list marker or progress format
30
+ changes, `job-progress` in the service reads recorded runtime output — keep them in step.
@@ -1,5 +1,5 @@
1
- include README.md
2
- include LICENSE
3
- recursive-include renderflow *.py
4
- recursive-exclude scripts *
5
- recursive-exclude examples *
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include renderflow *.py *.pyi openapi.json
4
+ recursive-exclude scripts *
5
+ recursive-exclude examples *
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.5
2
+ Name: pulze-renderflow
3
+ Version: 2.0.7
4
+ Summary: Official RenderFlow API client for Python
5
+ Project-URL: Homepage, https://pulze.io/products/renderflow
6
+ Author-email: Pulze <support@pulze.io>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: 3dsmax,arnold,blender,cinema4d,corona,fusion,houdini,maya,network-rendering,nuke,pulze,redshift,render-farm,renderflow,vray
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+
21
+ # renderflow
22
+
23
+ Official Python client for the [RenderFlow](https://pulze.io/products/renderflow) API.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install pulze-renderflow
29
+ ```
30
+
31
+ The package has **no dependencies** — the whole client is standard library. That is a feature,
32
+ not an accident: drop the folder on `sys.path` and it works inside a DCC's bundled Python (Maya,
33
+ Houdini, Nuke, VRED), where installing anything is a fight.
34
+
35
+ ## Quick start
36
+
37
+ ```python
38
+ import renderflow as rf
39
+
40
+ farm = rf.connect() # the local service on 127.0.0.1:44442
41
+ # farm = rf.connect("https://rf.studio.lan:44442", api_key="rf_...")
42
+
43
+ for job in farm.jobs.get_jobs(status="working").data:
44
+ print(job.name, job.status)
45
+ ```
46
+
47
+ `connect()` falls back to the environment for each argument it isn't given: `RENDERFLOW_URL`
48
+ for the address, `RENDERFLOW_API_KEY` for the key (the same variable `rfcli` reads). With
49
+ neither, it talks to the service on this machine — which needs no credential, and is how a DCC
50
+ plugin on a workstation submits.
51
+
52
+ ## Finding the operation you want
53
+
54
+ Operations are grouped by tag and named after the API document's `operationId`, so a route
55
+ reads the same here, in the TypeScript SDK, in `rfcli` and in the MCP tools:
56
+
57
+ ```python
58
+ farm.tags() # every group: jobs, nodes, pools, schedule, ...
59
+ dir(farm.jobs) # every operation in one: get_jobs, create_job, stop_job, ...
60
+ help(farm.jobs.get_jobs) # the operation's own documentation, generated from the document
61
+ ```
62
+
63
+ | you want | you write |
64
+ | --- | --- |
65
+ | `GET /jobs` | `farm.jobs.get_jobs()` |
66
+ | `GET /jobs/{id}` | `farm.jobs.get_job(id)` |
67
+ | `POST /jobs` | `farm.jobs.create_job(body=...)` |
68
+ | `POST /jobs/{id}/stop` | `farm.jobs.stop_job(id)` |
69
+ | `GET /nodes` | `farm.nodes.get_nodes()` |
70
+
71
+ ## Submitting a job
72
+
73
+ A recipe — `type` plus a file and the flat per-DCC fields — is the short way in; the service
74
+ expands it into the step tree:
75
+
76
+ ```python
77
+ job = farm.jobs.create_job(body={
78
+ "type": "blender.render",
79
+ "file": "//share/scenes/shot_010.blend",
80
+ "frame": "1-100",
81
+ "pool_id": "...",
82
+ })
83
+
84
+ print(job.id, job.status)
85
+ farm.jobs.stop_job(job.id)
86
+ ```
87
+
88
+ Bodies are plain dicts; answers read with dot access (`job.name`, `page.data`) and hand back the
89
+ raw payload via `.to_dict()`.
90
+
91
+ ## Models
92
+
93
+ Typed names for every request and response schema live in `renderflow.models`, and your editor
94
+ reads them from `models.pyi` — autocomplete and type checking with nothing to install:
95
+
96
+ ```python
97
+ from renderflow.models import MayaRenderRecipe
98
+
99
+ recipe: MayaRenderRecipe = {"type": "maya.render", "file": "//share/scenes/hero.mb", "host": {"name": "maya"}}
100
+ farm.jobs.create_job(body=recipe)
101
+ ```
102
+
103
+ At runtime a model is a dict — the wire is JSON and the service validates.
104
+
105
+ ## Errors
106
+
107
+ A refused call raises `renderflow.wire.ApiError`, carrying the status and the service's own
108
+ message — `str(error)` is the sentence an operator should read.
109
+
110
+ ## How this package is built
111
+
112
+ There is no generated client. `renderflow/wire.py` binds operations off the OpenAPI document at
113
+ runtime, and the document ships inside the package as `renderflow/openapi.json` — a committed
114
+ copy of the service's `openapi.public.json`. An operation added to the API arrives here the
115
+ moment that copy is refreshed:
116
+
117
+ ```bash
118
+ python scripts/generate.py # copies ../../service/openapi.public.json + rewrites models.pyi
119
+ ```
120
+
121
+ `models.pyi` is written by [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator),
122
+ a dev-only tool — never a dependency of the package.
@@ -0,0 +1,102 @@
1
+ # renderflow
2
+
3
+ Official Python client for the [RenderFlow](https://pulze.io/products/renderflow) API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install pulze-renderflow
9
+ ```
10
+
11
+ The package has **no dependencies** — the whole client is standard library. That is a feature,
12
+ not an accident: drop the folder on `sys.path` and it works inside a DCC's bundled Python (Maya,
13
+ Houdini, Nuke, VRED), where installing anything is a fight.
14
+
15
+ ## Quick start
16
+
17
+ ```python
18
+ import renderflow as rf
19
+
20
+ farm = rf.connect() # the local service on 127.0.0.1:44442
21
+ # farm = rf.connect("https://rf.studio.lan:44442", api_key="rf_...")
22
+
23
+ for job in farm.jobs.get_jobs(status="working").data:
24
+ print(job.name, job.status)
25
+ ```
26
+
27
+ `connect()` falls back to the environment for each argument it isn't given: `RENDERFLOW_URL`
28
+ for the address, `RENDERFLOW_API_KEY` for the key (the same variable `rfcli` reads). With
29
+ neither, it talks to the service on this machine — which needs no credential, and is how a DCC
30
+ plugin on a workstation submits.
31
+
32
+ ## Finding the operation you want
33
+
34
+ Operations are grouped by tag and named after the API document's `operationId`, so a route
35
+ reads the same here, in the TypeScript SDK, in `rfcli` and in the MCP tools:
36
+
37
+ ```python
38
+ farm.tags() # every group: jobs, nodes, pools, schedule, ...
39
+ dir(farm.jobs) # every operation in one: get_jobs, create_job, stop_job, ...
40
+ help(farm.jobs.get_jobs) # the operation's own documentation, generated from the document
41
+ ```
42
+
43
+ | you want | you write |
44
+ | --- | --- |
45
+ | `GET /jobs` | `farm.jobs.get_jobs()` |
46
+ | `GET /jobs/{id}` | `farm.jobs.get_job(id)` |
47
+ | `POST /jobs` | `farm.jobs.create_job(body=...)` |
48
+ | `POST /jobs/{id}/stop` | `farm.jobs.stop_job(id)` |
49
+ | `GET /nodes` | `farm.nodes.get_nodes()` |
50
+
51
+ ## Submitting a job
52
+
53
+ A recipe — `type` plus a file and the flat per-DCC fields — is the short way in; the service
54
+ expands it into the step tree:
55
+
56
+ ```python
57
+ job = farm.jobs.create_job(body={
58
+ "type": "blender.render",
59
+ "file": "//share/scenes/shot_010.blend",
60
+ "frame": "1-100",
61
+ "pool_id": "...",
62
+ })
63
+
64
+ print(job.id, job.status)
65
+ farm.jobs.stop_job(job.id)
66
+ ```
67
+
68
+ Bodies are plain dicts; answers read with dot access (`job.name`, `page.data`) and hand back the
69
+ raw payload via `.to_dict()`.
70
+
71
+ ## Models
72
+
73
+ Typed names for every request and response schema live in `renderflow.models`, and your editor
74
+ reads them from `models.pyi` — autocomplete and type checking with nothing to install:
75
+
76
+ ```python
77
+ from renderflow.models import MayaRenderRecipe
78
+
79
+ recipe: MayaRenderRecipe = {"type": "maya.render", "file": "//share/scenes/hero.mb", "host": {"name": "maya"}}
80
+ farm.jobs.create_job(body=recipe)
81
+ ```
82
+
83
+ At runtime a model is a dict — the wire is JSON and the service validates.
84
+
85
+ ## Errors
86
+
87
+ A refused call raises `renderflow.wire.ApiError`, carrying the status and the service's own
88
+ message — `str(error)` is the sentence an operator should read.
89
+
90
+ ## How this package is built
91
+
92
+ There is no generated client. `renderflow/wire.py` binds operations off the OpenAPI document at
93
+ runtime, and the document ships inside the package as `renderflow/openapi.json` — a committed
94
+ copy of the service's `openapi.public.json`. An operation added to the API arrives here the
95
+ moment that copy is refreshed:
96
+
97
+ ```bash
98
+ python scripts/generate.py # copies ../../service/openapi.public.json + rewrites models.pyi
99
+ ```
100
+
101
+ `models.pyi` is written by [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator),
102
+ a dev-only tool — never a dependency of the package.
@@ -1,57 +1,58 @@
1
- [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "pulze-renderflow"
7
- version = "1.2.7"
8
- description = "Official RenderFlow API client for Python"
9
- readme = "README.md"
10
- license = "MIT"
11
- requires-python = ">=3.10"
12
- authors = [
13
- { name = "Pulze", email = "support@pulze.io" }
14
- ]
15
- keywords = [
16
- "3dsmax",
17
- "arnold",
18
- "blender",
19
- "cinema4d",
20
- "corona",
21
- "fusion",
22
- "houdini",
23
- "maya",
24
- "nuke",
25
- "redshift",
26
- "vray",
27
- "network-rendering",
28
- "pulze",
29
- "render-farm",
30
- "renderflow",
31
- ]
32
- classifiers = [
33
- "Development Status :: 4 - Beta",
34
- "Intended Audience :: Developers",
35
- "License :: OSI Approved :: MIT License",
36
- "Programming Language :: Python :: 3",
37
- "Programming Language :: Python :: 3.10",
38
- "Programming Language :: Python :: 3.11",
39
- "Programming Language :: Python :: 3.12",
40
- "Programming Language :: Python :: 3.13",
41
- ]
42
- dependencies = [
43
- "httpx>=0.23.0,<1.0.0",
44
- "pydantic>=2.0.0,<3.0.0",
45
- ]
46
-
47
- [project.urls]
48
- Homepage = "https://pulze.io/products/renderflow"
49
-
50
- [tool.hatch.build.targets.wheel]
51
- packages = ["renderflow"]
52
-
53
- [tool.hatch.build.targets.sdist]
54
- exclude = [
55
- "scripts/",
56
- "examples/",
57
- ]
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pulze-renderflow"
7
+ version = "2.0.7"
8
+ description = "Official RenderFlow API client for Python"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "Pulze", email = "support@pulze.io" }
14
+ ]
15
+ keywords = [
16
+ "3dsmax",
17
+ "arnold",
18
+ "blender",
19
+ "cinema4d",
20
+ "corona",
21
+ "fusion",
22
+ "houdini",
23
+ "maya",
24
+ "nuke",
25
+ "redshift",
26
+ "vray",
27
+ "network-rendering",
28
+ "pulze",
29
+ "render-farm",
30
+ "renderflow",
31
+ ]
32
+ classifiers = [
33
+ "Development Status :: 4 - Beta",
34
+ "Intended Audience :: Developers",
35
+ "License :: OSI Approved :: MIT License",
36
+ "Programming Language :: Python :: 3",
37
+ "Programming Language :: Python :: 3.10",
38
+ "Programming Language :: Python :: 3.11",
39
+ "Programming Language :: Python :: 3.12",
40
+ "Programming Language :: Python :: 3.13",
41
+ ]
42
+ # None, deliberately. The client binds to the OpenAPI document shipped inside the package
43
+ # (`renderflow/wire.py` + `renderflow/openapi.json`), so the whole SDK is standard library —
44
+ # it imports inside a DCC's bundled Python with nothing installed. The typed models are
45
+ # `models.pyi`, which only editors read. datamodel-code-generator is a dev-only tool used by
46
+ # `scripts/generate.py`, never a requirement of the package.
47
+ dependencies = []
48
+
49
+ [project.urls]
50
+ Homepage = "https://pulze.io/products/renderflow"
51
+
52
+ [tool.hatch.build.targets.wheel]
53
+ packages = ["renderflow"]
54
+
55
+ [tool.hatch.build.targets.sdist]
56
+ exclude = [
57
+ "examples/",
58
+ ]
@@ -0,0 +1,176 @@
1
+ """RenderFlow — write a job, run it on the farm.
2
+
3
+ Two halves, one import:
4
+
5
+ import renderflow as rf
6
+
7
+ plates = rf.files("plates", pattern="*.exr") # declarations become UI fields and CLI flags
8
+
9
+ @rf.execute
10
+ def convert(plate): # runs on a render node, once per item
11
+ ...
12
+
13
+ farm = rf.connect() # and the REST API, when you want it
14
+ farm.jobs.get_jobs(status="working")
15
+
16
+ **Importing this package touches nothing but the standard library.** That is a requirement, not a
17
+ nicety: the runtime half runs on render nodes, under whatever Python the machine happens to have,
18
+ and a node has no site-packages of ours. `connect()` is where httpx and the generated client load,
19
+ which is why it is a function and not a class you construct at import time.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import atexit
25
+ import os
26
+ import sys
27
+ from typing import TYPE_CHECKING, Any
28
+
29
+ from . import runtime
30
+ from .runtime import (
31
+ AppNotFound,
32
+ Fatal,
33
+ WorkItem,
34
+ app,
35
+ args,
36
+ bool_,
37
+ choice,
38
+ cleanup,
39
+ done,
40
+ env,
41
+ execute,
42
+ file,
43
+ files,
44
+ float_,
45
+ folder,
46
+ frames,
47
+ int_,
48
+ job,
49
+ log,
50
+ node,
51
+ output,
52
+ pairs,
53
+ path,
54
+ paths,
55
+ prepare,
56
+ progress,
57
+ resolution,
58
+ step,
59
+ submit,
60
+ task,
61
+ text,
62
+ tool,
63
+ )
64
+
65
+ # `int`, `float`, `bool` and `file` are builtins; inside this module the declarations are spelled
66
+ # with a trailing underscore so they do not shadow them, and exported under the names the docs use.
67
+ # A caller writes `rf.int("samples")` — `rf` is a namespace, so nothing is shadowed there.
68
+ globals()["int"] = int_
69
+ globals()["float"] = float_
70
+ globals()["bool"] = bool_
71
+
72
+ if TYPE_CHECKING: # for editors only — importing this at runtime would pull in httpx
73
+ from .farm import Farm
74
+
75
+
76
+ _INSPECT_FLAG = "--rf-inspect"
77
+
78
+ if _INSPECT_FLAG in sys.argv:
79
+ # Inspecting means importing the script: its declarations only exist once its module body
80
+ # has run, so the schema is printed at interpreter exit rather than from here.
81
+ #
82
+ # The script's own prints go to stderr while that happens. They are still worth seeing when
83
+ # a schema comes out wrong, and they are not part of the answer.
84
+ sys.argv = [argument for argument in sys.argv if argument != _INSPECT_FLAG]
85
+ runtime._ctx.inspecting = True
86
+ _real_stdout = sys.stdout
87
+ sys.stdout = sys.stderr
88
+
89
+ @atexit.register
90
+ def _print_schema() -> None:
91
+ sys.stdout = _real_stdout
92
+ runtime._inspect()
93
+
94
+ else:
95
+ # A bare `python script.py --plates ./x` runs the whole list here, at exit — the declarations
96
+ # only exist once the module body has run. A bootstrap binds values before the script imports
97
+ # us, which sets `driven` and keeps this out of its way.
98
+ runtime._read_argv(sys.argv[1:])
99
+
100
+ _previous_excepthook = sys.excepthook
101
+
102
+ def _mark_crashed(kind, value, trace) -> None:
103
+ runtime._ctx.crashed = True
104
+ _previous_excepthook(kind, value, trace)
105
+
106
+ sys.excepthook = _mark_crashed
107
+
108
+ @atexit.register
109
+ def _run_bare() -> None:
110
+ if runtime._ctx.driven or runtime._ctx.crashed or runtime._ctx.hooks["execute"] is None:
111
+ return
112
+
113
+ code = runtime._local(sys.argv[1:])
114
+ if code:
115
+ sys.stdout.flush()
116
+ sys.stderr.flush()
117
+ os._exit(code) # atexit cannot set the exit code any other way
118
+
119
+ __all__ = [
120
+ # connecting
121
+ "connect", "Farm",
122
+ # declarations
123
+ "text", "int", "float", "bool", "choice", "file", "folder", "pairs", "resolution", "output",
124
+ "frames", "files", "args",
125
+ # lifecycle
126
+ "execute", "submit", "prepare", "cleanup",
127
+ # context
128
+ "job", "step", "task", "node", "env", "paths", "path", "app", "tool",
129
+ # reporting
130
+ "progress", "log", "fatal", "done",
131
+ # exceptions
132
+ "Fatal", "AppNotFound", "WorkItem",
133
+ ]
134
+
135
+
136
+ def connect(url: str | None = None, api_key: str | None = None, *, timeout: float = 60.0) -> "Farm":
137
+ """Connect to a farm.
138
+
139
+ Each argument falls back to the environment and then to the local service: ``url`` to
140
+ ``RENDERFLOW_URL`` then ``http://127.0.0.1:44442``, ``api_key`` to ``RENDERFLOW_API_KEY`` then
141
+ to no credential at all — which is what a caller on the machine itself needs.
142
+
143
+ The REST client loads here rather than at import, so a render node running a script never pays
144
+ for it and never needs it installed.
145
+ """
146
+ from .farm import Farm
147
+
148
+ return Farm(url, api_key, timeout=timeout)
149
+
150
+
151
+ def fatal(message: str) -> None:
152
+ """End this task with a named reason. Re-exported from the runtime for `rf.fatal(...)`."""
153
+ from .runtime import fatal as _fatal
154
+
155
+ _fatal(message)
156
+
157
+
158
+ def __getattr__(name: str) -> Any:
159
+ """`Farm` on demand — see the note at the top about staying stdlib-only at import."""
160
+ if name == "Farm":
161
+ from .farm import Farm
162
+
163
+ return Farm
164
+
165
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
166
+
167
+
168
+ from importlib.metadata import PackageNotFoundError, version as _version
169
+
170
+ # Read from the installed distribution rather than written here: the two spellings had already
171
+ # drifted (this said 2.0.0 while the package built 1.3.0), and a version a caller reads should be
172
+ # the version they installed. `pyproject.toml` is the one place it is set.
173
+ try:
174
+ __version__ = _version("pulze-renderflow")
175
+ except PackageNotFoundError: # a source checkout that was never installed
176
+ __version__ = "0.0.0.dev0"