Skip to content

Commit 30499fe

Browse files
andystaplesCopilot
andauthored
Add parent_instance_id to OrchestrationContext (#168)
* Add parent_instance_id to OrchestrationContext * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 8440526 commit 30499fe

4 files changed

Lines changed: 81 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## Unreleased
99

10+
ADDED
11+
12+
- Added `OrchestrationContext.parent_instance_id`, which returns the instance
13+
ID of the parent orchestration for a sub-orchestration, or `None` for a
14+
top-level orchestration.
15+
1016
CHANGED
1117

1218
- Changed the default large-payload externalization threshold (`LargePayloadStorageOptions.threshold_bytes`) from 900,000 bytes to 262,144 bytes (256 KiB), matching the .NET SDK default. Behavioral change (not source/binary breaking): payloads larger than 256 KiB are now externalized by default.

durabletask/task.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@ def instance_id(self) -> str:
3838
"""
3939
pass
4040

41+
@property
42+
@abstractmethod
43+
def parent_instance_id(self) -> str | None:
44+
"""Get the ID of the parent orchestration instance.
45+
46+
For a sub-orchestration, this is the instance ID of the orchestration
47+
that scheduled it. For a top-level orchestration, this is ``None``.
48+
49+
Returns
50+
-------
51+
str | None
52+
The parent orchestration instance ID, or ``None`` if this
53+
orchestration was not scheduled by a parent orchestration.
54+
"""
55+
pass
56+
4157
@property
4258
@abstractmethod
4359
def version(self) -> str | None:

durabletask/worker.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,6 +1483,7 @@ def __init__(self,
14831483
self._registry = registry
14841484
self._entity_context = OrchestrationEntityContext(instance_id)
14851485
self._version: str | None = None
1486+
self._parent_instance_id: str | None = None
14861487
self._completion_status: pb.OrchestrationStatus | None = None
14871488
self._received_events: dict[str, list[str | None]] = {}
14881489
self._pending_events: dict[str, list[task.CancellableTask[Any]]] = {}
@@ -1638,6 +1639,10 @@ def instance_id(self) -> str:
16381639
def version(self) -> str | None:
16391640
return self._version
16401641

1642+
@property
1643+
def parent_instance_id(self) -> str | None:
1644+
return self._parent_instance_id
1645+
16411646
@property
16421647
def current_utc_datetime(self) -> datetime:
16431648
return self._current_utc_datetime
@@ -2222,6 +2227,16 @@ def process_event(
22222227
if event.executionStarted.version:
22232228
ctx._version = event.executionStarted.version.value # pyright: ignore[reportPrivateUsage]
22242229

2230+
# Store the parent orchestration instance ID (set for
2231+
# sub-orchestrations; absent for top-level orchestrations)
2232+
if (
2233+
event.executionStarted.HasField("parentInstance")
2234+
and event.executionStarted.parentInstance.HasField("orchestrationInstance")
2235+
):
2236+
ctx._parent_instance_id = ( # pyright: ignore[reportPrivateUsage]
2237+
event.executionStarted.parentInstance.orchestrationInstance.instanceId
2238+
)
2239+
22252240
# Store the parent trace context for propagation to child tasks
22262241
if event.executionStarted.HasField("parentTraceContext"):
22272242
ctx._parent_trace_context = event.executionStarted.parentTraceContext # pyright: ignore[reportPrivateUsage]

tests/durabletask/test_orchestration_executor.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,50 @@ def orchestrator(ctx: task.OrchestrationContext, my_input: int):
4949
assert complete_action.result.value == json.dumps(expected_output)
5050

5151

52+
def test_orchestrator_parent_instance_id():
53+
"""A sub-orchestration exposes its parent's instance ID on the context."""
54+
55+
parent_id = "parent-instance-42"
56+
observed: dict[str, str | None] = {}
57+
58+
def orchestrator(ctx: task.OrchestrationContext, _):
59+
observed["parent"] = ctx.parent_instance_id
60+
return "done"
61+
62+
registry = worker._Registry()
63+
name = registry.add_orchestrator(orchestrator)
64+
65+
started = helpers.new_execution_started_event(name, TEST_INSTANCE_ID, encoded_input=None)
66+
started.executionStarted.parentInstance.CopyFrom(
67+
pb.ParentInstanceInfo(
68+
taskScheduledId=1,
69+
orchestrationInstance=pb.OrchestrationInstance(instanceId=parent_id)))
70+
71+
executor = worker._OrchestrationExecutor(registry, TEST_LOGGER, JsonDataConverter())
72+
executor.execute(TEST_INSTANCE_ID, [], [started])
73+
74+
assert observed["parent"] == parent_id
75+
76+
77+
def test_orchestrator_parent_instance_id_none_for_top_level():
78+
"""A top-level orchestration has no parent instance ID."""
79+
80+
observed: dict[str, str | None] = {}
81+
82+
def orchestrator(ctx: task.OrchestrationContext, _):
83+
observed["parent"] = ctx.parent_instance_id
84+
return "done"
85+
86+
registry = worker._Registry()
87+
name = registry.add_orchestrator(orchestrator)
88+
89+
new_events = [helpers.new_execution_started_event(name, TEST_INSTANCE_ID, encoded_input=None)]
90+
executor = worker._OrchestrationExecutor(registry, TEST_LOGGER, JsonDataConverter())
91+
executor.execute(TEST_INSTANCE_ID, [], new_events)
92+
93+
assert observed["parent"] is None
94+
95+
5296
def test_complete_orchestration_actions():
5397
"""Tests the actions output for a completed orchestration"""
5498

0 commit comments

Comments
 (0)