Skip to main content

Activities

Activities are where non-deterministic work happens: database calls, HTTP requests, file I/O, and anything else that touches the outside world.

Defining activities

Use @activity.defn() for standalone functions:

from cadence import activity

@activity.defn()
async def fetch_order(order_id: str) -> dict:
# HTTP call, DB query, etc.
return {"id": order_id, "status": "pending"}

@activity.defn()
def send_email(to: str, subject: str) -> None:
# synchronous activities work too
...

Use name= to give the activity an explicit name:

@activity.defn(name="fetch-order-v2")
async def fetch_order_v2(order_id: str) -> dict:
...

Method activities

Group related activities into a class using @activity.method():

from cadence import activity

class OrderActivities:
@activity.method()
async def fetch_order(self, order_id: str) -> dict:
...

@activity.method()
async def update_status(self, order_id: str, status: str) -> None:
...

Register an instance with the registry:

registry.register_activities(OrderActivities())

Registering activities

from cadence.worker import Registry

registry = Registry()

# Standalone function
registry.register_activity(fetch_order)

# All methods on an instance
registry.register_activities(OrderActivities())

Or use the registry decorator directly:

@registry.activity()
async def process_payment(amount: float) -> str:
...

Executing activities from a workflow

Inside a workflow, call execute_activity with the activity name, expected return type, arguments, and options:

from datetime import timedelta
from cadence.workflow import execute_activity

@registry.workflow()
class OrderWorkflow:
@workflow.run
async def run(self, order_id: str) -> str:
order = await execute_activity(
"fetch_order",
dict,
order_id,
start_to_close_timeout=timedelta(minutes=5),
)
await execute_activity(
"send_email",
type(None),
order["email"],
"Your order is ready",
start_to_close_timeout=timedelta(seconds=30),
)
return "done"

Activity options

OptionDescription
start_to_close_timeoutMax time for one activity attempt (required if schedule_to_close_timeout is not set)
schedule_to_close_timeoutMax total time including scheduling and all retries
schedule_to_start_timeoutMax time waiting in the task list before execution starts
heartbeat_timeoutMax time between heartbeats for long-running activities
task_listOverride the task list for this activity
retry_policyRetry policy (see Retries)

Heartbeating

Long-running activities should call activity.heartbeat() periodically so the server can detect failures and reschedule:

from cadence import activity
import asyncio

@activity.defn()
async def process_large_file(file_path: str) -> int:
rows_processed = 0
with open(file_path) as f:
for line in f:
process_line(line)
rows_processed += 1
if rows_processed % 1000 == 0:
activity.heartbeat(rows_processed) # report progress
await asyncio.sleep(0) # yield control
return rows_processed

Pass progress details to heartbeat() and retrieve them on restart with activity.heartbeat_details().

Activity context

Inside an activity, use the activity module to access context:

@activity.defn()
async def my_activity() -> None:
info = activity.info()
print(info.activity_id)
print(info.workflow_id)
print(info.workflow_run_id)
print(info.attempt)
print(info.heartbeat_timeout)