Batch & Async Execution
One HTTP request, one DazScript evaluation
A Batch combines every queued operation into a single
generated DazScript IIFE and sends it as one /execute request. A
batch of 20 operations is one evaluation on Studio’s main thread; two
separate execute() calls are two evaluations, even if
each only holds one operation. This is what batching buys you: fewer
main-thread handoffs, fewer JSON parses, fewer HTTP round-trips per
operation — not parallelism. Studio still runs every operation’s generated
JS serially, in submission order, inside that one script; scene mutations
are not parallelized or reordered.
Whole-batch failure, no rollback
If any operation in the batch throws, the entire /execute call fails —
execute() raises the same
ScriptError a single failing call would, and no
partial per-operation results are available. Earlier operations in the same
script that already mutated the scene are not rolled back — a batch is
not a transaction. Order operations so that a failure midway through leaves
the scene in a state you can reason about, and keep destructive operations
late in the batch if a partial application would be hard to recover from.
Batch
- class dazpy.Batch(client, max_operations=500, max_script_length=900000)[source]
Bases:
objectCollect multiple DazScript operations and execute them in a single HTTP round-trip.
Usage as a context manager (recommended):
with Batch(client) as b: pos_future = b.add(["var pos = Scene.findNode('Figure').getWSPos();", "var pos = [pos.x, pos.y, pos.z];"]) name_future = b.add(["var name = Scene.findNode('Figure').getName();"]) # Both futures resolved after the `with` block print(pos_future.value, name_future.value)
Or manually:
b = Batch(client) f = b.add(["var x = 42;"]) b.execute() print(f.value)
High-level helpers that generate operations programmatically should use
add_operation()instead ofadd()— it does not require the caller to know the internally generated result-variable name, andadd_prelude()lets multiple operations share one setup block (e.g. a node lookup) emitted only once.- Parameters:
max_operations (int) – Maximum number of queued operations before
add_operation()raisesBatchLimitExceededError.max_script_length (int) – Maximum generated script length (characters) before
execute()raisesBatchLimitExceededError.
- add(lines)[source]
Queue a list of DazScript lines to be included in the batch.
The last line in lines should assign the desired result to a variable named after the internally generated key (
_r0,_r1, … in call order) — inspect a priorexecute()call’s generated script if the exact naming matters, or preferadd_operation(), which does not require guessing the key name.- Parameters:
lines (list[str]) – DazScript source lines (no
returnneeded).- Returns:
A
BatchFuturethat resolves afterexecute().- Return type:
- add_prelude(prelude_key, lines)[source]
Register a shared setup block, emitted once per unique prelude_key.
Call this before
add_operation()calls whose bodies depend on the prelude’s bound variable(s) (e.g. a node lookup bound to_node_Fig). Repeated calls with the same prelude_key are no-ops after the first — use this instead of re-emitting an identical lookup once per operation.
- add_operation(body_lines, result_expression)[source]
Queue an operation whose result the builder assigns internally.
Unlike
add(), the caller does not need to know the generated key name — pass the JS expression that yields the result (result_expression, e.g. a variable set inside body_lines, or a literal expression), and the builder emitsvar _rN = <result_expression>;itself.- Parameters:
- Returns:
A
BatchFuturethat resolves afterexecute().- Raises:
BatchLimitExceededError – If this call would exceed the batch’s configured
max_operations.- Return type:
- execute()[source]
Execute all queued operations in a single HTTP request and resolve all futures.
- Raises:
BatchLimitExceededError – If the generated script exceeds
max_script_length. Raised before any HTTP call.
add() vs add_operation()
add() requires the caller’s script lines to assign the
result to an internally generated _rN variable name, which means the
caller has to know or guess that name. add_operation()
avoids this: pass body_lines (side effects only) and a
result_expression (a JS expression evaluated once immediately after),
and the builder emits the var _rN = <result_expression>; assignment
itself. Prefer add_operation() for anything generated
programmatically (loops building operations from data); reach for
add() only for a handful of hand-written, one-off
operations where the exact key name doesn’t matter.
add_prelude()
add_prelude() registers a shared setup block — e.g. a
node lookup — under a stable key. It is emitted once per unique key no
matter how many times it’s called with that key, so several operations that
all need the same lookup (say, several property writes on one node) can
share one lookup instead of repeating it per operation. Pick keys that
collide exactly when — and only when — the generated lines are identical
(e.g. f"node:{node_name}").
Size limits
Batch(client, max_operations=..., max_script_length=...) bounds a batch
in two ways:
max_operations(default 500) —add_operation()raisesBatchLimitExceededErroronce the queue would exceed this count.max_script_length(default 900,000 characters, comfortably under the server’s default 1 MB script cap) —execute()raisesBatchLimitExceededErrorif the generated script would exceed this length.
Both checks happen client-side before any HTTP call, so an oversized batch never reaches Studio’s main thread.
BatchFuture
- class dazpy.BatchFuture(key)[source]
Bases:
objectPlaceholder for a single result within a
Batchexecution.Created by
Batch.add()orBatch.add_operation(); thevalueproperty blocks until the batch has been executed.- property value: object
The result value.
- Raises:
RuntimeError – If
Batch.execute()has not been called yet.
execute_long
- dazpy.execute_long(client, script, args=None, timeout=120.0, poll_interval=0.5)[source]
Execute a potentially long-running script via the async endpoint with polling.
Submits script asynchronously, then polls
/requests/:id/resultwith long-polling until the script completes or timeout is exceeded.- Parameters:
- Returns:
An
ExecutionResulton success.- Raises:
AsyncExecutionError – If the script fails or the request is cancelled.
TimeoutError – If timeout is exceeded before the script completes.
- Return type:
execute_batch_async
execute_batch_async() gives the one-script guarantee
of Batch without holding an HTTP worker thread and a
blocking client call for the duration. Instead of add_operation() calls
on a Batch instance, pass a list of
{"body_lines": [...], "result_expression": "..."} dicts directly — the
same shape add_operation() takes, minus the futures. It
builds the identical combined script internally and submits it to
/execute/async as a single queue item, returning a request_id
immediately:
from dazpy import DazClient
client = DazClient()
request_id = client.execute_batch_async([
{"body_lines": ["var n = Scene.getNumNodes();"], "result_expression": "n"},
{"body_lines": [], "result_expression": "Scene.getNumCameras()"},
])
data = client.get_request_result(request_id, wait=True, wait_timeout=30)
print(data["result"]["_r0"], data["result"]["_r1"])
Poll it like any other async request, with
get_request_status() /
get_request_result(). The completed result’s
result field is a dict keyed "_r0", "_r1", … in submission
order — the same key scheme Batch uses internally, just
without a BatchFuture wrapper resolving each one.
UndoGroup
- class dazpy.UndoGroup(client, label)[source]
Bases:
objectContext manager that groups DAZ Studio operations into a single undo step.
On successful exit the changes are committed with
acceptUndo(label). If an exception propagates,cancelUndo()is called instead.Obtain via
DazScene.undo()rather than constructing directly:with scene.undo("Rotate arm"): skel.find_bone("r_forearm").set_local_rotation(0, 0, 45)