Metadata-Version: 2.4
Name: getratchet
Version: 0.2.2
Summary: Durable tool execution, bounded retries and workflow recovery with customer-owned Python workers
License: MIT
Project-URL: Homepage, https://getratchet.app
Project-URL: Documentation, https://getratchet.app/docs/python
Project-URL: Issues, https://github.com/wauul/getratchet/issues
Keywords: durable-execution,background-jobs,api-retries,retry-policy,circuit-breaker,idempotency,workflow-recovery
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# GetRatchet Python SDK

[SDK documentation](https://getratchet.app/docs/python) · [Retry decisions](https://getratchet.app/guides/retryable-http-status-codes) · [Idempotency guide](https://getratchet.app/guides/idempotency-api-retries)

For operator and CI workflows, the Node.js [`ratchet` CLI](../packages/cli/README.md) uses the same API for contracts, synthetic tests, runs, replay/cancellation and endpoint policies. Python handlers still run only in your separate worker.

Install with `pip install getratchet`. For local development, use `pip install ./python`. The package uses only the Python standard library and supports Python 3.10+.

```python
from getratchet import Ratchet

ratchet = Ratchet("https://getratchet.app", "YOUR_PRODUCER_API_KEY")
run_id = ratchet.start_run("customer-onboarding", project_id="YOUR_PROJECT_ID", environment_id="YOUR_ENVIRONMENT_ID")
job = ratchet.enqueue(
    "send_welcome_email", "1", {"customerId": "cus_42"},
    idempotency_key="welcome:cus_42",
)
ratchet.finish_run()
print(run_id, job["stepId"])
```

Run a separate, long-lived worker process with a **WORKER** scoped API key. Register handlers before starting the worker. Restarting a worker registers it again; leased jobs return to the queue after the lease expires. Register the exact version used by queued jobs.

```python
from getratchet import Ratchet

worker = Ratchet("https://getratchet.app", "YOUR_WORKER_API_KEY")

def send_welcome(payload, context):
    context.check_cancelled()
    # Pass context.idempotency_key to the mail/payment provider if it supports one.
    return {"providerMessageId": send_mail(payload, idempotency_key=context.idempotency_key)}

worker.register_tool("send_welcome_email", "1", send_welcome)
worker.worker.start(concurrency=4)
```

Use `worker.worker.stop()` for graceful shutdown. Handlers should call `context.check_cancelled()` around expensive or irreversible operations. Python cannot forcibly terminate a running thread; a handler that blocks forever also blocks worker shutdown. Set explicit timeouts on downstream network calls. A failure between an external side effect and the job report can repeat that effect, so pass the stable idempotency key to the destination service or record it there. Raising `NonRetryableError` prevents another attempt; other handler errors are retried under the endpoint policy.

Use `ratchet.replay_preview(step_id)` to review replay risk before calling `ratchet.replay(step_id, reason=...)`. Replay can run an irreversible action again and should use a fresh destination-side deduplication decision.

Pass `after_step_id=primary["stepId"], after_status="FAILED"` to `enqueue` for a fallback, or `after_status="SUCCEEDED"` for a follow-up. Enqueue dependencies before `finish_run()`. Independent calls may run in parallel. Unused branches are marked `SKIPPED` without invoking a handler.


## Durable retry policies and error decisions

Durable jobs support STANDARD (5 attempts, 30s initial, 15m cap), AGGRESSIVE (8 attempts, 5s initial, 5m cap), RELAXED (3 attempts, 120s initial, 30m cap), and CUSTOM. Built-ins double their delay with ±20% jitter; CUSTOM supplies `maxAttempts`, `initialDelayMs`, `multiplier`, `maxDelayMs`, and `jitterPercent`. Counts include the first execution. Caps precede jitter; the final safety limit is 24 hours.

A selected tool contract overrides the endpoint policy. Explicit enqueue `maxAttempts` overrides only the attempt count. Each new job snapshots the complete policy; later endpoint edits or contract publication do not change its policy or saved due time. Existing jobs without snapshot data keep the old preset behavior. Synthetic tests remain one attempt.

TypeScript handlers can throw `RetryableError(message, { retryAfterMs })` or `NonRetryableError(message)` and register `classifyError(error, context)` for third-party failures. Python provides `RetryableError(message, retry_after_ms=...)`, `NonRetryableError`, and `classify_error`. Unknown errors default to retryable; schema failures and cancellation remain non-retryable. A classifier failure is reported only to the local error callback and falls back to retryable.

Retry hints must be integer milliseconds from 1,000 to 86,400,000. The persisted delay is the greater of the normal jittered policy delay and the hint, capped at 24 hours; the hint itself is never jittered. An open circuit, pause, concurrency gate or attempt limit is never bypassed. Error objects, stacks and classifier metadata are not sent to the API.

Execution is **at least once**: a crash or lost report can repeat a completed side effect. Pass the stable job idempotency key to the destination and require destination-side deduplication for irreversible writes.

See [custom policies and provider examples](../docs/worker.md#custom-durable-retry-policies) for endpoint/tool publication, validation limits, HTTP 429 with Retry-After, HTTP 503, non-retryable HTTP 400 validation failures, and unknown-error fallback.


### Automatically generated idempotency keys

When omitted, the durable key hashes the run ID, tool name, handler version, selected contract version (or null), effective endpoint (defaults to the tool name), dependency step/status (or null), and input. Nested JSON object keys are sorted; array order is significant. Equivalent operations in one SDK therefore reuse a key regardless of object insertion order. TypeScript process-bound wrappers also include the effective endpoint. Explicit customer keys are never changed.

TypeScript and Python use the same operation fields and canonical JSON ordering, but native numeric serialization can differ between languages (for example Python `1.0` versus JavaScript `1`). Use an explicit shared key when producers in different languages must deduplicate the same business operation. Generated keys changed in this release; carry forward explicit keys for operations queued by older SDKs. Selecting an implicit contract does not pin future publications: supply a contract version for a fixed operation identity.
