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.
- {durable_workflow-0.4.90/src/durable_workflow.egg-info → durable_workflow-0.4.91}/PKG-INFO +1 -1
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/pyproject.toml +1 -1
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/client.py +61 -5
- {durable_workflow-0.4.90 → durable_workflow-0.4.91/src/durable_workflow.egg-info}/PKG-INFO +1 -1
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_client.py +6 -1
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/LICENSE +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/README.md +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/setup.cfg +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/__init__.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/_avro.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/activity.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/auth_composition.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/errors.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/external_storage.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/external_task_input.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/external_task_result.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/history_bundle_verify.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/interceptors.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/invocable.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/metrics.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/py.typed +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/python_conformance.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/replay_conformance.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/replay_verify.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/retry_policy.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/serializer.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/sync.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/testing.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/worker.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/workflow.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow.egg-info/SOURCES.txt +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow.egg-info/entry_points.txt +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow.egg-info/requires.txt +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow.egg-info/top_level.txt +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_activity_context.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_auth_composition.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_control_plane_parity_fixtures.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_errors.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_external_storage.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_external_task_input.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_external_task_result.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_golden_history_replay.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_history_bundle_verify.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_history_event_contract.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_invocable.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_metrics.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_order_processing_example.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_public_boundary_scanner.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_python_conformance.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_queries.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_readme_quickstart.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_release_docs_audit_workflow.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_replay.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_replay_conformance.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_replay_verify.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_retry_policy.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_schedules.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_serializer.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_signals.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_sleep.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_standalone_activity_client.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_sync.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_testing_harness.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_updates.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_wait_condition.py +0 -0
- {durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_worker.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "durable-workflow"
|
|
7
|
-
version = "0.4.
|
|
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",
|
|
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",
|
|
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",
|
|
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
|
|
@@ -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"] ==
|
|
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
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/auth_composition.py
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/external_storage.py
RENAMED
|
File without changes
|
{durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/external_task_input.py
RENAMED
|
File without changes
|
{durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/external_task_result.py
RENAMED
|
File without changes
|
{durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/history_bundle_verify.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow/python_conformance.py
RENAMED
|
File without changes
|
{durable_workflow-0.4.90 → durable_workflow-0.4.91}/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-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow.egg-info/requires.txt
RENAMED
|
File without changes
|
{durable_workflow-0.4.90 → durable_workflow-0.4.91}/src/durable_workflow.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.90 → durable_workflow-0.4.91}/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
|
|
File without changes
|
{durable_workflow-0.4.90 → durable_workflow-0.4.91}/tests/test_release_docs_audit_workflow.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-0.4.90 → durable_workflow-0.4.91}/tests/test_standalone_activity_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|