Skip to main content

pulze-renderflow

Official Python client for the RenderFlow API.

Installation

pip install pulze-renderflow

The package has no dependencies — the whole client is standard library. That is a feature, not an accident: drop the folder on sys.path and it works inside a DCC's bundled Python (Maya, Houdini, Nuke, VRED), where installing anything is a fight.

Quick start

import renderflow as rf

farm = rf.connect()                     # the local service on 127.0.0.1:44442
# farm = rf.connect("https://rf.studio.lan:44445", api_key="rf_...")

for job in farm.jobs.get_jobs(status="working").data:
    print(job.name, job.status)

connect() falls back to the environment for each argument it isn't given: RENDERFLOW_URL for the address, RENDERFLOW_API_KEY for the key (the same variable rfcli reads). With neither, it talks to the service on this machine — which needs no credential, and is how a DCC plugin on a workstation submits.

Finding the operation you want

Operations are grouped by tag and named after the API document's operationId, so a route reads the same here, in the TypeScript SDK, in rfcli and in the MCP tools:

farm.tags()                 # every group: jobs, nodes, pools, schedule, licenses, ...
dir(farm.jobs)              # every operation in one: get_jobs, create_job, stop_job, ...
help(farm.jobs.get_jobs)    # the operation's own documentation, generated from the document
you want you write
GET /jobs farm.jobs.get_jobs()
GET /jobs/{id} farm.jobs.get_job(id)
POST /jobs farm.jobs.create_job(body=...)
POST /jobs/{id}/stop farm.jobs.stop_job(id)
GET /nodes farm.nodes.get_nodes()

Submitting a job

A recipe — type plus a file and the flat per-DCC fields — is the short way in; the service expands it into the step tree:

Every recipe requires type, file and host — host is which application renders it, and only its id is mandatory:

job = farm.jobs.create_job(body={
    "type": "blender.render",
    "file": "//share/scenes/shot_010.blend",
    "host": {"id": "blender", "version": "4.5"},
    "frame": "1-100",
    "pool_id": "...",
})

print(job.id, job.status)
farm.jobs.stop_job(job.id)

Bodies are plain dicts; answers read with dot access (job.name, page.data) and hand back the raw payload via .to_dict(). An answer the service does not send as JSON comes back as bytes — farm.tasks.get_task_preview(id) is a rendered frame, ready to write to a file.

Waiting for a job

job = farm.jobs.settled(job.id)                  # answers on completed or failed
job = farm.jobs.settled(job.id, interval=30)     # a render measured in hours

None means the job is no longer there. Archiving and deleting look the same from out here, and neither is going to finish.

A stop is not an end, because suspending a job is resumable — pass statuses=["completed", "failed", "suspended"] if a stop is an answer for your caller. timeout is in seconds and there is none by default, since a long render legitimately takes days; it raises TimeoutError rather than handing back a job that has not finished.

This polls rather than watching, so a wait needs no socket, no thread and no reconnect behaviour, and it works where a proxy passes HTTP and refuses an upgrade. interval is the cost: two seconds is right for a test render and wrong for an overnight one. When the moment a job finishes is what matters, watch it instead.

Live updates

One socket, shared by every subscription, opened on the first and closed with the last. No polling.

def draw(event):
    if event.op == "resync":
        return refetch()          # events were missed, read again
    print(event.op, event.id, event.patch)

stop = farm.jobs.on(draw, {"id": job.id})
stop()

on is on every group whose collection the farm pushes: jobs, tasks, nodes, pools, users, user_groups, errors, schedule and licenses. The second argument narrows it — id on any of them, step_id on tasks, node_id on the node joins — and a list is one subscription over several ids rather than several subscriptions.

An insert carries the whole row on event.doc. An update carries event.patch, keyed by dotted path, so a job's progress arrives as event.patch["progress.percent"]. A delete carries the id.

A resync event is not a change. It means events were missed and the collection has to be read again, because the socket has no replay: a reconnect, a dropped frame, or a client the farm outran all mean the same thing. It arrives through the same handler precisely so there is nothing to forget to pass.

farm.live.close() drops everything, and farm.live.on_error reports a refused credential; it writes to stderr until you replace it. The socket runs on a daemon thread, so a script that forgot to unsubscribe still exits.

There is still no dependency: renderflow/live.py is the client half of RFC 6455 hand-rolled on socket, ssl and struct, because installing websocket-client into Maya is the fight this package exists to avoid.

Models

Typed names for every request and response schema live in renderflow.models, and your editor reads them from models.pyi — autocomplete and type checking with nothing to install:

from renderflow.models import MayaRenderRecipe

recipe: MayaRenderRecipe = {"type": "maya.render", "file": "//share/scenes/hero.mb", "host": {"id": "maya", "version": "2026"}}
farm.jobs.create_job(body=recipe)

At runtime a model is a dict — the wire is JSON and the service validates.

Errors

A refused call raises renderflow.wire.ApiError, carrying the status and the service's own message — str(error) is the sentence an operator should read.

How this package is built

There is no generated client. renderflow/wire.py binds operations off the OpenAPI document at runtime, and the document ships inside the package as renderflow/openapi.json — a committed copy of the service's openapi.public.json. An operation added to the API arrives here the moment that copy is refreshed:

python scripts/generate.py     # copies ../../service/openapi.public.json + rewrites models.pyi

models.pyi is written by datamodel-code-generator, a dev-only tool — never a dependency of the package.

Metadata

Release files for pulze-renderflow 2.0.16

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pulze-renderflow 2.0.16
File Size Uploaded
pulze_renderflow-2.0.16.tar.gz 98.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pulze-renderflow 2.0.16
File Interpreter ABI Platform
pulze_renderflow-2.0.16-py3-none-any.whl Python 3 none any Details

Total release size: 186.6 kB

Release files / pulze_renderflow-2.0.16.tar.gz

Download URL pulze_renderflow-2.0.16.tar.gz
Size 98.7 kB
Tags Source
SHA-256 checksum
How to use checksums
d71a1cf91ee6e0375109ad41a2929d058cf2541bfcfabc8c3febc5c761b48601
BLAKE2b-256 checksum
How to use checksums
f53de349a4232fa578a55643be49c2b216be74f8ae5abb0fe863005f0613dc84
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.4

Release files / pulze_renderflow-2.0.16-py3-none-any.whl

Download URL pulze_renderflow-2.0.16-py3-none-any.whl
Size 87.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
44305900479b002b4fafabf32a64a0460ff4edd830883e31f8d3d061a3dc7ba9
BLAKE2b-256 checksum
How to use checksums
878532c6553055a8e11aa0e54dec08a75fdda05a1af8429d6281077765314dee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.4

Release history Release notifications | RSS feed

This release

2.0.16 This release

2 release files

2.0.9

2 release files

2.0.8

2 release files

2.0.7

2 release files

1.3.0

2 release files

1.2.7

2 release files

1.2.5

2 release files

1.2.4

2 release files

1.2.2

2 release files

1.2.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page