Metadata-Version: 2.4
Name: dicabrio-durable-sdk
Version: 0.1.0
Summary: Python SDK for the Durable execution engine
Author: dicabrio
License-Expression: MIT
Project-URL: Homepage, https://durable.dicabrio.com
Project-URL: Repository, https://bitbucket.org/dicabriocom/durable
Keywords: durable,workflow,background-jobs
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# dicabrio-durable-sdk

Dependency-free Python 3.10+ SDK for the Durable execution engine.

```bash
pip install dicabrio-durable-sdk
```

## Define a function

```python
from durable import DurableFunction, NonRetriableError


def onboarding(event, step):
    def load_user():
        user_id = event.get("data", {}).get("id")
        if not user_id:
            raise NonRetriableError("user id required", code="USER_ID_REQUIRED")
        return {"id": user_id}

    user = step.run("load-user", load_user)
    step.sleep("cooldown", "3s")
    return {"user": user}


functions = [
    DurableFunction(
        id="onboarding-python",
        trigger={"event": "user.created"},
        retries=2,
        handler=onboarding,
    )
]
```

## Local callback and discovery

The SDK includes dependency-free WSGI and ASGI adapters. A WSGI app on port
3000 is enough for the default `durable dev` discovery:

```python
import os
from durable import wsgi

application = wsgi(
    functions,
    signing_key=os.environ["DURABLE_APP_KEY"],
    app_id=os.environ.get("DURABLE_APP_ID", "app_python_dev"),
    app_name="python-app",
    dev=True,
)
```

Run it with your WSGI server at `/api/durable`, or start the stdlib example:

```bash
DURABLE_APP_KEY=$(openssl rand -hex 24) DURABLE_DEV=1 \
  PYTHONPATH=src python3 example/app.py
```

`dev=True` discloses the local signing key to the discovery request and must
never be enabled in production. The example therefore requires explicit
`DURABLE_DEV=1`. For ASGI, use `application = asgi(functions, ...)`;
synchronous workflow handlers execute in a worker thread so the event loop is
not blocked.

Point Durable elsewhere when the app does not run on port 3000:

```bash
DURABLE_DEV_APP_URL=http://localhost:8000/api/durable npx @dicabrio/durable
```

## Client

```python
import os
from durable import Client, provision_app

credentials = provision_app(
    base_url="https://app.durable.dicabrio.com",
    name="python-app",
    app_url="https://python-app.example/api/durable",
    environment="prod",
    admin_token=os.environ["DURABLE_ADMIN_TOKEN"],
)

client = Client(
    "https://app.durable.dicabrio.com",
    credentials["id"],
    credentials["key"],
)
client.sync(functions)
receipt = client.send("user.created", {"id": "u1"}, event_id="user:u1")
```

Production provisioning is preferably done in the dashboard. The callback URL
is stored on the workspace; it is not sent by `sync()`.

Available client operations: `sync`, `send`, `send_many`, `search_runs`,
`get_run`, `cancel_run`, `rerun`, `rerun_from_step`, `list_failures`,
`retry_failure`, and `get_version_status`. Ambiguous event and rerun failures
expose their original IDs through `EventSendError`, `BulkEventSendError`, and
`RunActionError` so application-level retries remain idempotent.

Available steps: `run`, threaded composite `parallel`, `sleep`,
`wait_for_event`, and transactional `send_event`. Function options include
versions, enable/retire state, retries, callback timeout, failure handlers,
concurrency, priority, throttle, rate limit, debounce, and batch.

A callback may execute again when its response is lost. Keep steps bounded and
use idempotency keys for external side effects.
