Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One risk from this change is that an API call that previously returned a 400 now attempts to delete everything, which can be quite destructive. I'm not as familiar with the original issue, but we should think carefully about whether this risk is worth the benefit.


## 1.0.0b6

- [Create timer](https://github.com/Azure/azure-functions-durable-python/issues/35) functionality available
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand All @@ -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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's my understanding that datetime.min results in a date like 0001-01-01T00:00:00.000000Z. One concern I have is whether all the backends are able to correctly interpret such an early date. Some backends, like MSSQL, are sensitive about date ranges and might choke on this. Consider whether something like 2000-01-01T00:00:00.000000Z would be better.


options = RpcManagementOptions(created_time_from=created_time_from,
created_time_to=created_time_to,
runtime_status=runtime_status)
Expand Down
3 changes: 2 additions & 1 deletion azure/durable_functions/models/RpcManagementOptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
21 changes: 15 additions & 6 deletions tests/models/test_DurableOrchestrationClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I don't think purging "Running" instances is actually valid, so I suggest that these tests use a valid status filter to avoid confusing readers (or agents) about what a valid use case for purge is.

response=[200, dict(instancesDeleted=1)])
client = DurableOrchestrationClient(binding_string)
client._delete_async_request = mock_request.delete

Expand All @@ -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

Expand All @@ -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

Expand Down
10 changes: 10 additions & 0 deletions tests/models/test_RpcManagementOptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Loading