durable-workflow 2.1.0__tar.gz → 2.1.1__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.
Files changed (88) hide show
  1. {durable_workflow-2.1.0/src/durable_workflow.egg-info → durable_workflow-2.1.1}/PKG-INFO +1 -1
  2. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/pyproject.toml +3 -3
  3. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/workflow.py +12 -2
  4. {durable_workflow-2.1.0 → durable_workflow-2.1.1/src/durable_workflow.egg-info}/PKG-INFO +1 -1
  5. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_pypi_project_surface.py +5 -0
  6. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_release_metadata.py +2 -2
  7. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_replay.py +51 -0
  8. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_replay_regression_corpus.py +10 -0
  9. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/LICENSE +0 -0
  10. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/README.md +0 -0
  11. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/setup.cfg +0 -0
  12. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/__init__.py +0 -0
  13. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/_avro.py +0 -0
  14. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/activity.py +0 -0
  15. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/auth_composition.py +0 -0
  16. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/client.py +0 -0
  17. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/errors.py +0 -0
  18. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/external_storage.py +0 -0
  19. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/external_task_input.py +0 -0
  20. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/external_task_result.py +0 -0
  21. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/history_bundle_verify.py +0 -0
  22. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/interceptors.py +0 -0
  23. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/invocable.py +0 -0
  24. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/metrics.py +0 -0
  25. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/nexus.py +0 -0
  26. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/py.typed +0 -0
  27. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/python_conformance.py +0 -0
  28. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/replay_conformance.py +0 -0
  29. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/replay_verify.py +0 -0
  30. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/retry_policy.py +0 -0
  31. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/serializer.py +0 -0
  32. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/sync.py +0 -0
  33. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/testing.py +0 -0
  34. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/worker.py +0 -0
  35. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow/workflow_updates_conformance.py +0 -0
  36. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow.egg-info/SOURCES.txt +0 -0
  37. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
  38. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow.egg-info/entry_points.txt +0 -0
  39. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow.egg-info/requires.txt +0 -0
  40. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/src/durable_workflow.egg-info/top_level.txt +0 -0
  41. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_activity_context.py +0 -0
  42. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_auth_composition.py +0 -0
  43. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_avro_value_benchmark.py +0 -0
  44. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_avro_value_protocol.py +0 -0
  45. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_ci_checkout.py +0 -0
  46. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_ci_integration_endpoint.py +0 -0
  47. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_client.py +0 -0
  48. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_codec_regression_corpus.py +0 -0
  49. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_control_plane_parity_fixtures.py +0 -0
  50. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_docstring_cross_references.py +0 -0
  51. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_durable_selection.py +0 -0
  52. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_errors.py +0 -0
  53. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_external_storage.py +0 -0
  54. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_external_task_input.py +0 -0
  55. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_external_task_result.py +0 -0
  56. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_golden_history_replay.py +0 -0
  57. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_history_bundle_verify.py +0 -0
  58. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_history_event_contract.py +0 -0
  59. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_invocable.py +0 -0
  60. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_message_streams.py +0 -0
  61. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_metrics.py +0 -0
  62. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_order_processing_example.py +0 -0
  63. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_parallel_completion_order_replay.py +0 -0
  64. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_parallel_saga_authoring.py +0 -0
  65. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_public_boundary_scanner.py +0 -0
  66. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_python_conformance.py +0 -0
  67. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_queries.py +0 -0
  68. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_readme_quickstart.py +0 -0
  69. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_redrive_replay.py +0 -0
  70. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_replay_conformance.py +0 -0
  71. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_replay_verify.py +0 -0
  72. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_retry_policy.py +0 -0
  73. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_runtime_external_payload_transport.py +0 -0
  74. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_schedules.py +0 -0
  75. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_serializer.py +0 -0
  76. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_signals.py +0 -0
  77. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_sleep.py +0 -0
  78. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_standalone_activity_client.py +0 -0
  79. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_storage_admission.py +0 -0
  80. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_sync.py +0 -0
  81. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_testing_harness.py +0 -0
  82. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_update_signal_condition_replay.py +0 -0
  83. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_updates.py +0 -0
  84. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_wait_condition.py +0 -0
  85. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_worker.py +0 -0
  86. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_workflow_result_timeout.py +0 -0
  87. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_workflow_streams.py +0 -0
  88. {durable_workflow-2.1.0 → durable_workflow-2.1.1}/tests/test_workflow_updates_conformance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: durable-workflow
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: Python client and worker SDK for Durable Workflow Cloud and self-hosted Server
5
5
  Author: Durable Workflow Contributors
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "durable-workflow"
7
- version = "2.1.0"
7
+ version = "2.1.1"
8
8
  description = "Python client and worker SDK for Durable Workflow Cloud and self-hosted Server"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -71,8 +71,8 @@ durable-workflow-replay-conformance = "durable_workflow.replay_conformance:main"
71
71
  durable-workflow-workflow-updates-conformance = "durable_workflow.workflow_updates_conformance:main"
72
72
 
73
73
  [tool.durable-workflow]
74
- product-train = "2.1.0"
75
- registry-version = "2.1.0"
74
+ product-train = "2.1.1"
75
+ registry-version = "2.1.1"
76
76
  supported-server-versions = "2.4.0"
77
77
  worker-protocol-version = "1.19"
78
78
  control-plane-version = "2"
@@ -762,6 +762,7 @@ class RecordSideEffect:
762
762
 
763
763
  result: Any
764
764
  workflow_stream: dict[str, Any] | None = None
765
+ _effect: Callable[[], Any] | None = field(default=None, repr=False, compare=False)
765
766
 
766
767
  def to_server_command(
767
768
  self,
@@ -1799,8 +1800,7 @@ class WorkflowContext:
1799
1800
  return SelectGroup(entries)
1800
1801
 
1801
1802
  def side_effect(self, fn: Callable[[], Any]) -> RecordSideEffect:
1802
- result = fn()
1803
- return RecordSideEffect(result=result)
1803
+ return RecordSideEffect(result=None, _effect=fn)
1804
1804
 
1805
1805
  def append_workflow_stream(
1806
1806
  self,
@@ -5139,6 +5139,16 @@ def _replay_state(
5139
5139
  continue
5140
5140
  ctx.logger._set_replaying(False)
5141
5141
  _assert_pending_step_matches(cmd)
5142
+ if cmd._effect is not None:
5143
+ try:
5144
+ cmd.result = cmd._effect()
5145
+ except Exception as exc:
5146
+ try:
5147
+ advanced_cmd = gen.throw(exc)
5148
+ continue
5149
+ except StopIteration as stop:
5150
+ return _terminal_state(stop.value, include_pending=True)
5151
+ cmd._effect = None
5142
5152
  pending.append(cmd)
5143
5153
  next_value = cmd.result
5144
5154
  continue
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: durable-workflow
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: Python client and worker SDK for Durable Workflow Cloud and self-hosted Server
5
5
  Author: Durable Workflow Contributors
6
6
  License-Expression: MIT
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import subprocess
5
+ from dataclasses import replace
5
6
  from pathlib import Path
6
7
  from typing import Any
7
8
 
@@ -91,6 +92,10 @@ def pip_report(version: str) -> dict[str, object]:
91
92
  def test_release_channel_distinguishes_prerelease_and_stable() -> None:
92
93
  assert release_channel(source_metadata()) == "prerelease"
93
94
  assert release_channel(source_metadata(stable=True)) == "stable"
95
+ stable_minor = replace(source_metadata(stable=True), version="2.1.0", registry_version="2.1.0")
96
+ prerelease_minor = replace(source_metadata(), version="2.1.0-rc.1", registry_version="2.1.0rc1")
97
+ assert release_channel(stable_minor) == "stable"
98
+ assert release_channel(prerelease_minor) == "prerelease"
94
99
 
95
100
 
96
101
  def test_prerelease_with_existing_final_skips_project_root_and_bare_install(
@@ -41,9 +41,9 @@ def test_worker_release_identity_matches_supported_server_and_protocol() -> None
41
41
  project = manifest["project"]
42
42
  release = manifest["tool"]["durable-workflow"]
43
43
 
44
- assert project["version"] == "2.1.0"
44
+ assert check_release_metadata.VERSION_PATTERN.fullmatch(project["version"]) is not None
45
45
  assert release["product-train"] == project["version"]
46
- assert release["registry-version"] == "2.1.0"
46
+ assert release["registry-version"] == project["version"].replace("-rc.", "rc")
47
47
  assert release["supported-server-versions"] == "2.4.0"
48
48
  assert release["worker-protocol-version"] == PROTOCOL_VERSION == "1.19"
49
49
  assert release["durable-selection"] is True
@@ -1158,6 +1158,57 @@ class TestContinueAsNew:
1158
1158
 
1159
1159
 
1160
1160
  class TestSideEffect:
1161
+ def test_user_callable_runs_only_for_an_unrecorded_side_effect(self) -> None:
1162
+ calls: list[str] = []
1163
+
1164
+ @workflow.defn(name="side-effect-call-count-wf")
1165
+ class CountedSideEffectWorkflow:
1166
+ def run(self, ctx: WorkflowContext): # type: ignore[no-untyped-def]
1167
+ value = yield ctx.side_effect(lambda: calls.append("called") or 42)
1168
+ return value
1169
+
1170
+ first = replay(CountedSideEffectWorkflow, [], [])
1171
+ assert calls == ["called"]
1172
+ assert isinstance(first.commands[0], RecordSideEffect)
1173
+ assert first.commands[0].result == 42
1174
+
1175
+ history = [{"event_type": "SideEffectRecorded", "payload": {"result": _avro(99)}}]
1176
+ replayed = replay(CountedSideEffectWorkflow, history, [])
1177
+ assert calls == ["called"]
1178
+ assert isinstance(replayed.commands[0], CompleteWorkflow)
1179
+ assert replayed.commands[0].result == 99
1180
+
1181
+ def test_user_callable_is_not_run_when_history_shape_mismatches(self) -> None:
1182
+ calls: list[str] = []
1183
+
1184
+ @workflow.defn(name="side-effect-mismatch-wf")
1185
+ class MismatchedSideEffectWorkflow:
1186
+ def run(self, ctx: WorkflowContext): # type: ignore[no-untyped-def]
1187
+ return (yield ctx.side_effect(lambda: calls.append("called") or 42))
1188
+
1189
+ history = [{"event_type": "TimerScheduled", "payload": {"sequence": 1, "timer_kind": "durable_timer"}}]
1190
+ with pytest.raises(NonDeterministicReplayError):
1191
+ replay(MismatchedSideEffectWorkflow, history, [])
1192
+ assert calls == []
1193
+
1194
+ def test_user_callable_error_can_be_handled_by_workflow(self) -> None:
1195
+ @workflow.defn(name="side-effect-error-wf")
1196
+ class HandledSideEffectWorkflow:
1197
+ def run(self, ctx: WorkflowContext): # type: ignore[no-untyped-def]
1198
+ def fail() -> int:
1199
+ raise ValueError("expected failure")
1200
+
1201
+ try:
1202
+ yield ctx.side_effect(fail)
1203
+ except ValueError:
1204
+ return "handled"
1205
+ return "unexpected"
1206
+
1207
+ outcome = replay(HandledSideEffectWorkflow, [], [])
1208
+ assert len(outcome.commands) == 1
1209
+ assert isinstance(outcome.commands[0], CompleteWorkflow)
1210
+ assert outcome.commands[0].result == "handled"
1211
+
1161
1212
  def test_first_replay_records_and_continues(self) -> None:
1162
1213
  outcome = replay(SideEffectWorkflow, [], [])
1163
1214
  assert len(outcome.commands) == 2
@@ -147,6 +147,15 @@ class WorkflowStreamAuthorWorkflow:
147
147
  return "done"
148
148
 
149
149
 
150
+ @workflow.defn(name="tests.replay.recorded-side-effect")
151
+ class RecordedSideEffectWorkflow:
152
+ def run(self, ctx: WorkflowContext): # type: ignore[no-untyped-def]
153
+ def unexpected_execution() -> int:
154
+ raise AssertionError("recorded side effect callable ran during replay")
155
+
156
+ return (yield ctx.side_effect(unexpected_execution))
157
+
158
+
150
159
  @workflow.defn(name="tests.replay.message-stream-consumer")
151
160
  class MessageStreamConsumerWorkflow:
152
161
  def run(self, ctx: WorkflowContext): # type: ignore[no-untyped-def]
@@ -197,6 +206,7 @@ WORKFLOWS = [
197
206
  ParallelMetadataProducerWorkflow,
198
207
  ParallelResultBindingWorkflow,
199
208
  PostConditionReceiversWorkflow,
209
+ RecordedSideEffectWorkflow,
200
210
  SelectionAwaitMarkerWorkflow,
201
211
  SelectionCancellationQueryWorkflow,
202
212
  SelectionMissingNestedOpeningWorkflow,