durable-workflow 0.4.44__tar.gz → 0.4.45__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.44/src/durable_workflow.egg-info → durable_workflow-0.4.45}/PKG-INFO +1 -1
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/pyproject.toml +1 -1
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/worker.py +26 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45/src/durable_workflow.egg-info}/PKG-INFO +1 -1
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_worker.py +65 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/LICENSE +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/README.md +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/setup.cfg +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/__init__.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/_avro.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/activity.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/auth_composition.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/client.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/errors.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/external_storage.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/external_task_input.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/external_task_result.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/history_bundle_verify.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/interceptors.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/invocable.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/metrics.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/py.typed +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/replay_verify.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/retry_policy.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/serializer.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/sync.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/testing.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/workflow.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow.egg-info/SOURCES.txt +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow.egg-info/entry_points.txt +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow.egg-info/requires.txt +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow.egg-info/top_level.txt +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_activity_context.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_auth_composition.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_client.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_control_plane_parity_fixtures.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_errors.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_external_storage.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_external_task_input.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_external_task_result.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_golden_history_replay.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_history_bundle_verify.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_history_event_contract.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_invocable.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_metrics.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_order_processing_example.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_public_boundary_scanner.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_queries.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_readme_quickstart.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_replay.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_replay_verify.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_retry_policy.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_schedules.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_serializer.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_signals.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_sleep.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_standalone_activity_client.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_sync.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_testing_harness.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_updates.py +0 -0
- {durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_wait_condition.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.45"
|
|
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"
|
|
@@ -775,6 +775,8 @@ class Worker:
|
|
|
775
775
|
)
|
|
776
776
|
except Exception as e:
|
|
777
777
|
log.warning("failed to complete workflow update task %s: %s", task_id, e)
|
|
778
|
+
await self._fail_workflow_task_after_completion_error(task_id, attempt, e)
|
|
779
|
+
return None
|
|
778
780
|
return [command]
|
|
779
781
|
|
|
780
782
|
try:
|
|
@@ -847,8 +849,32 @@ class Worker:
|
|
|
847
849
|
)
|
|
848
850
|
except Exception as e:
|
|
849
851
|
log.warning("failed to complete workflow task %s: %s", task_id, e)
|
|
852
|
+
await self._fail_workflow_task_after_completion_error(task_id, attempt, e)
|
|
853
|
+
return None
|
|
850
854
|
return commands
|
|
851
855
|
|
|
856
|
+
async def _fail_workflow_task_after_completion_error(
|
|
857
|
+
self,
|
|
858
|
+
task_id: str,
|
|
859
|
+
attempt: int,
|
|
860
|
+
error: Exception,
|
|
861
|
+
) -> None:
|
|
862
|
+
try:
|
|
863
|
+
await self.client.fail_workflow_task(
|
|
864
|
+
task_id=task_id,
|
|
865
|
+
lease_owner=self.worker_id,
|
|
866
|
+
workflow_task_attempt=attempt,
|
|
867
|
+
message=f"workflow task completion failed after commands were produced: {error}",
|
|
868
|
+
failure_type=type(error).__name__,
|
|
869
|
+
stack_trace=traceback.format_exc(),
|
|
870
|
+
)
|
|
871
|
+
except Exception as fail_error:
|
|
872
|
+
log.warning(
|
|
873
|
+
"failed to report workflow task %s completion failure: %s",
|
|
874
|
+
task_id,
|
|
875
|
+
fail_error,
|
|
876
|
+
)
|
|
877
|
+
|
|
852
878
|
async def _run_activity_task(self, task: dict[str, Any]) -> str:
|
|
853
879
|
task_id: str = task["task_id"]
|
|
854
880
|
attempt_id: str = task.get("activity_attempt_id") or task.get("attempt_id", "")
|
|
@@ -501,6 +501,33 @@ class TestWorkflowTaskExecution:
|
|
|
501
501
|
assert commands[0]["arguments"]["codec"] == "json"
|
|
502
502
|
assert serializer.decode(commands[0]["arguments"]["blob"], codec="json") == ["hello"]
|
|
503
503
|
|
|
504
|
+
@pytest.mark.asyncio
|
|
505
|
+
async def test_workflow_task_completion_error_fails_task_for_fast_redispatch(
|
|
506
|
+
self, mock_client: AsyncMock
|
|
507
|
+
) -> None:
|
|
508
|
+
mock_client.complete_workflow_task.side_effect = TimeoutError("completion timed out")
|
|
509
|
+
worker = Worker(mock_client, task_queue="q1", workflows=[TestWorkflow], activities=[])
|
|
510
|
+
task = {
|
|
511
|
+
"task_id": "t-complete-timeout",
|
|
512
|
+
"workflow_type": "test-wf",
|
|
513
|
+
"workflow_task_attempt": 2,
|
|
514
|
+
"history_events": [],
|
|
515
|
+
"arguments": '["hello"]',
|
|
516
|
+
"payload_codec": "json",
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
result = await worker._run_workflow_task(task)
|
|
520
|
+
|
|
521
|
+
assert result is None
|
|
522
|
+
mock_client.complete_workflow_task.assert_awaited_once()
|
|
523
|
+
mock_client.fail_workflow_task.assert_awaited_once()
|
|
524
|
+
call_kwargs = mock_client.fail_workflow_task.await_args.kwargs
|
|
525
|
+
assert call_kwargs["task_id"] == "t-complete-timeout"
|
|
526
|
+
assert call_kwargs["workflow_task_attempt"] == 2
|
|
527
|
+
assert call_kwargs["lease_owner"] == worker.worker_id
|
|
528
|
+
assert call_kwargs["failure_type"] == "TimeoutError"
|
|
529
|
+
assert "completion timed out" in call_kwargs["message"]
|
|
530
|
+
|
|
504
531
|
@pytest.mark.asyncio
|
|
505
532
|
async def test_workflow_command_payload_warning_uses_client_policy(
|
|
506
533
|
self, mock_client: AsyncMock, caplog: pytest.LogCaptureFixture
|
|
@@ -673,6 +700,44 @@ class TestWorkflowTaskExecution:
|
|
|
673
700
|
]
|
|
674
701
|
mock_client.fail_workflow_task.assert_not_called()
|
|
675
702
|
|
|
703
|
+
@pytest.mark.asyncio
|
|
704
|
+
async def test_update_task_completion_error_fails_task_for_fast_redispatch(
|
|
705
|
+
self, mock_client: AsyncMock
|
|
706
|
+
) -> None:
|
|
707
|
+
mock_client.complete_workflow_task.side_effect = TimeoutError("update completion timed out")
|
|
708
|
+
worker = Worker(mock_client, task_queue="q1", workflows=[UpdateWorkflow], activities=[])
|
|
709
|
+
task = {
|
|
710
|
+
"task_id": "t-update-timeout",
|
|
711
|
+
"workflow_type": "update-wf",
|
|
712
|
+
"workflow_task_attempt": 3,
|
|
713
|
+
"workflow_update_id": "upd-worker-1",
|
|
714
|
+
"workflow_wait_kind": "update",
|
|
715
|
+
"history_events": [
|
|
716
|
+
{
|
|
717
|
+
"event_type": "UpdateAccepted",
|
|
718
|
+
"payload": {
|
|
719
|
+
"update_id": "upd-worker-1",
|
|
720
|
+
"update_name": "increment",
|
|
721
|
+
"arguments": serializer.encode([6], codec="json"),
|
|
722
|
+
"payload_codec": "json",
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
],
|
|
726
|
+
"arguments": "[]",
|
|
727
|
+
"payload_codec": "json",
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
result = await worker._run_workflow_task(task)
|
|
731
|
+
|
|
732
|
+
assert result is None
|
|
733
|
+
mock_client.complete_workflow_task.assert_awaited_once()
|
|
734
|
+
mock_client.fail_workflow_task.assert_awaited_once()
|
|
735
|
+
call_kwargs = mock_client.fail_workflow_task.await_args.kwargs
|
|
736
|
+
assert call_kwargs["task_id"] == "t-update-timeout"
|
|
737
|
+
assert call_kwargs["workflow_task_attempt"] == 3
|
|
738
|
+
assert call_kwargs["failure_type"] == "TimeoutError"
|
|
739
|
+
assert "update completion timed out" in call_kwargs["message"]
|
|
740
|
+
|
|
676
741
|
@pytest.mark.asyncio
|
|
677
742
|
async def test_query_task_executes_registered_query(self, mock_client: AsyncMock) -> None:
|
|
678
743
|
worker = Worker(mock_client, task_queue="q1", workflows=[QueryWorkflow], activities=[])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/auth_composition.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/external_storage.py
RENAMED
|
File without changes
|
{durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/external_task_input.py
RENAMED
|
File without changes
|
{durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow/external_task_result.py
RENAMED
|
File without changes
|
{durable_workflow-0.4.44 → durable_workflow-0.4.45}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow.egg-info/requires.txt
RENAMED
|
File without changes
|
{durable_workflow-0.4.44 → durable_workflow-0.4.45}/src/durable_workflow.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.44 → durable_workflow-0.4.45}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.44 → durable_workflow-0.4.45}/tests/test_standalone_activity_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|