Skip to content

Commit 428a3fd

Browse files
committed
Add Azure Functions Durable V2 samples
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8be112df-948c-4136-a13a-67c8dceb9513
1 parent b871df8 commit 428a3fd

20 files changed

Lines changed: 705 additions & 2 deletions

azure-functions-durable/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ ADDED
1313
the synchronous client into synchronous functions and the asynchronous client
1414
into coroutine functions. Both clients support scheduled-task and history-export
1515
APIs without an async-to-sync bridge.
16+
- Added runnable 2.x samples for function chaining, fan-out/fan-in, human
17+
interaction, and durable entities, plus a migration guide for applications
18+
upgrading from 1.x.
1619

1720
## 2.0.0b1
1821

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
# Migrate from Azure Functions Durable 1.x to 2.x
2+
3+
This guide explains how to move an application from
4+
`azure-functions-durable` 1.x to 2.x. Version 2.x is a ground-up rewrite built
5+
on the [`durabletask`](https://pypi.org/project/durabletask/) SDK and its gRPC
6+
runtime.
7+
8+
> [!WARNING]
9+
> `azure-functions-durable` 2.x is currently in preview. Its APIs may change
10+
> before the stable 2.0.0 release.
11+
12+
## Choose a migration path
13+
14+
Version 2.x includes compatibility shims for most 1.x APIs used by
15+
decorator-based apps. This supports a staged migration:
16+
17+
1. **Compatibility upgrade:** update the runtime and dependencies while keeping
18+
existing one-argument orchestrators, entities, and client calls. These APIs
19+
continue to work but emit `DeprecationWarning` where a native replacement
20+
exists.
21+
2. **Native API migration:** move to durabletask-native function signatures,
22+
client methods, retry policies, and entity types.
23+
24+
Use the compatibility upgrade first when minimizing application changes is more
25+
important than adopting the new API immediately.
26+
27+
## Requirements and breaking changes
28+
29+
Before upgrading, account for these 2.x requirements:
30+
31+
- **Python 3.13 or later is required.**
32+
- **Only the Azure Functions Python V2 programming model is supported.** Apps
33+
must use `DFApp` or `Blueprint` decorators. The classic programming model with
34+
one directory and `function.json` per function is not supported.
35+
- **A modern Durable Functions host is required.** Local sample apps use the
36+
preview extension bundle shown below.
37+
- **The OpenAI Agents integration was removed.** The
38+
`azure.durable_functions.openai_agents` package is not available in 2.x.
39+
- **The primary APIs now come from `durabletask`.** Compatibility aliases are
40+
available to stage the migration, but new code should use the native names.
41+
42+
If the app still uses the classic Python programming model, migrate it to the
43+
[decorator-based model](https://learn.microsoft.com/azure/azure-functions/functions-reference-python)
44+
before upgrading this package.
45+
46+
## Prepare running orchestrations
47+
48+
Orchestrator code must remain deterministic and replay-compatible for existing
49+
instances. Before changing function names, activity schedules, branching logic,
50+
or serialized inputs:
51+
52+
- allow existing instances to finish; or
53+
- deploy the migrated app with a new task hub and route new instances there.
54+
55+
Test replay behavior with representative orchestration histories in a
56+
non-production environment before updating a task hub that contains running
57+
instances.
58+
59+
## Perform a compatibility upgrade
60+
61+
### 1. Update the Python runtime
62+
63+
Update local development, CI, and the deployed Function App to Python 3.13 or
64+
later.
65+
66+
### 2. Update dependencies
67+
68+
Replace the 1.x package constraint in `requirements.txt`:
69+
70+
```text
71+
azure-functions>=2.3.0b2
72+
azure-functions-durable>=2.0.0b1,<3
73+
```
74+
75+
### 3. Update the extension bundle
76+
77+
Use the preview extension bundle that contains the required Durable Task gRPC
78+
runtime:
79+
80+
```json
81+
{
82+
"version": "2.0",
83+
"extensionBundle": {
84+
"id": "Microsoft.Azure.Functions.ExtensionBundle.Preview",
85+
"version": "[4.*, 5.0.0)"
86+
},
87+
"extensions": {
88+
"durableTask": {
89+
"storageProvider": {
90+
"type": "AzureStorage"
91+
}
92+
}
93+
}
94+
}
95+
```
96+
97+
### 4. Keep supported 1.x code temporarily
98+
99+
A decorator-based 1.x orchestrator and client starter can run through the 2.x
100+
compatibility layer without changing their function shape:
101+
102+
```python
103+
import azure.functions as func
104+
import azure.durable_functions as df
105+
106+
app = df.DFApp(http_auth_level=func.AuthLevel.ANONYMOUS)
107+
108+
109+
@app.orchestration_trigger(context_name="context")
110+
def hello(context: df.DurableOrchestrationContext):
111+
name = context.get_input()
112+
return (yield context.call_activity("say_hello", name))
113+
114+
115+
@app.route(route="start", methods=["POST"])
116+
@app.durable_client_input(client_name="client")
117+
async def start(
118+
req: func.HttpRequest,
119+
client: df.DurableFunctionsClient) -> func.HttpResponse:
120+
instance_id = await client.start_new("hello", client_input="Tokyo")
121+
return client.create_check_status_response(req, instance_id)
122+
```
123+
124+
The single-argument orchestrator receives the compatibility
125+
`DurableOrchestrationContext`, and `start_new` delegates to the native client.
126+
Use this state to validate the infrastructure upgrade before changing
127+
application APIs.
128+
129+
## Migrate to native orchestration APIs
130+
131+
Native orchestrators accept the durabletask context and input as separate
132+
arguments:
133+
134+
```python
135+
from typing import Any
136+
137+
import azure.functions as func
138+
import azure.durable_functions as df
139+
from durabletask import task
140+
141+
app = df.DFApp(http_auth_level=func.AuthLevel.ANONYMOUS)
142+
143+
144+
@app.orchestration_trigger(context_name="context")
145+
def hello(ctx: task.OrchestrationContext, name: Any):
146+
return (yield ctx.call_activity("say_hello", input=name))
147+
148+
149+
@app.route(route="start", methods=["POST"])
150+
@app.durable_client_input(client_name="client")
151+
async def start(
152+
req: func.HttpRequest,
153+
client: df.DurableFunctionsClient) -> func.HttpResponse:
154+
instance_id = await client.schedule_new_orchestration(
155+
"hello", input="Tokyo")
156+
return client.create_check_status_response(req, instance_id)
157+
```
158+
159+
Activity functions can keep their existing single-input signature:
160+
161+
```python
162+
@app.activity_trigger(input_name="name")
163+
def say_hello(name: str) -> str:
164+
return f"Hello {name}!"
165+
```
166+
167+
### Orchestration API mapping
168+
169+
| 1.x API | Native 2.x API |
170+
| --- | --- |
171+
| `context.get_input()` | The orchestrator's second argument |
172+
| `context.call_activity(name, input_)` | `ctx.call_activity(name, input=input_)` |
173+
| `context.call_activity_with_retry(...)` | `ctx.call_activity(..., retry_policy=policy)` |
174+
| `context.call_sub_orchestrator(name, input_)` | `ctx.call_sub_orchestrator(name, input=input_)` |
175+
| `context.call_sub_orchestrator_with_retry(...)` | `ctx.call_sub_orchestrator(...)` |
176+
| `context.task_all(tasks)` | `task.when_all(tasks)` |
177+
| `context.task_any(tasks)` | `task.when_any(tasks)` |
178+
| `context.new_guid()` | `ctx.new_uuid()` |
179+
| `df.EntityId(name, key)` | `entities.EntityInstanceId(name, key)` |
180+
181+
Methods including `create_timer`, `wait_for_external_event`,
182+
`continue_as_new`, `set_custom_status`, `call_entity`, and `signal_entity`
183+
retain their names. Activity, sub-orchestration, and entity calls use keyword
184+
`input` instead of the 1.x `input_` or `operationInput` names.
185+
`continue_as_new` uses `new_input`.
186+
187+
### Retry policy mapping
188+
189+
`RetryOptions` accepted millisecond values. Native `RetryPolicy` uses
190+
`timedelta`:
191+
192+
```python
193+
from datetime import timedelta
194+
195+
from durabletask import task
196+
197+
policy = task.RetryPolicy(
198+
first_retry_interval=timedelta(seconds=1),
199+
max_number_of_attempts=3,
200+
backoff_coefficient=2.0,
201+
)
202+
203+
result = yield ctx.call_activity(
204+
"call_service",
205+
input=request,
206+
retry_policy=policy,
207+
)
208+
```
209+
210+
## Migrate client APIs
211+
212+
`durable_client_input` supplies `DurableFunctionsClient` to coroutine functions
213+
and `SyncDurableFunctionsClient` to synchronous functions. Async client calls
214+
must be awaited; the corresponding sync calls must not be awaited.
215+
216+
| 1.x client API | Native 2.x client API |
217+
| --- | --- |
218+
| `start_new` | `schedule_new_orchestration` |
219+
| `get_status` | `get_orchestration_state` |
220+
| `get_status_all` | `get_all_orchestration_states` |
221+
| `get_status_by` | `get_all_orchestration_states` with `OrchestrationQuery` |
222+
| `raise_event` | `raise_orchestration_event` |
223+
| `terminate` | `terminate_orchestration` |
224+
| `suspend` | `suspend_orchestration` |
225+
| `resume` | `resume_orchestration` |
226+
| `restart` | `restart_orchestration` |
227+
| `rewind` | `rewind_orchestration` |
228+
| `purge_instance_history` | `purge_orchestration` |
229+
| `purge_instance_history_by` | `purge_orchestrations_by` |
230+
| `read_entity_state` | `get_entity` |
231+
| `get_client_response_links` | `create_http_management_payload` |
232+
233+
Native `restart_orchestration` reuses the current instance ID by default. Pass
234+
`restart_with_new_instance_id=True` to preserve the 1.x `restart` default.
235+
236+
Native status APIs return durabletask models rather than the compatibility
237+
wrappers `DurableOrchestrationStatus`, `PurgeHistoryResult`, and
238+
`EntityStateResponse`. Update code that serializes or checks those return
239+
values. Use `get_orchestration_history` when history events are required.
240+
241+
## Migrate durable entities
242+
243+
One-argument entity functions remain available through
244+
`DurableEntityContext`. Native code can use a two-argument entity function or a
245+
`DurableEntity` class. A class exposes each method as an entity operation:
246+
247+
```python
248+
from typing import Any
249+
250+
import azure.durable_functions as df
251+
from durabletask.entities import DurableEntity
252+
253+
254+
@app.entity_trigger(context_name="context")
255+
class Counter(DurableEntity):
256+
def add(self, input_: Any = None) -> int:
257+
value = self.get_state(int, 0) + (input_ or 0)
258+
self.set_state(value)
259+
return value
260+
261+
def get(self, input_: Any = None) -> int:
262+
return self.get_state(int, 0)
263+
```
264+
265+
Replace `df.EntityId` with `durabletask.entities.EntityInstanceId` at call
266+
sites. For function-based entities, return the operation result directly
267+
instead of calling `context.set_result`.
268+
269+
## Review compatibility limitations
270+
271+
- `DurableOrchestrationContext.histories` is unavailable. Retrieve history with
272+
`client.get_orchestration_history(instance_id)`.
273+
- The compatibility `show_history` and `show_history_output` status flags are
274+
accepted but ignored.
275+
- Distributed tracing is not yet wired through the Python provider.
276+
- Continuous history export is not supported by Azure Functions.
277+
- Unusual callable signatures can be misclassified by the compatibility layer.
278+
Keep compatibility orchestrators and entities to exactly one positional
279+
argument, and native functions to exactly two.
280+
281+
See the [changelog](CHANGELOG.md) for the complete compatibility surface and
282+
known limitations.
283+
284+
## Validate and deploy
285+
286+
Before production rollout:
287+
288+
1. Run unit tests against Python 3.13 or later.
289+
2. Run the app locally with Azurite and Azure Functions Core Tools.
290+
3. Start new orchestration, sub-orchestration, entity, retry, timer, and
291+
external-event scenarios used by the app.
292+
4. Verify status queries and HTTP management URLs.
293+
5. Test existing orchestration histories or drain existing instances.
294+
6. Deploy to a staging slot or non-production task hub before production.
295+
296+
The [2.x samples](samples/) provide runnable native examples for common
297+
orchestration patterns.

azure-functions-durable/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ built on top of the [`durabletask`](https://pypi.org/project/durabletask/) SDK.
77
> [!NOTE]
88
> 2.x is a ground-up rewrite of the Durable Functions Python SDK on top of the
99
> `durabletask` runtime. It is currently a preview (beta) release; APIs may
10-
> change before the stable 2.0.0. See [CHANGELOG.md](CHANGELOG.md) for the
11-
> migration summary from 1.x, including breaking changes.
10+
> change before the stable 2.0.0.
1211
1312
## Requirements
1413

@@ -35,6 +34,8 @@ calls (`context.call_http(...)`), recurring scheduled tasks, and history export.
3534

3635
## Links
3736

37+
- [2.x samples](samples/)
38+
- [Migration guide from 1.x](MIGRATION_GUIDE.md)
3839
- [Changelog](CHANGELOG.md)
3940
- [Durable Functions documentation](https://learn.microsoft.com/azure/azure-functions/durable/)
4041
- [`durabletask` on PyPI](https://pypi.org/project/durabletask/)

0 commit comments

Comments
 (0)