Skip to content

Commit e4df2af

Browse files
committed
Merge branch 'main' into bundled-optional-exts
2 parents 59ae8e9 + 2fd3237 commit e4df2af

5 files changed

Lines changed: 44 additions & 37 deletions

File tree

dapr/ext/workflow/propagation.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ class PropagationScope(Enum):
3434
3535
Values map 1:1 to the protobuf ``HistoryPropagationScope`` enum; the
3636
plumbing layer reads ``.value`` when writing to proto fields.
37+
38+
* ``OWN_HISTORY`` — propagate the caller's events only; drop any ancestor
39+
chain. Use as a trust boundary, where downstream code should only see
40+
the immediate caller.
41+
* ``LINEAGE`` — propagate the caller's events plus any ancestor events it
42+
received. Use for chain-of-custody verification, where downstream code
43+
needs visibility into the full lineage of upstream workflows.
3744
"""
3845

3946
NONE = int(pb.HISTORY_PROPAGATION_SCOPE_NONE)
@@ -144,7 +151,7 @@ def _resolve_child_workflow(
144151
class WorkflowResult:
145152
"""A scoped view of a single workflow's chunk in propagated history.
146153
147-
Use :meth:`get_activity_by_name` / :meth:`get_child_workflow_by_name`
154+
Use :meth:`get_last_activity_by_name` / :meth:`get_last_child_workflow_by_name`
148155
to query specific items inside this chunk. Methods return the most-recent
149156
occurrence by execution order.
150157
"""
@@ -158,15 +165,15 @@ def get_activities_by_name(self, name: str) -> list[ActivityResult]:
158165
"""Return every activity in this chunk whose scheduled name matches, in
159166
execution order. Empty list if none.
160167
161-
See also: :meth:`get_activity_by_name` for the most recent match only.
168+
See also: :meth:`get_last_activity_by_name` for the most recent match only.
162169
"""
163170
return [
164171
_resolve_activity(self._events, e)
165172
for e in self._events
166173
if e.HasField('taskScheduled') and e.taskScheduled.name == name
167174
]
168175

169-
def get_activity_by_name(self, name: str) -> ActivityResult:
176+
def get_last_activity_by_name(self, name: str) -> ActivityResult:
170177
"""Return the most recent activity in this chunk whose name matches.
171178
172179
Raises :class:`PropagationNotFoundError` if no activity scheduled with
@@ -186,7 +193,7 @@ def get_child_workflows_by_name(self, name: str) -> list[ChildWorkflowResult]:
186193
"""Return every child workflow in this chunk whose name matches, in
187194
execution order.
188195
189-
See also: :meth:`get_child_workflow_by_name` for the most recent match.
196+
See also: :meth:`get_last_child_workflow_by_name` for the most recent match.
190197
"""
191198
return [
192199
_resolve_child_workflow(self._events, e.eventId, name)
@@ -195,7 +202,7 @@ def get_child_workflows_by_name(self, name: str) -> list[ChildWorkflowResult]:
195202
and e.childWorkflowInstanceCreated.name == name
196203
]
197204

198-
def get_child_workflow_by_name(self, name: str) -> ChildWorkflowResult:
205+
def get_last_child_workflow_by_name(self, name: str) -> ChildWorkflowResult:
199206
"""Return the most recent child workflow in this chunk whose name matches.
200207
201208
Raises :class:`PropagationNotFoundError` if no match is found.
@@ -301,12 +308,12 @@ def get_workflows_by_name(self, name: str) -> list[WorkflowResult]:
301308
"""All workflows whose name matches, in execution order. Useful when
302309
the chain contains the same name more than once (recursion / ContinueAsNew).
303310
304-
See also: :meth:`get_workflow_by_name` for a single-result helper that
311+
See also: :meth:`get_last_workflow_by_name` for a single-result helper that
305312
returns only the most recent match.
306313
"""
307314
return [self._make_workflow_result(c) for c in self._chunks if c.workflow_name == name]
308315

309-
def get_workflow_by_name(self, name: str) -> WorkflowResult:
316+
def get_last_workflow_by_name(self, name: str) -> WorkflowResult:
310317
"""Most recent workflow in the chain whose name matches.
311318
312319
Raises :class:`PropagationNotFoundError` if no match is found.

examples/workflow/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -548,8 +548,8 @@ It shows:
548548
forwards the caller's events only.
549549
- `propagation=PropagationScope.LINEAGE` on an activity call — forwards the
550550
caller's events *plus* anything the caller itself received from its parent.
551-
- `PropagatedHistory.get_workflow_by_name(...)` and `WorkflowResult.get_activity_by_name(...)`
552-
on the receiving side.
551+
- `PropagatedHistory.get_last_workflow_by_name(...)` and
552+
`WorkflowResult.get_last_activity_by_name(...)` on the receiving side.
553553

554554
> **Requires** a Dapr sidecar with workflow history propagation support
555555
> (durabletask-go PR #85 / runtime 1.18+ ). With an older sidecar the

examples/workflow/history_propagation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def log_summary(ctx: wf.WorkflowActivityContext, _: None) -> str:
5353

5454
parent = workflows[-1]
5555
try:
56-
validate = parent.get_activity_by_name('validate_merchant')
56+
validate = parent.get_last_activity_by_name('validate_merchant')
5757
except wf.PropagationNotFoundError:
5858
print('*** log_summary: parent did not run validate_merchant', flush=True)
5959
return 'parent-missing-validate'
@@ -81,7 +81,7 @@ def process_payment(ctx: wf.DaprWorkflowContext, _: None):
8181

8282
parent = workflows[-1]
8383
try:
84-
validate = parent.get_activity_by_name('validate_merchant')
84+
validate = parent.get_last_activity_by_name('validate_merchant')
8585
except wf.PropagationNotFoundError:
8686
print('*** process_payment: parent did not run validate_merchant', flush=True)
8787
return 'parent-missing-validate'

tests/ext/workflow/durabletask/test_propagation.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -197,21 +197,21 @@ def test_get_workflows_returns_chunks_in_order(history: PropagatedHistory):
197197
assert workflows[1].instance_id == 'wf-002'
198198

199199

200-
def test_get_workflow_by_name_returns_match(history: PropagatedHistory):
201-
wf = history.get_workflow_by_name('ProcessPayment')
200+
def test_get_last_workflow_by_name_returns_match(history: PropagatedHistory):
201+
wf = history.get_last_workflow_by_name('ProcessPayment')
202202
assert wf.name == 'ProcessPayment'
203203
assert wf.instance_id == 'wf-002'
204204

205205

206-
def test_get_workflow_by_name_raises_when_missing(history: PropagatedHistory):
206+
def test_get_last_workflow_by_name_raises_when_missing(history: PropagatedHistory):
207207
with pytest.raises(PropagationNotFoundError):
208-
history.get_workflow_by_name('NotARealWorkflow')
208+
history.get_last_workflow_by_name('NotARealWorkflow')
209209

210210

211211
def test_get_workflows_by_name_returns_all_matches():
212212
"""If the same workflow name appears in multiple chunks (e.g. ContinueAsNew
213213
or recursion), get_workflows_by_name returns every occurrence and
214-
get_workflow_by_name returns the last."""
214+
get_last_workflow_by_name returns the last."""
215215

216216
chunk_events = [_execution_started('Loop')]
217217
proto = pb.PropagatedHistory(
@@ -226,15 +226,15 @@ def test_get_workflows_by_name_returns_all_matches():
226226

227227
all_loops = ph.get_workflows_by_name('Loop')
228228
assert len(all_loops) == 2
229-
assert ph.get_workflow_by_name('Loop').instance_id == 'wf-2'
229+
assert ph.get_last_workflow_by_name('Loop').instance_id == 'wf-2'
230230

231231

232232
# --- Activity resolution ----------------------------------------------------
233233

234234

235-
def test_get_activity_by_name_returns_completed_result(history: PropagatedHistory):
236-
merchant = history.get_workflow_by_name('MerchantCheckout')
237-
activity = merchant.get_activity_by_name('ValidateMerchant')
235+
def test_get_last_activity_by_name_returns_completed_result(history: PropagatedHistory):
236+
merchant = history.get_last_workflow_by_name('MerchantCheckout')
237+
activity = merchant.get_last_activity_by_name('ValidateMerchant')
238238

239239
assert activity.name == 'ValidateMerchant'
240240
assert activity.started
@@ -246,7 +246,7 @@ def test_get_activity_by_name_returns_completed_result(history: PropagatedHistor
246246

247247

248248
def test_get_activities_by_name_returns_all_invocations(history: PropagatedHistory):
249-
payment = history.get_workflow_by_name('ProcessPayment')
249+
payment = history.get_last_workflow_by_name('ProcessPayment')
250250
cards = payment.get_activities_by_name('ValidateCard')
251251

252252
assert len(cards) == 2
@@ -258,20 +258,20 @@ def test_get_activities_by_name_returns_all_invocations(history: PropagatedHisto
258258
assert cards[1].error.errorMessage == 'card declined'
259259

260260

261-
def test_get_activity_by_name_returns_last_invocation(history: PropagatedHistory):
262-
"""get_activity_by_name returns the most recent invocation in execution
261+
def test_get_last_activity_by_name_returns_last_invocation(history: PropagatedHistory):
262+
"""get_last_activity_by_name returns the most recent invocation in execution
263263
order, matching Go semantics."""
264-
payment = history.get_workflow_by_name('ProcessPayment')
265-
last = payment.get_activity_by_name('ValidateCard')
264+
payment = history.get_last_workflow_by_name('ProcessPayment')
265+
last = payment.get_last_activity_by_name('ValidateCard')
266266
assert last.failed
267267
assert last.error is not None
268268
assert last.error.errorMessage == 'card declined'
269269

270270

271-
def test_get_activity_by_name_raises_when_missing(history: PropagatedHistory):
272-
payment = history.get_workflow_by_name('ProcessPayment')
271+
def test_get_last_activity_by_name_raises_when_missing(history: PropagatedHistory):
272+
payment = history.get_last_workflow_by_name('ProcessPayment')
273273
with pytest.raises(PropagationNotFoundError):
274-
payment.get_activity_by_name('NotAnActivity')
274+
payment.get_last_activity_by_name('NotAnActivity')
275275

276276

277277
def test_activity_not_yet_completed_reports_started_only():
@@ -287,7 +287,7 @@ def test_activity_not_yet_completed_reports_started_only():
287287
)
288288
ph = PropagatedHistory.from_proto(proto)
289289
assert ph is not None
290-
pending = ph.get_workflow_by_name('StillRunning').get_activity_by_name('Pending')
290+
pending = ph.get_last_workflow_by_name('StillRunning').get_last_activity_by_name('Pending')
291291

292292
assert pending.started
293293
assert not pending.completed
@@ -299,18 +299,18 @@ def test_activity_not_yet_completed_reports_started_only():
299299
# --- Child workflow resolution ----------------------------------------------
300300

301301

302-
def test_get_child_workflow_by_name(history: PropagatedHistory):
303-
merchant = history.get_workflow_by_name('MerchantCheckout')
304-
child = merchant.get_child_workflow_by_name('ProcessPayment')
302+
def test_get_last_child_workflow_by_name(history: PropagatedHistory):
303+
merchant = history.get_last_workflow_by_name('MerchantCheckout')
304+
child = merchant.get_last_child_workflow_by_name('ProcessPayment')
305305

306306
assert child.name == 'ProcessPayment'
307307
assert child.started
308308

309309

310-
def test_get_child_workflow_by_name_raises_when_missing(history: PropagatedHistory):
311-
merchant = history.get_workflow_by_name('MerchantCheckout')
310+
def test_get_last_child_workflow_by_name_raises_when_missing(history: PropagatedHistory):
311+
merchant = history.get_last_workflow_by_name('MerchantCheckout')
312312
with pytest.raises(PropagationNotFoundError):
313-
merchant.get_child_workflow_by_name('NotAChild')
313+
merchant.get_last_child_workflow_by_name('NotAChild')
314314

315315

316316
# --- from_proto / structural validation -------------------------------------

tests/ext/workflow/durabletask/test_propagation_wiring.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ def orchestrator(ctx: task.OrchestrationContext, _):
158158
history = captured['history']
159159
assert history is not None
160160
assert history.get_app_ids() == ['parent-app']
161-
assert history.get_workflow_by_name('Parent').instance_id == 'parent-instance'
161+
assert history.get_last_workflow_by_name('Parent').instance_id == 'parent-instance'
162162

163163

164164
def test_orchestration_executor_propagated_history_is_none_by_default():
@@ -209,7 +209,7 @@ def reading_activity(ctx: task.ActivityContext, _):
209209
history = captured['history']
210210
assert history is not None
211211
assert history.get_app_ids() == ['parent-app']
212-
assert history.get_workflow_by_name('Caller').instance_id == 'parent-instance'
212+
assert history.get_last_workflow_by_name('Caller').instance_id == 'parent-instance'
213213

214214

215215
def test_activity_executor_propagated_history_is_none_by_default():

0 commit comments

Comments
 (0)