mrkeyoor.com_
Tue 22 Sept 18:49 UTC
PyPIInfraupdated 22 Sept 2026

temporalio review

temporalio is Temporal's Python SDK for business processes that must resume after worker crashes, deployments, or long waits. A Temporal service stores event history; workers replay deterministic workflow functions and run side effects in retryable activities. Version 1.32.0 adds deterministic `workflow.uuid7()`, replay-safe OpenTelemetry meter and logger wrappers, configurable Core log formats, payload-validation errors, and a bounded Pydantic type-adapter cache. It also changes experimental OpenAI Agents and Nexus behavior. The SDK alone is not a queue: it needs a Temporal Cloud or self-hosted service plus continuously polling workers.

Verdict

temporalio 1.31.0 installed in 0.7 seconds and used 56 MB across 5 packages with 0 audit findings in our sandbox; PyPI has since moved to 1.32.0. Adopt it when business state must survive failures and long waits, and avoid the service plus replay model for ordinary queued jobs.

We installed it

Lab card: what happened when we installed temporalioScreenshot of temporalio documentation
Install✓ · 0.7s5 packages on disk · 56 MB
Importimport temporalio in 0.45s · compiled extensions · py.typed · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does temporalio install cleanly?

Yes. In a fresh container with an empty cache, pip install temporalio finished in 0.7s, leaving 5 packages and 56 MB on disk. pip-audit reported no known vulnerabilities.

What does temporalio need to run?

Python >=3.10, and a platform wheel with compiled extensions. In our run import temporalio succeeded in 0.45s, and the package ships py.typed for type checkers.

temporalio or celery: which should you use?

celery: Choose it for broker-backed jobs when durable event-history replay and workflow signals are unnecessary. temporalio 1.31.0 installed in 0.7 seconds and used 56 MB across 5 packages with 0 audit findings in our sandbox; PyPI has since moved to 1.32.0.

When should you not use temporalio?

You only need fire-and-forget background tasks. Celery has fewer concepts and does not require workflow history replay.

API stability4/5Client, Worker, workflow and activity decorators, signals, queries, updates, retry policies, and history replay follow the same Temporal model across SDK releases. Compatibility is stricter than a normal Python import contract because deployed workflow code must replay old histories. Version 1.32.0 also contains breaking changes in experimental OpenAI Agents integration, so stable core APIs and preview integrations need different upgrade policies.
Docs5/5Temporal publishes a Python development guide, generated package reference, runnable samples, Cloud and self-host connection instructions, determinism restrictions, activity guidance, worker tuning, testing, replay, payload conversion, encryption, observability, and deployment versioning. The repository README adds lower-level details such as clients not surviving forks and the single typed workflow argument rule. The difficult part is learning the system model, which no short quick start can remove.
Maintenance5/5Version 1.32.0 was released on August 24, 2026, and GitHub shows another repository push on August 25. The unarchived project has 1,169 stars and 98 open issues and pull requests in the combined counter. The release includes deterministic UUIDv7, replay-safe telemetry, Pydantic decode caching, worker heartbeat controls, logging formats, and several Core replay and cancellation fixes, showing active first-party work across Python and shared runtime code.
Ecosystem5/5The supplied weekly figure is 8,461,685 PyPI downloads. The SDK works with Temporal Cloud and self-hosted clusters, testing servers, replay tools, codecs, Pydantic conversion, OpenTelemetry, interceptors, Nexus, and worker deployment controls. Temporal SDKs in other languages share workflow and service concepts, which helps polyglot systems. Most of that value only appears after adopting the server, operations tooling, and deterministic-development rules.

Use it if

  • A payment, fulfillment, approval, or onboarding process must preserve progress across outages and deploys.
  • The design needs durable timers, signals, queries, updates, child workflows, retries, or compensation with recorded history.
  • The team can operate a Temporal namespace, task queues, workers, workflow IDs, and deployment compatibility.
  • External I/O can live in idempotent activities while workflow code stays deterministic.
Skip it if

Setup reality

We installed temporalio 1.31.0 in a fresh Python 3.12 Bookworm sandbox. The install took 0.7 seconds, produced 5 packages, and occupied 56 MB. That release declared 23 direct dependencies, required Python 3.10 or newer, and shipped compiled .so files plus py.typed. import temporalio succeeded in 0.45 seconds, and pip-audit found 0 known vulnerabilities. The license was MIT. PyPI now serves 1.32.0, so none of those measured sizes or timings describe the newer wheel.

A client needs a service address and namespace. Production commonly adds TLS plus an API key or mTLS certificate. A worker must reach that service and poll the exact task_queue used when a workflow starts. Choose workflow IDs around business uniqueness to prevent duplicate starts. Arguments and results become history payloads, so keep credentials and large blobs out unless a reviewed codec or external payload store protects them.

Workflow functions can replay many times. Use workflow.now(), workflow.random(), and workflow.uuid4() or the new 1.32.0 workflow.uuid7() instead of ordinary time and randomness. HTTP, SQL, file access, and third-party SDK calls belong in activities. Replay captured production histories before deployment, and use versioning APIs for command changes. The sandbox helps detect determinism mistakes; it is not a security boundary.

An async activity must not block the event loop. Synchronous activities need an executor sized for worker concurrency. Set an activity timeout, choose retries around duplicate-safe effects, and heartbeat long jobs so cancellation and progress work. Worker slots, pollers, sticky workflow cache, shutdown grace, and actual I/O latency determine capacity. Version 1.32.0 also warns when a workflow task exceeds 5 seconds by default, which is a signal to move blocking or CPU work out of the workflow.

Patterns

Put payment I/O in an activity define-idempotent-activity

from temporalio import activity

@activity.defn
async def charge_order(order_id: str) -> str:
    return await payments.charge(
        order_id,
        idempotency_key=order_id,
    )

An activity can execute more than once, so the payment API needs a stable idempotency key.

Run an activity from a workflow define-workflow

from datetime import timedelta
from temporalio import workflow

with workflow.unsafe.imports_passed_through():
    from activities import charge_order

@workflow.defn
class Checkout:
    @workflow.run
    async def run(self, order_id: str) -> str:
        return await workflow.execute_activity(
            charge_order,
            order_id,
            start_to_close_timeout=timedelta(minutes=2),
        )

Workflow code must replay deterministically; the activity contains the external side effect.

Connect to a secured namespace connect-secure-client

from temporalio.client import Client

client = await Client.connect(
    'cluster.example.com:7233',
    namespace='payments',
    tls=True,
    api_key=temporal_api_key,
)

Keep the API key in worker or client configuration, never in workflow arguments that become history.

Poll the payment task queue run-task-queue-worker

from temporalio.worker import Worker

worker = Worker(
    client,
    task_queue='payments',
    workflows=[Checkout],
    activities=[charge_order],
)
await worker.run()

A worker can only execute types it registers, and its task queue must match the workflow start request.

Use a business-specific workflow ID start-unique-workflow

handle = await client.start_workflow(
    Checkout.run,
    'order-123',
    id='checkout-order-123',
    task_queue='payments',
)
result = await handle.result()

Workflow ID reuse and conflict policies decide what a duplicate business request does; choose them deliberately.

Wait for an approval signal receive-workflow-signal

@workflow.defn
class Approval:
    def __init__(self) -> None:
        self.approved = False

    @workflow.run
    async def run(self) -> None:
        await workflow.wait_condition(lambda: self.approved)

    @workflow.signal
    def approve(self) -> None:
        self.approved = True

A signal is durable and asynchronous. Authorization belongs in the service that sends it through a workflow handle.

Reject an invalid update validate-workflow-update

@workflow.update
def set_limit(self, value: int) -> int:
    previous = self.limit
    self.limit = value
    return previous

@set_limit.validator
def validate_limit(self, value: int) -> None:
    if value < 0:
        raise ValueError('limit must be non-negative')

The validator must be deterministic and cannot change workflow state; the update handler may mutate state and return a result.

Create a sortable UUID during replay generate-deterministic-uuid7

from temporalio import workflow

request_id = workflow.uuid7()

workflow.uuid7() is new in 1.32.0 and derives from workflow time plus deterministic randomness. Standard-library uuid7 is restricted in the sandbox.

Cap retries for a permanent effect bound-activity-retries

from datetime import timedelta
from temporalio.common import RetryPolicy

result = await workflow.execute_activity(
    send_invoice,
    invoice,
    start_to_close_timeout=timedelta(seconds=30),
    retry_policy=RetryPolicy(
        initial_interval=timedelta(seconds=1),
        maximum_attempts=5,
    ),
)

Five attempts is a business decision, not a safe default. Mark permanent application failures non-retryable.

Checkpoint progress and receive cancellation heartbeat-long-activity

from temporalio import activity

@activity.defn
def import_rows(rows: list[str]) -> int:
    start = next(iter(activity.info().heartbeat_details), 0)
    for index, row in enumerate(rows[start:], start=start):
        write_row(row)
        activity.heartbeat(index + 1)
    return len(rows)

Schedule this with heartbeat_timeout. A synchronous activity observes cancellation when it heartbeats.

Serialize Pydantic 2 models use-pydantic-converter

from temporalio.client import Client
from temporalio.contrib.pydantic import pydantic_data_converter

client = await Client.connect(
    'localhost:7233',
    data_converter=pydantic_data_converter,
)

The built-in converter supports Pydantic 2, not Pydantic 1. Version 1.32.0 caches up to 1024 type adapters per converter by default.

Test new code against stored history replay-production-history

from temporalio.client import WorkflowHistory
from temporalio.worker import Replayer

history = WorkflowHistory.from_json(workflow_id, history_json)
await Replayer(workflows=[Checkout]).replay_workflow(history)

Replay representative production histories before a deploy to catch nondeterministic command changes.

Alternatives

PackageRegistryPick it when
celeryPyPIChoose it for broker-backed jobs when durable event-history replay and workflow signals are unnecessary.
prefectPyPIChoose it when Python data pipelines, scheduling, retries, and an operations UI are the main problem.
durabletaskPyPIChoose it when an existing Microsoft Durable Task backend and its orchestration model are already part of the stack.

More infra guides

boto3 · opentelemetry-api · psutil · distro · @opentelemetry/api · google-cloud-storage · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.