From e6f2fdcbe05ea92246556ba63ae15c42b7bbd0ae Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Wed, 29 Jul 2026 10:09:32 -0600 Subject: [PATCH] Fix default purge history start time Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ed5b5ae5-2c5e-4422-9786-a23ad1618634 --- CHANGELOG.md | 4 ++++ .../models/DurableOrchestrationClient.py | 6 +++++- .../models/RpcManagementOptions.py | 3 ++- .../models/test_DurableOrchestrationClient.py | 21 +++++++++++++------ tests/models/test_RpcManagementOptions.py | 10 +++++++++ 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0751fd63..70403f0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ All notable changes to this project will be documented in this file. - Type-hint-driven validation via `df_loads(s, expected_type=...)`: when the V2 programming model provides a return-type annotation for an activity or sub-orchestrator, the annotation is threaded through call sites so the SDK's `df_loads` can validate the deserialized payload against that type (when available). On older `azure-functions` releases the argument is accepted but ignored. - Return-type discovery for V2 decorated activities/sub-orchestrators (`azure.durable_functions.models.utils.type_discovery`): resolves the concrete return annotation from the user's registered function, used to supply `expected_type` to `df_loads`. +### Fixed + +- `purge_instance_history_by` now supplies the earliest supported creation time when `created_time_from` is omitted, avoiding HTTP 400 responses from the Durable extension. + ## 1.0.0b6 - [Create timer](https://github.com/Azure/azure-functions-durable-python/issues/35) functionality available diff --git a/azure/durable_functions/models/DurableOrchestrationClient.py b/azure/durable_functions/models/DurableOrchestrationClient.py index b6acc389..e8887887 100644 --- a/azure/durable_functions/models/DurableOrchestrationClient.py +++ b/azure/durable_functions/models/DurableOrchestrationClient.py @@ -419,7 +419,8 @@ async def purge_instance_history_by( Parameters ---------- created_time_from : Optional[datetime] - Delete orchestration history which were created after this Date. + Delete orchestration history which were created after this Date. Defaults to the + earliest datetime supported by Python. created_time_to: Optional[datetime] Delete orchestration history which were created before this Date. runtime_status: Optional[List[OrchestrationRuntimeStatus]] @@ -431,6 +432,9 @@ async def purge_instance_history_by( PurgeHistoryResult The results of the request to purge history """ + if created_time_from is None: + created_time_from = datetime.min + options = RpcManagementOptions(created_time_from=created_time_from, created_time_to=created_time_to, runtime_status=runtime_status) diff --git a/azure/durable_functions/models/RpcManagementOptions.py b/azure/durable_functions/models/RpcManagementOptions.py index b41d1493..18d43398 100644 --- a/azure/durable_functions/models/RpcManagementOptions.py +++ b/azure/durable_functions/models/RpcManagementOptions.py @@ -37,7 +37,8 @@ def _add_arg(query: List[str], name: str, value: Any): @staticmethod def _add_date_arg(query: List[str], name: str, value: Optional[datetime]): if value: - date_as_string = value.strftime(DATETIME_STRING_FORMAT) + date_format = DATETIME_STRING_FORMAT.replace("%Y", f"{value.year:04d}", 1) + date_as_string = value.strftime(date_format) RpcManagementOptions._add_arg(query, name, date_as_string) def to_url(self, base_url: Optional[str]) -> str: diff --git a/tests/models/test_DurableOrchestrationClient.py b/tests/models/test_DurableOrchestrationClient.py index 1466587c..a00ff750 100644 --- a/tests/models/test_DurableOrchestrationClient.py +++ b/tests/models/test_DurableOrchestrationClient.py @@ -351,8 +351,11 @@ async def test_delete_500_purge_instance_history(binding_string): @pytest.mark.asyncio async def test_delete_200_purge_instance_history_by(binding_string): - mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/?runtimeStatus=Running", - response=[200, dict(instancesDeleted=1)]) + mock_request = MockRequest( + expected_url=f"{RPC_BASE_URL}instances/" + "?createdTimeFrom=0001-01-01T00:00:00.000000Z" + "&runtimeStatus=Running", + response=[200, dict(instancesDeleted=1)]) client = DurableOrchestrationClient(binding_string) client._delete_async_request = mock_request.delete @@ -364,8 +367,11 @@ async def test_delete_200_purge_instance_history_by(binding_string): @pytest.mark.asyncio async def test_delete_404_purge_instance_history_by(binding_string): - mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/?runtimeStatus=Running", - response=[404, MESSAGE_404]) + mock_request = MockRequest( + expected_url=f"{RPC_BASE_URL}instances/" + "?createdTimeFrom=0001-01-01T00:00:00.000000Z" + "&runtimeStatus=Running", + response=[404, MESSAGE_404]) client = DurableOrchestrationClient(binding_string) client._delete_async_request = mock_request.delete @@ -377,8 +383,11 @@ async def test_delete_404_purge_instance_history_by(binding_string): @pytest.mark.asyncio async def test_delete_500_purge_instance_history_by(binding_string): - mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/?runtimeStatus=Running", - response=[500, MESSAGE_500]) + mock_request = MockRequest( + expected_url=f"{RPC_BASE_URL}instances/" + "?createdTimeFrom=0001-01-01T00:00:00.000000Z" + "&runtimeStatus=Running", + response=[500, MESSAGE_500]) client = DurableOrchestrationClient(binding_string) client._delete_async_request = mock_request.delete diff --git a/tests/models/test_RpcManagementOptions.py b/tests/models/test_RpcManagementOptions.py index 86e76ba5..c5887b49 100644 --- a/tests/models/test_RpcManagementOptions.py +++ b/tests/models/test_RpcManagementOptions.py @@ -77,3 +77,13 @@ def test_datetime_status(): f"&createdTimeTo={to_as_string}" assert_urls_match(expected=expected, result=result) + + +def test_min_datetime_status_uses_four_digit_year(): + options = RpcManagementOptions(created_time_from=datetime.min) + + result = options.to_url(RPC_BASE_URL) + + expected = f"{RPC_BASE_URL}instances/" \ + "?createdTimeFrom=0001-01-01T00:00:00.000000Z" + assert_urls_match(expected=expected, result=result)