Skip to content

Commit ad5d081

Browse files
committed
Conform standalone activity API to cross-language design spec
This commit updates the Python SDK standalone activity implementation to match Maciej's cross-language design specification. - ActivityIdReusePolicy → ActivityIDReusePolicy - ActivityIdConflictPolicy → ActivityIDConflictPolicy - Renamed property 'id' → 'activity_id' - Renamed property 'run_id' → 'activity_run_id' - Updated internal _id and _run_id variables accordingly - Renamed 'run_id' → 'activity_run_id' - Renamed 'schedule_time' → 'scheduled_time' - Added 'namespace' field (copied from calling client) - Removed 'state_transition_count' field - Removed the separate class; all list results now return ActivityExecution - Renamed 'run_id' → 'activity_run_id' - Renamed 'schedule_time' → 'scheduled_time' - Added 'namespace', 'close_time', 'execution_duration' fields - Removed 'state_transition_count' field - Added ActivityExecutionCountAggregationGroup class - Added 'groups' field for aggregation support - Renamed 'ret_type' → 'result_type' - Renamed 'static_summary' → 'summary' - Renamed 'static_details' → 'details' - CancelActivityInput.run_id → activity_run_id - TerminateActivityInput.run_id → activity_run_id - DescribeActivityInput.run_id → activity_run_id - Added 'wait_for_cancel_completed: bool' field - Renamed parameter 'run_id' → 'activity_run_id' - Added 'namespace: str' field - Added 'activity_run_id: str | None' field (None for workflow activities) - Added 'in_workflow' property - Made workflow_id, workflow_namespace, workflow_run_id, workflow_type optional (None for standalone activities) - Deprecated 'workflow_namespace' in favor of 'namespace' - Updated tests/test_activity.py and tests/worker/test_activity.py - Fixed temporalio/contrib/opentelemetry.py - Fixed temporalio/contrib/openai_agents/_mcp.py - GetActivityResultInput and interceptor method - would require significant refactoring of the result caching mechanism in ActivityHandle
1 parent 83b11e7 commit ad5d081

12 files changed

Lines changed: 2824 additions & 269 deletions

File tree

cross-sdk-design.md

Lines changed: 2334 additions & 0 deletions
Large diffs are not rendered by default.

temporalio/activity.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,26 @@ class Info:
105105
heartbeat_details: Sequence[Any]
106106
heartbeat_timeout: timedelta | None
107107
is_local: bool
108+
namespace: str
109+
"""Namespace the activity is running in."""
108110
schedule_to_close_timeout: timedelta | None
109111
scheduled_time: datetime
110112
start_to_close_timeout: timedelta | None
111113
started_time: datetime
112114
task_queue: str
113115
task_token: bytes
114-
workflow_id: str
115-
workflow_namespace: str
116-
workflow_run_id: str
117-
workflow_type: str
116+
workflow_id: str | None
117+
"""ID of the workflow that started this activity. None for standalone activities."""
118+
workflow_namespace: str | None
119+
"""Namespace of the workflow that started this activity. None for standalone activities.
120+
121+
.. deprecated::
122+
Use :py:attr:`namespace` instead.
123+
"""
124+
workflow_run_id: str | None
125+
"""Run ID of the workflow that started this activity. None for standalone activities."""
126+
workflow_type: str | None
127+
"""Type of the workflow that started this activity. None for standalone activities."""
118128
priority: temporalio.common.Priority
119129
retry_policy: temporalio.common.RetryPolicy | None
120130
"""The retry policy of this activity.
@@ -123,14 +133,22 @@ class Info:
123133
If the value is None, it means the server didn't send information about retry policy (e.g. due to old server
124134
version), but it may still be defined server-side."""
125135

136+
activity_run_id: str | None = None
137+
"""Run ID of this standalone activity. None for workflow activities."""
138+
139+
@property
140+
def in_workflow(self) -> bool:
141+
"""Whether this activity was started by a workflow (vs. standalone)."""
142+
return self.workflow_id is not None
143+
126144
# TODO(cretz): Consider putting identity on here for "worker_id" for logger?
127145

128146
def _logger_details(self) -> Mapping[str, Any]:
129147
return {
130148
"activity_id": self.activity_id,
131149
"activity_type": self.activity_type,
132150
"attempt": self.attempt,
133-
"namespace": self.workflow_namespace,
151+
"namespace": self.namespace,
134152
"task_queue": self.task_queue,
135153
"workflow_id": self.workflow_id,
136154
"workflow_run_id": self.workflow_run_id,
@@ -239,7 +257,7 @@ def metric_meter(self) -> temporalio.common.MetricMeter:
239257
info = self.info()
240258
self._metric_meter = self.runtime_metric_meter.with_additional_attributes(
241259
{
242-
"namespace": info.workflow_namespace,
260+
"namespace": info.namespace,
243261
"task_queue": info.task_queue,
244262
"activity_type": info.activity_type,
245263
}

temporalio/api/enums/v1/activity_pb2.py

Lines changed: 53 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""
2+
@generated by mypy-protobuf. Do not edit manually!
3+
isort:skip_file
4+
"""
5+
6+
import builtins
7+
import sys
8+
import typing
9+
10+
import google.protobuf.descriptor
11+
import google.protobuf.internal.enum_type_wrapper
12+
13+
if sys.version_info >= (3, 10):
14+
import typing as typing_extensions
15+
else:
16+
import typing_extensions
17+
18+
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
19+
20+
class _ActivityExecutionStatus:
21+
ValueType = typing.NewType("ValueType", builtins.int)
22+
V: typing_extensions.TypeAlias = ValueType
23+
24+
class _ActivityExecutionStatusEnumTypeWrapper(
25+
google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[
26+
_ActivityExecutionStatus.ValueType
27+
],
28+
builtins.type,
29+
): # noqa: F821
30+
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
31+
ACTIVITY_EXECUTION_STATUS_UNSPECIFIED: _ActivityExecutionStatus.ValueType # 0
32+
ACTIVITY_EXECUTION_STATUS_RUNNING: _ActivityExecutionStatus.ValueType # 1
33+
"""The activity has not reached a terminal status. See PendingActivityState for the run state
34+
(SCHEDULED, STARTED, or CANCEL_REQUESTED).
35+
"""
36+
ACTIVITY_EXECUTION_STATUS_COMPLETED: _ActivityExecutionStatus.ValueType # 2
37+
"""The activity completed successfully. An activity can complete even after cancellation is
38+
requested if the worker calls RespondActivityTaskCompleted before acknowledging cancellation.
39+
"""
40+
ACTIVITY_EXECUTION_STATUS_FAILED: _ActivityExecutionStatus.ValueType # 3
41+
"""The activity failed. Causes:
42+
- Worker returned a non-retryable failure
43+
- RetryPolicy.maximum_attempts exhausted
44+
- Attempt failed after cancellation was requested (retries blocked)
45+
"""
46+
ACTIVITY_EXECUTION_STATUS_CANCELED: _ActivityExecutionStatus.ValueType # 4
47+
"""The activity was canceled. Reached when:
48+
- Cancellation requested while SCHEDULED (immediate), or
49+
- Cancellation requested while STARTED and worker called RespondActivityTaskCanceled.
50+
51+
Workers discover cancellation requests via heartbeat responses (cancel_requested=true).
52+
Activities that do not heartbeat will not learn of cancellation and may complete, fail, or
53+
time out normally. CANCELED requires explicit worker acknowledgment or immediate cancellation
54+
of a SCHEDULED activity.
55+
"""
56+
ACTIVITY_EXECUTION_STATUS_TERMINATED: _ActivityExecutionStatus.ValueType # 5
57+
"""The activity was terminated. Immediate; does not wait for worker acknowledgment."""
58+
ACTIVITY_EXECUTION_STATUS_TIMED_OUT: _ActivityExecutionStatus.ValueType # 6
59+
"""The activity timed out. See TimeoutType for the specific timeout.
60+
- SCHEDULE_TO_START and SCHEDULE_TO_CLOSE timeouts always result in TIMED_OUT.
61+
- START_TO_CLOSE and HEARTBEAT may retry if RetryPolicy permits; TIMED_OUT is
62+
reached when retry is blocked (RetryPolicy.maximum_attempts exhausted,
63+
SCHEDULE_TO_CLOSE would be exceeded, or cancellation has been requested).
64+
"""
65+
66+
class ActivityExecutionStatus(
67+
_ActivityExecutionStatus, metaclass=_ActivityExecutionStatusEnumTypeWrapper
68+
):
69+
"""Status of a standalone activity.
70+
The status is updated once, when the activity is originally scheduled, and again when the activity reaches a terminal
71+
status.
72+
(-- api-linter: core::0216::synonyms=disabled
73+
aip.dev/not-precedent: Named consistently with WorkflowExecutionStatus. --)
74+
"""
75+
76+
ACTIVITY_EXECUTION_STATUS_UNSPECIFIED: ActivityExecutionStatus.ValueType # 0
77+
ACTIVITY_EXECUTION_STATUS_RUNNING: ActivityExecutionStatus.ValueType # 1
78+
"""The activity has not reached a terminal status. See PendingActivityState for the run state
79+
(SCHEDULED, STARTED, or CANCEL_REQUESTED).
80+
"""
81+
ACTIVITY_EXECUTION_STATUS_COMPLETED: ActivityExecutionStatus.ValueType # 2
82+
"""The activity completed successfully. An activity can complete even after cancellation is
83+
requested if the worker calls RespondActivityTaskCompleted before acknowledging cancellation.
84+
"""
85+
ACTIVITY_EXECUTION_STATUS_FAILED: ActivityExecutionStatus.ValueType # 3
86+
"""The activity failed. Causes:
87+
- Worker returned a non-retryable failure
88+
- RetryPolicy.maximum_attempts exhausted
89+
- Attempt failed after cancellation was requested (retries blocked)
90+
"""
91+
ACTIVITY_EXECUTION_STATUS_CANCELED: ActivityExecutionStatus.ValueType # 4
92+
"""The activity was canceled. Reached when:
93+
- Cancellation requested while SCHEDULED (immediate), or
94+
- Cancellation requested while STARTED and worker called RespondActivityTaskCanceled.
95+
96+
Workers discover cancellation requests via heartbeat responses (cancel_requested=true).
97+
Activities that do not heartbeat will not learn of cancellation and may complete, fail, or
98+
time out normally. CANCELED requires explicit worker acknowledgment or immediate cancellation
99+
of a SCHEDULED activity.
100+
"""
101+
ACTIVITY_EXECUTION_STATUS_TERMINATED: ActivityExecutionStatus.ValueType # 5
102+
"""The activity was terminated. Immediate; does not wait for worker acknowledgment."""
103+
ACTIVITY_EXECUTION_STATUS_TIMED_OUT: ActivityExecutionStatus.ValueType # 6
104+
"""The activity timed out. See TimeoutType for the specific timeout.
105+
- SCHEDULE_TO_START and SCHEDULE_TO_CLOSE timeouts always result in TIMED_OUT.
106+
- START_TO_CLOSE and HEARTBEAT may retry if RetryPolicy permits; TIMED_OUT is
107+
reached when retry is blocked (RetryPolicy.maximum_attempts exhausted,
108+
SCHEDULE_TO_CLOSE would be exceeded, or cancellation has been requested).
109+
"""
110+
global___ActivityExecutionStatus = ActivityExecutionStatus
111+
112+
class _ActivityIdReusePolicy:
113+
ValueType = typing.NewType("ValueType", builtins.int)
114+
V: typing_extensions.TypeAlias = ValueType
115+
116+
class _ActivityIdReusePolicyEnumTypeWrapper(
117+
google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[
118+
_ActivityIdReusePolicy.ValueType
119+
],
120+
builtins.type,
121+
): # noqa: F821
122+
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
123+
ACTIVITY_ID_REUSE_POLICY_UNSPECIFIED: _ActivityIdReusePolicy.ValueType # 0
124+
ACTIVITY_ID_REUSE_POLICY_ALLOW_DUPLICATE: _ActivityIdReusePolicy.ValueType # 1
125+
"""Always allow starting an activity using the same activity ID."""
126+
ACTIVITY_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY: (
127+
_ActivityIdReusePolicy.ValueType
128+
) # 2
129+
"""Allow starting an activity using the same ID only when the last activity's final state is one
130+
of {failed, canceled, terminated, timed out}.
131+
"""
132+
ACTIVITY_ID_REUSE_POLICY_REJECT_DUPLICATE: _ActivityIdReusePolicy.ValueType # 3
133+
"""Do not permit re-use of the ID for this activity. Future start requests could potentially change the policy,
134+
allowing re-use of the ID.
135+
"""
136+
137+
class ActivityIdReusePolicy(
138+
_ActivityIdReusePolicy, metaclass=_ActivityIdReusePolicyEnumTypeWrapper
139+
):
140+
"""Defines whether to allow re-using an activity ID from a previously *closed* activity.
141+
If the request is denied, the server returns an `ActivityExecutionAlreadyStarted` error.
142+
143+
See `ActivityIdConflictPolicy` for handling ID duplication with a *running* activity.
144+
"""
145+
146+
ACTIVITY_ID_REUSE_POLICY_UNSPECIFIED: ActivityIdReusePolicy.ValueType # 0
147+
ACTIVITY_ID_REUSE_POLICY_ALLOW_DUPLICATE: ActivityIdReusePolicy.ValueType # 1
148+
"""Always allow starting an activity using the same activity ID."""
149+
ACTIVITY_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY: (
150+
ActivityIdReusePolicy.ValueType
151+
) # 2
152+
"""Allow starting an activity using the same ID only when the last activity's final state is one
153+
of {failed, canceled, terminated, timed out}.
154+
"""
155+
ACTIVITY_ID_REUSE_POLICY_REJECT_DUPLICATE: ActivityIdReusePolicy.ValueType # 3
156+
"""Do not permit re-use of the ID for this activity. Future start requests could potentially change the policy,
157+
allowing re-use of the ID.
158+
"""
159+
global___ActivityIdReusePolicy = ActivityIdReusePolicy
160+
161+
class _ActivityIdConflictPolicy:
162+
ValueType = typing.NewType("ValueType", builtins.int)
163+
V: typing_extensions.TypeAlias = ValueType
164+
165+
class _ActivityIdConflictPolicyEnumTypeWrapper(
166+
google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[
167+
_ActivityIdConflictPolicy.ValueType
168+
],
169+
builtins.type,
170+
): # noqa: F821
171+
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
172+
ACTIVITY_ID_CONFLICT_POLICY_UNSPECIFIED: _ActivityIdConflictPolicy.ValueType # 0
173+
ACTIVITY_ID_CONFLICT_POLICY_FAIL: _ActivityIdConflictPolicy.ValueType # 1
174+
"""Don't start a new activity; instead return `ActivityExecutionAlreadyStarted` error."""
175+
ACTIVITY_ID_CONFLICT_POLICY_USE_EXISTING: _ActivityIdConflictPolicy.ValueType # 2
176+
"""Don't start a new activity; instead return a handle for the running activity."""
177+
178+
class ActivityIdConflictPolicy(
179+
_ActivityIdConflictPolicy, metaclass=_ActivityIdConflictPolicyEnumTypeWrapper
180+
):
181+
"""Defines what to do when trying to start an activity with the same ID as a *running* activity.
182+
Note that it is *never* valid to have two running instances of the same activity ID.
183+
184+
See `ActivityIdReusePolicy` for handling activity ID duplication with a *closed* activity.
185+
"""
186+
187+
ACTIVITY_ID_CONFLICT_POLICY_UNSPECIFIED: ActivityIdConflictPolicy.ValueType # 0
188+
ACTIVITY_ID_CONFLICT_POLICY_FAIL: ActivityIdConflictPolicy.ValueType # 1
189+
"""Don't start a new activity; instead return `ActivityExecutionAlreadyStarted` error."""
190+
ACTIVITY_ID_CONFLICT_POLICY_USE_EXISTING: ActivityIdConflictPolicy.ValueType # 2
191+
"""Don't start a new activity; instead return a handle for the running activity."""
192+
global___ActivityIdConflictPolicy = ActivityIdConflictPolicy

0 commit comments

Comments
 (0)