durable-workflow 2.3.8__tar.gz → 2.3.9__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 (91) hide show
  1. {durable_workflow-2.3.8/src/durable_workflow.egg-info → durable_workflow-2.3.9}/PKG-INFO +1 -1
  2. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/pyproject.toml +3 -3
  3. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/client.py +1 -1
  4. {durable_workflow-2.3.8 → durable_workflow-2.3.9/src/durable_workflow.egg-info}/PKG-INFO +1 -1
  5. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow.egg-info/SOURCES.txt +1 -0
  6. durable_workflow-2.3.9/tests/test_client_timeouts.py +93 -0
  7. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/LICENSE +0 -0
  8. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/README.md +0 -0
  9. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/setup.cfg +0 -0
  10. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/__init__.py +0 -0
  11. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/_avro.py +0 -0
  12. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/activity.py +0 -0
  13. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/auth_composition.py +0 -0
  14. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/errors.py +0 -0
  15. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/external_storage.py +0 -0
  16. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/external_task_input.py +0 -0
  17. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/external_task_result.py +0 -0
  18. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/history_bundle_verify.py +0 -0
  19. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/interceptors.py +0 -0
  20. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/invocable.py +0 -0
  21. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/metrics.py +0 -0
  22. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/nexus.py +0 -0
  23. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/py.typed +0 -0
  24. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/python_conformance.py +0 -0
  25. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/replay_conformance.py +0 -0
  26. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/replay_verify.py +0 -0
  27. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/retry_policy.py +0 -0
  28. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/serializer.py +0 -0
  29. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/sync.py +0 -0
  30. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/testing.py +0 -0
  31. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/worker.py +0 -0
  32. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/worker_session.py +0 -0
  33. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/workflow.py +0 -0
  34. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow/workflow_updates_conformance.py +0 -0
  35. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
  36. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow.egg-info/entry_points.txt +0 -0
  37. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow.egg-info/requires.txt +0 -0
  38. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/src/durable_workflow.egg-info/top_level.txt +0 -0
  39. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_activity_context.py +0 -0
  40. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_auth_composition.py +0 -0
  41. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_avro_value_benchmark.py +0 -0
  42. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_avro_value_protocol.py +0 -0
  43. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_ci_checkout.py +0 -0
  44. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_ci_integration_endpoint.py +0 -0
  45. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_client.py +0 -0
  46. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_codec_regression_corpus.py +0 -0
  47. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_control_plane_parity_fixtures.py +0 -0
  48. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_docstring_cross_references.py +0 -0
  49. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_durable_selection.py +0 -0
  50. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_errors.py +0 -0
  51. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_external_storage.py +0 -0
  52. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_external_task_input.py +0 -0
  53. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_external_task_result.py +0 -0
  54. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_golden_history_replay.py +0 -0
  55. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_history_bundle_verify.py +0 -0
  56. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_history_event_contract.py +0 -0
  57. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_invocable.py +0 -0
  58. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_message_streams.py +0 -0
  59. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_metrics.py +0 -0
  60. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_order_processing_example.py +0 -0
  61. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_parallel_completion_order_replay.py +0 -0
  62. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_parallel_saga_authoring.py +0 -0
  63. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_public_boundary_scanner.py +0 -0
  64. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_pypi_project_surface.py +0 -0
  65. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_python_conformance.py +0 -0
  66. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_queries.py +0 -0
  67. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_readme_quickstart.py +0 -0
  68. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_redrive_replay.py +0 -0
  69. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_release_metadata.py +0 -0
  70. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_replay.py +0 -0
  71. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_replay_conformance.py +0 -0
  72. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_replay_regression_corpus.py +0 -0
  73. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_replay_verify.py +0 -0
  74. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_retry_policy.py +0 -0
  75. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_runtime_external_payload_transport.py +0 -0
  76. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_schedules.py +0 -0
  77. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_serializer.py +0 -0
  78. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_signals.py +0 -0
  79. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_sleep.py +0 -0
  80. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_standalone_activity_client.py +0 -0
  81. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_storage_admission.py +0 -0
  82. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_sync.py +0 -0
  83. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_testing_harness.py +0 -0
  84. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_update_signal_condition_replay.py +0 -0
  85. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_updates.py +0 -0
  86. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_wait_condition.py +0 -0
  87. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_worker.py +0 -0
  88. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_worker_session.py +0 -0
  89. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_workflow_result_timeout.py +0 -0
  90. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/tests/test_workflow_streams.py +0 -0
  91. {durable_workflow-2.3.8 → durable_workflow-2.3.9}/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.3.8
3
+ Version: 2.3.9
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.3.8"
7
+ version = "2.3.9"
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.8"
75
- registry-version = "2.3.8"
74
+ product-train = "2.3.9"
75
+ registry-version = "2.3.9"
76
76
  supported-server-versions = "2.4.8"
77
77
  worker-protocol-version = "1.19"
78
78
  control-plane-version = "2"
@@ -1794,7 +1794,7 @@ class Client:
1794
1794
  f"/api{path}",
1795
1795
  headers=self._discovery_headers() if discovery else self._headers(worker=worker),
1796
1796
  json=request_json,
1797
- timeout=timeout,
1797
+ timeout=self.timeout if timeout is None else timeout,
1798
1798
  )
1799
1799
  # Raise HTTPStatusError for 4xx/5xx so retry policy can catch it
1800
1800
  resp.raise_for_status()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: durable-workflow
3
- Version: 2.3.8
3
+ Version: 2.3.9
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
@@ -40,6 +40,7 @@ tests/test_avro_value_protocol.py
40
40
  tests/test_ci_checkout.py
41
41
  tests/test_ci_integration_endpoint.py
42
42
  tests/test_client.py
43
+ tests/test_client_timeouts.py
43
44
  tests/test_codec_regression_corpus.py
44
45
  tests/test_control_plane_parity_fixtures.py
45
46
  tests/test_docstring_cross_references.py
@@ -0,0 +1,93 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ from collections.abc import AsyncIterator
6
+ from contextlib import asynccontextmanager
7
+
8
+ import httpx
9
+ import pytest
10
+
11
+ from durable_workflow.client import Client
12
+ from durable_workflow.retry_policy import TransportRetryPolicy
13
+
14
+
15
+ @asynccontextmanager
16
+ async def response_server(*, delay: float | None) -> AsyncIterator[str]:
17
+ """A real HTTP peer, with response progress controlled by the test."""
18
+ writers: list[asyncio.StreamWriter] = []
19
+ handlers: set[asyncio.Task[None]] = set()
20
+
21
+ async def respond(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
22
+ task = asyncio.current_task()
23
+ assert task is not None
24
+ handlers.add(task)
25
+ writers.append(writer)
26
+ try:
27
+ headers = await reader.readuntil(b"\r\n\r\n")
28
+ for header in headers.split(b"\r\n"):
29
+ if header.lower().startswith(b"content-length:"):
30
+ await reader.readexactly(int(header.split(b":", 1)[1]))
31
+ if delay is None:
32
+ await reader.read()
33
+ return
34
+ await asyncio.sleep(delay)
35
+ body = json.dumps({"task": None, "poll_status": "empty"}).encode()
36
+ writer.write(
37
+ b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n"
38
+ + f"Content-Length: {len(body)}\r\nConnection: close\r\n\r\n".encode()
39
+ + body
40
+ )
41
+ await writer.drain()
42
+ finally:
43
+ writer.close()
44
+ await writer.wait_closed()
45
+ handlers.discard(task)
46
+
47
+ server = await asyncio.start_server(respond, "127.0.0.1", 0)
48
+ try:
49
+ assert server.sockets
50
+ yield f"http://127.0.0.1:{server.sockets[0].getsockname()[1]}"
51
+ finally:
52
+ server.close()
53
+ await server.wait_closed()
54
+ for writer in writers:
55
+ writer.close()
56
+ if handlers:
57
+ await asyncio.gather(*handlers)
58
+
59
+
60
+ @pytest.mark.asyncio
61
+ @pytest.mark.parametrize("operation", ["control", "discovery", "worker"])
62
+ async def test_configured_timeout_bounds_a_stalled_response(operation: str) -> None:
63
+ async with (
64
+ response_server(delay=None) as endpoint,
65
+ Client(endpoint, timeout=0.05, retry_policy=TransportRetryPolicy(max_attempts=1)) as client,
66
+ ):
67
+ if operation == "control":
68
+ request = client.health()
69
+ elif operation == "discovery":
70
+ request = client.get_cluster_info()
71
+ else:
72
+ request = client.complete_workflow_task(
73
+ task_id="synthetic-timeout-task",
74
+ lease_owner="synthetic-timeout-worker",
75
+ workflow_task_attempt=1,
76
+ commands=[],
77
+ )
78
+ with pytest.raises(httpx.ReadTimeout):
79
+ await asyncio.wait_for(request, timeout=2)
80
+
81
+
82
+ @pytest.mark.asyncio
83
+ async def test_explicit_poll_timeout_overrides_a_shorter_client_default() -> None:
84
+ async with (
85
+ response_server(delay=0.1) as endpoint,
86
+ Client(endpoint, timeout=0.01, retry_policy=TransportRetryPolicy(max_attempts=1)) as client,
87
+ ):
88
+ task = await client.poll_workflow_task(
89
+ worker_id="synthetic-timeout-worker",
90
+ task_queue="synthetic-timeout-queue",
91
+ timeout=0,
92
+ )
93
+ assert task is None