durable-workflow 2.1.1__tar.gz → 2.1.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.1.1/src/durable_workflow.egg-info → durable_workflow-2.1.2}/PKG-INFO +1 -1
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/pyproject.toml +3 -3
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/worker.py +12 -3
- {durable_workflow-2.1.1 → durable_workflow-2.1.2/src/durable_workflow.egg-info}/PKG-INFO +1 -1
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_worker.py +59 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/LICENSE +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/README.md +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/setup.cfg +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/__init__.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/_avro.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/activity.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/auth_composition.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/client.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/errors.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/external_storage.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/external_task_input.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/external_task_result.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/history_bundle_verify.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/interceptors.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/invocable.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/metrics.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/nexus.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/py.typed +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/python_conformance.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/replay_conformance.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/replay_verify.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/retry_policy.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/serializer.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/sync.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/testing.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/workflow.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/workflow_updates_conformance.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/SOURCES.txt +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/entry_points.txt +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/requires.txt +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/top_level.txt +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_activity_context.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_auth_composition.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_avro_value_benchmark.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_avro_value_protocol.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_ci_checkout.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_ci_integration_endpoint.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_client.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_codec_regression_corpus.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_control_plane_parity_fixtures.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_docstring_cross_references.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_durable_selection.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_errors.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_external_storage.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_external_task_input.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_external_task_result.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_golden_history_replay.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_history_bundle_verify.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_history_event_contract.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_invocable.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_message_streams.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_metrics.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_order_processing_example.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_parallel_completion_order_replay.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_parallel_saga_authoring.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_public_boundary_scanner.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_pypi_project_surface.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_python_conformance.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_queries.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_readme_quickstart.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_redrive_replay.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_release_metadata.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_replay.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_replay_conformance.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_replay_regression_corpus.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_replay_verify.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_retry_policy.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_runtime_external_payload_transport.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_schedules.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_serializer.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_signals.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_sleep.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_standalone_activity_client.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_storage_admission.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_sync.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_testing_harness.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_update_signal_condition_replay.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_updates.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_wait_condition.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_workflow_result_timeout.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_workflow_streams.py +0 -0
- {durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_workflow_updates_conformance.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "durable-workflow"
|
|
7
|
-
version = "2.1.
|
|
7
|
+
version = "2.1.2"
|
|
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.
|
|
75
|
-
registry-version = "2.1.
|
|
74
|
+
product-train = "2.1.2"
|
|
75
|
+
registry-version = "2.1.2"
|
|
76
76
|
supported-server-versions = "2.4.0"
|
|
77
77
|
worker-protocol-version = "1.19"
|
|
78
78
|
control-plane-version = "2"
|
|
@@ -162,6 +162,15 @@ def _is_storage_admission_error(error: BaseException) -> bool:
|
|
|
162
162
|
)
|
|
163
163
|
|
|
164
164
|
|
|
165
|
+
def _poll_error_summary(error: BaseException) -> str:
|
|
166
|
+
if not isinstance(error, ServerError):
|
|
167
|
+
return str(error)
|
|
168
|
+
reason = error.reason()
|
|
169
|
+
if not isinstance(reason, str) or not reason:
|
|
170
|
+
return f"server returned {error.status}"
|
|
171
|
+
return f"server returned {error.status} reason={reason[:64]!r}"
|
|
172
|
+
|
|
173
|
+
|
|
165
174
|
def _command_payload_codec(codec: object) -> str:
|
|
166
175
|
if codec is None:
|
|
167
176
|
return serializer.AVRO_CODEC
|
|
@@ -2184,7 +2193,7 @@ class Worker:
|
|
|
2184
2193
|
if _is_storage_admission_error(e):
|
|
2185
2194
|
raise
|
|
2186
2195
|
self._record_poll_metrics("workflow", "error", time.perf_counter() - poll_start)
|
|
2187
|
-
log.warning("workflow poll error: %s", e)
|
|
2196
|
+
log.warning("workflow poll error: %s", _poll_error_summary(e))
|
|
2188
2197
|
await asyncio.sleep(1.0)
|
|
2189
2198
|
continue
|
|
2190
2199
|
if task is None:
|
|
@@ -2282,7 +2291,7 @@ class Worker:
|
|
|
2282
2291
|
if _is_storage_admission_error(e):
|
|
2283
2292
|
raise
|
|
2284
2293
|
self._record_poll_metrics("activity", "error", time.perf_counter() - poll_start)
|
|
2285
|
-
log.warning("activity poll error: %s", e)
|
|
2294
|
+
log.warning("activity poll error: %s", _poll_error_summary(e))
|
|
2286
2295
|
await asyncio.sleep(1.0)
|
|
2287
2296
|
continue
|
|
2288
2297
|
if task is None:
|
|
@@ -2333,7 +2342,7 @@ class Worker:
|
|
|
2333
2342
|
if _is_storage_admission_error(e):
|
|
2334
2343
|
raise
|
|
2335
2344
|
self._record_poll_metrics("query", "error", time.perf_counter() - poll_start)
|
|
2336
|
-
log.warning("query poll error: %s", e)
|
|
2345
|
+
log.warning("query poll error: %s", _poll_error_summary(e))
|
|
2337
2346
|
await asyncio.sleep(1.0)
|
|
2338
2347
|
continue
|
|
2339
2348
|
if task is None:
|
|
@@ -820,6 +820,65 @@ class TestWorkerRegistration:
|
|
|
820
820
|
assert query_called.is_set()
|
|
821
821
|
assert query_kwargs["build_id"] == "release-2026.04.22-a1"
|
|
822
822
|
|
|
823
|
+
@pytest.mark.parametrize(
|
|
824
|
+
("poll_method", "loop_method", "task_kind"),
|
|
825
|
+
[
|
|
826
|
+
("poll_workflow_task", "_poll_workflow_tasks", "workflow"),
|
|
827
|
+
("poll_activity_task", "_poll_activity_tasks", "activity"),
|
|
828
|
+
("poll_query_task", "_poll_query_tasks", "query"),
|
|
829
|
+
],
|
|
830
|
+
)
|
|
831
|
+
@pytest.mark.asyncio
|
|
832
|
+
async def test_poll_warning_omits_large_server_response(
|
|
833
|
+
self,
|
|
834
|
+
mock_client: AsyncMock,
|
|
835
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
836
|
+
caplog: pytest.LogCaptureFixture,
|
|
837
|
+
poll_method: str,
|
|
838
|
+
loop_method: str,
|
|
839
|
+
task_kind: str,
|
|
840
|
+
) -> None:
|
|
841
|
+
worker = Worker(
|
|
842
|
+
mock_client,
|
|
843
|
+
task_queue="q1",
|
|
844
|
+
workflows=[TestWorkflow],
|
|
845
|
+
activities=[echo_activity],
|
|
846
|
+
)
|
|
847
|
+
getattr(mock_client, poll_method).side_effect = ServerError(
|
|
848
|
+
429,
|
|
849
|
+
{
|
|
850
|
+
"reason": "long_poll_capacity_exhausted",
|
|
851
|
+
"server_capabilities": {"large": "x" * 10_000},
|
|
852
|
+
},
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
async def stop_after_backoff(seconds: float) -> None:
|
|
856
|
+
assert seconds == 1.0
|
|
857
|
+
worker._stop.set()
|
|
858
|
+
|
|
859
|
+
monkeypatch.setattr(worker_module.asyncio, "sleep", stop_after_backoff)
|
|
860
|
+
with caplog.at_level(logging.WARNING, logger="durable_workflow.worker"):
|
|
861
|
+
await getattr(worker, loop_method)()
|
|
862
|
+
|
|
863
|
+
warnings = [
|
|
864
|
+
record.getMessage()
|
|
865
|
+
for record in caplog.records
|
|
866
|
+
if record.name == "durable_workflow.worker"
|
|
867
|
+
]
|
|
868
|
+
assert warnings == [
|
|
869
|
+
f"{task_kind} poll error: server returned 429 "
|
|
870
|
+
"reason='long_poll_capacity_exhausted'"
|
|
871
|
+
]
|
|
872
|
+
assert "server_capabilities" not in caplog.text
|
|
873
|
+
|
|
874
|
+
def test_poll_error_summary_bounds_reason(self) -> None:
|
|
875
|
+
summary = worker_module._poll_error_summary(
|
|
876
|
+
ServerError(429, {"reason": "x" * 10_000})
|
|
877
|
+
)
|
|
878
|
+
assert summary == f"server returned 429 reason={'x' * 64!r}"
|
|
879
|
+
assert worker_module._poll_error_summary(ServerError(503, {})) == "server returned 503"
|
|
880
|
+
assert worker_module._poll_error_summary(ValueError("transport failed")) == "transport failed"
|
|
881
|
+
|
|
823
882
|
@pytest.mark.asyncio
|
|
824
883
|
async def test_register_omits_build_id_when_not_configured(
|
|
825
884
|
self, mock_client: AsyncMock
|
|
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-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/external_task_input.py
RENAMED
|
File without changes
|
{durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/external_task_result.py
RENAMED
|
File without changes
|
{durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/history_bundle_verify.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow/python_conformance.py
RENAMED
|
File without changes
|
{durable_workflow-2.1.1 → durable_workflow-2.1.2}/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
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/requires.txt
RENAMED
|
File without changes
|
{durable_workflow-2.1.1 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/top_level.txt
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-2.1.1 → durable_workflow-2.1.2}/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
|
{durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_parallel_completion_order_replay.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
|
{durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_runtime_external_payload_transport.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-2.1.1 → durable_workflow-2.1.2}/tests/test_update_signal_condition_replay.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.1.1 → durable_workflow-2.1.2}/tests/test_workflow_updates_conformance.py
RENAMED
|
File without changes
|