durable-workflow 2.0.0rc44__tar.gz → 2.0.2__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 (95) hide show
  1. durable_workflow-2.0.2/PKG-INFO +177 -0
  2. durable_workflow-2.0.2/README.md +137 -0
  3. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/pyproject.toml +11 -8
  4. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/__init__.py +2 -0
  5. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/client.py +6 -2
  6. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/errors.py +11 -0
  7. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/workflow.py +5 -0
  8. durable_workflow-2.0.2/src/durable_workflow.egg-info/PKG-INFO +177 -0
  9. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow.egg-info/SOURCES.txt +1 -6
  10. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_ci_checkout.py +0 -1
  11. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_ci_integration_endpoint.py +0 -1
  12. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_pypi_project_surface.py +15 -51
  13. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_release_metadata.py +16 -68
  14. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_replay_regression_corpus.py +5 -1
  15. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_update_signal_condition_replay.py +129 -0
  16. durable_workflow-2.0.2/tests/test_workflow_result_timeout.py +61 -0
  17. durable_workflow-2.0.0rc44/PKG-INFO +0 -831
  18. durable_workflow-2.0.0rc44/README.md +0 -791
  19. durable_workflow-2.0.0rc44/src/durable_workflow.egg-info/PKG-INFO +0 -831
  20. durable_workflow-2.0.0rc44/tests/test_api_reference_install.py +0 -410
  21. durable_workflow-2.0.0rc44/tests/test_ci_qualification_policy.py +0 -286
  22. durable_workflow-2.0.0rc44/tests/test_docs_promotion_qualification.py +0 -223
  23. durable_workflow-2.0.0rc44/tests/test_docs_workflow_policy.py +0 -401
  24. durable_workflow-2.0.0rc44/tests/test_release_docs_audit_workflow.py +0 -172
  25. durable_workflow-2.0.0rc44/tests/test_release_docs_source.py +0 -123
  26. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/LICENSE +0 -0
  27. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/setup.cfg +0 -0
  28. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/_avro.py +0 -0
  29. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/activity.py +0 -0
  30. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/auth_composition.py +0 -0
  31. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/external_storage.py +0 -0
  32. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/external_task_input.py +0 -0
  33. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/external_task_result.py +0 -0
  34. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/history_bundle_verify.py +0 -0
  35. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/interceptors.py +0 -0
  36. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/invocable.py +0 -0
  37. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/metrics.py +0 -0
  38. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/nexus.py +0 -0
  39. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/py.typed +0 -0
  40. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/python_conformance.py +0 -0
  41. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/replay_conformance.py +0 -0
  42. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/replay_verify.py +0 -0
  43. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/retry_policy.py +0 -0
  44. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/serializer.py +0 -0
  45. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/sync.py +0 -0
  46. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/testing.py +0 -0
  47. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/worker.py +0 -0
  48. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/workflow_updates_conformance.py +0 -0
  49. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
  50. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow.egg-info/entry_points.txt +0 -0
  51. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow.egg-info/requires.txt +0 -0
  52. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow.egg-info/top_level.txt +0 -0
  53. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_activity_context.py +0 -0
  54. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_auth_composition.py +0 -0
  55. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_avro_value_benchmark.py +0 -0
  56. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_avro_value_protocol.py +0 -0
  57. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_client.py +0 -0
  58. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_codec_regression_corpus.py +0 -0
  59. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_control_plane_parity_fixtures.py +0 -0
  60. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_docstring_cross_references.py +0 -0
  61. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_durable_selection.py +0 -0
  62. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_errors.py +0 -0
  63. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_external_storage.py +0 -0
  64. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_external_task_input.py +0 -0
  65. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_external_task_result.py +0 -0
  66. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_golden_history_replay.py +0 -0
  67. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_history_bundle_verify.py +0 -0
  68. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_history_event_contract.py +0 -0
  69. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_invocable.py +0 -0
  70. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_message_streams.py +0 -0
  71. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_metrics.py +0 -0
  72. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_order_processing_example.py +0 -0
  73. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_parallel_completion_order_replay.py +0 -0
  74. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_parallel_saga_authoring.py +0 -0
  75. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_public_boundary_scanner.py +0 -0
  76. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_python_conformance.py +0 -0
  77. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_queries.py +0 -0
  78. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_readme_quickstart.py +0 -0
  79. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_replay.py +0 -0
  80. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_replay_conformance.py +0 -0
  81. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_replay_verify.py +0 -0
  82. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_retry_policy.py +0 -0
  83. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_runtime_external_payload_transport.py +0 -0
  84. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_schedules.py +0 -0
  85. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_serializer.py +0 -0
  86. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_signals.py +0 -0
  87. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_sleep.py +0 -0
  88. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_standalone_activity_client.py +0 -0
  89. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_sync.py +0 -0
  90. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_testing_harness.py +0 -0
  91. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_updates.py +0 -0
  92. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_wait_condition.py +0 -0
  93. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_worker.py +0 -0
  94. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_workflow_streams.py +0 -0
  95. {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_workflow_updates_conformance.py +0 -0
@@ -0,0 +1,177 @@
1
+ Metadata-Version: 2.4
2
+ Name: durable-workflow
3
+ Version: 2.0.2
4
+ Summary: Python client and worker SDK for Durable Workflow Cloud and self-hosted Server
5
+ Author: Durable Workflow Contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://python.durable-workflow.com/
8
+ Project-URL: Documentation, https://python.durable-workflow.com/
9
+ Project-URL: Repository, https://github.com/durable-workflow/sdk-python
10
+ Project-URL: Issues, https://github.com/durable-workflow/sdk-python/issues
11
+ Keywords: cloud,durable-execution,workflow,durable,orchestration,python,sdk,saga
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: httpx>=0.27
23
+ Requires-Dist: fastavro<2,>=1.12.2
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8.0; extra == "dev"
26
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
27
+ Requires-Dist: mypy>=1.10; extra == "dev"
28
+ Requires-Dist: playwright<2,>=1.48; extra == "dev"
29
+ Requires-Dist: PyYAML>=6.0; extra == "dev"
30
+ Requires-Dist: ruff>=0.4; extra == "dev"
31
+ Requires-Dist: tomli>=2; python_version < "3.11" and extra == "dev"
32
+ Provides-Extra: prometheus
33
+ Requires-Dist: prometheus-client>=0.20; extra == "prometheus"
34
+ Provides-Extra: docs
35
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
36
+ Requires-Dist: mkdocstrings[python]>=0.25; extra == "docs"
37
+ Requires-Dist: playwright<2,>=1.48; extra == "docs"
38
+ Requires-Dist: tomli>=2; python_version < "3.11" and extra == "docs"
39
+ Dynamic: license-file
40
+
41
+ # Durable Workflow Python SDK
42
+
43
+ [![CI](https://github.com/durable-workflow/sdk-python/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/durable-workflow/sdk-python/actions/workflows/ci.yml)
44
+ [![PyPI](https://img.shields.io/pypi/v/durable-workflow.svg)](https://pypi.org/project/durable-workflow/)
45
+ [![Python](https://img.shields.io/pypi/pyversions/durable-workflow.svg)](https://pypi.org/project/durable-workflow/)
46
+ [![License](https://img.shields.io/github/license/durable-workflow/sdk-python.svg)](LICENSE)
47
+
48
+ Build durable Python workflows and activities against [Durable Workflow
49
+ Cloud](https://cloud.durable-workflow.com/) or a
50
+ [self-hosted Server](https://github.com/durable-workflow/server). The SDK uses
51
+ the same language-neutral runtime protocol as the first-party PHP and Rust
52
+ SDKs.
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ pip install durable-workflow
58
+ ```
59
+
60
+ Python 3.10 or newer is required.
61
+
62
+ ## Quickstart
63
+
64
+ ```python
65
+ import asyncio
66
+ from uuid import uuid4
67
+
68
+ from durable_workflow import Client, Worker, workflow, activity
69
+
70
+ @activity.defn(name="greet")
71
+ def greet(name: str) -> str:
72
+ return f"hello, {name}"
73
+
74
+ @workflow.defn(name="greeter")
75
+ class GreeterWorkflow:
76
+ def run(self, ctx, name):
77
+ result = yield ctx.schedule_activity("greet", [name])
78
+ return result
79
+
80
+ async def main():
81
+ workflow_id = f"greet-{uuid4().hex}"
82
+ async with Client(
83
+ "http://server:8080",
84
+ token="dev-token-123",
85
+ namespace="default",
86
+ ) as client:
87
+ worker = Worker(
88
+ client,
89
+ task_queue="python-workers",
90
+ workflows=[GreeterWorkflow],
91
+ activities=[greet],
92
+ )
93
+ handle = await client.start_workflow(
94
+ workflow_type="greeter",
95
+ workflow_id=workflow_id,
96
+ task_queue="python-workers",
97
+ input=["world"],
98
+ )
99
+ await worker.run_until(workflow_id=workflow_id, timeout=30.0)
100
+ result = await client.get_result(handle)
101
+ print(result) # "hello, world"
102
+
103
+ if __name__ == "__main__":
104
+ asyncio.run(main())
105
+ ```
106
+
107
+ Pass the Server origin to `Client` without a trailing `/api`. For Cloud, pass
108
+ the complete namespace runtime URL exactly as provisioned. Cloud client and
109
+ worker processes use separate runtime credentials:
110
+
111
+ ```python
112
+ client = Client(
113
+ runtime_url,
114
+ control_token=client_token,
115
+ worker_token=worker_token,
116
+ namespace=namespace,
117
+ )
118
+ ```
119
+
120
+ Keep the client token in application processes and the worker token in worker
121
+ processes when deploying them separately.
122
+
123
+ ## Capabilities
124
+
125
+ - Workflows, activities, child workflows, timers, and continue-as-new
126
+ - Signals, queries, validated updates, schedules, and message streams
127
+ - Activity retries, timeouts, cancellation, and heartbeats
128
+ - Deterministic parallel work, side effects, version markers, and sagas
129
+ - Replay verification and an in-process workflow test environment
130
+ - Avro payloads, external payload storage, metrics, and interceptors
131
+
132
+ See the [capability matrix](https://durable-workflow.com/docs/2.0/capabilities/)
133
+ for the complete cross-SDK contract.
134
+
135
+ ## Documentation
136
+
137
+ - [Python SDK portal and API reference](https://python.durable-workflow.com/)
138
+ - [Python SDK guide](https://durable-workflow.com/docs/2.0/polyglot/python/)
139
+ - [Complete SDK reference](docs/sdk-reference.md)
140
+ - [Runnable examples](examples/)
141
+ - [Symmetric SDK playground](https://github.com/durable-workflow/sample-app#symmetric-sdk-playground)
142
+
143
+ ## Runtime choices
144
+
145
+ Use [Durable Workflow Cloud](https://cloud.durable-workflow.com/early-access)
146
+ for a managed namespace, or run the published
147
+ [`durableworkflow/server`](https://hub.docker.com/r/durableworkflow/server)
148
+ image yourself. Workflow and activity type names, task queues, and payloads are
149
+ portable between both runtime choices.
150
+
151
+ ## Compatibility
152
+
153
+ Stable `2.x` SDK releases follow semantic versioning and negotiate runtime
154
+ capabilities with Server at startup. Use stable `2.x` SDK and Server channels
155
+ for new applications. The [compatibility guide](https://durable-workflow.com/docs/2.0/compatibility/)
156
+ documents protocol and upgrade guarantees.
157
+
158
+ ## Development
159
+
160
+ ```bash
161
+ pip install -e '.[dev]'
162
+ ruff check src/ tests/
163
+ mypy src/durable_workflow/
164
+ pytest tests/ -m "not integration"
165
+ ```
166
+
167
+ Integration tests use Docker:
168
+
169
+ ```bash
170
+ docker compose -f docker-compose.test.yml up -d --build --wait
171
+ pytest tests/integration/ -v
172
+ docker compose -f docker-compose.test.yml down -v
173
+ ```
174
+
175
+ ## License
176
+
177
+ [MIT](LICENSE)
@@ -0,0 +1,137 @@
1
+ # Durable Workflow Python SDK
2
+
3
+ [![CI](https://github.com/durable-workflow/sdk-python/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/durable-workflow/sdk-python/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/durable-workflow.svg)](https://pypi.org/project/durable-workflow/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/durable-workflow.svg)](https://pypi.org/project/durable-workflow/)
6
+ [![License](https://img.shields.io/github/license/durable-workflow/sdk-python.svg)](LICENSE)
7
+
8
+ Build durable Python workflows and activities against [Durable Workflow
9
+ Cloud](https://cloud.durable-workflow.com/) or a
10
+ [self-hosted Server](https://github.com/durable-workflow/server). The SDK uses
11
+ the same language-neutral runtime protocol as the first-party PHP and Rust
12
+ SDKs.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install durable-workflow
18
+ ```
19
+
20
+ Python 3.10 or newer is required.
21
+
22
+ ## Quickstart
23
+
24
+ ```python
25
+ import asyncio
26
+ from uuid import uuid4
27
+
28
+ from durable_workflow import Client, Worker, workflow, activity
29
+
30
+ @activity.defn(name="greet")
31
+ def greet(name: str) -> str:
32
+ return f"hello, {name}"
33
+
34
+ @workflow.defn(name="greeter")
35
+ class GreeterWorkflow:
36
+ def run(self, ctx, name):
37
+ result = yield ctx.schedule_activity("greet", [name])
38
+ return result
39
+
40
+ async def main():
41
+ workflow_id = f"greet-{uuid4().hex}"
42
+ async with Client(
43
+ "http://server:8080",
44
+ token="dev-token-123",
45
+ namespace="default",
46
+ ) as client:
47
+ worker = Worker(
48
+ client,
49
+ task_queue="python-workers",
50
+ workflows=[GreeterWorkflow],
51
+ activities=[greet],
52
+ )
53
+ handle = await client.start_workflow(
54
+ workflow_type="greeter",
55
+ workflow_id=workflow_id,
56
+ task_queue="python-workers",
57
+ input=["world"],
58
+ )
59
+ await worker.run_until(workflow_id=workflow_id, timeout=30.0)
60
+ result = await client.get_result(handle)
61
+ print(result) # "hello, world"
62
+
63
+ if __name__ == "__main__":
64
+ asyncio.run(main())
65
+ ```
66
+
67
+ Pass the Server origin to `Client` without a trailing `/api`. For Cloud, pass
68
+ the complete namespace runtime URL exactly as provisioned. Cloud client and
69
+ worker processes use separate runtime credentials:
70
+
71
+ ```python
72
+ client = Client(
73
+ runtime_url,
74
+ control_token=client_token,
75
+ worker_token=worker_token,
76
+ namespace=namespace,
77
+ )
78
+ ```
79
+
80
+ Keep the client token in application processes and the worker token in worker
81
+ processes when deploying them separately.
82
+
83
+ ## Capabilities
84
+
85
+ - Workflows, activities, child workflows, timers, and continue-as-new
86
+ - Signals, queries, validated updates, schedules, and message streams
87
+ - Activity retries, timeouts, cancellation, and heartbeats
88
+ - Deterministic parallel work, side effects, version markers, and sagas
89
+ - Replay verification and an in-process workflow test environment
90
+ - Avro payloads, external payload storage, metrics, and interceptors
91
+
92
+ See the [capability matrix](https://durable-workflow.com/docs/2.0/capabilities/)
93
+ for the complete cross-SDK contract.
94
+
95
+ ## Documentation
96
+
97
+ - [Python SDK portal and API reference](https://python.durable-workflow.com/)
98
+ - [Python SDK guide](https://durable-workflow.com/docs/2.0/polyglot/python/)
99
+ - [Complete SDK reference](docs/sdk-reference.md)
100
+ - [Runnable examples](examples/)
101
+ - [Symmetric SDK playground](https://github.com/durable-workflow/sample-app#symmetric-sdk-playground)
102
+
103
+ ## Runtime choices
104
+
105
+ Use [Durable Workflow Cloud](https://cloud.durable-workflow.com/early-access)
106
+ for a managed namespace, or run the published
107
+ [`durableworkflow/server`](https://hub.docker.com/r/durableworkflow/server)
108
+ image yourself. Workflow and activity type names, task queues, and payloads are
109
+ portable between both runtime choices.
110
+
111
+ ## Compatibility
112
+
113
+ Stable `2.x` SDK releases follow semantic versioning and negotiate runtime
114
+ capabilities with Server at startup. Use stable `2.x` SDK and Server channels
115
+ for new applications. The [compatibility guide](https://durable-workflow.com/docs/2.0/compatibility/)
116
+ documents protocol and upgrade guarantees.
117
+
118
+ ## Development
119
+
120
+ ```bash
121
+ pip install -e '.[dev]'
122
+ ruff check src/ tests/
123
+ mypy src/durable_workflow/
124
+ pytest tests/ -m "not integration"
125
+ ```
126
+
127
+ Integration tests use Docker:
128
+
129
+ ```bash
130
+ docker compose -f docker-compose.test.yml up -d --build --wait
131
+ pytest tests/integration/ -v
132
+ docker compose -f docker-compose.test.yml down -v
133
+ ```
134
+
135
+ ## License
136
+
137
+ [MIT](LICENSE)
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "durable-workflow"
7
- version = "2.0.0-rc.44"
8
- description = "Release candidate Python SDK for the Durable Workflow 2.0 train"
7
+ version = "2.0.2"
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"
11
11
  license = "MIT"
@@ -14,10 +14,13 @@ authors = [
14
14
  { name = "Durable Workflow Contributors" },
15
15
  ]
16
16
  keywords = [
17
+ "cloud",
18
+ "durable-execution",
17
19
  "workflow",
18
20
  "durable",
19
21
  "orchestration",
20
- "temporal",
22
+ "python",
23
+ "sdk",
21
24
  "saga",
22
25
  ]
23
26
  classifiers = [
@@ -55,8 +58,8 @@ docs = [
55
58
  ]
56
59
 
57
60
  [project.urls]
58
- Homepage = "https://github.com/durable-workflow/sdk-python"
59
- Documentation = "https://durable-workflow.github.io/docs/2.0/polyglot/python"
61
+ Homepage = "https://python.durable-workflow.com/"
62
+ Documentation = "https://python.durable-workflow.com/"
60
63
  Repository = "https://github.com/durable-workflow/sdk-python"
61
64
  Issues = "https://github.com/durable-workflow/sdk-python/issues"
62
65
 
@@ -68,9 +71,9 @@ durable-workflow-replay-conformance = "durable_workflow.replay_conformance:main"
68
71
  durable-workflow-workflow-updates-conformance = "durable_workflow.workflow_updates_conformance:main"
69
72
 
70
73
  [tool.durable-workflow]
71
- product-train = "2.0.0-rc.44"
72
- registry-version = "2.0.0rc44"
73
- supported-server-versions = "2.0.0-rc.68"
74
+ product-train = "2.0.2"
75
+ registry-version = "2.0.2"
76
+ supported-server-versions = "2.0.0"
74
77
  worker-protocol-version = "1.19"
75
78
  control-plane-version = "2"
76
79
  durable-selection = true
@@ -95,6 +95,7 @@ from .errors import (
95
95
  WorkflowNotFound,
96
96
  WorkflowPayloadDecodeError,
97
97
  WorkflowTerminated,
98
+ WorkflowTimedOut,
98
99
  )
99
100
  from .external_storage import (
100
101
  EXTERNAL_PAYLOAD_REFERENCE_SCHEMA,
@@ -374,6 +375,7 @@ __all__ = [
374
375
  "WorkflowFailed",
375
376
  "WorkflowNotFound",
376
377
  "WorkflowTerminated",
378
+ "WorkflowTimedOut",
377
379
  "EXTERNAL_TASK_INPUT_CONTRACT_SCHEMA",
378
380
  "EXTERNAL_TASK_INPUT_MEDIA_TYPE",
379
381
  "EXTERNAL_TASK_INPUT_SCHEMA",
@@ -46,6 +46,7 @@ from .errors import (
46
46
  WorkflowCancelled,
47
47
  WorkflowFailed,
48
48
  WorkflowTerminated,
49
+ WorkflowTimedOut,
49
50
  _raise_for_status,
50
51
  )
51
52
  from .external_storage import (
@@ -4141,8 +4142,9 @@ class Client:
4141
4142
  Raises :class:`~durable_workflow.errors.WorkflowFailed`,
4142
4143
  :class:`~durable_workflow.errors.WorkflowCancelled`, or
4143
4144
  :class:`~durable_workflow.errors.WorkflowTerminated` if the workflow
4144
- ended in a non-success state, or :class:`TimeoutError` if ``timeout``
4145
- seconds elapse before the workflow terminates.
4145
+ ended in a non-success state. A persisted execution or run deadline
4146
+ raises :class:`~durable_workflow.errors.WorkflowTimedOut`; a caller's
4147
+ :class:`TimeoutError` means ``timeout`` seconds elapsed while waiting.
4146
4148
  """
4147
4149
  deadline = asyncio.get_running_loop().time() + timeout
4148
4150
  while True:
@@ -4177,6 +4179,8 @@ class Client:
4177
4179
  raise WorkflowCancelled(
4178
4180
  payload.get("reason", "workflow was cancelled")
4179
4181
  )
4182
+ if etype == "WorkflowTimedOut":
4183
+ raise WorkflowTimedOut()
4180
4184
  return None
4181
4185
  if asyncio.get_running_loop().time() > deadline:
4182
4186
  raise TimeoutError(
@@ -560,6 +560,17 @@ class WorkflowTerminated(DurableWorkflowError):
560
560
  super().__init__(message)
561
561
 
562
562
 
563
+ class WorkflowTimedOut(DurableWorkflowError):
564
+ """A persisted workflow execution or run deadline expired.
565
+
566
+ Unlike a caller's :class:`TimeoutError` while polling, this is a terminal
567
+ workflow outcome recorded by the runtime.
568
+ """
569
+
570
+ def __init__(self, message: str = "workflow execution timed out") -> None:
571
+ super().__init__(message)
572
+
573
+
563
574
  class SagaCompensationFailed(DurableWorkflowError):
564
575
  """A saga compensation failed after an earlier workflow failure.
565
576
 
@@ -3925,6 +3925,11 @@ def _replay_state(
3925
3925
  if current_wait_id is None:
3926
3926
  if prefix_can_bind_to_first_wait or explicit_sequence is None:
3927
3927
  prefix_receivers.append(index)
3928
+ else:
3929
+ # A durable step separated this receiver from the last
3930
+ # condition. Record that boundary so a stale sequence
3931
+ # cannot bind it back to the completed wait.
3932
+ bindings[index] = None
3928
3933
  continue
3929
3934
 
3930
3935
  receivers_since_wait.append(index)
@@ -0,0 +1,177 @@
1
+ Metadata-Version: 2.4
2
+ Name: durable-workflow
3
+ Version: 2.0.2
4
+ Summary: Python client and worker SDK for Durable Workflow Cloud and self-hosted Server
5
+ Author: Durable Workflow Contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://python.durable-workflow.com/
8
+ Project-URL: Documentation, https://python.durable-workflow.com/
9
+ Project-URL: Repository, https://github.com/durable-workflow/sdk-python
10
+ Project-URL: Issues, https://github.com/durable-workflow/sdk-python/issues
11
+ Keywords: cloud,durable-execution,workflow,durable,orchestration,python,sdk,saga
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: httpx>=0.27
23
+ Requires-Dist: fastavro<2,>=1.12.2
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8.0; extra == "dev"
26
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
27
+ Requires-Dist: mypy>=1.10; extra == "dev"
28
+ Requires-Dist: playwright<2,>=1.48; extra == "dev"
29
+ Requires-Dist: PyYAML>=6.0; extra == "dev"
30
+ Requires-Dist: ruff>=0.4; extra == "dev"
31
+ Requires-Dist: tomli>=2; python_version < "3.11" and extra == "dev"
32
+ Provides-Extra: prometheus
33
+ Requires-Dist: prometheus-client>=0.20; extra == "prometheus"
34
+ Provides-Extra: docs
35
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
36
+ Requires-Dist: mkdocstrings[python]>=0.25; extra == "docs"
37
+ Requires-Dist: playwright<2,>=1.48; extra == "docs"
38
+ Requires-Dist: tomli>=2; python_version < "3.11" and extra == "docs"
39
+ Dynamic: license-file
40
+
41
+ # Durable Workflow Python SDK
42
+
43
+ [![CI](https://github.com/durable-workflow/sdk-python/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/durable-workflow/sdk-python/actions/workflows/ci.yml)
44
+ [![PyPI](https://img.shields.io/pypi/v/durable-workflow.svg)](https://pypi.org/project/durable-workflow/)
45
+ [![Python](https://img.shields.io/pypi/pyversions/durable-workflow.svg)](https://pypi.org/project/durable-workflow/)
46
+ [![License](https://img.shields.io/github/license/durable-workflow/sdk-python.svg)](LICENSE)
47
+
48
+ Build durable Python workflows and activities against [Durable Workflow
49
+ Cloud](https://cloud.durable-workflow.com/) or a
50
+ [self-hosted Server](https://github.com/durable-workflow/server). The SDK uses
51
+ the same language-neutral runtime protocol as the first-party PHP and Rust
52
+ SDKs.
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ pip install durable-workflow
58
+ ```
59
+
60
+ Python 3.10 or newer is required.
61
+
62
+ ## Quickstart
63
+
64
+ ```python
65
+ import asyncio
66
+ from uuid import uuid4
67
+
68
+ from durable_workflow import Client, Worker, workflow, activity
69
+
70
+ @activity.defn(name="greet")
71
+ def greet(name: str) -> str:
72
+ return f"hello, {name}"
73
+
74
+ @workflow.defn(name="greeter")
75
+ class GreeterWorkflow:
76
+ def run(self, ctx, name):
77
+ result = yield ctx.schedule_activity("greet", [name])
78
+ return result
79
+
80
+ async def main():
81
+ workflow_id = f"greet-{uuid4().hex}"
82
+ async with Client(
83
+ "http://server:8080",
84
+ token="dev-token-123",
85
+ namespace="default",
86
+ ) as client:
87
+ worker = Worker(
88
+ client,
89
+ task_queue="python-workers",
90
+ workflows=[GreeterWorkflow],
91
+ activities=[greet],
92
+ )
93
+ handle = await client.start_workflow(
94
+ workflow_type="greeter",
95
+ workflow_id=workflow_id,
96
+ task_queue="python-workers",
97
+ input=["world"],
98
+ )
99
+ await worker.run_until(workflow_id=workflow_id, timeout=30.0)
100
+ result = await client.get_result(handle)
101
+ print(result) # "hello, world"
102
+
103
+ if __name__ == "__main__":
104
+ asyncio.run(main())
105
+ ```
106
+
107
+ Pass the Server origin to `Client` without a trailing `/api`. For Cloud, pass
108
+ the complete namespace runtime URL exactly as provisioned. Cloud client and
109
+ worker processes use separate runtime credentials:
110
+
111
+ ```python
112
+ client = Client(
113
+ runtime_url,
114
+ control_token=client_token,
115
+ worker_token=worker_token,
116
+ namespace=namespace,
117
+ )
118
+ ```
119
+
120
+ Keep the client token in application processes and the worker token in worker
121
+ processes when deploying them separately.
122
+
123
+ ## Capabilities
124
+
125
+ - Workflows, activities, child workflows, timers, and continue-as-new
126
+ - Signals, queries, validated updates, schedules, and message streams
127
+ - Activity retries, timeouts, cancellation, and heartbeats
128
+ - Deterministic parallel work, side effects, version markers, and sagas
129
+ - Replay verification and an in-process workflow test environment
130
+ - Avro payloads, external payload storage, metrics, and interceptors
131
+
132
+ See the [capability matrix](https://durable-workflow.com/docs/2.0/capabilities/)
133
+ for the complete cross-SDK contract.
134
+
135
+ ## Documentation
136
+
137
+ - [Python SDK portal and API reference](https://python.durable-workflow.com/)
138
+ - [Python SDK guide](https://durable-workflow.com/docs/2.0/polyglot/python/)
139
+ - [Complete SDK reference](docs/sdk-reference.md)
140
+ - [Runnable examples](examples/)
141
+ - [Symmetric SDK playground](https://github.com/durable-workflow/sample-app#symmetric-sdk-playground)
142
+
143
+ ## Runtime choices
144
+
145
+ Use [Durable Workflow Cloud](https://cloud.durable-workflow.com/early-access)
146
+ for a managed namespace, or run the published
147
+ [`durableworkflow/server`](https://hub.docker.com/r/durableworkflow/server)
148
+ image yourself. Workflow and activity type names, task queues, and payloads are
149
+ portable between both runtime choices.
150
+
151
+ ## Compatibility
152
+
153
+ Stable `2.x` SDK releases follow semantic versioning and negotiate runtime
154
+ capabilities with Server at startup. Use stable `2.x` SDK and Server channels
155
+ for new applications. The [compatibility guide](https://durable-workflow.com/docs/2.0/compatibility/)
156
+ documents protocol and upgrade guarantees.
157
+
158
+ ## Development
159
+
160
+ ```bash
161
+ pip install -e '.[dev]'
162
+ ruff check src/ tests/
163
+ mypy src/durable_workflow/
164
+ pytest tests/ -m "not integration"
165
+ ```
166
+
167
+ Integration tests use Docker:
168
+
169
+ ```bash
170
+ docker compose -f docker-compose.test.yml up -d --build --wait
171
+ pytest tests/integration/ -v
172
+ docker compose -f docker-compose.test.yml down -v
173
+ ```
174
+
175
+ ## License
176
+
177
+ [MIT](LICENSE)
@@ -33,18 +33,14 @@ src/durable_workflow.egg-info/entry_points.txt
33
33
  src/durable_workflow.egg-info/requires.txt
34
34
  src/durable_workflow.egg-info/top_level.txt
35
35
  tests/test_activity_context.py
36
- tests/test_api_reference_install.py
37
36
  tests/test_auth_composition.py
38
37
  tests/test_avro_value_benchmark.py
39
38
  tests/test_avro_value_protocol.py
40
39
  tests/test_ci_checkout.py
41
40
  tests/test_ci_integration_endpoint.py
42
- tests/test_ci_qualification_policy.py
43
41
  tests/test_client.py
44
42
  tests/test_codec_regression_corpus.py
45
43
  tests/test_control_plane_parity_fixtures.py
46
- tests/test_docs_promotion_qualification.py
47
- tests/test_docs_workflow_policy.py
48
44
  tests/test_docstring_cross_references.py
49
45
  tests/test_durable_selection.py
50
46
  tests/test_errors.py
@@ -65,8 +61,6 @@ tests/test_pypi_project_surface.py
65
61
  tests/test_python_conformance.py
66
62
  tests/test_queries.py
67
63
  tests/test_readme_quickstart.py
68
- tests/test_release_docs_audit_workflow.py
69
- tests/test_release_docs_source.py
70
64
  tests/test_release_metadata.py
71
65
  tests/test_replay.py
72
66
  tests/test_replay_conformance.py
@@ -85,5 +79,6 @@ tests/test_update_signal_condition_replay.py
85
79
  tests/test_updates.py
86
80
  tests/test_wait_condition.py
87
81
  tests/test_worker.py
82
+ tests/test_workflow_result_timeout.py
88
83
  tests/test_workflow_streams.py
89
84
  tests/test_workflow_updates_conformance.py
@@ -67,7 +67,6 @@ def test_public_checkout_uses_github_authority_on_every_runner(
67
67
  def test_ci_workflow_uses_portable_public_checkouts() -> None:
68
68
  workflow = CI_WORKFLOW.read_text()
69
69
 
70
- assert "checkout-public-repository.py cli cli" in workflow
71
70
  assert "checkout-public-repository.py server server" in workflow
72
71
  public_repository_inputs = [
73
72
  line.strip() for line in workflow.splitlines() if line.strip().startswith("repository: durable-workflow/")
@@ -193,7 +193,6 @@ def test_ci_isolates_the_stack_and_emits_diagnostics_before_teardown() -> None:
193
193
  assert 'logs --no-color --tail 200 "$service"' in integration_job
194
194
  assert diagnostics_offset < teardown_offset
195
195
  assert "DURABLE_WORKFLOW_SERVER_URL: http://localhost:8080" not in integration_job
196
- assert " - qualification-class\n" in qualification_job
197
196
  assert " - integration\n" in qualification_job
198
197
  assert 'test "$INTEGRATION_RESULT" = success' in qualification_job
199
198