Skip to content

Commit a1b54bd

Browse files
committed
Add shims for old orchestration and entity call arg patterns
1 parent d4a84bc commit a1b54bd

9 files changed

Lines changed: 654 additions & 52 deletions

File tree

azure-functions-durable/CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- One-argument (Azure Functions / v1-style) entity functions
13+
(``def entity(context):``) are now supported. The worker detects the entity's
14+
shape and, for single-argument functions, delivers a functional
15+
`DurableEntityContext` that wraps the durabletask `EntityContext` and exposes
16+
the v1 entity API: `entity_name`, `entity_key`, `operation_name`,
17+
`get_input()`, `get_state()` (with `initializer`), `set_state()`,
18+
`set_result()`, and `destruct_on_exit()`. The operation result is taken from
19+
`set_result(...)`, falling back to the function's return value.
20+
durabletask-native two-argument entity functions and class-based
21+
(`DurableEntity`) entities continue to work unchanged.
22+
- One-argument (Azure Functions / v1-style) orchestrator functions
23+
(``def orchestrator(context):``) are now supported. The worker detects the
24+
orchestrator's arity and, for single-argument functions, delivers a
25+
functional `DurableOrchestrationContext` that wraps the durabletask
26+
`OrchestrationContext` and exposes the v1 context API: `get_input()`,
27+
`call_activity`/`call_activity_with_retry`,
28+
`call_sub_orchestrator`/`call_sub_orchestrator_with_retry`, `create_timer`,
29+
`wait_for_external_event`, `continue_as_new`, `set_custom_status`,
30+
`task_all`/`task_any`, `call_entity`/`signal_entity`, and `new_uuid`/`new_guid`.
31+
Two-argument (durabletask-native) orchestrators continue to work unchanged.
32+
`DurableOrchestrationContext.call_http` raises `NotImplementedError` pending a
33+
durabletask durable-HTTP implementation.
34+
1235
- Backwards-compatible, deprecated aliases on `DurableFunctionsClient` for the
1336
v1 `DurableOrchestrationClient` method names: `start_new`, `get_status`,
1437
`get_status_all`, `get_status_by`, `raise_event`, `terminate`,

azure-functions-durable/azure/durable_functions/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
from .internal.compat.entity_state_response import EntityStateResponse
1717
from .internal.compat.entity_id import EntityId
1818
from .internal.compat.token_source import ManagedIdentityTokenSource, TokenSource
19+
from .internal.compat.orchestration_context import DurableOrchestrationContext
20+
from .internal.compat.entity_context import DurableEntityContext
1921
from .internal.compat.compat_aliases import (
20-
DurableEntityContext,
2122
DurableOrchestrationClient,
22-
DurableOrchestrationContext,
2323
Entity,
2424
)
2525

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

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT License.
33

4-
from typing import Any, Optional
4+
from typing import Any
55

66
from typing_extensions import deprecated
77

8-
from durabletask.entities import EntityContext
9-
from durabletask.task import OrchestrationContext
10-
118
from ...client import DurableFunctionsClient
12-
from .token_source import TokenSource
139

1410

1511
@deprecated(
@@ -18,42 +14,6 @@ class DurableOrchestrationClient(DurableFunctionsClient):
1814
"""Deprecated alias for :class:`DurableFunctionsClient`."""
1915

2016

21-
@deprecated(
22-
"DurableOrchestrationContext is deprecated; use "
23-
"durabletask.task.OrchestrationContext instead.")
24-
class DurableOrchestrationContext(OrchestrationContext):
25-
"""Deprecated alias for :class:`durabletask.task.OrchestrationContext`.
26-
27-
Retained so v1 type hints (``def my_orchestrator(context: DurableOrchestrationContext)``)
28-
continue to import. At runtime the durabletask worker passes an
29-
``OrchestrationContext`` instance.
30-
"""
31-
32-
def call_http(self,
33-
method: str,
34-
uri: str,
35-
content: Optional[str] = None,
36-
headers: Optional[dict[str, str]] = None,
37-
token_source: Optional[TokenSource] = None,
38-
is_raw_str: bool = False) -> Any:
39-
"""Schedule a durable HTTP call (v1 API).
40-
41-
Not yet supported: durabletask has no durable-HTTP (``call_http``)
42-
equivalent, so this raises ``NotImplementedError``. It is present to
43-
document the v1 API surface and the current gap.
44-
"""
45-
raise NotImplementedError(
46-
"call_http is not yet supported by durabletask. The durable-HTTP "
47-
"API (and its TokenSource auth) has no durabletask equivalent yet.")
48-
49-
50-
@deprecated(
51-
"DurableEntityContext is deprecated; use "
52-
"durabletask.entities.EntityContext instead.")
53-
class DurableEntityContext(EntityContext):
54-
"""Deprecated alias for :class:`durabletask.entities.EntityContext`."""
55-
56-
5717
@deprecated(
5818
"The Entity class is deprecated and unsupported in v2; register entities "
5919
"with the entity_trigger decorator instead.")
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
from typing import Any, Callable, Optional
5+
6+
from durabletask.entities import EntityContext
7+
8+
from .orchestration_context import accepts_two_positional_args
9+
10+
11+
class DurableEntityContext:
12+
"""Azure Functions-style entity context (v1-compatible).
13+
14+
Wraps a durabletask :class:`~durabletask.entities.EntityContext` (and the
15+
operation input) and exposes the v1 ``DurableEntityContext`` API. It is
16+
delivered to one-argument entity functions (``def entity(context):``).
17+
durabletask-native two-argument entity functions
18+
(``def entity(ctx, input):``) and class-based entities are used directly.
19+
"""
20+
21+
def __init__(self, ctx: EntityContext, operation_input: Any = None):
22+
self._ctx = ctx
23+
self._input = operation_input
24+
self._result: Any = None
25+
26+
# -- identity ------------------------------------------------------------
27+
@property
28+
def entity_name(self) -> str:
29+
"""Get the entity name."""
30+
return self._ctx.entity_id.entity
31+
32+
@property
33+
def entity_key(self) -> str:
34+
"""Get the entity key."""
35+
return self._ctx.entity_id.key
36+
37+
@property
38+
def operation_name(self) -> str:
39+
"""Get the current operation name."""
40+
return self._ctx.operation
41+
42+
@property
43+
def is_newly_constructed(self) -> bool:
44+
"""Whether the entity was newly constructed.
45+
46+
The v1 semantics of this flag were unspecified; it is always ``False``.
47+
"""
48+
return False
49+
50+
# -- input / state / result ---------------------------------------------
51+
def get_input(self, expected_type: Optional[type] = None) -> Any:
52+
"""Get the input for the current operation.
53+
54+
``expected_type`` is accepted for v1 compatibility but the input is
55+
already deserialized by durabletask, so it is returned as-is.
56+
"""
57+
return self._input
58+
59+
def get_state(self,
60+
initializer: Optional[Callable[[], Any]] = None,
61+
expected_type: Optional[type] = None) -> Any:
62+
"""Get the current state of the entity.
63+
64+
Parameters
65+
----------
66+
initializer : Optional[Callable[[], Any]]
67+
A zero-argument callable providing the initial state when no state
68+
exists yet.
69+
expected_type : Optional[type]
70+
Optional type used to reconstruct the state.
71+
"""
72+
default = initializer() if callable(initializer) else None
73+
return self._ctx.get_state(expected_type, default)
74+
75+
def set_state(self, state: Any) -> None:
76+
"""Set the state of the entity."""
77+
self._ctx.set_state(state)
78+
79+
def set_result(self, result: Any) -> None:
80+
"""Set the result (return value) of the current operation."""
81+
self._result = result
82+
83+
def resolve_result(self, fallback: Any) -> Any:
84+
"""Return the value set via :meth:`set_result`, or ``fallback`` if unset."""
85+
return self._result if self._result is not None else fallback
86+
87+
def destruct_on_exit(self) -> None:
88+
"""Delete this entity after the operation completes."""
89+
self._ctx.set_state(None)
90+
91+
92+
def wrap_entity(fn: Callable[..., Any]) -> Callable[..., Any]:
93+
"""Adapt a v1-style one-argument entity function to durabletask's ``(ctx, input)``.
94+
95+
Class-based entities and durabletask-native two-argument entity functions
96+
are returned unchanged. For a wrapped v1 entity, the operation result is
97+
taken from ``context.set_result(...)`` (falling back to the function's
98+
return value).
99+
"""
100+
if isinstance(fn, type):
101+
# Class-based entity: handled natively by durabletask.
102+
return fn
103+
if accepts_two_positional_args(fn):
104+
# durabletask-native (ctx, input) entity function.
105+
return fn
106+
107+
def _wrapper(ctx: EntityContext, _input: Any = None) -> Any:
108+
adapter = DurableEntityContext(ctx, _input)
109+
returned = fn(adapter)
110+
return adapter.resolve_result(returned)
111+
112+
_wrapper.__name__ = getattr(fn, "__name__", "entity")
113+
durable_entity_name = getattr(fn, "__durable_entity_name__", None)
114+
if durable_entity_name is not None:
115+
_wrapper.__durable_entity_name__ = durable_entity_name # type: ignore[attr-defined]
116+
return _wrapper

0 commit comments

Comments
 (0)