durable-workflow 2.3.6__tar.gz → 2.3.8__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.3.6/src/durable_workflow.egg-info → durable_workflow-2.3.8}/PKG-INFO +1 -1
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/pyproject.toml +3 -3
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/worker.py +15 -41
- {durable_workflow-2.3.6 → durable_workflow-2.3.8/src/durable_workflow.egg-info}/PKG-INFO +1 -1
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_replay_regression_corpus.py +50 -2
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_worker.py +38 -4
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/LICENSE +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/README.md +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/setup.cfg +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/__init__.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/_avro.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/activity.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/auth_composition.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/client.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/errors.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/external_storage.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/external_task_input.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/external_task_result.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/history_bundle_verify.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/interceptors.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/invocable.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/metrics.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/nexus.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/py.typed +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/python_conformance.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/replay_conformance.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/replay_verify.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/retry_policy.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/serializer.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/sync.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/testing.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/worker_session.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/workflow.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/workflow_updates_conformance.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow.egg-info/SOURCES.txt +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow.egg-info/entry_points.txt +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow.egg-info/requires.txt +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow.egg-info/top_level.txt +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_activity_context.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_auth_composition.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_avro_value_benchmark.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_avro_value_protocol.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_ci_checkout.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_ci_integration_endpoint.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_client.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_codec_regression_corpus.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_control_plane_parity_fixtures.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_docstring_cross_references.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_durable_selection.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_errors.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_external_storage.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_external_task_input.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_external_task_result.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_golden_history_replay.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_history_bundle_verify.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_history_event_contract.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_invocable.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_message_streams.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_metrics.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_order_processing_example.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_parallel_completion_order_replay.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_parallel_saga_authoring.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_public_boundary_scanner.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_pypi_project_surface.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_python_conformance.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_queries.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_readme_quickstart.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_redrive_replay.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_release_metadata.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_replay.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_replay_conformance.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_replay_verify.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_retry_policy.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_runtime_external_payload_transport.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_schedules.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_serializer.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_signals.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_sleep.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_standalone_activity_client.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_storage_admission.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_sync.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_testing_harness.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_update_signal_condition_replay.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_updates.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_wait_condition.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_worker_session.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_workflow_result_timeout.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_workflow_streams.py +0 -0
- {durable_workflow-2.3.6 → durable_workflow-2.3.8}/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.3.
|
|
7
|
+
version = "2.3.8"
|
|
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.3.
|
|
75
|
-
registry-version = "2.3.
|
|
74
|
+
product-train = "2.3.8"
|
|
75
|
+
registry-version = "2.3.8"
|
|
76
76
|
supported-server-versions = "2.4.8"
|
|
77
77
|
worker-protocol-version = "1.19"
|
|
78
78
|
control-plane-version = "2"
|
|
@@ -98,6 +98,7 @@ from .workflow import (
|
|
|
98
98
|
log = logging.getLogger("durable_workflow.worker")
|
|
99
99
|
|
|
100
100
|
QUERY_TASKS_CAPABILITY = "query_tasks"
|
|
101
|
+
WORKFLOW_HISTORY_PAGE_SIZE = 500
|
|
101
102
|
UPDATE_VALIDATION_TASKS_CAPABILITY = "update_validation_tasks"
|
|
102
103
|
WORKFLOW_UPDATES_CAPABILITY = "workflow_updates"
|
|
103
104
|
MESSAGE_STREAMS_CAPABILITY = "message_streams"
|
|
@@ -950,30 +951,6 @@ def _server_supports_update_validation_tasks(info: dict[str, Any]) -> bool:
|
|
|
950
951
|
)
|
|
951
952
|
|
|
952
953
|
|
|
953
|
-
def _server_long_poll_timeout(info: dict[str, Any]) -> float | None:
|
|
954
|
-
worker_protocol = info.get("worker_protocol")
|
|
955
|
-
if not isinstance(worker_protocol, dict):
|
|
956
|
-
return None
|
|
957
|
-
|
|
958
|
-
capabilities = worker_protocol.get("server_capabilities")
|
|
959
|
-
if not isinstance(capabilities, dict):
|
|
960
|
-
return None
|
|
961
|
-
|
|
962
|
-
timeout = capabilities.get("long_poll_timeout")
|
|
963
|
-
if isinstance(timeout, bool):
|
|
964
|
-
return None
|
|
965
|
-
if isinstance(timeout, int | float):
|
|
966
|
-
return float(timeout) if timeout > 0 else None
|
|
967
|
-
if isinstance(timeout, str):
|
|
968
|
-
try:
|
|
969
|
-
parsed = float(timeout)
|
|
970
|
-
except ValueError:
|
|
971
|
-
return None
|
|
972
|
-
return parsed if parsed > 0 else None
|
|
973
|
-
|
|
974
|
-
return None
|
|
975
|
-
|
|
976
|
-
|
|
977
954
|
def _contract_version_matches(value: Any, expected: int) -> bool:
|
|
978
955
|
if isinstance(value, int):
|
|
979
956
|
return value == expected
|
|
@@ -1040,6 +1017,7 @@ class Worker:
|
|
|
1040
1017
|
raise ValueError("heartbeat_interval must be positive")
|
|
1041
1018
|
|
|
1042
1019
|
self._poll_timeout = poll_timeout
|
|
1020
|
+
# Client supplies HTTP grace separately from this requested poll window.
|
|
1043
1021
|
self._poll_http_timeout = poll_timeout
|
|
1044
1022
|
self.max_concurrent_workflow_tasks = max_concurrent_workflow_tasks
|
|
1045
1023
|
self.max_concurrent_activity_tasks = max_concurrent_activity_tasks
|
|
@@ -1190,9 +1168,6 @@ class Worker:
|
|
|
1190
1168
|
"multiplexed workflow/update-validation polling. Refusing registration so validated "
|
|
1191
1169
|
"updates cannot be accepted without validator approval or exceed worker capacity."
|
|
1192
1170
|
)
|
|
1193
|
-
server_long_poll_timeout = _server_long_poll_timeout(info)
|
|
1194
|
-
if server_long_poll_timeout is not None:
|
|
1195
|
-
self._poll_http_timeout = max(self._poll_http_timeout, server_long_poll_timeout + 5.0)
|
|
1196
1171
|
log.debug(
|
|
1197
1172
|
"server compatibility accepted: app_version=%s control_plane=%s worker_protocol=%s",
|
|
1198
1173
|
info.get("version", "unknown"),
|
|
@@ -1637,22 +1612,19 @@ class Worker:
|
|
|
1637
1612
|
wf_type: str = task.get("workflow_type", "")
|
|
1638
1613
|
history = task.get("history_events", [])
|
|
1639
1614
|
|
|
1640
|
-
#
|
|
1615
|
+
# The worker requests bounded history pages when polling. Do not replay
|
|
1616
|
+
# an incomplete history if fetching a later page fails.
|
|
1641
1617
|
next_page_token = task.get("next_history_page_token")
|
|
1642
1618
|
while next_page_token:
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
next_page_token = page_data.get("next_history_page_token") if page_data else None
|
|
1653
|
-
except Exception as e:
|
|
1654
|
-
log.warning("failed to fetch history page for task %s: %s", task_id, e)
|
|
1655
|
-
break
|
|
1619
|
+
page_data = await self.client.workflow_task_history(
|
|
1620
|
+
task_id=task_id,
|
|
1621
|
+
next_history_page_token=next_page_token,
|
|
1622
|
+
lease_owner=self.worker_id,
|
|
1623
|
+
workflow_task_attempt=attempt,
|
|
1624
|
+
)
|
|
1625
|
+
if page_data and page_data.get("history_events"):
|
|
1626
|
+
history.extend(page_data["history_events"])
|
|
1627
|
+
next_page_token = page_data.get("next_history_page_token") if page_data else None
|
|
1656
1628
|
|
|
1657
1629
|
start_input: list[Any] = []
|
|
1658
1630
|
codec = task.get("payload_codec")
|
|
@@ -2535,6 +2507,7 @@ class Worker:
|
|
|
2535
2507
|
timeout=self._poll_http_timeout,
|
|
2536
2508
|
build_id=self.build_id,
|
|
2537
2509
|
task_kinds=self._workflow_poll_task_kinds(),
|
|
2510
|
+
history_page_size=WORKFLOW_HISTORY_PAGE_SIZE,
|
|
2538
2511
|
)
|
|
2539
2512
|
except asyncio.CancelledError:
|
|
2540
2513
|
self._release_workflow_capacity()
|
|
@@ -3224,6 +3197,7 @@ class Worker:
|
|
|
3224
3197
|
timeout=self._poll_http_timeout,
|
|
3225
3198
|
build_id=self.build_id,
|
|
3226
3199
|
task_kinds=self._workflow_poll_task_kinds(),
|
|
3200
|
+
history_page_size=WORKFLOW_HISTORY_PAGE_SIZE,
|
|
3227
3201
|
)
|
|
3228
3202
|
except BaseException:
|
|
3229
3203
|
self._release_workflow_capacity()
|
|
@@ -4,11 +4,12 @@ import json
|
|
|
4
4
|
from collections.abc import Mapping, Sequence
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any
|
|
7
|
+
from unittest.mock import AsyncMock
|
|
7
8
|
|
|
8
9
|
import pytest
|
|
9
10
|
|
|
10
|
-
from durable_workflow import Replayer, serializer, workflow
|
|
11
|
-
from durable_workflow.client import WorkflowStreamAppendItem
|
|
11
|
+
from durable_workflow import Replayer, Worker, serializer, workflow
|
|
12
|
+
from durable_workflow.client import Client, WorkflowStreamAppendItem
|
|
12
13
|
from durable_workflow.errors import NonDeterministicReplayError, WorkflowPayloadDecodeError
|
|
13
14
|
from durable_workflow.workflow import WorkflowContext, commands_to_server_commands, query_state
|
|
14
15
|
from tests.test_golden_history_replay import (
|
|
@@ -156,6 +157,17 @@ class RecordedSideEffectWorkflow:
|
|
|
156
157
|
return (yield ctx.side_effect(unexpected_execution))
|
|
157
158
|
|
|
158
159
|
|
|
160
|
+
@workflow.defn(name="tests.replay.paged-recorded-side-effects")
|
|
161
|
+
class PagedRecordedSideEffectsWorkflow:
|
|
162
|
+
def run(self, ctx: WorkflowContext): # type: ignore[no-untyped-def]
|
|
163
|
+
def unexpected_execution() -> int:
|
|
164
|
+
raise AssertionError("recorded side effect callable ran during replay")
|
|
165
|
+
|
|
166
|
+
first = yield ctx.side_effect(unexpected_execution)
|
|
167
|
+
second = yield ctx.side_effect(unexpected_execution)
|
|
168
|
+
return first + second
|
|
169
|
+
|
|
170
|
+
|
|
159
171
|
@workflow.defn(name="tests.replay.message-stream-consumer")
|
|
160
172
|
class MessageStreamConsumerWorkflow:
|
|
161
173
|
def run(self, ctx: WorkflowContext): # type: ignore[no-untyped-def]
|
|
@@ -212,6 +224,7 @@ WORKFLOWS = [
|
|
|
212
224
|
NestedParallelPathWorkflow,
|
|
213
225
|
ParallelMetadataProducerWorkflow,
|
|
214
226
|
ParallelResultBindingWorkflow,
|
|
227
|
+
PagedRecordedSideEffectsWorkflow,
|
|
215
228
|
PostConditionReceiversWorkflow,
|
|
216
229
|
RecordedSideEffectWorkflow,
|
|
217
230
|
SelectionAwaitMarkerWorkflow,
|
|
@@ -394,6 +407,41 @@ def test_checked_in_replay_regression_corpus_uses_official_replayer(
|
|
|
394
407
|
_execute_fixture(fixture)
|
|
395
408
|
|
|
396
409
|
|
|
410
|
+
@pytest.mark.asyncio
|
|
411
|
+
async def test_worker_replays_recorded_side_effects_across_history_pages() -> None:
|
|
412
|
+
fixture = json.loads((FIXTURE_DIR / "paged-recorded-side-effects.json").read_text(encoding="utf-8"))
|
|
413
|
+
history = fixture["history"]
|
|
414
|
+
client = AsyncMock(spec=Client)
|
|
415
|
+
client.workflow_task_history.return_value = {
|
|
416
|
+
"history_events": history[1:],
|
|
417
|
+
"next_history_page_token": None,
|
|
418
|
+
}
|
|
419
|
+
worker = Worker(client, task_queue="paged-history", workflows=[PagedRecordedSideEffectsWorkflow])
|
|
420
|
+
|
|
421
|
+
commands = await worker._run_workflow_task_core({
|
|
422
|
+
"task_id": "task-paged-side-effects",
|
|
423
|
+
"workflow_id": "workflow-paged-side-effects",
|
|
424
|
+
"run_id": "run-paged-side-effects",
|
|
425
|
+
"workflow_type": fixture["workflow"]["type"],
|
|
426
|
+
"workflow_task_attempt": 1,
|
|
427
|
+
"history_events": history[:1],
|
|
428
|
+
"next_history_page_token": "second-page",
|
|
429
|
+
"arguments": serializer.encode([], codec="avro"),
|
|
430
|
+
"payload_codec": "avro",
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
client.workflow_task_history.assert_awaited_once_with(
|
|
434
|
+
task_id="task-paged-side-effects",
|
|
435
|
+
next_history_page_token="second-page",
|
|
436
|
+
lease_owner=worker.worker_id,
|
|
437
|
+
workflow_task_attempt=1,
|
|
438
|
+
)
|
|
439
|
+
client.complete_workflow_task.assert_awaited_once()
|
|
440
|
+
assert commands is not None
|
|
441
|
+
assert [command["type"] for command in commands] == ["complete_workflow"]
|
|
442
|
+
assert serializer.decode_envelope(commands[0]["result"]) == 198
|
|
443
|
+
|
|
444
|
+
|
|
397
445
|
@pytest.mark.parametrize(
|
|
398
446
|
"fixture",
|
|
399
447
|
[
|
|
@@ -711,7 +711,14 @@ class TestWorkerRegistration:
|
|
|
711
711
|
)
|
|
712
712
|
|
|
713
713
|
@pytest.mark.asyncio
|
|
714
|
-
|
|
714
|
+
@pytest.mark.parametrize(("poll_loop", "poll_method"), [
|
|
715
|
+
("_poll_workflow_tasks", "poll_workflow_task"),
|
|
716
|
+
("_poll_activity_tasks", "poll_activity_task"),
|
|
717
|
+
("_poll_query_tasks", "poll_query_task"),
|
|
718
|
+
])
|
|
719
|
+
async def test_registration_preserves_configured_poll_window(
|
|
720
|
+
self, mock_client: AsyncMock, poll_loop: str, poll_method: str,
|
|
721
|
+
) -> None:
|
|
715
722
|
mock_client.get_cluster_info = AsyncMock(
|
|
716
723
|
return_value=compatible_cluster_info(
|
|
717
724
|
worker_protocol={
|
|
@@ -736,12 +743,12 @@ class TestWorkerRegistration:
|
|
|
736
743
|
worker._stop.set()
|
|
737
744
|
return None
|
|
738
745
|
|
|
739
|
-
mock_client.
|
|
746
|
+
getattr(mock_client, poll_method).side_effect = poll_once
|
|
740
747
|
|
|
741
748
|
await worker._register()
|
|
742
|
-
await worker
|
|
749
|
+
await getattr(worker, poll_loop)()
|
|
743
750
|
|
|
744
|
-
assert mock_client.
|
|
751
|
+
assert getattr(mock_client, poll_method).call_args.kwargs["timeout"] == 0.01
|
|
745
752
|
|
|
746
753
|
@pytest.mark.asyncio
|
|
747
754
|
async def test_register_keeps_baseline_capabilities_when_server_does_not_support_query_tasks(
|
|
@@ -836,6 +843,7 @@ class TestWorkerRegistration:
|
|
|
836
843
|
workflow_kwargs = mock_client.poll_workflow_task.call_args.kwargs
|
|
837
844
|
assert workflow_called.is_set()
|
|
838
845
|
assert workflow_kwargs["build_id"] == "release-2026.04.22-a1"
|
|
846
|
+
assert workflow_kwargs["history_page_size"] == 500
|
|
839
847
|
|
|
840
848
|
worker._stop.clear()
|
|
841
849
|
mock_client.poll_activity_task.side_effect = activity_poll_once
|
|
@@ -1189,6 +1197,31 @@ class TestWorkerRegistration:
|
|
|
1189
1197
|
|
|
1190
1198
|
|
|
1191
1199
|
class TestWorkflowTaskExecution:
|
|
1200
|
+
@pytest.mark.asyncio
|
|
1201
|
+
async def test_history_page_failure_does_not_replay_partial_history(
|
|
1202
|
+
self, mock_client: AsyncMock
|
|
1203
|
+
) -> None:
|
|
1204
|
+
mock_client.workflow_task_history.side_effect = RuntimeError("history page unavailable")
|
|
1205
|
+
worker = Worker(mock_client, task_queue="q1", workflows=[TestWorkflow])
|
|
1206
|
+
task = {
|
|
1207
|
+
"task_id": "t-paged-history",
|
|
1208
|
+
"workflow_type": "test-wf",
|
|
1209
|
+
"workflow_task_attempt": 1,
|
|
1210
|
+
"history_events": [{"event_id": "first-page"}],
|
|
1211
|
+
"next_history_page_token": "next-page",
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
with pytest.raises(RuntimeError, match="history page unavailable"):
|
|
1215
|
+
await worker._run_workflow_task_core(task)
|
|
1216
|
+
|
|
1217
|
+
mock_client.workflow_task_history.assert_awaited_once_with(
|
|
1218
|
+
task_id="t-paged-history",
|
|
1219
|
+
next_history_page_token="next-page",
|
|
1220
|
+
lease_owner=worker.worker_id,
|
|
1221
|
+
workflow_task_attempt=1,
|
|
1222
|
+
)
|
|
1223
|
+
mock_client.complete_workflow_task.assert_not_awaited()
|
|
1224
|
+
|
|
1192
1225
|
@pytest.mark.asyncio
|
|
1193
1226
|
async def test_local_activity_runs_in_workflow_worker_and_records_heartbeats(
|
|
1194
1227
|
self, mock_client: AsyncMock
|
|
@@ -4948,6 +4981,7 @@ class TestRunUntil:
|
|
|
4948
4981
|
assert worker._stop.is_set()
|
|
4949
4982
|
mock_client.register_worker.assert_awaited_once()
|
|
4950
4983
|
assert mock_client.describe_workflow.await_count == 2
|
|
4984
|
+
assert mock_client.poll_workflow_task.call_args.kwargs["history_page_size"] == 500
|
|
4951
4985
|
|
|
4952
4986
|
@pytest.mark.asyncio
|
|
4953
4987
|
async def test_run_until_times_out_and_stops_worker(self, mock_client: AsyncMock) -> None:
|
|
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.3.6 → durable_workflow-2.3.8}/src/durable_workflow/external_task_input.py
RENAMED
|
File without changes
|
{durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow/external_task_result.py
RENAMED
|
File without changes
|
{durable_workflow-2.3.6 → durable_workflow-2.3.8}/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.3.6 → durable_workflow-2.3.8}/src/durable_workflow/python_conformance.py
RENAMED
|
File without changes
|
{durable_workflow-2.3.6 → durable_workflow-2.3.8}/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
|
|
File without changes
|
{durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{durable_workflow-2.3.6 → durable_workflow-2.3.8}/src/durable_workflow.egg-info/requires.txt
RENAMED
|
File without changes
|
{durable_workflow-2.3.6 → durable_workflow-2.3.8}/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.3.6 → durable_workflow-2.3.8}/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.3.6 → durable_workflow-2.3.8}/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
|
{durable_workflow-2.3.6 → durable_workflow-2.3.8}/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.3.6 → durable_workflow-2.3.8}/tests/test_update_signal_condition_replay.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.3.6 → durable_workflow-2.3.8}/tests/test_workflow_updates_conformance.py
RENAMED
|
File without changes
|