durable-workflow 2.5.11__tar.gz → 2.5.12__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. {durable_workflow-2.5.11/src/durable_workflow.egg-info → durable_workflow-2.5.12}/PKG-INFO +1 -1
  2. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/pyproject.toml +3 -3
  3. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/client.py +3 -1
  4. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/retry_policy.py +22 -1
  5. {durable_workflow-2.5.11 → durable_workflow-2.5.12/src/durable_workflow.egg-info}/PKG-INFO +1 -1
  6. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow.egg-info/SOURCES.txt +1 -0
  7. durable_workflow-2.5.12/tests/test_query_timeout_retry.py +73 -0
  8. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/LICENSE +0 -0
  9. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/README.md +0 -0
  10. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/setup.cfg +0 -0
  11. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/__init__.py +0 -0
  12. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/_activity_process.py +0 -0
  13. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/_avro.py +0 -0
  14. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/_cancellation_scope.py +0 -0
  15. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/_cancellation_scope_history.py +0 -0
  16. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/_cooperative_cancellation.py +0 -0
  17. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/_prepared_local_activity.py +0 -0
  18. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/_sticky_workflow_cache.py +0 -0
  19. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/activity.py +0 -0
  20. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/auth_composition.py +0 -0
  21. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/cancellation.py +0 -0
  22. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/errors.py +0 -0
  23. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/external_storage.py +0 -0
  24. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/external_task_input.py +0 -0
  25. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/external_task_result.py +0 -0
  26. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/history_bundle_verify.py +0 -0
  27. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/interceptors.py +0 -0
  28. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/invocable.py +0 -0
  29. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/metrics.py +0 -0
  30. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/nexus.py +0 -0
  31. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/py.typed +0 -0
  32. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/python_conformance.py +0 -0
  33. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/replay_conformance.py +0 -0
  34. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/replay_verify.py +0 -0
  35. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/serializer.py +0 -0
  36. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/sync.py +0 -0
  37. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/testing.py +0 -0
  38. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/worker.py +0 -0
  39. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/worker_session.py +0 -0
  40. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/workflow.py +0 -0
  41. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow/workflow_updates_conformance.py +0 -0
  42. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
  43. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow.egg-info/entry_points.txt +0 -0
  44. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow.egg-info/requires.txt +0 -0
  45. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/src/durable_workflow.egg-info/top_level.txt +0 -0
  46. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_activity_cancellation_policies.py +0 -0
  47. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_activity_context.py +0 -0
  48. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_activity_process.py +0 -0
  49. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_auth_composition.py +0 -0
  50. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_avro_value_benchmark.py +0 -0
  51. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_avro_value_protocol.py +0 -0
  52. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_cancellation_context.py +0 -0
  53. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_cancellation_remaining_time.py +0 -0
  54. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_cancellation_scope_admission.py +0 -0
  55. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_cancellation_scope_authoring.py +0 -0
  56. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_cancellation_scope_boundary.py +0 -0
  57. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_cancellation_scope_opening.py +0 -0
  58. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_child_workflow_policies.py +0 -0
  59. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_ci_checkout.py +0 -0
  60. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_ci_integration_endpoint.py +0 -0
  61. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_client.py +0 -0
  62. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_client_timeouts.py +0 -0
  63. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_codec_regression_corpus.py +0 -0
  64. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_committed_cancellation_scope_descendants.py +0 -0
  65. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_committed_cancellation_scope_groups.py +0 -0
  66. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_committed_cancellation_scope_history.py +0 -0
  67. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_committed_cancellation_scope_replay.py +0 -0
  68. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_condition_replay_drift.py +0 -0
  69. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_control_plane_parity_fixtures.py +0 -0
  70. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_cooperative_cancellation.py +0 -0
  71. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_cooperative_cancellation_client.py +0 -0
  72. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_cooperative_cancellation_worker.py +0 -0
  73. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_cooperative_remote_worker.py +0 -0
  74. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_docstring_cross_references.py +0 -0
  75. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_durable_selection.py +0 -0
  76. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_errors.py +0 -0
  77. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_external_storage.py +0 -0
  78. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_external_task_input.py +0 -0
  79. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_external_task_result.py +0 -0
  80. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_golden_history_replay.py +0 -0
  81. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_history_bundle_verify.py +0 -0
  82. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_history_event_contract.py +0 -0
  83. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_invocable.py +0 -0
  84. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_message_streams.py +0 -0
  85. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_metrics.py +0 -0
  86. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_nexus_namespace.py +0 -0
  87. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_nexus_wire.py +0 -0
  88. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_order_processing_example.py +0 -0
  89. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_parallel_completion_order_replay.py +0 -0
  90. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_parallel_saga_authoring.py +0 -0
  91. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_patch_history.py +0 -0
  92. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_pending_operation_replay.py +0 -0
  93. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_prepared_local_activity_client.py +0 -0
  94. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_prepared_local_activity_groups.py +0 -0
  95. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_prepared_local_activity_policies.py +0 -0
  96. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_prepared_local_activity_worker.py +0 -0
  97. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_public_boundary_scanner.py +0 -0
  98. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_pypi_project_surface.py +0 -0
  99. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_python_conformance.py +0 -0
  100. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_queries.py +0 -0
  101. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_readme_quickstart.py +0 -0
  102. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_redrive_replay.py +0 -0
  103. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_registration_ambiguity.py +0 -0
  104. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_release_metadata.py +0 -0
  105. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_replay.py +0 -0
  106. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_replay_conformance.py +0 -0
  107. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_replay_regression_corpus.py +0 -0
  108. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_replay_verify.py +0 -0
  109. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_retry_policy.py +0 -0
  110. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_run_cancellation_scope_replay.py +0 -0
  111. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_runtime_external_payload_transport.py +0 -0
  112. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_schedules.py +0 -0
  113. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_scoped_run_cancellation_context.py +0 -0
  114. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_serializer.py +0 -0
  115. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_signals.py +0 -0
  116. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_sleep.py +0 -0
  117. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_standalone_activity_client.py +0 -0
  118. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_sticky_execution.py +0 -0
  119. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_storage_admission.py +0 -0
  120. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_sync.py +0 -0
  121. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_testing_harness.py +0 -0
  122. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_update_signal_condition_replay.py +0 -0
  123. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_updates.py +0 -0
  124. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_wait_condition.py +0 -0
  125. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_worker.py +0 -0
  126. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_worker_session.py +0 -0
  127. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_workflow_entrypoint_validation.py +0 -0
  128. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_workflow_result_timeout.py +0 -0
  129. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_workflow_streams.py +0 -0
  130. {durable_workflow-2.5.11 → durable_workflow-2.5.12}/tests/test_workflow_updates_conformance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: durable-workflow
3
- Version: 2.5.11
3
+ Version: 2.5.12
4
4
  Summary: Python client and worker SDK for Durable Workflow Cloud and self-hosted Server
5
5
  Author: Durable Workflow Contributors
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "durable-workflow"
7
- version = "2.5.11"
7
+ version = "2.5.12"
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.5.11"
75
- registry-version = "2.5.11"
74
+ product-train = "2.5.12"
75
+ registry-version = "2.5.12"
76
76
  supported-server-versions = "2.5.16"
77
77
  worker-protocol-version = "1.20"
78
78
  control-plane-version = "2"
@@ -4323,7 +4323,9 @@ class Client:
4323
4323
  Queries are synchronous and non-mutating. The server runs the named
4324
4324
  query handler inside the workflow process and returns the decoded
4325
4325
  result. Raises :class:`~durable_workflow.errors.QueryFailed` if the
4326
- query was rejected or the handler errored.
4326
+ query was rejected, the handler errored, or the original query timed
4327
+ out. A canonical query timeout is surfaced without automatically
4328
+ admitting another query.
4327
4329
  """
4328
4330
  await self._require_query_support()
4329
4331
  body: dict[str, Any] = {}
@@ -17,6 +17,7 @@ import contextvars
17
17
  import json
18
18
  import logging
19
19
  import random
20
+ import re
20
21
  from collections.abc import Awaitable, Callable
21
22
  from dataclasses import dataclass
22
23
  from typing import TypeVar
@@ -34,6 +35,24 @@ _worker_storage_admission_stop: contextvars.ContextVar[Callable[[], bool] | None
34
35
  )
35
36
 
36
37
 
38
+ def _query_timeout_refusal(exc: httpx.HTTPStatusError) -> bool:
39
+ request = exc.request
40
+ if (exc.response.status_code != 504 or request.method != "POST"
41
+ or request.headers.get("X-Durable-Workflow-Control-Plane-Version") != "2"
42
+ or re.search(r"/api/workflows/[^/]+/query/[^/]+$", request.url.path) is None):
43
+ return False
44
+ try:
45
+ body = exc.response.json()
46
+ except ValueError:
47
+ return False
48
+ # This response ends an admitted query. Another POST creates a new task,
49
+ # rather than retrying the original task or its remaining caller budget.
50
+ reason = body.get("reason") if isinstance(body, dict) else None
51
+ return isinstance(reason, str) and reason in {
52
+ "query_worker_timeout", "query_worker_execution_timeout", "query_task_not_claimed",
53
+ }
54
+
55
+
37
56
  def _storage_refusal(exc: Exception) -> tuple[ServerError, str | None] | None:
38
57
  if not isinstance(exc, httpx.HTTPStatusError):
39
58
  return None
@@ -194,6 +213,8 @@ class TransportRetryPolicy:
194
213
  :class:`durable_workflow.workflow.ChildWorkflowRetryPolicy`.
195
214
 
196
215
  Uses exponential backoff with jitter to avoid thundering herd.
216
+ Canonical query timeouts end the original query and are surfaced without
217
+ another query admission; unrelated transient HTTP failures still retry.
197
218
  """
198
219
 
199
220
  max_attempts: int = 3
@@ -213,7 +234,7 @@ class TransportRetryPolicy:
213
234
 
214
235
  # Retry 5xx server errors and 429 rate limit
215
236
  if isinstance(exc, httpx.HTTPStatusError):
216
- if _poll_capacity_refusal(exc):
237
+ if _query_timeout_refusal(exc) or _poll_capacity_refusal(exc):
217
238
  return False
218
239
  return exc.response.status_code >= 500 or exc.response.status_code == 429
219
240
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: durable-workflow
3
- Version: 2.5.11
3
+ Version: 2.5.12
4
4
  Summary: Python client and worker SDK for Durable Workflow Cloud and self-hosted Server
5
5
  Author: Durable Workflow Contributors
6
6
  License-Expression: MIT
@@ -95,6 +95,7 @@ tests/test_public_boundary_scanner.py
95
95
  tests/test_pypi_project_surface.py
96
96
  tests/test_python_conformance.py
97
97
  tests/test_queries.py
98
+ tests/test_query_timeout_retry.py
98
99
  tests/test_readme_quickstart.py
99
100
  tests/test_redrive_replay.py
100
101
  tests/test_registration_ambiguity.py
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ import httpx
4
+ import pytest
5
+
6
+ from durable_workflow import Client
7
+ from durable_workflow.errors import QueryFailed
8
+ from durable_workflow.retry_policy import TransportRetryPolicy
9
+
10
+
11
+ @pytest.mark.parametrize("protocol", ["1.20", "1.19"])
12
+ @pytest.mark.parametrize("reason", [
13
+ "query_worker_execution_timeout", "query_task_not_claimed", "query_worker_timeout",
14
+ ])
15
+ @pytest.mark.parametrize("prefix", ["", "/api/runtime/v1/namespaces/example"])
16
+ async def test_query_exposes_original_canonical_timeout_without_another_admission(
17
+ monkeypatch: pytest.MonkeyPatch, protocol: str, reason: str, prefix: str,
18
+ ) -> None:
19
+ monkeypatch.setenv("DURABLE_WORKFLOW_WORKER_PROTOCOL_VERSION", protocol)
20
+ original = {"reason": reason, "message": "Original query ended.",
21
+ "control_plane": {"operation": "query", "reason": reason}}
22
+ admissions = 0
23
+ discovery_protocols = []
24
+
25
+ def handle(request: httpx.Request) -> httpx.Response:
26
+ nonlocal admissions
27
+ if request.url.path.endswith("/api/cluster/info"):
28
+ discovery_protocols.append(request.headers["X-Durable-Workflow-Protocol-Version"])
29
+ return httpx.Response(200, json={
30
+ "worker_protocol": {"server_capabilities": {"query_tasks": True}},
31
+ })
32
+ assert request.url.path == prefix + "/api/workflows/original/query/total"
33
+ assert request.headers["X-Durable-Workflow-Control-Plane-Version"] == "2"
34
+ admissions += 1
35
+ if admissions == 1:
36
+ return httpx.Response(504, json=original)
37
+ # The old policy hides the original failure behind a distinct admission.
38
+ return httpx.Response(200, json={"result": "replacement-query"})
39
+
40
+ async with Client("https://runtime.example" + prefix, worker_token="worker", control_token="control",
41
+ retry_policy=TransportRetryPolicy(initial_backoff_seconds=0, jitter=False)) as client:
42
+ await client._http.aclose()
43
+ client._http = httpx.AsyncClient(base_url=client.base_url, transport=httpx.MockTransport(handle))
44
+ with pytest.raises(QueryFailed) as failure:
45
+ await client.query_workflow("original", "total")
46
+ assert admissions == 1
47
+ assert discovery_protocols == [protocol]
48
+ assert failure.value.status == 504 and failure.value.reason == reason
49
+ assert failure.value.body == original
50
+
51
+
52
+ @pytest.mark.parametrize(("method", "path", "status", "body", "headers"), [
53
+ ("POST", "/api/workflows/w/query/q", 504, {"reason": "gateway_timeout"},
54
+ {"X-Durable-Workflow-Control-Plane-Version": "2"}),
55
+ ("POST", "/api/workflows/w/query/q", 503, {"reason": "query_worker_timeout"},
56
+ {"X-Durable-Workflow-Control-Plane-Version": "2"}),
57
+ ("GET", "/api/workflows/w/query/q", 504, {"reason": "query_worker_timeout"},
58
+ {"X-Durable-Workflow-Control-Plane-Version": "2"}),
59
+ ("POST", "/api/workflows/w/signals/q", 504, {"reason": "query_worker_timeout"},
60
+ {"X-Durable-Workflow-Control-Plane-Version": "2"}),
61
+ ("POST", "/api/workflows/w/query/q", 504, {"reason": "query_worker_timeout"}, {}),
62
+ ("POST", "/api/workflows/w/query/q", 504, [],
63
+ {"X-Durable-Workflow-Control-Plane-Version": "2"}),
64
+ ("POST", "/api/workflows/w/query/q", 504, {"reason": []},
65
+ {"X-Durable-Workflow-Control-Plane-Version": "2"}),
66
+ ])
67
+ def test_other_transport_failures_keep_existing_retry_policy(
68
+ method: str, path: str, status: int, body: object, headers: dict[str, str],
69
+ ) -> None:
70
+ request = httpx.Request(method, "https://runtime.example" + path, headers=headers)
71
+ response = httpx.Response(status, request=request, json=body)
72
+ error = httpx.HTTPStatusError("transport failure", request=request, response=response)
73
+ assert TransportRetryPolicy().should_retry(error, 0) is True