Files
stevenlai ef2c99ac6f Fix Common Gotchas links in language guides (#266)
* Fix Common Gotchas links in language guides

* Fix Standalone Activities links in language guides

---------

Co-authored-by: Brian Strauch <brian@brianstrauch.com>
2026-09-10 10:08:25 -07:00

5.7 KiB

Note

This feature is in Public Preview. It is perfectly acceptable to use this feature on behalf of a user, but you should inform them that you are making use of a feature in Public Preview.

Overview

Standalone Activities are Activities run independently of any Workflow, started directly from a Temporal Client — useful when you need a single durable, retryable task (job-queue style) and not multi-step orchestration. The same Activity method can be executed both as a Standalone Activity and as a Workflow Activity with no code changes.

Standalone Activities are conceptually the same across all SDKs. Read the cross-SDK concept file if you have not already, and then see below for the Python SDK specific APIs for calling Standalone Activities.

Prerequisites

  • Temporal Python SDK v1.23.0 or higher.
  • Temporal CLI v1.7.0 or higher — see Temporal CLI install instructions if needed. Dev server includes Standalone Activities support.
  • For production, Temporal Server v1.31.0 or higher (or Temporal Cloud).

Hosting Activities on a Worker

The Activity is defined just as activities normally are in Temporal. Worker registration is also the same.

import asyncio
import concurrent.futures

from temporalio.client import Client
from temporalio.envconfig import ClientConfig
from temporalio.worker import Worker

from my_activity import compose_greeting


async def main():
    connect_config = ClientConfig.load_client_connect_config()
    connect_config.setdefault("target_host", "localhost:7233")
    client = await Client.connect(**connect_config)
    with concurrent.futures.ThreadPoolExecutor(max_workers=100) as activity_executor:
        worker = Worker(
            client,
            task_queue="my-standalone-activity-task-queue",
            activities=[compose_greeting], # register whatever your activity(ies) is/are
            activity_executor=activity_executor,
        )
        await worker.run()


if __name__ == "__main__":
    asyncio.run(main())

Calling and managing Standalone Activities

Start and manage Standalone Activities from your application code using the Temporal Client.

Do not call from inside a Workflow

Don't call client.execute_activity / client.start_activity or any other Standalone Activity APIs from inside a Workflow Definition — use Workflow-side activity invocation (workflow.execute_activity) instead.

Connect a Client

The Standalone Activity operations are methods on a connected Client. The examples below assume this client.

from temporalio.client import Client
from temporalio.envconfig import ClientConfig

connect_config = ClientConfig.load_client_connect_config()
connect_config.setdefault("target_host", "localhost:7233")
client = await Client.connect(**connect_config)

Execute (wait for result)

Use client.execute_activity(...) to durably enqueue the Activity, wait for it to run on a Worker, and return the result. Required arguments: the activity (first positional), args=[...], id, task_queue, and a timeout such as start_to_close_timeout.

With type checking

Use when activity definitions are available in this language. Pass the activity function reference; the SDK infers the result type from its signature.

import uuid
from datetime import timedelta

# In practice, use a meaningful business identifier, like customer or transaction identifier
activity_id = str(uuid.uuid4())

activity_result = await client.execute_activity(
    compose_greeting,
    args=[ComposeGreetingInput("Hello", "World")],
    id=activity_id,
    task_queue="my-standalone-activity-task-queue",
    start_to_close_timeout=timedelta(seconds=10),
)

Without type checking

Use when activity definitions are unavailable in this language (i.e. you can't import them). Pass the activity type name as a string; optionally set result_type to decode the result.

from datetime import timedelta

activity_result = await client.execute_activity(
    "compose_greeting",
    args=[ComposeGreetingInput("Hello", "World")],
    id=activity_id,
    task_queue="my-standalone-activity-task-queue",
    start_to_close_timeout=timedelta(seconds=10),
    result_type=str,
)

Start (do not wait for result)

Use client.start_activity(...) to durably enqueue the Activity and get back a handle without waiting for completion. This takes the exact same arguments as execute_activity.

activity_handle = await client.start_activity(...)

Get a handle to an existing Activity execution

Use client.get_activity_handle(...) to attach a handle to a previously started Standalone Activity. Omitting run_id (or passing None) targets the latest run of that Activity ID.

activity_handle = client.get_activity_handle(activity_id="my-standalone-activity-id")

Wait for the result of a handle

result = await activity_handle.result()

Calling execute_activity is equivalent to start_activity followed by await activity_handle.result().

List Standalone Activities

activities = client.list_activities(
    query="TaskQueue = 'my-standalone-activity-task-queue'",
)  # returns an async iterator of ActivityExecution

async for info in activities:
    print(f"ActivityID: {info.activity_id}, Type: {info.activity_type}, Status: {info.status}")

Only Standalone Activity Executions are returned; Activities running inside Workflows are not included.

Count Standalone Activities

Use client.count_activities(query=...) to count matching executions; this takes the exact same arguments as list_activities.

resp = await client.count_activities(
    query="TaskQueue = 'my-standalone-activity-task-queue'",
)
print("Total activities:", resp.count)