Skip to content

Commit 60b6195

Browse files
committed
More compat layer stuff
1 parent 013f674 commit 60b6195

6 files changed

Lines changed: 93 additions & 35 deletions

File tree

azure-functions-durable/CHANGELOG.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
(`output`, `input`, `customStatus`) as their raw JSON representation instead
2424
of reconstructed Python objects, so the result is always JSON-serializable
2525
even when payloads are custom types.
26+
- Restored v1 members that were missing on the compatibility types, avoiding
27+
`AttributeError`/`TypeError` for existing code that used them:
28+
- `create_http_management_payload(...)` now returns a `dict`-based
29+
`HttpManagementPayload`, so `json.dumps(payload)` works directly again.
30+
- `RetryOptions.to_json()` returns the v1
31+
`firstRetryIntervalInMilliseconds`/`maxNumberOfAttempts` dictionary, and the
32+
`first_retry_interval_in_milliseconds` / `max_number_of_attempts` getters
33+
remain available.
34+
- `DurableOrchestrationStatus.from_json(...)` reconstructs a status from its
35+
`to_json()` representation (or the equivalent v1 JSON schema).
36+
- `PurgeHistoryResult.from_json(...)` reconstructs a result from its v1 JSON
37+
representation.
38+
- `DurableOrchestrationContext.version` returns the orchestration instance
39+
version (or `None`).
2640

2741
### Added
2842

@@ -103,11 +117,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
103117
- `create_http_management_payload` now accepts either the durabletask
104118
`(request, instance_id)` signature or the v1 `(instance_id)` signature for
105119
backwards compatibility.
106-
- `HttpManagementPayload` now supports mapping-style access
120+
- `HttpManagementPayload` now subclasses `dict`, so it is directly
121+
JSON-serializable via `json.dumps(payload)` and supports mapping-style access
107122
(`payload["statusQueryGetUri"]`, iteration, `in`, `keys()`/`items()`/`values()`)
108123
so v1 code that treated the payload as a `dict` keeps working.
109124

110-
111125
## v0.1.0
112126

113127
- Initial implementation

azure-functions-durable/azure/durable_functions/http/http_management_payload.py

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@
22
# Licensed under the MIT License.
33

44
import json
5-
from collections.abc import Iterator
5+
from typing import Any
66

77

8-
class HttpManagementPayload:
8+
class HttpManagementPayload(dict[str, str]):
99
"""A class representing the HTTP management payload for a Durable Function orchestration instance.
1010
1111
Contains URLs for managing the instance, such as querying status,
1212
sending events, terminating, restarting, etc.
1313
14-
Supports mapping-style access (``payload["statusQueryGetUri"]``, iteration,
15-
``in``, ``.keys()``/``.items()``/``.values()``) for backwards compatibility
16-
with the v1 API, which returned a plain ``dict``.
14+
Subclasses ``dict`` for backwards compatibility with the v1 API, which
15+
returned a plain ``dict``. As a result the payload supports mapping-style
16+
access (``payload["statusQueryGetUri"]``, iteration, ``in``,
17+
``.keys()``/``.items()``/``.values()``) and is directly JSON-serializable
18+
via ``json.dumps(payload)``.
1719
"""
1820

1921
def __init__(self, instance_id: str, instance_status_url: str, required_query_string_parameters: str):
@@ -24,7 +26,7 @@ def __init__(self, instance_id: str, instance_status_url: str, required_query_st
2426
instance_status_url (str): The base URL for the instance status.
2527
required_query_string_parameters (str): The required URL parameters provided by the Durable extension.
2628
"""
27-
self.urls = {
29+
super().__init__({
2830
'id': instance_id,
2931
'purgeHistoryDeleteUri': instance_status_url + "?" + required_query_string_parameters,
3032
'restartPostUri': instance_status_url + "/restart?" + required_query_string_parameters,
@@ -33,31 +35,16 @@ def __init__(self, instance_id: str, instance_status_url: str, required_query_st
3335
'terminatePostUri': instance_status_url + "/terminate?reason={text}&" + required_query_string_parameters,
3436
'resumePostUri': instance_status_url + "/resume?reason={text}&" + required_query_string_parameters,
3537
'suspendPostUri': instance_status_url + "/suspend?reason={text}&" + required_query_string_parameters
36-
}
38+
})
3739

38-
def __str__(self):
39-
return json.dumps(self.urls)
40+
def __str__(self) -> str:
41+
return json.dumps(self)
4042

41-
def __getitem__(self, key: str) -> str:
42-
return self.urls[key]
43+
@property
44+
def urls(self) -> dict[str, Any]:
45+
"""Return the management URLs as a plain ``dict`` (v1 compatibility)."""
46+
return dict(self)
4347

44-
def __iter__(self) -> Iterator[str]:
45-
return iter(self.urls)
46-
47-
def __len__(self) -> int:
48-
return len(self.urls)
49-
50-
def __contains__(self, key: object) -> bool:
51-
return key in self.urls
52-
53-
def keys(self):
54-
"""Return the management URL keys."""
55-
return self.urls.keys()
56-
57-
def items(self):
58-
"""Return the management URL (key, value) pairs."""
59-
return self.urls.items()
60-
61-
def values(self):
62-
"""Return the management URL values."""
63-
return self.urls.values()
48+
def to_json(self) -> dict[str, Any]:
49+
"""Return the management URLs as a plain ``dict``."""
50+
return dict(self)

azure-functions-durable/azure/durable_functions/internal/compat/durable_orchestration_status.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33

44
import json
55
from datetime import datetime
6-
from typing import Any, Optional
6+
from typing import Any, Optional, cast
77

8-
from durabletask.client import OrchestrationState
8+
from durabletask.client import OrchestrationState, OrchestrationStatus
99

1010
from .orchestration_runtime_status import (
1111
OrchestrationRuntimeStatus,
1212
from_durabletask_status,
13+
to_durabletask_status,
1314
)
1415

1516

@@ -35,6 +36,43 @@ def from_orchestration_state(
3536
"""Wrap a durabletask ``OrchestrationState`` (or ``None``)."""
3637
return cls(state)
3738

39+
@classmethod
40+
def from_json(cls, json_obj: Any) -> "DurableOrchestrationStatus":
41+
"""Reconstruct a status from its v1 JSON representation.
42+
43+
Accepts the dictionary produced by :meth:`to_json` (or the equivalent v1
44+
schema); a JSON string is parsed first. The wrapped
45+
``OrchestrationState`` is rebuilt so the resulting object exposes the
46+
same attribute surface as one returned by the client.
47+
"""
48+
if isinstance(json_obj, str):
49+
json_obj = json.loads(json_obj)
50+
data = dict(json_obj)
51+
52+
runtime_status = data.get("runtimeStatus")
53+
dt_status = (
54+
to_durabletask_status(OrchestrationRuntimeStatus(runtime_status))
55+
if runtime_status is not None else None)
56+
57+
def _parse_datetime(value: Any) -> Any:
58+
return datetime.fromisoformat(value) if isinstance(value, str) else value
59+
60+
def _reserialize(value: Any) -> Optional[str]:
61+
return None if value is None else json.dumps(value)
62+
63+
state = OrchestrationState(
64+
instance_id=cast(str, data.get("instanceId")),
65+
name=cast(str, data.get("name")),
66+
runtime_status=cast(OrchestrationStatus, dt_status),
67+
created_at=cast(datetime, _parse_datetime(data.get("createdTime"))),
68+
last_updated_at=cast(datetime, _parse_datetime(data.get("lastUpdatedTime"))),
69+
serialized_input=_reserialize(data.get("input")),
70+
serialized_output=_reserialize(data.get("output")),
71+
serialized_custom_status=_reserialize(data.get("customStatus")),
72+
failure_details=None,
73+
)
74+
return cls(state)
75+
3876
def __bool__(self) -> bool:
3977
return self._state is not None
4078

azure-functions-durable/azure/durable_functions/internal/compat/orchestration_context.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ def will_continue_as_new(self) -> bool:
8181
"""Whether :meth:`continue_as_new` has been called in this execution."""
8282
return self._will_continue_as_new
8383

84+
@property
85+
def version(self) -> Optional[str]:
86+
"""Get the version assigned to the orchestration instance (or ``None``)."""
87+
return self._ctx.version
88+
8489
@property
8590
def parent_instance_id(self) -> str:
8691
"""Get the ID of the parent orchestration.

azure-functions-durable/azure/durable_functions/internal/compat/purge_history_result.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT License.
33

4+
from typing import Any
5+
46
from durabletask.client import PurgeInstancesResult
57

68

@@ -21,6 +23,11 @@ def from_purge_result(cls, result: PurgeInstancesResult) -> "PurgeHistoryResult"
2123
"""Wrap a durabletask ``PurgeInstancesResult``."""
2224
return cls(result.deleted_instance_count)
2325

26+
@classmethod
27+
def from_json(cls, json_obj: "dict[str, Any]") -> "PurgeHistoryResult":
28+
"""Reconstruct a result from its v1 JSON representation."""
29+
return cls(instances_deleted=json_obj["instancesDeleted"])
30+
2431
@property
2532
def instances_deleted(self) -> int:
2633
"""Get the number of deleted instances."""

azure-functions-durable/azure/durable_functions/internal/compat/retry_options.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,10 @@ def __init__(
4444
def first_retry_interval_in_milliseconds(self) -> int:
4545
"""Get the first retry interval, in milliseconds."""
4646
return int(self.first_retry_interval / timedelta(milliseconds=1))
47+
48+
def to_json(self) -> dict[str, int]:
49+
"""Return the v1 JSON representation of these retry options."""
50+
return {
51+
"firstRetryIntervalInMilliseconds": self.first_retry_interval_in_milliseconds,
52+
"maxNumberOfAttempts": self.max_number_of_attempts,
53+
}

0 commit comments

Comments
 (0)