durable-workflow 2.1.0__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.0/src/durable_workflow.egg-info → durable_workflow-2.1.2}/PKG-INFO +1 -1
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/pyproject.toml +3 -3
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/worker.py +12 -3
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/workflow.py +12 -2
- {durable_workflow-2.1.0 → durable_workflow-2.1.2/src/durable_workflow.egg-info}/PKG-INFO +1 -1
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_pypi_project_surface.py +5 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_release_metadata.py +2 -2
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_replay.py +51 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_replay_regression_corpus.py +10 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_worker.py +59 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/LICENSE +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/README.md +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/setup.cfg +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/__init__.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/_avro.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/activity.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/auth_composition.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/client.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/errors.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/external_storage.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/external_task_input.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/external_task_result.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/history_bundle_verify.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/interceptors.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/invocable.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/metrics.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/nexus.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/py.typed +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/python_conformance.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/replay_conformance.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/replay_verify.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/retry_policy.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/serializer.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/sync.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/testing.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/workflow_updates_conformance.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/SOURCES.txt +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/entry_points.txt +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/requires.txt +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/top_level.txt +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_activity_context.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_auth_composition.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_avro_value_benchmark.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_avro_value_protocol.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_ci_checkout.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_ci_integration_endpoint.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_client.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_codec_regression_corpus.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_control_plane_parity_fixtures.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_docstring_cross_references.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_durable_selection.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_errors.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_external_storage.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_external_task_input.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_external_task_result.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_golden_history_replay.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_history_bundle_verify.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_history_event_contract.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_invocable.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_message_streams.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_metrics.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_order_processing_example.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_parallel_completion_order_replay.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_parallel_saga_authoring.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_public_boundary_scanner.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_python_conformance.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_queries.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_readme_quickstart.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_redrive_replay.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_replay_conformance.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_replay_verify.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_retry_policy.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_runtime_external_payload_transport.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_schedules.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_serializer.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_signals.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_sleep.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_standalone_activity_client.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_storage_admission.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_sync.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_testing_harness.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_update_signal_condition_replay.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_updates.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_wait_condition.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_workflow_result_timeout.py +0 -0
- {durable_workflow-2.1.0 → durable_workflow-2.1.2}/tests/test_workflow_streams.py +0 -0
- {durable_workflow-2.1.0 → 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:
|
|
@@ -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 =
|
|
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
|
|
@@ -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"]
|
|
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"] == "
|
|
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,
|
|
@@ -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.0 → durable_workflow-2.1.2}/src/durable_workflow/external_task_input.py
RENAMED
|
File without changes
|
{durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow/external_task_result.py
RENAMED
|
File without changes
|
{durable_workflow-2.1.0 → 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.0 → durable_workflow-2.1.2}/src/durable_workflow/python_conformance.py
RENAMED
|
File without changes
|
{durable_workflow-2.1.0 → 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
|
{durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{durable_workflow-2.1.0 → durable_workflow-2.1.2}/src/durable_workflow.egg-info/requires.txt
RENAMED
|
File without changes
|
{durable_workflow-2.1.0 → 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.0 → 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.0 → 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
|
{durable_workflow-2.1.0 → 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.0 → 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.0 → durable_workflow-2.1.2}/tests/test_workflow_updates_conformance.py
RENAMED
|
File without changes
|