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.17

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.17
File Size Uploaded
pulze_renderflow-2.0.17.tar.gz 101.4 kB Details

Built distribution (wheel)

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

Total release size: 192.7 kB

Release files / pulze_renderflow-2.0.17.tar.gz

Download URL pulze_renderflow-2.0.17.tar.gz
Size 101.4 kB
Tags Source
SHA-256 checksum
How to use checksums
973ac00ad72d9693374b3a5f51159acee7ac26fd02b94964960c5423aec46d77
BLAKE2b-256 checksum
How to use checksums
2258a40fe36fdc972cfcf64fad7ef33ab61055a918038f1140ecc06b41b5ece9
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.17-py3-none-any.whl

Download URL pulze_renderflow-2.0.17-py3-none-any.whl
Size 91.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4be84332371bb39ec604a1ebc954811e57d57591ef775676deb05c05440661b3
BLAKE2b-256 checksum
How to use checksums
03c07d871b6c3b54cccf4eaabb9380f32a9043449eb7cf229a2c35a8db8a5a44
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.17 This release

2 release files

2.0.16

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