durable-workflow 0.4.89__tar.gz → 0.4.90__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {durable_workflow-0.4.89/src/durable_workflow.egg-info → durable_workflow-0.4.90}/PKG-INFO +1 -1
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/pyproject.toml +1 -1
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/workflow.py +11 -1
- {durable_workflow-0.4.89 → durable_workflow-0.4.90/src/durable_workflow.egg-info}/PKG-INFO +1 -1
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_wait_condition.py +67 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_worker.py +112 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/LICENSE +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/README.md +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/setup.cfg +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/__init__.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/_avro.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/activity.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/auth_composition.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/client.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/errors.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/external_storage.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/external_task_input.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/external_task_result.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/history_bundle_verify.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/interceptors.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/invocable.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/metrics.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/py.typed +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/python_conformance.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/replay_conformance.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/replay_verify.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/retry_policy.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/serializer.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/sync.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/testing.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/worker.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow.egg-info/SOURCES.txt +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow.egg-info/entry_points.txt +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow.egg-info/requires.txt +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow.egg-info/top_level.txt +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_activity_context.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_auth_composition.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_client.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_control_plane_parity_fixtures.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_errors.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_external_storage.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_external_task_input.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_external_task_result.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_golden_history_replay.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_history_bundle_verify.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_history_event_contract.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_invocable.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_metrics.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_order_processing_example.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_public_boundary_scanner.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_python_conformance.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_queries.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_readme_quickstart.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_release_docs_audit_workflow.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_replay.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_replay_conformance.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_replay_verify.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_retry_policy.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_schedules.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_serializer.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_signals.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_sleep.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_standalone_activity_client.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_sync.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_testing_harness.py +0 -0
- {durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_updates.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "durable-workflow"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.90"
|
|
8
8
|
description = "Python SDK for the Durable Workflow server (language-neutral HTTP protocol)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -2762,7 +2762,17 @@ def _replay_state(
|
|
|
2762
2762
|
exception_type=type(exc).__name__,
|
|
2763
2763
|
)])
|
|
2764
2764
|
if resolution == "satisfied":
|
|
2765
|
-
|
|
2765
|
+
if has_reopened_same_wait:
|
|
2766
|
+
if not satisfied:
|
|
2767
|
+
consumed_reopen_for_current_wait = True
|
|
2768
|
+
wait_yield_count = next_wait_index
|
|
2769
|
+
continue
|
|
2770
|
+
|
|
2771
|
+
terminal_condition_reopen_cmd = (
|
|
2772
|
+
cmd
|
|
2773
|
+
if has_reopened_same_wait and consumed_reopen_for_current_wait
|
|
2774
|
+
else None
|
|
2775
|
+
)
|
|
2766
2776
|
next_value = True
|
|
2767
2777
|
wait_yield_count += 1
|
|
2768
2778
|
break
|
|
@@ -172,6 +172,41 @@ class SignalCounterUntilFinished:
|
|
|
172
172
|
return self.current()
|
|
173
173
|
|
|
174
174
|
|
|
175
|
+
@workflow.defn(name="signal-counter-single-predicate-per-reopen")
|
|
176
|
+
class SignalCounterSinglePredicatePerReopen:
|
|
177
|
+
def __init__(self) -> None:
|
|
178
|
+
self.count = 0
|
|
179
|
+
self.done = False
|
|
180
|
+
self.receiver_generation = 0
|
|
181
|
+
self.last_predicate_generation: int | None = None
|
|
182
|
+
self.predicate_calls = 0
|
|
183
|
+
|
|
184
|
+
@workflow.signal("increment")
|
|
185
|
+
def increment(self, amount: int) -> None:
|
|
186
|
+
self.count += amount
|
|
187
|
+
self.receiver_generation += 1
|
|
188
|
+
|
|
189
|
+
@workflow.signal("finish")
|
|
190
|
+
def finish(self) -> None:
|
|
191
|
+
self.done = True
|
|
192
|
+
self.receiver_generation += 1
|
|
193
|
+
|
|
194
|
+
def done_once_per_receiver(self) -> bool:
|
|
195
|
+
self.predicate_calls += 1
|
|
196
|
+
if self.last_predicate_generation == self.receiver_generation:
|
|
197
|
+
raise RuntimeError("predicate re-evaluated before replay advanced")
|
|
198
|
+
self.last_predicate_generation = self.receiver_generation
|
|
199
|
+
return self.done
|
|
200
|
+
|
|
201
|
+
def run(self, ctx: WorkflowContext): # type: ignore[no-untyped-def]
|
|
202
|
+
satisfied = yield ctx.wait_condition(self.done_once_per_receiver, key="done")
|
|
203
|
+
return {
|
|
204
|
+
"count": self.count,
|
|
205
|
+
"satisfied": satisfied,
|
|
206
|
+
"predicate_calls": self.predicate_calls,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
175
210
|
@workflow.defn(name="two-same-key-signal-waits")
|
|
176
211
|
class TwoSameKeySignalWaits:
|
|
177
212
|
def __init__(self) -> None:
|
|
@@ -455,6 +490,38 @@ class TestReplayWaitCondition:
|
|
|
455
490
|
],
|
|
456
491
|
}
|
|
457
492
|
|
|
493
|
+
def test_satisfied_reopened_wait_reuses_current_predicate_result(self) -> None:
|
|
494
|
+
history = [
|
|
495
|
+
{
|
|
496
|
+
"event_type": "ConditionWaitOpened",
|
|
497
|
+
"payload": {"condition_wait_id": "wait-count", "condition_key": "done"},
|
|
498
|
+
},
|
|
499
|
+
_signal_received_event("increment", [3]),
|
|
500
|
+
{
|
|
501
|
+
"event_type": "ConditionWaitSatisfied",
|
|
502
|
+
"payload": {"condition_wait_id": "wait-count", "condition_key": "done"},
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
"event_type": "ConditionWaitOpened",
|
|
506
|
+
"payload": {"condition_wait_id": "wait-finish", "condition_key": "done"},
|
|
507
|
+
},
|
|
508
|
+
_signal_received_event("finish", []),
|
|
509
|
+
{
|
|
510
|
+
"event_type": "ConditionWaitSatisfied",
|
|
511
|
+
"payload": {"condition_wait_id": "wait-finish", "condition_key": "done"},
|
|
512
|
+
},
|
|
513
|
+
]
|
|
514
|
+
|
|
515
|
+
outcome = replay(SignalCounterSinglePredicatePerReopen, history, [])
|
|
516
|
+
|
|
517
|
+
assert len(outcome.commands) == 1
|
|
518
|
+
assert isinstance(outcome.commands[0], CompleteWorkflow)
|
|
519
|
+
assert outcome.commands[0].result == {
|
|
520
|
+
"count": 3,
|
|
521
|
+
"satisfied": True,
|
|
522
|
+
"predicate_calls": 2,
|
|
523
|
+
}
|
|
524
|
+
|
|
458
525
|
def test_signal_received_before_next_wait_uses_declared_workflow_sequence(self) -> None:
|
|
459
526
|
history = [
|
|
460
527
|
{
|
|
@@ -1443,6 +1443,118 @@ class TestWorkflowTaskExecution:
|
|
|
1443
1443
|
)
|
|
1444
1444
|
mock_client.fail_query_task.assert_not_called()
|
|
1445
1445
|
|
|
1446
|
+
@pytest.mark.asyncio
|
|
1447
|
+
async def test_query_task_replays_signal_woken_false_wait_reopens(
|
|
1448
|
+
self, mock_client: AsyncMock
|
|
1449
|
+
) -> None:
|
|
1450
|
+
worker = Worker(mock_client, task_queue="q1", workflows=[CounterQueryWorkflow], activities=[])
|
|
1451
|
+
first_signal_arguments = serializer.encode([3], codec="json")
|
|
1452
|
+
second_signal_arguments = serializer.encode([5], codec="json")
|
|
1453
|
+
task = {
|
|
1454
|
+
"query_task_id": "qt-false-wait-reopens",
|
|
1455
|
+
"query_task_attempt": 1,
|
|
1456
|
+
"workflow_type": "counter-query-wf",
|
|
1457
|
+
"workflow_id": "wf-counter",
|
|
1458
|
+
"run_id": "run-counter",
|
|
1459
|
+
"query_name": "current",
|
|
1460
|
+
"history_events": [
|
|
1461
|
+
{
|
|
1462
|
+
"event_type": "ConditionWaitOpened",
|
|
1463
|
+
"payload": {
|
|
1464
|
+
"condition_wait_id": "wait-count-0",
|
|
1465
|
+
"condition_key": "done",
|
|
1466
|
+
},
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
"event_type": "SignalReceived",
|
|
1470
|
+
"workflow_command_id": "cmd-increment-3",
|
|
1471
|
+
"payload": {
|
|
1472
|
+
"signal_id": "sig-increment-3",
|
|
1473
|
+
"workflow_command_id": "cmd-increment-3",
|
|
1474
|
+
"signal_name": "increment",
|
|
1475
|
+
},
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
"event_type": "ConditionWaitSatisfied",
|
|
1479
|
+
"payload": {
|
|
1480
|
+
"condition_wait_id": "wait-count-0",
|
|
1481
|
+
"condition_key": "done",
|
|
1482
|
+
"workflow_signal_id": "sig-increment-3",
|
|
1483
|
+
"signal_name": "increment",
|
|
1484
|
+
},
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
"event_type": "ConditionWaitOpened",
|
|
1488
|
+
"payload": {
|
|
1489
|
+
"condition_wait_id": "wait-count-3",
|
|
1490
|
+
"condition_key": "done",
|
|
1491
|
+
},
|
|
1492
|
+
},
|
|
1493
|
+
{
|
|
1494
|
+
"event_type": "SignalReceived",
|
|
1495
|
+
"workflow_command_id": "cmd-increment-5",
|
|
1496
|
+
"payload": {
|
|
1497
|
+
"signal_id": "sig-increment-5",
|
|
1498
|
+
"workflow_command_id": "cmd-increment-5",
|
|
1499
|
+
"signal_name": "increment",
|
|
1500
|
+
},
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
"event_type": "ConditionWaitSatisfied",
|
|
1504
|
+
"payload": {
|
|
1505
|
+
"condition_wait_id": "wait-count-3",
|
|
1506
|
+
"condition_key": "done",
|
|
1507
|
+
"workflow_signal_id": "sig-increment-5",
|
|
1508
|
+
"signal_name": "increment",
|
|
1509
|
+
},
|
|
1510
|
+
},
|
|
1511
|
+
{
|
|
1512
|
+
"event_type": "ConditionWaitOpened",
|
|
1513
|
+
"payload": {
|
|
1514
|
+
"condition_wait_id": "wait-count-8",
|
|
1515
|
+
"condition_key": "done",
|
|
1516
|
+
},
|
|
1517
|
+
},
|
|
1518
|
+
],
|
|
1519
|
+
"history_export": {
|
|
1520
|
+
"payloads": {"codec": "json"},
|
|
1521
|
+
"signals": [
|
|
1522
|
+
{
|
|
1523
|
+
"id": "sig-increment-3",
|
|
1524
|
+
"command_id": "cmd-increment-3",
|
|
1525
|
+
"name": "increment",
|
|
1526
|
+
"payload_codec": "json",
|
|
1527
|
+
"arguments": first_signal_arguments,
|
|
1528
|
+
},
|
|
1529
|
+
{
|
|
1530
|
+
"id": "sig-increment-5",
|
|
1531
|
+
"command_id": "cmd-increment-5",
|
|
1532
|
+
"name": "increment",
|
|
1533
|
+
"payload_codec": "json",
|
|
1534
|
+
"arguments": second_signal_arguments,
|
|
1535
|
+
},
|
|
1536
|
+
],
|
|
1537
|
+
},
|
|
1538
|
+
"workflow_arguments": serializer.envelope([], codec="json"),
|
|
1539
|
+
"query_arguments": serializer.envelope([], codec="json"),
|
|
1540
|
+
"payload_codec": "json",
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
outcome = await worker._run_query_task(task)
|
|
1544
|
+
|
|
1545
|
+
assert outcome == "completed"
|
|
1546
|
+
mock_client.complete_query_task.assert_awaited_once_with(
|
|
1547
|
+
query_task_id="qt-false-wait-reopens",
|
|
1548
|
+
lease_owner=worker.worker_id,
|
|
1549
|
+
query_task_attempt=1,
|
|
1550
|
+
result=8,
|
|
1551
|
+
codec="json",
|
|
1552
|
+
workflow_id="wf-counter",
|
|
1553
|
+
run_id="run-counter",
|
|
1554
|
+
query_name="current",
|
|
1555
|
+
)
|
|
1556
|
+
mock_client.fail_query_task.assert_not_called()
|
|
1557
|
+
|
|
1446
1558
|
@pytest.mark.asyncio
|
|
1447
1559
|
async def test_query_task_replays_history_from_export_after_worker_restart(
|
|
1448
1560
|
self, mock_client: AsyncMock
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/auth_composition.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/external_storage.py
RENAMED
|
File without changes
|
{durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/external_task_input.py
RENAMED
|
File without changes
|
{durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/external_task_result.py
RENAMED
|
File without changes
|
{durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/history_bundle_verify.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/python_conformance.py
RENAMED
|
File without changes
|
{durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow/replay_conformance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow.egg-info/requires.txt
RENAMED
|
File without changes
|
{durable_workflow-0.4.89 → durable_workflow-0.4.90}/src/durable_workflow.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_control_plane_parity_fixtures.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_release_docs_audit_workflow.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.89 → durable_workflow-0.4.90}/tests/test_standalone_activity_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|