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.
- durable_workflow-2.0.2/PKG-INFO +177 -0
- durable_workflow-2.0.2/README.md +137 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/pyproject.toml +11 -8
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/__init__.py +2 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/client.py +6 -2
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/errors.py +11 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/workflow.py +5 -0
- durable_workflow-2.0.2/src/durable_workflow.egg-info/PKG-INFO +177 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow.egg-info/SOURCES.txt +1 -6
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_ci_checkout.py +0 -1
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_ci_integration_endpoint.py +0 -1
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_pypi_project_surface.py +15 -51
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_release_metadata.py +16 -68
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_replay_regression_corpus.py +5 -1
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_update_signal_condition_replay.py +129 -0
- durable_workflow-2.0.2/tests/test_workflow_result_timeout.py +61 -0
- durable_workflow-2.0.0rc44/PKG-INFO +0 -831
- durable_workflow-2.0.0rc44/README.md +0 -791
- durable_workflow-2.0.0rc44/src/durable_workflow.egg-info/PKG-INFO +0 -831
- durable_workflow-2.0.0rc44/tests/test_api_reference_install.py +0 -410
- durable_workflow-2.0.0rc44/tests/test_ci_qualification_policy.py +0 -286
- durable_workflow-2.0.0rc44/tests/test_docs_promotion_qualification.py +0 -223
- durable_workflow-2.0.0rc44/tests/test_docs_workflow_policy.py +0 -401
- durable_workflow-2.0.0rc44/tests/test_release_docs_audit_workflow.py +0 -172
- durable_workflow-2.0.0rc44/tests/test_release_docs_source.py +0 -123
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/LICENSE +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/setup.cfg +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/_avro.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/activity.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/auth_composition.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/external_storage.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/external_task_input.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/external_task_result.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/history_bundle_verify.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/interceptors.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/invocable.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/metrics.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/nexus.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/py.typed +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/python_conformance.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/replay_conformance.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/replay_verify.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/retry_policy.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/serializer.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/sync.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/testing.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/worker.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow/workflow_updates_conformance.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow.egg-info/entry_points.txt +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow.egg-info/requires.txt +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow.egg-info/top_level.txt +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_activity_context.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_auth_composition.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_avro_value_benchmark.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_avro_value_protocol.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_client.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_codec_regression_corpus.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_control_plane_parity_fixtures.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_docstring_cross_references.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_durable_selection.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_errors.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_external_storage.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_external_task_input.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_external_task_result.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_golden_history_replay.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_history_bundle_verify.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_history_event_contract.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_invocable.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_message_streams.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_metrics.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_order_processing_example.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_parallel_completion_order_replay.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_parallel_saga_authoring.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_public_boundary_scanner.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_python_conformance.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_queries.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_readme_quickstart.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_replay.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_replay_conformance.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_replay_verify.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_retry_policy.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_runtime_external_payload_transport.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_schedules.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_serializer.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_signals.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_sleep.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_standalone_activity_client.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_sync.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_testing_harness.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_updates.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_wait_condition.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_worker.py +0 -0
- {durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/tests/test_workflow_streams.py +0 -0
- {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
|
+
[](https://github.com/durable-workflow/sdk-python/actions/workflows/ci.yml)
|
|
44
|
+
[](https://pypi.org/project/durable-workflow/)
|
|
45
|
+
[](https://pypi.org/project/durable-workflow/)
|
|
46
|
+
[](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
|
+
[](https://github.com/durable-workflow/sdk-python/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/durable-workflow/)
|
|
5
|
+
[](https://pypi.org/project/durable-workflow/)
|
|
6
|
+
[](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.
|
|
8
|
-
description = "
|
|
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
|
-
"
|
|
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://
|
|
59
|
-
Documentation = "https://durable-workflow.
|
|
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.
|
|
72
|
-
registry-version = "2.0.
|
|
73
|
-
supported-server-versions = "2.0.0
|
|
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
|
|
4145
|
-
|
|
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
|
+
[](https://github.com/durable-workflow/sdk-python/actions/workflows/ci.yml)
|
|
44
|
+
[](https://pypi.org/project/durable-workflow/)
|
|
45
|
+
[](https://pypi.org/project/durable-workflow/)
|
|
46
|
+
[](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)
|
{durable_workflow-2.0.0rc44 → durable_workflow-2.0.2}/src/durable_workflow.egg-info/SOURCES.txt
RENAMED
|
@@ -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
|
|