Add child workflow versioning overrides (#1944) · temporalio/sdk-python@dca3a00 · GitHub
Skip to content

Commit dca3a00

Browse files
Add child workflow versioning overrides (#1944)
* Add child workflow versioning overrides * Mark child versioning interceptor override experimental * Decode one-time Nexus versioning overrides * Run workflow activation tests off loop
1 parent 93cb530 commit dca3a00

11 files changed

Lines changed: 490 additions & 2 deletions

File tree

‎changelog/added/dancing-otter.md‎

Lines changed: 1 addition & 0 deletions

‎temporalio/common.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,6 +1314,29 @@ def _to_proto(self) -> temporalio.api.workflow.v1.VersioningOverride:
13141314
)
13151315

13161316

1317+
@dataclass(frozen=True)
1318+
class OneTimeVersioningOverride(VersioningOverride):
1319+
"""Route workflow tasks to a specific deployment version until a task completes
1320+
on that version, then clear the override.
1321+
1322+
The worker's reported versioning behavior governs subsequent tasks. This does
1323+
not pin the workflow to the target version.
1324+
1325+
.. warning::
1326+
This API is experimental and requires Temporal Server 1.32 or newer.
1327+
"""
1328+
1329+
target_version: WorkerDeploymentVersion
1330+
1331+
def _to_proto(self) -> temporalio.api.workflow.v1.VersioningOverride:
1332+
"""Convert to proto representation."""
1333+
return temporalio.api.workflow.v1.VersioningOverride(
1334+
one_time=temporalio.api.workflow.v1.VersioningOverride.OneTimeOverride(
1335+
target_deployment_version=self.target_version._to_proto(),
1336+
),
1337+
)
1338+
1339+
13171340
# Should be set as the "arg" argument for _arg_or_args checks where the argument
13181341
# is unset. This is different than None which is a legitimate argument.
13191342
_arg_unset = object()

‎temporalio/exceptions.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ def __init__(
7878
self.first_run_id = first_run_id
7979

8080

81+
class InvalidVersioningOverrideError(FailureError):
82+
"""Thrown by a workflow when a child cannot start because its versioning
83+
override is invalid.
84+
"""
85+
86+
def __init__(self) -> None:
87+
"""Initialize an invalid versioning override error."""
88+
super().__init__("Invalid versioning override")
89+
90+
8191
class ActivityAlreadyStartedError(FailureError):
8292
"""Thrown by a client when an activity execution has already started.
8393

‎temporalio/nexus/system/_support.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,16 @@ def versioning_override_from_proto(
263263
build_id=version.build_id,
264264
)
265265
)
266+
if proto.HasField("one_time") and proto.one_time.HasField(
267+
"target_deployment_version"
268+
):
269+
version = proto.one_time.target_deployment_version
270+
return temporalio.common.OneTimeVersioningOverride(
271+
temporalio.common.WorkerDeploymentVersion(
272+
deployment_name=version.deployment_name,
273+
build_id=version.build_id,
274+
)
275+
)
266276
if proto.pinned_version:
267277
return temporalio.common.PinnedVersioningOverride(
268278
temporalio.common.WorkerDeploymentVersion.from_canonical_string(

‎temporalio/worker/_interceptor.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,12 @@ class StartChildWorkflowInput:
311311
versioning_intent: VersioningIntent | None
312312
static_summary: str | None
313313
static_details: str | None
314+
versioning_override: temporalio.common.VersioningOverride | None = field(
315+
default=None, kw_only=True
316+
)
317+
""".. warning::
318+
Child workflow versioning overrides are experimental and may change without notice.
319+
"""
314320
event_groups: Sequence[temporalio.workflow.EventGroup] | None = field(
315321
default=None, kw_only=True
316322
)

‎temporalio/worker/_workflow_instance.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,13 @@ def _apply_resolve_child_workflow_execution_start(
987987
job.failed.workflow_id, job.failed.workflow_type
988988
)
989989
)
990+
elif (
991+
job.failed.cause
992+
== temporalio.bridge.proto.child_workflow.StartChildWorkflowExecutionFailedCause.START_CHILD_WORKFLOW_EXECUTION_FAILED_CAUSE_INVALID_VERSIONING_OVERRIDE
993+
):
994+
handle._resolve_failure(
995+
temporalio.exceptions.InvalidVersioningOverrideError()
996+
)
990997
else:
991998
handle._resolve_failure(
992999
RuntimeError(f"Unknown child start fail cause: {job.failed.cause}")
@@ -1655,6 +1662,7 @@ async def workflow_start_child_workflow(
16551662
static_details: str | None = None,
16561663
event_groups: Sequence[temporalio.workflow.EventGroup] | None = None,
16571664
priority: temporalio.common.Priority = temporalio.common.Priority.default,
1665+
versioning_override: temporalio.common.VersioningOverride | None = None,
16581666
) -> temporalio.workflow.ChildWorkflowHandle[Any, Any]:
16591667
# Use definition if callable
16601668
name: str
@@ -1692,6 +1700,7 @@ async def workflow_start_child_workflow(
16921700
arg_types=arg_types,
16931701
ret_type=ret_type,
16941702
versioning_intent=versioning_intent,
1703+
versioning_override=versioning_override,
16951704
static_summary=static_summary,
16961705
static_details=static_details,
16971706
event_groups=event_groups,
@@ -3667,6 +3676,8 @@ def _apply_start_command(self) -> None:
36673676
)
36683677
if self._input.versioning_intent:
36693678
v.versioning_intent = self._input.versioning_intent._to_proto()
3679+
if self._input.versioning_override:
3680+
v.versioning_override.CopyFrom(self._input.versioning_override._to_proto())
36703681
if self._input.static_summary:
36713682
command.user_metadata.summary.CopyFrom(
36723683
self._payload_converter.to_payload(self._input.static_summary)

‎temporalio/workflow/_context.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ async def workflow_start_child_workflow(
463463
static_details: str | None = None,
464464
event_groups: Sequence[EventGroup] | None = None,
465465
priority: temporalio.common.Priority = temporalio.common.Priority.default,
466+
versioning_override: temporalio.common.VersioningOverride | None = None,
466467
) -> ChildWorkflowHandle[Any, Any]: ...
467468

468469
@abstractmethod

‎temporalio/workflow/_workflow_ops.py‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ class ChildWorkflowConfig(TypedDict, total=False):
164164
"""TypedDict of config that can be used for :py:func:`start_child_workflow`
165165
and :py:func:`execute_child_workflow`.
166166
167-
``event_groups`` is experimental and may change without notice.
167+
``event_groups`` and ``versioning_override`` are experimental and may change
168+
without notice.
168169
"""
169170

170171
id: str | None
@@ -182,6 +183,7 @@ class ChildWorkflowConfig(TypedDict, total=False):
182183
temporalio.common.SearchAttributes | temporalio.common.TypedSearchAttributes
183184
)
184185
versioning_intent: VersioningIntent | None
186+
versioning_override: temporalio.common.VersioningOverride | None
185187
static_summary: str | None
186188
static_details: str | None
187189
event_groups: Sequence[EventGroup] | None
@@ -213,6 +215,7 @@ async def start_child_workflow(
213215
static_details: str | None = None,
214216
event_groups: Sequence[EventGroup] | None = None,
215217
priority: temporalio.common.Priority = temporalio.common.Priority.default,
218+
versioning_override: temporalio.common.VersioningOverride | None = None,
216219
) -> ChildWorkflowHandle[SelfType, ReturnType]: ...
217220

218221

@@ -242,6 +245,7 @@ async def start_child_workflow(
242245
static_details: str | None = None,
243246
event_groups: Sequence[EventGroup] | None = None,
244247
priority: temporalio.common.Priority = temporalio.common.Priority.default,
248+
versioning_override: temporalio.common.VersioningOverride | None = None,
245249
) -> ChildWorkflowHandle[SelfType, ReturnType]: ...
246250

247251

@@ -271,6 +275,7 @@ async def start_child_workflow(
271275
static_details: str | None = None,
272276
event_groups: Sequence[EventGroup] | None = None,
273277
priority: temporalio.common.Priority = temporalio.common.Priority.default,
278+
versioning_override: temporalio.common.VersioningOverride | None = None,
274279
) -> ChildWorkflowHandle[SelfType, ReturnType]: ...
275280

276281

@@ -302,6 +307,7 @@ async def start_child_workflow(
302307
static_details: str | None = None,
303308
event_groups: Sequence[EventGroup] | None = None,
304309
priority: temporalio.common.Priority = temporalio.common.Priority.default,
310+
versioning_override: temporalio.common.VersioningOverride | None = None,
305311
) -> ChildWorkflowHandle[Any, Any]: ...
306312

307313

@@ -331,6 +337,7 @@ async def start_child_workflow(
331337
static_details: str | None = None,
332338
event_groups: Sequence[EventGroup] | None = None,
333339
priority: temporalio.common.Priority = temporalio.common.Priority.default,
340+
versioning_override: temporalio.common.VersioningOverride | None = None,
334341
) -> ChildWorkflowHandle[Any, Any]:
335342
"""Start a child workflow and return its handle.
336343
@@ -371,6 +378,9 @@ async def start_child_workflow(
371378
addition to those active in the current scope. See
372379
:py:func:`temporalio.workflow.create_event_group`.
373380
priority: Priority to use for this workflow.
381+
versioning_override: Experimental override of the child workflow's versioning
382+
behavior. Takes precedence over versioning inherited from the parent
383+
workflow. Requires Temporal Server 1.32 or newer.
374384
375385
Returns:
376386
A workflow handle to the started/existing workflow.
@@ -397,6 +407,7 @@ async def start_child_workflow(
397407
static_details=static_details,
398408
event_groups=event_groups,
399409
priority=priority,
410+
versioning_override=versioning_override,
400411
)
401412

402413

@@ -425,6 +436,7 @@ async def execute_child_workflow(
425436
static_details: str | None = None,
426437
event_groups: Sequence[EventGroup] | None = None,
427438
priority: temporalio.common.Priority = temporalio.common.Priority.default,
439+
versioning_override: temporalio.common.VersioningOverride | None = None,
428440
) -> ReturnType: ...
429441

430442

@@ -454,6 +466,7 @@ async def execute_child_workflow(
454466
static_details: str | None = None,
455467
event_groups: Sequence[EventGroup] | None = None,
456468
priority: temporalio.common.Priority = temporalio.common.Priority.default,
469+
versioning_override: temporalio.common.VersioningOverride | None = None,
457470
) -> ReturnType: ...
458471

459472

@@ -483,6 +496,7 @@ async def execute_child_workflow(
483496
static_details: str | None = None,
484497
event_groups: Sequence[EventGroup] | None = None,
485498
priority: temporalio.common.Priority = temporalio.common.Priority.default,
499+
versioning_override: temporalio.common.VersioningOverride | None = None,
486500
) -> ReturnType: ...
487501

488502

@@ -514,6 +528,7 @@ async def execute_child_workflow(
514528
static_details: str | None = None,
515529
event_groups: Sequence[EventGroup] | None = None,
516530
priority: temporalio.common.Priority = temporalio.common.Priority.default,
531+
versioning_override: temporalio.common.VersioningOverride | None = None,
517532
) -> Any: ...
518533

519534

@@ -543,6 +558,7 @@ async def execute_child_workflow(
543558
static_details: str | None = None,
544559
event_groups: Sequence[EventGroup] | None = None,
545560
priority: temporalio.common.Priority = temporalio.common.Priority.default,
561+
versioning_override: temporalio.common.VersioningOverride | None = None,
546562
) -> Any:
547563
"""Start a child workflow and wait for completion.
548564
@@ -572,6 +588,7 @@ async def execute_child_workflow(
572588
static_details=static_details,
573589
event_groups=event_groups,
574590
priority=priority,
591+
versioning_override=versioning_override,
575592
)
576593
return await handle
577594

‎tests/nexus/test_temporal_system_nexus.py‎

Lines changed: 19 additions & 1 deletion

0 commit comments

Comments
 (0)