RunR v0.5.7

Documentation

runr-workflows

Durable workflows for RunR: write a workflow as a plain Python function; every step is memoized, retried with backoff and survives sleeps of days.

pip install runr-workflows

Python 3.9 or newer, no dependencies. Deploy the function in a project with the trigger Workflow; the SDK speaks the workflow protocol v1.

from runr_workflows import Workflows, NonRetriableError

wf = Workflows()  # signing key from RUNR_WORKFLOW_SIGNING_KEY (set by RunR on deploy)

@wf.workflow("process-order",
             triggers=[{"event": "order.created"}],
             retries=3, timeout="7d",
             concurrency={"limit": 5, "key": "event.data.customer_id"},
             idempotency="event.data.order_id",
             cancel_on=[{"event": "order.cancelled", "match": "data.order_id"}])
def process_order(ctx, step):
    order = ctx.event["data"]
    charge = step.run("charge-card", charge_card, order)
    step.sleep("cool-off", "1h")
    shipped = step.wait_for_event("shipped", "order.shipped", match="data.order_id", timeout="3d")
    if shipped is None:
        step.run("refund", refund, charge)
        return {"status": "refunded"}
    step.send_event("notify", {"name": "order.completed", "data": {"order_id": order["order_id"]}})
    return {"status": "done"}

handler = wf.handler()      # RunR python runtime: handler(event, context)
# app = wf.wsgi()           # any WSGI server
# wf.serve(port=8080)       # standalone HTTP server (container projects)

Step API#

Call Result
step.run(id, fn, *args) fn's return value (JSON); retried on exceptions
step.sleep(id, "1h") / step.sleep_until(id, datetime) None once the time passed
step.wait_for_event(id, name, match=, if_=, timeout=) the event {id, name, data, ts} or None on timeout
step.invoke(id, workflow, data, timeout=) the invoked workflow's output
step.send_event(id, event_or_events) {"ids": [...]}

Raise NonRetriableError to fail without retries, RetryAfterError(msg, "30s") to choose the delay. A step that failed for good raises StepError into the workflow — catch it to compensate. async def workflows get the same methods as coroutines.

EventsClient(base_url, token).send(name, data) sends events to POST /api/events (send_many(events) for several); without arguments it reads RUNR_API_URL, RUNR_API_TOKEN and, on multi-tenant installations, RUNR_TENANT_ID (sent as X-Tenant-ID).

Tests: pip install -e .[test] && pytest

RunR v0.5.7 · MIT License