pulze-renderflow 2.0.8__tar.gz → 2.0.16__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.
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/CLAUDE.md +19 -1
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/PKG-INFO +60 -4
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/README.md +59 -3
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/pyproject.toml +58 -58
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/renderflow/__init__.py +14 -31
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/renderflow/__init__.pyi +132 -113
- pulze_renderflow-2.0.16/renderflow/farm.py +243 -0
- pulze_renderflow-2.0.16/renderflow/live.py +533 -0
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/renderflow/models.pyi +3280 -2722
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/renderflow/openapi.json +4357 -2568
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/renderflow/runtime.py +884 -757
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/renderflow/wire.py +10 -2
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/scripts/build.py +60 -63
- pulze_renderflow-2.0.16/scripts/test.mjs +48 -0
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/test/test_client.py +242 -136
- pulze_renderflow-2.0.16/test/test_live.py +404 -0
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/test/test_runtime.py +994 -678
- pulze_renderflow-2.0.8/renderflow/farm.py +0 -125
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/.gitignore +0 -0
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/LICENSE +0 -0
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/MANIFEST.in +0 -0
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/renderflow/models.py +0 -0
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/renderflow/py.typed +0 -0
- {pulze_renderflow-2.0.8 → pulze_renderflow-2.0.16}/scripts/generate.py +0 -0
|
@@ -19,9 +19,27 @@ plugin that embeds it.
|
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
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
|
|
22
|
+
python test/test_runtime.py # the plugin runtime; stdlib unittest, no pytest
|
|
23
|
+
python test/test_client.py # the wire client
|
|
24
|
+
python test/test_live.py # the hand-rolled WebSocket
|
|
25
|
+
npm run test-python # from the repo root: all three, and what the gate runs
|
|
23
26
|
```
|
|
24
27
|
|
|
28
|
+
All three suites are in `test-suites` through `scripts/test.mjs`, which the root's `test-python`
|
|
29
|
+
calls — this package is not an npm workspace, so there is no `npm test --workspace` that reaches it.
|
|
30
|
+
A missing interpreter **fails** that script rather than skipping: `renderflow/runtime.py` is the
|
|
31
|
+
plugin API a node imports, and no type-checker anywhere can see it.
|
|
32
|
+
|
|
33
|
+
`farm.py` holds what is not an operation of the document: `JobsGroup.settled`, the wait, and
|
|
34
|
+
`LiveGroup.on`, the subscription. `GROUPS` is how one tag gets more than the document gives it, and
|
|
35
|
+
`LIVE_ENTITIES` is the rest — group name to socket entity, so a group whose collection the farm
|
|
36
|
+
pushes gets `on` and one that has no live collection does not. A group that adds something overrides
|
|
37
|
+
`__dir__` too, because `dir(farm.jobs)` is how this client is meant to be explored.
|
|
38
|
+
|
|
39
|
+
`live.py` is the client half of RFC 6455, hand-rolled on `socket`, `ssl` and `struct`, because the
|
|
40
|
+
standard library has no WebSocket client and this package is allowed no dependency. It speaks the
|
|
41
|
+
same protocol as `sdks/typescript/src/live.ts`, frame for frame.
|
|
42
|
+
|
|
25
43
|
Both generated files are **committed** — review them like code. Publishing is manual
|
|
26
44
|
(`python -m build` + `twine upload`), on its own cadence; this package's version field is real.
|
|
27
45
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pulze-renderflow
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.16
|
|
4
4
|
Summary: Official RenderFlow API client for Python
|
|
5
5
|
Project-URL: Homepage, https://pulze.io/products/renderflow
|
|
6
6
|
Author-email: Pulze <support@pulze.io>
|
|
@@ -38,7 +38,7 @@ Houdini, Nuke, VRED), where installing anything is a fight.
|
|
|
38
38
|
import renderflow as rf
|
|
39
39
|
|
|
40
40
|
farm = rf.connect() # the local service on 127.0.0.1:44442
|
|
41
|
-
# farm = rf.connect("https://rf.studio.lan:
|
|
41
|
+
# farm = rf.connect("https://rf.studio.lan:44445", api_key="rf_...")
|
|
42
42
|
|
|
43
43
|
for job in farm.jobs.get_jobs(status="working").data:
|
|
44
44
|
print(job.name, job.status)
|
|
@@ -55,7 +55,7 @@ Operations are grouped by tag and named after the API document's `operationId`,
|
|
|
55
55
|
reads the same here, in the TypeScript SDK, in `rfcli` and in the MCP tools:
|
|
56
56
|
|
|
57
57
|
```python
|
|
58
|
-
farm.tags() # every group: jobs, nodes, pools, schedule,
|
|
58
|
+
farm.tags() # every group: jobs, nodes, pools, schedule, licenses, ...
|
|
59
59
|
dir(farm.jobs) # every operation in one: get_jobs, create_job, stop_job, ...
|
|
60
60
|
help(farm.jobs.get_jobs) # the operation's own documentation, generated from the document
|
|
61
61
|
```
|
|
@@ -90,7 +90,63 @@ farm.jobs.stop_job(job.id)
|
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
Bodies are plain dicts; answers read with dot access (`job.name`, `page.data`) and hand back the
|
|
93
|
-
raw payload via `.to_dict()`.
|
|
93
|
+
raw payload via `.to_dict()`. An answer the service does not send as JSON comes back as **bytes** —
|
|
94
|
+
`farm.tasks.get_task_preview(id)` is a rendered frame, ready to write to a file.
|
|
95
|
+
|
|
96
|
+
## Waiting for a job
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
job = farm.jobs.settled(job.id) # answers on completed or failed
|
|
100
|
+
job = farm.jobs.settled(job.id, interval=30) # a render measured in hours
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`None` means the job is no longer there. Archiving and deleting look the same from out here, and
|
|
104
|
+
neither is going to finish.
|
|
105
|
+
|
|
106
|
+
A stop is **not** an end, because suspending a job is resumable — pass
|
|
107
|
+
`statuses=["completed", "failed", "suspended"]` if a stop is an answer for your caller. `timeout` is
|
|
108
|
+
in seconds and there is none by default, since a long render legitimately takes days; it raises
|
|
109
|
+
`TimeoutError` rather than handing back a job that has not finished.
|
|
110
|
+
|
|
111
|
+
This polls rather than watching, so a wait needs no socket, no thread and no reconnect behaviour,
|
|
112
|
+
and it works where a proxy passes HTTP and refuses an upgrade. `interval` is the cost: two seconds is
|
|
113
|
+
right for a test render and wrong for an overnight one. When the moment a job finishes is what
|
|
114
|
+
matters, watch it instead.
|
|
115
|
+
|
|
116
|
+
## Live updates
|
|
117
|
+
|
|
118
|
+
One socket, shared by every subscription, opened on the first and closed with the last. No polling.
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
def draw(event):
|
|
122
|
+
if event.op == "resync":
|
|
123
|
+
return refetch() # events were missed, read again
|
|
124
|
+
print(event.op, event.id, event.patch)
|
|
125
|
+
|
|
126
|
+
stop = farm.jobs.on(draw, {"id": job.id})
|
|
127
|
+
stop()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`on` is on every group whose collection the farm pushes: `jobs`, `tasks`, `nodes`, `pools`, `users`,
|
|
131
|
+
`user_groups`, `errors`, `schedule` and `licenses`. The second argument narrows it — `id` on
|
|
132
|
+
any of them, `step_id` on tasks, `node_id` on the node joins — and a list is one subscription over
|
|
133
|
+
several ids rather than several subscriptions.
|
|
134
|
+
|
|
135
|
+
An insert carries the whole row on `event.doc`. An update carries `event.patch`, **keyed by dotted
|
|
136
|
+
path**, so a job's progress arrives as `event.patch["progress.percent"]`. A delete carries the id.
|
|
137
|
+
|
|
138
|
+
**A `resync` event is not a change.** It means events were missed and the collection has to be read
|
|
139
|
+
again, because the socket has no replay: a reconnect, a dropped frame, or a client the farm outran
|
|
140
|
+
all mean the same thing. It arrives through the same handler precisely so there is nothing to forget
|
|
141
|
+
to pass.
|
|
142
|
+
|
|
143
|
+
`farm.live.close()` drops everything, and `farm.live.on_error` reports a refused credential; it
|
|
144
|
+
writes to stderr until you replace it. The socket runs on a daemon thread, so a script that forgot to
|
|
145
|
+
unsubscribe still exits.
|
|
146
|
+
|
|
147
|
+
There is still **no dependency**: `renderflow/live.py` is the client half of RFC 6455 hand-rolled on
|
|
148
|
+
`socket`, `ssl` and `struct`, because installing `websocket-client` into Maya is the fight this
|
|
149
|
+
package exists to avoid.
|
|
94
150
|
|
|
95
151
|
## Models
|
|
96
152
|
|
|
@@ -18,7 +18,7 @@ Houdini, Nuke, VRED), where installing anything is a fight.
|
|
|
18
18
|
import renderflow as rf
|
|
19
19
|
|
|
20
20
|
farm = rf.connect() # the local service on 127.0.0.1:44442
|
|
21
|
-
# farm = rf.connect("https://rf.studio.lan:
|
|
21
|
+
# farm = rf.connect("https://rf.studio.lan:44445", api_key="rf_...")
|
|
22
22
|
|
|
23
23
|
for job in farm.jobs.get_jobs(status="working").data:
|
|
24
24
|
print(job.name, job.status)
|
|
@@ -35,7 +35,7 @@ Operations are grouped by tag and named after the API document's `operationId`,
|
|
|
35
35
|
reads the same here, in the TypeScript SDK, in `rfcli` and in the MCP tools:
|
|
36
36
|
|
|
37
37
|
```python
|
|
38
|
-
farm.tags() # every group: jobs, nodes, pools, schedule,
|
|
38
|
+
farm.tags() # every group: jobs, nodes, pools, schedule, licenses, ...
|
|
39
39
|
dir(farm.jobs) # every operation in one: get_jobs, create_job, stop_job, ...
|
|
40
40
|
help(farm.jobs.get_jobs) # the operation's own documentation, generated from the document
|
|
41
41
|
```
|
|
@@ -70,7 +70,63 @@ farm.jobs.stop_job(job.id)
|
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
Bodies are plain dicts; answers read with dot access (`job.name`, `page.data`) and hand back the
|
|
73
|
-
raw payload via `.to_dict()`.
|
|
73
|
+
raw payload via `.to_dict()`. An answer the service does not send as JSON comes back as **bytes** —
|
|
74
|
+
`farm.tasks.get_task_preview(id)` is a rendered frame, ready to write to a file.
|
|
75
|
+
|
|
76
|
+
## Waiting for a job
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
job = farm.jobs.settled(job.id) # answers on completed or failed
|
|
80
|
+
job = farm.jobs.settled(job.id, interval=30) # a render measured in hours
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`None` means the job is no longer there. Archiving and deleting look the same from out here, and
|
|
84
|
+
neither is going to finish.
|
|
85
|
+
|
|
86
|
+
A stop is **not** an end, because suspending a job is resumable — pass
|
|
87
|
+
`statuses=["completed", "failed", "suspended"]` if a stop is an answer for your caller. `timeout` is
|
|
88
|
+
in seconds and there is none by default, since a long render legitimately takes days; it raises
|
|
89
|
+
`TimeoutError` rather than handing back a job that has not finished.
|
|
90
|
+
|
|
91
|
+
This polls rather than watching, so a wait needs no socket, no thread and no reconnect behaviour,
|
|
92
|
+
and it works where a proxy passes HTTP and refuses an upgrade. `interval` is the cost: two seconds is
|
|
93
|
+
right for a test render and wrong for an overnight one. When the moment a job finishes is what
|
|
94
|
+
matters, watch it instead.
|
|
95
|
+
|
|
96
|
+
## Live updates
|
|
97
|
+
|
|
98
|
+
One socket, shared by every subscription, opened on the first and closed with the last. No polling.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
def draw(event):
|
|
102
|
+
if event.op == "resync":
|
|
103
|
+
return refetch() # events were missed, read again
|
|
104
|
+
print(event.op, event.id, event.patch)
|
|
105
|
+
|
|
106
|
+
stop = farm.jobs.on(draw, {"id": job.id})
|
|
107
|
+
stop()
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`on` is on every group whose collection the farm pushes: `jobs`, `tasks`, `nodes`, `pools`, `users`,
|
|
111
|
+
`user_groups`, `errors`, `schedule` and `licenses`. The second argument narrows it — `id` on
|
|
112
|
+
any of them, `step_id` on tasks, `node_id` on the node joins — and a list is one subscription over
|
|
113
|
+
several ids rather than several subscriptions.
|
|
114
|
+
|
|
115
|
+
An insert carries the whole row on `event.doc`. An update carries `event.patch`, **keyed by dotted
|
|
116
|
+
path**, so a job's progress arrives as `event.patch["progress.percent"]`. A delete carries the id.
|
|
117
|
+
|
|
118
|
+
**A `resync` event is not a change.** It means events were missed and the collection has to be read
|
|
119
|
+
again, because the socket has no replay: a reconnect, a dropped frame, or a client the farm outran
|
|
120
|
+
all mean the same thing. It arrives through the same handler precisely so there is nothing to forget
|
|
121
|
+
to pass.
|
|
122
|
+
|
|
123
|
+
`farm.live.close()` drops everything, and `farm.live.on_error` reports a refused credential; it
|
|
124
|
+
writes to stderr until you replace it. The socket runs on a daemon thread, so a script that forgot to
|
|
125
|
+
unsubscribe still exits.
|
|
126
|
+
|
|
127
|
+
There is still **no dependency**: `renderflow/live.py` is the client half of RFC 6455 hand-rolled on
|
|
128
|
+
`socket`, `ssl` and `struct`, because installing `websocket-client` into Maya is the fight this
|
|
129
|
+
package exists to avoid.
|
|
74
130
|
|
|
75
131
|
## Models
|
|
76
132
|
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
3
|
-
build-backend = "hatchling.build"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "pulze-renderflow"
|
|
7
|
-
version = "2.0.
|
|
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
|
-
]
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pulze-renderflow"
|
|
7
|
+
version = "2.0.16"
|
|
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
|
+
]
|
|
@@ -4,7 +4,7 @@ Two halves, one import:
|
|
|
4
4
|
|
|
5
5
|
import renderflow as rf
|
|
6
6
|
|
|
7
|
-
plates = rf.
|
|
7
|
+
plates = rf.folder("plates") # a declaration is the field and the value
|
|
8
8
|
|
|
9
9
|
@rf.execute
|
|
10
10
|
def convert(plate): # runs on a render node, once per item
|
|
@@ -22,7 +22,6 @@ which is why it is a function and not a class you construct at import time.
|
|
|
22
22
|
from __future__ import annotations
|
|
23
23
|
|
|
24
24
|
import atexit
|
|
25
|
-
import os
|
|
26
25
|
import sys
|
|
27
26
|
from typing import TYPE_CHECKING, Any
|
|
28
27
|
|
|
@@ -39,14 +38,15 @@ from .runtime import (
|
|
|
39
38
|
done,
|
|
40
39
|
env,
|
|
41
40
|
execute,
|
|
41
|
+
expand,
|
|
42
42
|
file,
|
|
43
|
-
files,
|
|
44
43
|
float_,
|
|
45
44
|
folder,
|
|
46
45
|
frames,
|
|
47
46
|
int_,
|
|
48
47
|
job,
|
|
49
48
|
log,
|
|
49
|
+
list_,
|
|
50
50
|
node,
|
|
51
51
|
output,
|
|
52
52
|
pairs,
|
|
@@ -54,20 +54,24 @@ from .runtime import (
|
|
|
54
54
|
paths,
|
|
55
55
|
prepare,
|
|
56
56
|
progress,
|
|
57
|
+
read,
|
|
57
58
|
resolution,
|
|
59
|
+
state,
|
|
58
60
|
step,
|
|
59
61
|
submit,
|
|
60
62
|
task,
|
|
63
|
+
tasks,
|
|
61
64
|
text,
|
|
62
65
|
tool,
|
|
63
66
|
)
|
|
64
67
|
|
|
65
|
-
# `int`, `float`, `bool` and `
|
|
68
|
+
# `int`, `float`, `bool` and `list` are builtins; inside this module they are spelled
|
|
66
69
|
# with a trailing underscore so they do not shadow them, and exported under the names the docs use.
|
|
67
70
|
# A caller writes `rf.int("samples")` — `rf` is a namespace, so nothing is shadowed there.
|
|
68
71
|
globals()["int"] = int_
|
|
69
72
|
globals()["float"] = float_
|
|
70
73
|
globals()["bool"] = bool_
|
|
74
|
+
globals()["list"] = list_
|
|
71
75
|
|
|
72
76
|
if TYPE_CHECKING: # for editors only — importing this at runtime would pull in httpx
|
|
73
77
|
from .farm import Farm
|
|
@@ -91,41 +95,20 @@ if _INSPECT_FLAG in sys.argv:
|
|
|
91
95
|
sys.stdout = _real_stdout
|
|
92
96
|
runtime._inspect()
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
98
|
+
# Nothing happens otherwise. Importing this package does not make a script runnable as a program:
|
|
99
|
+
# a plugin runs as a task, where a bootstrap binds its values, its node and the farm's path mapping
|
|
100
|
+
# first, and none of that exists in a terminal.
|
|
118
101
|
|
|
119
102
|
__all__ = [
|
|
120
103
|
# connecting
|
|
121
104
|
"connect", "Farm",
|
|
122
105
|
# declarations
|
|
123
106
|
"text", "int", "float", "bool", "choice", "file", "folder", "pairs", "resolution", "output",
|
|
124
|
-
"frames", "
|
|
107
|
+
"frames", "args", "expand", "list",
|
|
125
108
|
# lifecycle
|
|
126
|
-
"execute", "submit", "prepare", "cleanup",
|
|
109
|
+
"execute", "submit", "tasks", "read", "prepare", "cleanup",
|
|
127
110
|
# context
|
|
128
|
-
"job", "step", "task", "node", "env", "paths", "path", "app", "tool",
|
|
111
|
+
"job", "step", "task", "node", "env", "paths", "path", "app", "tool", "state",
|
|
129
112
|
# reporting
|
|
130
113
|
"progress", "log", "fatal", "done",
|
|
131
114
|
# exceptions
|