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: object

Collect 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 of add() — it does not require the caller to know the internally generated result-variable name, and add_prelude() lets multiple operations share one setup block (e.g. a node lookup) emitted only once.

Parameters:
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 prior execute() call’s generated script if the exact naming matters, or prefer add_operation(), which does not require guessing the key name.

Parameters:

lines (list[str]) – DazScript source lines (no return needed).

Returns:

A BatchFuture that resolves after execute().

Return type:

BatchFuture

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.

Parameters:
  • prelude_key (str) – Stable identifier for this setup block (e.g. "node:Fig"). Callers must pick keys that collide exactly when — and only when — the generated lines are identical.

  • lines (list[str]) – DazScript source lines for the shared setup.

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 emits var _rN = <result_expression>; itself.

Parameters:
  • body_lines (list[str]) – DazScript source lines with no trailing result assignment (side effects only, e.g. property writes).

  • result_expression (str) – A JS expression evaluated once, immediately after body_lines run, and used as this operation’s result. Mutation-only operations should pass "null".

Returns:

A BatchFuture that resolves after execute().

Raises:

BatchLimitExceededError – If this call would exceed the batch’s configured max_operations.

Return type:

BatchFuture

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:

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: object

Placeholder for a single result within a Batch execution.

Created by Batch.add() or Batch.add_operation(); the value property 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/result with long-polling until the script completes or timeout is exceeded.

Parameters:
  • client (DazClient) – The DazClient to use.

  • script (str) – DazScript source code.

  • args (object) – Optional argument passed to the script.

  • timeout (float) – Maximum total wall-clock seconds to wait.

  • poll_interval (float) – Seconds to sleep between short polls when the server returns before the long-poll timeout.

Returns:

An ExecutionResult on success.

Raises:
Return type:

ExecutionResult

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: object

Context 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)
Parameters:
  • client (DazClient) – The DazClient to use.

  • label (str) – The label shown in DAZ Studio’s Edit > Undo menu.