Skip to content

Commit c8522ec

Browse files
andystaplesCopilotberndverst
authored
Add azure-functions-durable v2: a durabletask-based rewrite of Durable Functions for Python (#155)
* Storing changes commit * Working orchestrators + activities * Nitpicks and cleanup * Save-all nits * Add entity support (needs extension change) * Refine entity support - Still needs eventSent and eventRecieved implementations * Finish entity support * Fixes and improvements - Add new_uuid method to OrchestrationContext for deterministic replay-safe UUIDs - Fix entity locking behavior for Functions - Align _RuntimeOrchestrationContext param names with OrchestrationContext - Remap __init__.py files for new module - Update version to 0.0.1dev0 - Add docstrings to missing methods - Move code for executing orchestrators/entities to DurableFunctionsWorker - Add function metadata to triggers for detection by extension * Bump durabletask version, fix metadata * Use Protocol for stubs * Update to new workflow pattern * Rename stub file * Fix import * Experimental dependency revision * Update to match changes in functions SDK * Merge issue fix * Various * Add Functions to requirements * Rename to azure-functions-durable v2 * Re-add Orchestrator object/model * Modernize pipelines for functions package * Cleanup pyright errors * Remove non-existent extension call * Serialization compat, fix typing, working again * Use the async client (match old behavior) * Compat layer V1 * Reorganize compat files * Add shims for old orchestration and entity call arg patterns * Add remaining orchestration surface * Add missing type coersion, rich client, tests * Fix gaps found via integration tests * Fix old-name interceptor lookup * More compat layer stuff * Return an emtpy function context instead of raising * Asyncio in test matrix * CI Tweaks * Add Durable HTTP feature * CHANGELOG for now * Add unit testing coverage * Add E2E harness * Expand E2E coverage * More E2E test edge cases, fixes, add scheduled tasks * Add history export to afd * Add afd rewind * lint fix * Pyright fixes * Review feedback * Review Feedback 2 * 2.0.0b1 CHANGELOG rewrite * Review Feedback 3 * Fix CI * Review Feedback 4 * History export pagination fix * Allow both activity signatures * Record DT limitation * Comment tweak * Address YunchuWang review items * afd: address PR #155 review feedback Fixes from berndverst's review of the Azure Functions Durable v2 provider: - durable_app: preserve the configured entity name (mirror orchestrator wiring) - client: accept the v1 'entityId' keyword on entity APIs - client: return the full status JSON for TERMINATED/FAILED and surface failure details via DurableOrchestrationStatus.to_json() - entity_id: fix get_entity_id - http/builtin: resolve a relative Location header against the request URI - serialization: decode plainly and let coerce() reconstruct typed values (loose-mode portion of the strict-typing fix) - replace typing_extensions.deprecated with stdlib warnings.deprecated and drop the typing_extensions dependency * afd: forward destination type through payload deserialization - FunctionsDataConverter.deserialize now passes the target type to df_loads as expected_type so payloads are deserialized with type validation instead of decoding untyped and coercing afterward. - Depend directly on azure-functions' df_dumps/df_loads; remove the legacy custom-object serialization fallback path and its tests. - DurableEntityContext.get_input coerces the operation input to expected_type when one is supplied (previously ignored). - wrap_orchestrator stamps the decorator-declared input_type onto the wrapper's input annotation so type discovery deserializes the orchestration input to that type. - builtin_http_poll_orchestrator returns a DurableHttpResponse object so the call_http sub-orchestration result round-trips as its declared type. * Make scheduled payloads and the Functions converter strict-typing compatible - ScheduleOperationRequest.to_json emits its options input as a plain mapping and from_json rebuilds the concrete options type from the operation name, so the payload is JSON-native (no nested custom-object envelope a strict-typing serializer cannot encode). - ScheduleState.to_json emits its schedule configuration as a plain mapping instead of the raw object; from_json already rebuilds it. - FunctionsDataConverter.coerce validates through serialize/deserialize so a coercion applies the same codec type rules as a wire payload, and can_reconstruct returns True so type discovery always hands the declared type to the codec. - Run the azure-functions-durable end-to-end suite in both the default and strict payload-typing modes via a CI matrix. Verified: unit suites, plus the e2e suite in both loose and strict modes. * Resolve the history-export client per invocation via a durable client binding The export activities previously resolved their durabletask client from a process-global context bound in the request handler, which is not visible to an activity running in another worker process. They now declare a durable_client_input binding and build the client per invocation from it, so the host supplies it wherever the activity runs. The HistoryWriter, which the host cannot inject, is registered via configure_history_export(writer=...) at app startup and reused. Supporting SDK fixes for functions that carry a durable client binding alongside their trigger: - wrap_activity leaves a native activity whose first parameter is the trigger input untouched (so additional bindings such as a durable client bind by name) instead of adapting it as a (context, input) activity. - the durable client middleware invokes synchronous user functions and returns their result instead of awaiting it. Verified: unit suites, plus the e2e suite (history export and full) in loose and strict modes. * Reject continuous history export on Azure Functions Add azure.durable_functions.extensions.history_export.ExportHistoryClient, a Functions-aware subclass that rejects ExportMode.CONTINUOUS at job creation and otherwise passes through to the durabletask client. Continuous export tails terminal instances indefinitely, which needs the host's ListInstanceIds (a server-side completed-time cursor); the Durable Functions host extension does not implement it, so the Functions enumeration path can only page a bounded completed-time window safely. Batch export is unaffected. Also refresh the changelog entry for configure_history_export to reflect the writer argument and per-invocation client binding. Verified: unit suites, plus the history-export e2e (batch) in loose mode. * Enforce the continuous-export rejection at the Functions export entity Rejecting ExportMode.CONTINUOUS only in an optional ExportHistoryClient subclass was bypassable: callers can use the core durabletask ExportHistoryClient (which the docs point to) to signal the same shared ExportJobEntity, whose create operation had no mode guard. Move the guard to a Functions-registered ExportJobEntity subclass that marks the job FAILED with an explanatory reason when created in continuous mode, so the unsupported mode cannot start regardless of which client is used. Remove the now-redundant azure.durable_functions extensions.history_export module (callers use the core client directly). Also narrowly suppress reportDeprecated on the internal EntityId self-references in EntityId.get_entity_id so the package's strict Pyright check passes while the public EntityId deprecation is preserved. Verified: unit suites, strict Pyright on the changed files, and the history-export e2e including a new continuous-rejection test. * Address review nits: cast target and unused logger - orchestrator.py: pass the Any type object to cast (was a forward-ref string). - serialization.py: remove the now-unused logger and logging import (dead after the fallback removal). * Adopt azure-functions register_converter API for durable bindings Define and register the durable binding converters in this package via azure-functions' new register_converter API, replacing the SDK's prior version-detection auto-registration. The durableClient converter now constructs the rich DurableFunctionsClient during decode, so the per-function _add_rich_client middleware (and its forced-str annotation hack) is removed. - Add internal/converters.py defining the four durable converters and register_durable_converters(); call it at package import. - Remove _add_rich_client and now-unused imports from durable_app.py. - Add test_converters.py; update decorator and smoke tests. * Back-compat unit testing for orchestrators/clients * Address PR feedback * Update azure-functions minimums * Address PR #155 review: schedule serialization, HTTP/export docs, client channel lifecycle - ScheduleState.to_json: add regression test pinning the DTS wire shape (plain PascalCase ScheduleConfiguration mapping, no __class__ envelope); confirmed no wire change under the default converter. - orchestrator_generator_wrapper: handle orchestrators that return without yielding (no StopIteration leak). - builtin http: cache DefaultAzureCredential process-wide to avoid re-running the credential chain on every managed-identity poll. - history export: mark batch export experimental/low-volume in changelog + docstring; clarify scaled-out wording is a correctness property, not a throughput guarantee. - Durable client channel lifecycle: close per-invocation async DurableFunctionsClient channels via an app-level post_invocation extension (schedule_close on the owning loop); close the per-process sync export client at interpreter exit (atexit) with a first-bind lock. Ref #181 for the future native sync binding. - Tests: new test_client_lifecycle.py + sync-client lifecycle, credential-reuse, immediate-return orchestrator, and wire-shape guard tests. Verified via unit suite and functions_e2e (durable client + history export slices). * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Run functions e2e unconditionally; install azure-functions from PyPI azure-functions 2.3.0b2 is now on PyPI, so remove the RUN_FUNCTIONS_E2E gate on the e2e job and drop the local azure-functions build fallback (AZURE_FUNCTIONS_PYTHON_LIBRARY / sibling repo) from nox. The provider's declared azure-functions>=2.3.0b2 dependency now resolves from PyPI. Verified with a clean-venv functions_e2e run (resolved azure-functions 2.3.0b2). * Min azfunc version in requirements.txt * Unpin azure-functions in shared requirements; install 2.3.0b2 explicitly in functions CI azure-functions>=2.3.0b2 requires Python 3.13+, but requirements.txt is shared with the core durabletask / azuremanaged / typecheck CI that runs a 3.10-3.14 matrix, so the pin produced 'No matching distribution' on 3.10-3.12. Unpin it back to bare azure-functions so those jobs resolve a 3.10-compatible build. The azure-functions-durable run-tests job installs the provider with --no-deps and runs only on 3.13/3.14, so add an explicit 'pip install azure-functions>=2.3.0b2' there to pull the build that provides register_converter. * CI: upload Functions host logs as an artifact on e2e failure The e2e harness writes each sample app's 'func start' output to <app>/_func_host.log, but only folds it into an exception on startup failures -- runtime 500s (e.g. durable operations failing after the host is up) leave the real host/worker error invisible in the CI log. Upload the host logs on failure so the durable-extension/worker error is retrievable for diagnosis. * Fix durable client 500 when host sends config fields as null The Durable Functions host client-config JSON can send fields explicitly as null (observed with maxGrpcMessageSizeInBytes on newer extension bundles) rather than omitting them. dict.get(key, default) only substitutes the default for absent keys, so a present-but-null value returned None -- crashing the 'maxGrpcMessageSizeInBytes > 0' guard in __init__ (TypeError: '>' not supported between NoneType and int) and failing every durable-client binding decode with a 500. Parse every field with 'client.get(key) or default' so an explicit null (like a missing key) collapses to its falsy default. Diagnosed from the e2e func host log; add regression tests for a single null field and for all fields sent as null. * Use preview bundles for compatible Durable extension version * Skip API version check azurite --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Bernd Verst <github@bernd.dev>
1 parent 4d87430 commit c8522ec

106 files changed

Lines changed: 10828 additions & 25 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.flake8

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@ ignore = E501,C901,W503
33
exclude =
44
.git
55
*_pb2*
6-
__pycache__
6+
__pycache__
7+
.venv
8+
.nox
9+
.python_packages
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
name: Durable Task Scheduler SDK (azure-functions-durable)
2+
3+
on:
4+
push:
5+
branches:
6+
- "main"
7+
tags:
8+
- "azurefunctions-v*" # Only run for tags starting with "azurefunctions-v"
9+
pull_request:
10+
branches:
11+
- "main"
12+
13+
permissions:
14+
contents: read
15+
16+
jobs:
17+
lint:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: actions/checkout@v4
21+
- name: Set up Python 3.13
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version: 3.13
25+
- name: Install dependencies
26+
working-directory: azure-functions-durable
27+
run: |
28+
python -m pip install --upgrade pip
29+
pip install setuptools wheel tox
30+
pip install flake8
31+
- name: Run flake8 Linter
32+
working-directory: azure-functions-durable
33+
run: flake8 .
34+
- name: Run flake8 Linter
35+
working-directory: tests/azure-functions-durable
36+
run: flake8 .
37+
38+
run-tests:
39+
strategy:
40+
fail-fast: false
41+
matrix:
42+
python-version: ["3.13", "3.14"]
43+
needs: lint
44+
runs-on: ubuntu-latest
45+
steps:
46+
- name: Checkout repository
47+
uses: actions/checkout@v4
48+
49+
- name: Set up Python ${{ matrix.python-version }}
50+
uses: actions/setup-python@v5
51+
with:
52+
python-version: ${{ matrix.python-version }}
53+
54+
- name: Install durabletask dependencies
55+
run: |
56+
python -m pip install --upgrade pip
57+
pip install flake8 pytest
58+
pip install -r requirements.txt
59+
60+
- name: Install durabletask locally
61+
run: |
62+
pip install . --no-deps --force-reinstall
63+
64+
- name: Install azure-functions-durable locally
65+
working-directory: azure-functions-durable
66+
run: |
67+
pip install . --no-deps --force-reinstall
68+
69+
# ``requirements.txt`` (shared with the core 3.10-3.14 CI) leaves
70+
# azure-functions unpinned so it can resolve on Python < 3.13. The
71+
# provider needs azure-functions>=2.3.0b2 (which requires 3.13+ and
72+
# provides ``register_converter``); the local installs above use
73+
# ``--no-deps``, so install that floor explicitly here. This job only runs
74+
# on 3.13/3.14, where 2.3.0b2 is available.
75+
- name: Install azure-functions (>=2.3.0b2)
76+
run: |
77+
pip install "azure-functions>=2.3.0b2"
78+
79+
- name: Run unit tests
80+
working-directory: tests/azure-functions-durable
81+
run: |
82+
pytest -m "not dts and not azurite and not functions_e2e" --verbose
83+
84+
e2e-tests:
85+
needs: run-tests
86+
runs-on: ubuntu-latest
87+
# The end-to-end suite launches a real Azure Functions host, which requires
88+
# ``azure-functions>=2.3.0b2``. That build is published to PyPI and pulled
89+
# in as a declared dependency of ``azure-functions-durable``.
90+
# Run the suite in both payload-typing modes: the default (loose) codec and
91+
# strict typing (``AZURE_FUNCTIONS_DURABLE_STRICT_TYPING=1``), which requires
92+
# every decode call site to supply the destination type. ``0`` is not a
93+
# truthy value for the flag, so it selects loose mode.
94+
strategy:
95+
fail-fast: false
96+
matrix:
97+
strict-typing: ["0", "1"]
98+
name: e2e-tests (strict-typing=${{ matrix.strict-typing }})
99+
steps:
100+
- name: Checkout repository
101+
uses: actions/checkout@v4
102+
103+
- name: Set up Python 3.13
104+
uses: actions/setup-python@v5
105+
with:
106+
python-version: "3.13"
107+
108+
- name: Set up Node.js (needed for Azurite and Functions Core Tools)
109+
uses: actions/setup-node@v4
110+
with:
111+
node-version: "20.x"
112+
113+
- name: Install Azurite and Azure Functions Core Tools
114+
run: |
115+
npm install -g azurite@latest
116+
npm install -g azure-functions-core-tools@4 --unsafe-perm true
117+
118+
- name: Start Azurite
119+
shell: bash
120+
# The DurableTask.AzureStorage provider in recent extension bundles uses
121+
# a newer Azure Storage REST API version than the CI Azurite build
122+
# recognizes, which otherwise fails task-hub creation with "The API
123+
# version ... is not supported by Azurite". ``--skipApiVersionCheck``
124+
# bypasses that guard so the durable provider can talk to Azurite.
125+
run: |
126+
azurite --silent --skipApiVersionCheck --location /tmp/azurite --blobPort 10000 --queuePort 10001 --tablePort 10002 &
127+
sleep 2
128+
129+
- name: Install nox
130+
run: |
131+
python -m pip install --upgrade pip
132+
pip install nox
133+
134+
- name: Run end-to-end tests
135+
# nox provisions each sample app's in-app virtual environment (a link to
136+
# a single editable install) so the Functions worker loads our
137+
# grpc/protobuf rather than its bundled copies.
138+
env:
139+
AZURE_FUNCTIONS_DURABLE_STRICT_TYPING: ${{ matrix.strict-typing }}
140+
run: nox -s functions_e2e
141+
142+
# The harness captures each sample app's ``func start`` stdout/stderr to
143+
# ``<app>/_func_host.log``. On failure that log holds the real host/worker
144+
# error (durable extension load, storage wiring, worker crash) that the
145+
# pytest summary does not surface, so upload it for diagnosis.
146+
- name: Upload Functions host logs
147+
if: failure()
148+
uses: actions/upload-artifact@v4
149+
with:
150+
name: func-host-logs-strict-${{ matrix.strict-typing }}
151+
path: tests/azure-functions-durable/e2e/apps/*/_func_host.log
152+
if-no-files-found: warn
153+
retention-days: 7
154+

.github/workflows/typecheck.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77
tags:
88
- "v*"
99
- "azuremanaged-v*"
10+
- "azurefunctions-v*"
1011
pull_request:
1112
branches:
1213
- "main"
@@ -46,3 +47,25 @@ jobs:
4647
4748
- name: Run pyright (strict, Python 3.10)
4849
run: pyright
50+
51+
pyright-azurefunctions:
52+
runs-on: ubuntu-latest
53+
steps:
54+
- name: Checkout repository
55+
uses: actions/checkout@v4
56+
57+
- name: Set up Python 3.13 (lowest supported by azure-functions-durable)
58+
uses: actions/setup-python@v5
59+
with:
60+
python-version: "3.13"
61+
62+
- name: Install packages and dependencies
63+
run: |
64+
python -m pip install --upgrade pip
65+
pip install -r requirements.txt
66+
pip install -e ".[azure-blob-payloads,opentelemetry]"
67+
pip install -e ./azure-functions-durable
68+
pip install pyright
69+
70+
- name: Run pyright (strict, Python 3.13)
71+
run: pyright -p azure-functions-durable/pyrightconfig.json

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ ENV/
111111
env.bak/
112112
venv.bak/
113113

114+
# Azure Functions E2E test host logs (written by the test harness)
115+
_func_host.log
116+
117+
# Azure Functions E2E history-export output (written by the sample app writer)
118+
_export_output/
119+
114120
# Spyder project settings
115121
.spyderproject
116122
.spyproject

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@
3131
"coverage.cobertura.xml"
3232
],
3333
"makefile.configureOnOpen": false,
34-
"debugpy.debugJustMyCode": false
34+
"debugpy.debugJustMyCode": false,
35+
"python-envs.defaultEnvManager": "ms-python.python:system"
3536
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## 2.0.0b1
9+
10+
First preview (beta) release of `azure-functions-durable` 2.x — a ground-up
11+
rewrite of the Azure Durable Functions Python SDK built on top of the
12+
[`durabletask`](https://pypi.org/project/durabletask/) SDK. This is a preview
13+
release; APIs may change before the stable 2.0.0.
14+
15+
### Why the rewrite
16+
17+
The 1.x SDK implemented the Durable Functions out-of-process programming model
18+
directly, with its own orchestration action/state model and a JSON protocol
19+
tailored to the classic Durable Functions host extension. 2.x instead builds on
20+
the `durabletask` Python SDK — the same gRPC-based runtime that powers the
21+
Durable Task Scheduler (DTS) and the modern Durable Functions host. Building on
22+
durabletask means:
23+
24+
- a single orchestration/entity execution core, serialization pipeline, and
25+
retry/versioning implementation shared with the broader durabletask
26+
ecosystem, instead of a Functions-specific reimplementation;
27+
- Functions users can adopt durabletask-native APIs and patterns directly,
28+
while existing v1 code keeps working through the compatibility layer; and
29+
- less protocol drift between the Python worker and the durable backend.
30+
31+
### Underlying packages
32+
33+
- [`durabletask`](https://pypi.org/project/durabletask/) — orchestration and
34+
entity client/worker, executed over gRPC.
35+
- [`azure-functions`](https://pypi.org/project/azure-functions/) — the
36+
decorator/binding programming model (`DFApp` / `Blueprint`).
37+
- [`azure-identity`](https://pypi.org/project/azure-identity/) — Managed
38+
Identity token acquisition for durable HTTP calls.
39+
40+
### Breaking changes (from `azure-functions-durable` 1.x)
41+
42+
- **Python 3.13+ is now required** (1.x supported 3.10+). On Functions Python
43+
workers older than 3.13, worker dependencies are not isolated from your app's
44+
dependencies, which causes a `grpc` version conflict with the durable runtime
45+
at load time. Python 3.13 enables worker dependency isolation, avoiding the
46+
collision.
47+
- **The classic (v1) programming model has been dropped.** Only the
48+
decorator-based application model (`DFApp` / `Blueprint`, the Python v2
49+
programming model) is supported; the `function.json`-based model is not.
50+
- **The OpenAI Agents integration has been removed.** The
51+
`azure.durable_functions.openai_agents` package (durable OpenAI Agents SDK
52+
orchestration) is not part of 2.x.
53+
- **Runtime target.** 2.x speaks the durabletask gRPC protocol used by the
54+
Durable Task Scheduler and the modern Durable Functions host, rather than the
55+
classic Durable Functions extension protocol.
56+
- **Primary client and context APIs use durabletask names.** The main surface
57+
is now durabletask's (e.g. `schedule_new_orchestration`,
58+
`get_orchestration_state`, `wait_for_orchestration_completion`). The v1
59+
`DurableOrchestrationClient` method names remain available as deprecated
60+
aliases that emit `DeprecationWarning` (see [Deprecated](#deprecated)).
61+
62+
### Added
63+
64+
New capabilities beyond the v1 surface, most inherited from `durabletask`:
65+
66+
- **durabletask-native authoring.** Two-argument orchestrator, entity, and
67+
activity functions (`def orchestrator(ctx, input)`, `def entity(ctx, input)`,
68+
`def activity(ctx, input)`) and class-based entities (`DurableEntity`) are
69+
first-class, alongside the supported v1-style single-argument functions. For
70+
activities, `activity_trigger` adapts a two-argument `(ctx, input)` function
71+
to the host's single-input calling convention automatically (the context is a
72+
placeholder object; accessing context attributes raises `NotImplementedError`).
73+
- **`DurableFunctionsClient.rewind_orchestration(...)`** rewinds a failed
74+
orchestration to its last known good state (inherited from durabletask).
75+
- **`DFApp.configure_scheduled_tasks()`** opts an app in to durabletask
76+
scheduled (recurring) tasks by registering the schedule entity and operation
77+
orchestrator. Schedules are then managed from a client via
78+
`durabletask.scheduled.ScheduledTaskClient`. Scheduled tasks are not
79+
registered unless this method is called.
80+
- **`DFApp.configure_history_export(writer=...)`** opts an app in to durabletask
81+
history export by registering the export-job entity, driving orchestrator, and
82+
activities. Supply the `HistoryWriter` here; the activities resolve their
83+
durabletask client per invocation from a `durable_client_input` binding, so
84+
the export activities run correctly across a scaled-out, multi-worker
85+
deployment (each invocation resolves its own client). This is a correctness
86+
property, not a large-export throughput guarantee. Export jobs are
87+
driven from a client via
88+
`durabletask.extensions.history_export.ExportHistoryClient`. Continuous export
89+
(`ExportMode.CONTINUOUS`) is not supported on Azure Functions: the
90+
Functions-registered export entity rejects it at job creation (the job ends
91+
`FAILED` with an explanatory reason). Continuous tailing needs the host's
92+
`ListInstanceIds` gRPC call, which the Durable Functions host extension does
93+
not implement; the instance-enumeration activity uses a Functions-specific
94+
implementation based on `QueryInstances` for the same reason. This is an
95+
experimental beta feature intended for bounded, low-volume batch-export
96+
windows: the `QueryInstances`-based enumeration re-scans and re-sorts the
97+
terminal-instance population for each batch, so it is not yet suited to
98+
production-scale history export. Efficient large exports depend on a
99+
host-side completed-time paging API that the host extension does not yet
100+
provide.
101+
- **`DurableOrchestrationContext.call_http(...)`** makes durable HTTP calls from
102+
orchestrators, restoring the v1 API. The request is executed by a built-in
103+
activity and, when the endpoint responds with `202 Accepted` and a `Location`
104+
header, is automatically polled to completion (honoring `Retry-After`).
105+
`ManagedIdentityTokenSource` can be supplied to attach a Managed Identity
106+
bearer token to the request. `DurableHttpRequest` and `DurableHttpResponse`
107+
are exported from `azure.durable_functions`.
108+
- **`orchestration_trigger(..., input_type=...)`** decodes a v1-style
109+
`context.get_input()` to the declared type; a call-site `expected_type` on
110+
`get_input` takes precedence.
111+
112+
### Compatibility with v1
113+
114+
To ease migration, 2.x ships a compatibility layer over the durabletask
115+
surface:
116+
117+
- **v1-style single-argument functions** (`def orchestrator(context)`,
118+
`def entity(context)`) are supported. The worker detects the function shape
119+
and, for single-argument functions, delivers a functional
120+
`DurableOrchestrationContext` / `DurableEntityContext` that wraps the
121+
durabletask context and exposes the v1 API — for orchestrations:
122+
`get_input`, `call_activity`/`call_activity_with_retry`,
123+
`call_sub_orchestrator`/`call_sub_orchestrator_with_retry`, `create_timer`,
124+
`wait_for_external_event`, `continue_as_new`, `set_custom_status`,
125+
`task_all`/`task_any`, `call_entity`/`signal_entity`, `new_uuid`/`new_guid`,
126+
`custom_status`, `will_continue_as_new`, `parent_instance_id`, and
127+
`function_context`; and for entities: `entity_name`, `entity_key`,
128+
`operation_name`, `get_input`, `get_state` (with `initializer`), `set_state`,
129+
`set_result`, and `destruct_on_exit`. The operation result is taken from
130+
`set_result(...)`, falling back to the function's return value.
131+
- **v1 return-type wrappers** `DurableOrchestrationStatus`,
132+
`PurgeHistoryResult`, and `EntityStateResponse` are returned by the deprecated
133+
client methods and exported from `azure.durable_functions`.
134+
- **`HttpManagementPayload`** subclasses `dict`, so it is directly
135+
JSON-serializable via `json.dumps(payload)` and supports mapping-style access,
136+
matching v1 usage.
137+
- **`create_http_management_payload`** accepts either the durabletask
138+
`(request, instance_id)` or the v1 `(instance_id)` signature.
139+
140+
### Deprecated
141+
142+
These v1 names are retained as shims that delegate to their durabletask
143+
equivalents and emit `DeprecationWarning`; prefer the durabletask names in new
144+
code:
145+
146+
- `DurableOrchestrationClient` (alias for `DurableFunctionsClient`) and its
147+
method names: `start_new`, `get_status`, `get_status_all`, `get_status_by`,
148+
`raise_event`, `terminate`, `purge_instance_history`,
149+
`purge_instance_history_by`, `suspend`, `resume`, `restart`,
150+
`read_entity_state`, `get_client_response_links`, and
151+
`wait_for_completion_or_create_check_status_response`.
152+
- `rewind(...)` — delegates to `rewind_orchestration(...)`.
153+
- `signal_entity(..., operation_input=...)``operation_input` is an alias for
154+
`input`; `task_hub_name` / `connection_name` are accepted and ignored.
155+
- `RetryOptions` — maps the v1 millisecond-based constructor onto durabletask
156+
`RetryPolicy` (which uses `timedelta`). `RetryPolicy` is also exported from
157+
`azure.durable_functions`.
158+
- Compatibility aliases exported from `azure.durable_functions`:
159+
`DurableOrchestrationContext`, `DurableEntityContext`, `EntityId`,
160+
`ManagedIdentityTokenSource`, `TokenSource`, `Entity`, and
161+
`OrchestrationRuntimeStatus`.
162+
163+
### Known limitations
164+
165+
- Orchestration history is not exposed on the context;
166+
`DurableOrchestrationContext.histories` raises `NotImplementedError`. Use the
167+
client's `get_orchestration_history(...)` instead.
168+
- The client status methods accept the v1 `show_history` /
169+
`show_history_output` flags for signature compatibility but ignore them, so
170+
the returned status has no `historyEvents`. Use
171+
`get_orchestration_history(...)` to retrieve history.
172+
- Distributed tracing is not yet wired up. The Durable Functions host delivers
173+
the parent trace context and emits the orchestration/activity spans itself,
174+
so orchestrator user-code spans in the Python worker are not yet correlated
175+
to it, and durabletask's own span emission is intentionally left disabled to
176+
avoid duplicating the host's spans.

0 commit comments

Comments
 (0)