durable-workflow 0.4.90__tar.gz → 0.4.91__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 (67) hide show
  1. {durable_workflow-0.4.90/src/durable_workflow.egg-info → durable_workflow-0.4.91}/PKG-INFO +1 -1
  2. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/pyproject.toml +1 -1
  3. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/client.py +61 -5
  4. {durable_workflow-0.4.90 → durable_workflow-0.4.91/src/durable_workflow.egg-info}/PKG-INFO +1 -1
  5. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_client.py +6 -1
  6. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/LICENSE +0 -0
  7. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/README.md +0 -0
  8. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/setup.cfg +0 -0
  9. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/__init__.py +0 -0
  10. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/_avro.py +0 -0
  11. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/activity.py +0 -0
  12. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/auth_composition.py +0 -0
  13. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/errors.py +0 -0
  14. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/external_storage.py +0 -0
  15. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/external_task_input.py +0 -0
  16. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/external_task_result.py +0 -0
  17. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/history_bundle_verify.py +0 -0
  18. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/interceptors.py +0 -0
  19. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/invocable.py +0 -0
  20. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/metrics.py +0 -0
  21. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/py.typed +0 -0
  22. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/python_conformance.py +0 -0
  23. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/replay_conformance.py +0 -0
  24. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/replay_verify.py +0 -0
  25. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/retry_policy.py +0 -0
  26. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/serializer.py +0 -0
  27. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/sync.py +0 -0
  28. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/testing.py +0 -0
  29. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/worker.py +0 -0
  30. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/workflow.py +0 -0
  31. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow.egg-info/SOURCES.txt +0 -0
  32. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
  33. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow.egg-info/entry_points.txt +0 -0
  34. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow.egg-info/requires.txt +0 -0
  35. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow.egg-info/top_level.txt +0 -0
  36. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_activity_context.py +0 -0
  37. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_auth_composition.py +0 -0
  38. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_control_plane_parity_fixtures.py +0 -0
  39. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_errors.py +0 -0
  40. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_external_storage.py +0 -0
  41. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_external_task_input.py +0 -0
  42. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_external_task_result.py +0 -0
  43. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_golden_history_replay.py +0 -0
  44. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_history_bundle_verify.py +0 -0
  45. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_history_event_contract.py +0 -0
  46. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_invocable.py +0 -0
  47. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_metrics.py +0 -0
  48. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_order_processing_example.py +0 -0
  49. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_public_boundary_scanner.py +0 -0
  50. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_python_conformance.py +0 -0
  51. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_queries.py +0 -0
  52. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_readme_quickstart.py +0 -0
  53. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_release_docs_audit_workflow.py +0 -0
  54. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_replay.py +0 -0
  55. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_replay_conformance.py +0 -0
  56. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_replay_verify.py +0 -0
  57. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_retry_policy.py +0 -0
  58. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_schedules.py +0 -0
  59. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_serializer.py +0 -0
  60. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_signals.py +0 -0
  61. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_sleep.py +0 -0
  62. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_standalone_activity_client.py +0 -0
  63. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_sync.py +0 -0
  64. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_testing_harness.py +0 -0
  65. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_updates.py +0 -0
  66. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_wait_condition.py +0 -0
  67. {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_worker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: durable-workflow
3
- Version: 0.4.90
3
+ Version: 0.4.91
4
4
  Summary: Python SDK for the Durable Workflow server (language-neutral HTTP protocol)
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 = "0.4.90"
7
+ version = "0.4.91"
8
8
  description = "Python SDK for the Durable Workflow server (language-neutral HTTP protocol)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -19,6 +19,7 @@ methods without repeating the id on every call.
19
19
  from __future__ import annotations
20
20
 
21
21
  import asyncio
22
+ import math
22
23
  import os
23
24
  import time
24
25
  import uuid
@@ -58,6 +59,31 @@ def _default_sdk_version() -> str:
58
59
 
59
60
 
60
61
  DEFAULT_SDK_VERSION = _default_sdk_version()
62
+ _WORKER_POLL_MAX_SERVER_TIMEOUT_SECONDS = 60
63
+ _WORKER_POLL_HTTP_TIMEOUT_GRACE_SECONDS = 5.0
64
+
65
+
66
+ def _worker_poll_timeout_seconds(timeout: float | None) -> int | None:
67
+ if timeout is None:
68
+ return None
69
+
70
+ value = float(timeout)
71
+ if not math.isfinite(value) or value < 0:
72
+ raise ValueError("timeout must be a non-negative finite number")
73
+
74
+ seconds = int(math.ceil(value)) if value >= 1 else 0
75
+ return min(seconds, _WORKER_POLL_MAX_SERVER_TIMEOUT_SECONDS)
76
+
77
+
78
+ def _worker_poll_http_timeout(timeout: float | None) -> float | None:
79
+ timeout_seconds = _worker_poll_timeout_seconds(timeout)
80
+ if timeout_seconds is None:
81
+ return None
82
+
83
+ if timeout_seconds == 0:
84
+ return max(float(timeout), 1.0)
85
+
86
+ return max(float(timeout), float(timeout_seconds) + _WORKER_POLL_HTTP_TIMEOUT_GRACE_SECONDS)
61
87
 
62
88
 
63
89
  def _protocol_version_from_env(name: str, default: str) -> str:
@@ -3217,22 +3243,31 @@ class Client:
3217
3243
  Returns the full worker-protocol response envelope, including
3218
3244
  ``poll_status`` when the server has no task to lease. Worker-plane
3219
3245
  endpoint — most applications use :class:`~durable_workflow.Worker`
3220
- rather than calling this directly.
3246
+ rather than calling this directly. ``timeout`` controls the server
3247
+ long-poll window; the HTTP request uses a small grace margin.
3221
3248
  """
3222
3249
  body: dict[str, Any] = {
3223
3250
  "worker_id": worker_id,
3224
3251
  "task_queue": task_queue,
3225
3252
  "poll_request_id": poll_request_id or f"wf-poll-{uuid.uuid4().hex}",
3226
3253
  }
3254
+ timeout_seconds = _worker_poll_timeout_seconds(timeout)
3255
+ if timeout_seconds is not None:
3256
+ body["timeout_seconds"] = timeout_seconds
3227
3257
  if build_id:
3228
3258
  body["build_id"] = build_id
3229
3259
  if history_page_size is not None:
3230
3260
  body["history_page_size"] = history_page_size
3261
+ http_timeout = _worker_poll_http_timeout(timeout)
3231
3262
 
3232
3263
  for _ in range(2):
3233
3264
  try:
3234
3265
  data = await self._request(
3235
- "POST", "/worker/workflow-tasks/poll", worker=True, json=body, timeout=timeout
3266
+ "POST",
3267
+ "/worker/workflow-tasks/poll",
3268
+ worker=True,
3269
+ json=body,
3270
+ timeout=http_timeout,
3236
3271
  )
3237
3272
  except httpx.TimeoutException:
3238
3273
  continue
@@ -3255,7 +3290,8 @@ class Client:
3255
3290
 
3256
3291
  Returns the task payload, or ``None`` on poll timeout. Worker-plane
3257
3292
  endpoint — most applications use :class:`~durable_workflow.Worker`
3258
- rather than calling this directly.
3293
+ rather than calling this directly. ``timeout`` controls the server
3294
+ long-poll window; the HTTP request uses a small grace margin.
3259
3295
  """
3260
3296
  data = await self.poll_workflow_task_response(
3261
3297
  worker_id=worker_id,
@@ -3363,19 +3399,29 @@ class Client:
3363
3399
 
3364
3400
  Query tasks are ephemeral worker-plane requests created when the server
3365
3401
  must route a control-plane query to a non-PHP workflow runtime.
3402
+ ``timeout`` controls the server long-poll window; the HTTP request
3403
+ uses a small grace margin.
3366
3404
  """
3367
3405
  body: dict[str, Any] = {
3368
3406
  "worker_id": worker_id,
3369
3407
  "task_queue": task_queue,
3370
3408
  "poll_request_id": poll_request_id or f"query-poll-{uuid.uuid4().hex}",
3371
3409
  }
3410
+ timeout_seconds = _worker_poll_timeout_seconds(timeout)
3411
+ if timeout_seconds is not None:
3412
+ body["timeout_seconds"] = timeout_seconds
3372
3413
  if build_id:
3373
3414
  body["build_id"] = build_id
3415
+ http_timeout = _worker_poll_http_timeout(timeout)
3374
3416
 
3375
3417
  for _ in range(2):
3376
3418
  try:
3377
3419
  data = await self._request(
3378
- "POST", "/worker/query-tasks/poll", worker=True, json=body, timeout=timeout
3420
+ "POST",
3421
+ "/worker/query-tasks/poll",
3422
+ worker=True,
3423
+ json=body,
3424
+ timeout=http_timeout,
3379
3425
  )
3380
3426
  except httpx.TimeoutException:
3381
3427
  continue
@@ -3457,19 +3503,29 @@ class Client:
3457
3503
 
3458
3504
  Returns the task payload, or ``None`` on poll timeout. Worker-plane
3459
3505
  endpoint — typically used by :class:`~durable_workflow.Worker`.
3506
+ ``timeout`` controls the server long-poll window; the HTTP request
3507
+ uses a small grace margin.
3460
3508
  """
3461
3509
  body: dict[str, Any] = {
3462
3510
  "worker_id": worker_id,
3463
3511
  "task_queue": task_queue,
3464
3512
  "poll_request_id": poll_request_id or f"activity-poll-{uuid.uuid4().hex}",
3465
3513
  }
3514
+ timeout_seconds = _worker_poll_timeout_seconds(timeout)
3515
+ if timeout_seconds is not None:
3516
+ body["timeout_seconds"] = timeout_seconds
3466
3517
  if build_id:
3467
3518
  body["build_id"] = build_id
3519
+ http_timeout = _worker_poll_http_timeout(timeout)
3468
3520
 
3469
3521
  for _ in range(2):
3470
3522
  try:
3471
3523
  data = await self._request(
3472
- "POST", "/worker/activity-tasks/poll", worker=True, json=body, timeout=timeout
3524
+ "POST",
3525
+ "/worker/activity-tasks/poll",
3526
+ worker=True,
3527
+ json=body,
3528
+ timeout=http_timeout,
3473
3529
  )
3474
3530
  except httpx.TimeoutException:
3475
3531
  continue
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: durable-workflow
3
- Version: 0.4.90
3
+ Version: 0.4.91
4
4
  Summary: Python SDK for the Durable Workflow server (language-neutral HTTP protocol)
5
5
  Author: Durable Workflow Contributors
6
6
  License-Expression: MIT
@@ -2292,8 +2292,9 @@ class TestFailWorkflowTask:
2292
2292
  assert request_body["task_queue"] == "queue-1"
2293
2293
  assert request_body["build_id"] == "build-v2"
2294
2294
  assert request_body["history_page_size"] == 100
2295
+ assert request_body["timeout_seconds"] == 3
2295
2296
  assert isinstance(request_body["poll_request_id"], str)
2296
- assert mock.await_args.kwargs["timeout"] == 3.0
2297
+ assert mock.await_args.kwargs["timeout"] == 8.0
2297
2298
 
2298
2299
  @pytest.mark.asyncio
2299
2300
  async def test_poll_workflow_task_keeps_returning_none_for_no_compatible_status(self, client: Client) -> None:
@@ -2351,6 +2352,7 @@ class TestFailWorkflowTask:
2351
2352
  assert request_body["worker_id"] == "worker-1"
2352
2353
  assert request_body["task_queue"] == "queue-1"
2353
2354
  assert request_body["build_id"] == "build-1"
2355
+ assert request_body["timeout_seconds"] == 35
2354
2356
  assert isinstance(request_body["poll_request_id"], str)
2355
2357
  assert request_body["poll_request_id"] != ""
2356
2358
 
@@ -2370,6 +2372,7 @@ class TestFailWorkflowTask:
2370
2372
  assert request_body["worker_id"] == "worker-1"
2371
2373
  assert request_body["task_queue"] == "queue-1"
2372
2374
  assert request_body["build_id"] == "build-1"
2375
+ assert request_body["timeout_seconds"] == 35
2373
2376
  assert isinstance(request_body["poll_request_id"], str)
2374
2377
  assert request_body["poll_request_id"] != ""
2375
2378
 
@@ -3016,6 +3019,8 @@ class TestQueryTasks:
3016
3019
  body = mock.call_args.kwargs["json"]
3017
3020
  assert body["worker_id"] == "w1"
3018
3021
  assert body["task_queue"] == "q1"
3022
+ assert body["timeout_seconds"] == 5
3023
+ assert mock.call_args.kwargs["timeout"] == 10.0
3019
3024
  assert isinstance(body["poll_request_id"], str)
3020
3025
  assert body["poll_request_id"] != ""
3021
3026