Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
7ff1525
Storing changes commit
andystaples Nov 6, 2025
552a2dd
Working orchestrators + activities
andystaples Nov 21, 2025
af0e3c2
Nitpicks and cleanup
andystaples Nov 21, 2025
86ee081
Merge branch 'main' into andystaples/add-functions-support
andystaples Nov 21, 2025
3497148
Save-all nits
andystaples Nov 21, 2025
57de878
Add entity support (needs extension change)
andystaples Nov 24, 2025
18145f8
Refine entity support
andystaples Dec 3, 2025
9965ba4
Finish entity support
andystaples Dec 3, 2025
209443e
Fixes and improvements
andystaples Dec 4, 2025
cc005ae
Bump durabletask version, fix metadata
andystaples Dec 4, 2025
bf6d6f2
Use Protocol for stubs
andystaples Dec 5, 2025
0a03bd1
Merge branch 'main' into andystaples/add-functions-support
andystaples Dec 5, 2025
1176d03
Update to new workflow pattern
andystaples Dec 5, 2025
7bf763a
Rename stub file
andystaples Dec 5, 2025
811653e
Fix import
andystaples Dec 5, 2025
068cb43
Merge branch 'main' into andystaples/add-functions-support
andystaples Dec 5, 2025
827d201
Experimental dependency revision
andystaples Dec 5, 2025
fde02c5
Update to match changes in functions SDK
andystaples Dec 11, 2025
5b9644d
Merge branch 'main' into andystaples/add-functions-support
andystaples Dec 12, 2025
2df96dc
Merge issue fix
andystaples Dec 12, 2025
eac9efd
Various
andystaples Jan 6, 2026
20aacab
Add Functions to requirements
andystaples Jan 6, 2026
5df87b1
Rename to azure-functions-durable v2
andystaples Jan 30, 2026
ea5c2b0
Re-add Orchestrator object/model
andystaples Feb 9, 2026
2d5fd7a
Merge remote-tracking branch 'origin/main' into andystaples/add-funct…
andystaples Jun 23, 2026
0505a0d
Modernize pipelines for functions package
andystaples Jun 23, 2026
c9ea2fb
Cleanup pyright errors
andystaples Jun 23, 2026
5b906bd
Remove non-existent extension call
andystaples Jun 23, 2026
643e6ea
Merge remote-tracking branch 'origin/main' into andystaples/add-funct…
andystaples Jun 30, 2026
df9932d
Serialization compat, fix typing, working again
andystaples Jul 1, 2026
a7bdd01
Use the async client (match old behavior)
andystaples Jul 2, 2026
1891d94
Compat layer V1
andystaples Jul 2, 2026
d4a84bc
Reorganize compat files
andystaples Jul 2, 2026
a1b54bd
Add shims for old orchestration and entity call arg patterns
andystaples Jul 2, 2026
3c05264
Add remaining orchestration surface
andystaples Jul 2, 2026
46570e9
Add missing type coersion, rich client, tests
andystaples Jul 2, 2026
af18afe
Fix gaps found via integration tests
andystaples Jul 2, 2026
013f674
Fix old-name interceptor lookup
andystaples Jul 2, 2026
60b6195
More compat layer stuff
andystaples Jul 2, 2026
16f3cf1
Merge remote-tracking branch 'origin/main' into andystaples/add-funct…
andystaples Jul 8, 2026
2c7d5b2
Return an emtpy function context instead of raising
andystaples Jul 9, 2026
69cbc45
Asyncio in test matrix
andystaples Jul 9, 2026
95e0b9e
CI Tweaks
andystaples Jul 9, 2026
b69eeeb
Add Durable HTTP feature
andystaples Jul 10, 2026
905a400
CHANGELOG for now
andystaples Jul 10, 2026
8de1900
Add unit testing coverage
andystaples Jul 10, 2026
7d48ad3
Add E2E harness
andystaples Jul 11, 2026
4a0ed32
Expand E2E coverage
andystaples Jul 11, 2026
ff203b7
More E2E test edge cases, fixes, add scheduled tasks
andystaples Jul 11, 2026
25f21df
Add history export to afd
andystaples Jul 11, 2026
914a083
Merge remote-tracking branch 'origin/main' into andystaples/add-funct…
andystaples Jul 11, 2026
f0fdf05
Add afd rewind
andystaples Jul 11, 2026
483c4aa
lint fix
andystaples Jul 11, 2026
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
5 changes: 4 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ ignore = E501,C901,W503
exclude =
.git
*_pb2*
__pycache__
__pycache__
.venv
.nox
.python_packages
119 changes: 119 additions & 0 deletions .github/workflows/durabletask-azurefunctions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
name: Durable Task Scheduler SDK (azure-functions-durable)

on:
push:
branches:
- "main"
tags:
- "azurefunctions-v*" # Only run for tags starting with "azurefunctions-v"
pull_request:
branches:
- "main"

permissions:
contents: read

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.14
uses: actions/setup-python@v5
with:
python-version: 3.14
- name: Install dependencies
working-directory: azure-functions-durable
run: |
python -m pip install --upgrade pip
pip install setuptools wheel tox
pip install flake8
- name: Run flake8 Linter
working-directory: azure-functions-durable
run: flake8 .
- name: Run flake8 Linter
working-directory: tests/azure-functions-durable
run: flake8 .

run-tests:
strategy:
fail-fast: false
matrix:
python-version: ["3.13", "3.14"]
needs: lint
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install durabletask dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
pip install -r requirements.txt

- name: Install durabletask locally
run: |
pip install . --no-deps --force-reinstall

- name: Install azure-functions-durable locally
working-directory: azure-functions-durable
run: |
pip install . --no-deps --force-reinstall

- name: Run unit tests
working-directory: tests/azure-functions-durable
run: |
pytest -m "not dts and not azurite and not functions_e2e" --verbose

e2e-tests:
needs: run-tests
runs-on: ubuntu-latest
# The end-to-end suite launches a real Azure Functions host, which requires
# ``azure-functions>=2.2.0b6``. That build is not yet published to PyPI, so
# this job is skipped by default. Enable it by setting the repository
# variable ``RUN_FUNCTIONS_E2E`` to ``true`` (Settings > Secrets and
# variables > Actions > Variables) once 2.2.0b6 is available on PyPI, or
# provide the library build via ``AZURE_FUNCTIONS_PYTHON_LIBRARY``.
if: ${{ vars.RUN_FUNCTIONS_E2E == 'true' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Set up Node.js (needed for Azurite and Functions Core Tools)
uses: actions/setup-node@v4
with:
node-version: "20.x"

- name: Install Azurite and Azure Functions Core Tools
run: |
npm install -g azurite
npm install -g azure-functions-core-tools@4 --unsafe-perm true

- name: Start Azurite
shell: bash
run: |
azurite --silent --location /tmp/azurite --blobPort 10000 --queuePort 10001 --tablePort 10002 &
sleep 2

- name: Install nox
run: |
python -m pip install --upgrade pip
pip install nox

- name: Run end-to-end tests
# nox provisions each sample app's in-app virtual environment (a link to
# a single editable install) so the Functions worker loads our
# grpc/protobuf rather than its bundled copies.
run: nox -s functions_e2e

23 changes: 23 additions & 0 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
tags:
- "v*"
- "azuremanaged-v*"
- "azurefunctions-v*"
pull_request:
branches:
- "main"
Expand Down Expand Up @@ -46,3 +47,25 @@ jobs:

- name: Run pyright (strict, Python 3.10)
run: pyright

pyright-azurefunctions:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python 3.13 (lowest supported by azure-functions-durable)
uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Install packages and dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -e ".[azure-blob-payloads,opentelemetry]"
pip install -e ./azure-functions-durable
pip install pyright

- name: Run pyright (strict, Python 3.13)
run: pyright -p azure-functions-durable/pyrightconfig.json
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ ENV/
env.bak/
venv.bak/

# Azure Functions E2E test host logs (written by the test harness)
_func_host.log

# Azure Functions E2E history-export output (written by the sample app writer)
_export_output/

# Spyder project settings
.spyderproject
.spyproject
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@
"coverage.cobertura.xml"
],
"makefile.configureOnOpen": false,
"debugpy.debugJustMyCode": false
"debugpy.debugJustMyCode": false,
"python-envs.defaultEnvManager": "ms-python.python:system"
}
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,41 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

ADDED

- Added a `result` property to `Task` as a convenience alias for `get_result()`.
- Added `TaskHubGrpcClient.rewind_orchestration()` to rewind a failed orchestration instance to its last known good state. Failed activity and sub-orchestration results are removed from the history and the orchestration replays from the last successful checkpoint, retrying only the failed work. The in-memory testing backend supports rewind as well.
- Exported `bind_context` and `clear_context` from
`durabletask.extensions.history_export` so hosts that register the export
functions themselves (rather than via `ExportHistoryClient.register_worker`)
can supply the activities' runtime dependencies.

FIXED

- Fixed orchestrations failing with `OrchestrationStateError: Don't know how to
handle event of type 'genericEvent'` after being rewound over the Azure
Functions Durable extension. The `genericEvent` history event (an
informational marker with no execution semantics) is now ignored during
replay, matching the .NET worker, so `rewind_orchestration` completes the
replay instead of re-failing.
- Fixed durabletask scheduled tasks (`durabletask.scheduled`) failing under
data converters that reconstruct nested custom-object envelopes bottom-up
(such as the Azure Functions Durable `df` codec). `ScheduleState.from_json`
now tolerates an already-reconstructed nested `ScheduleConfiguration` (and
accepts the active `DataConverter` for nested reconstruction), and
`ScheduleOperationRequest` gained `to_json`/`from_json` hooks so it can be
serialized by converters that require them. The default JSON converter's
behavior is unchanged.
- Fixed `OrchestrationContext.lock_entities` failing when used over the legacy
entity protocol (used by the Azure Functions Durable extension). Acquiring an
entity lock raised a `JSONDecodeError` because the worker tried to deserialize
an operation result from the lock-granted event, which carries none; the
result is now only read for entity operation calls, not lock acquisitions.
- Fixed `OrchestrationContext.version` returning an empty string (`''`) instead
of `None` for orchestrations started without an explicit version. The version
field is a protobuf wrapper (a singular message that is always truthy), so it
is now checked for presence with `HasField` before being read.
- `OrchestrationContext.create_timer` now accepts timezone-aware `datetime`
values, normalizing them to UTC instead of raising when compared against the
orchestration's internal clock.

## v1.7.2

Expand Down
159 changes: 159 additions & 0 deletions azure-functions-durable/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

### Added

- `DurableFunctionsClient.rewind_orchestration(...)` (inherited from durabletask)
rewinds a failed orchestration to its last known good state. The deprecated v1
`rewind(...)` method now delegates to it instead of raising
`NotImplementedError`.

- `DFApp.configure_scheduled_tasks()` opts an app in to durabletask scheduled
tasks by registering the schedule entity and operation orchestrator. Once
enabled, schedules are managed from a client via
`durabletask.scheduled.ScheduledTaskClient`. Scheduled tasks are not
registered unless this method is called.
- `DFApp.configure_history_export()` opts an app in to durabletask history
export by registering the export-job entity, driving orchestrator, and
activities. Once enabled, export jobs are driven from a client via
`durabletask.extensions.history_export.ExportHistoryClient`; supply the
activities' runtime dependencies with `history_export.bind_context(...)`.
The instance-enumeration activity uses a Functions-specific implementation
based on `QueryInstances` because the Durable Functions host extension does
not implement the `ListInstanceIds` gRPC call the core activity relies on.
- `DurableOrchestrationContext.call_http(...)` for making durable HTTP calls
from orchestrators, restoring the v1 API. The request is executed by a
built-in activity and, when the endpoint responds with `202 Accepted` and a
`Location` header, is automatically polled to completion (honoring
`Retry-After`). `ManagedIdentityTokenSource` can be supplied to attach a
Managed Identity bearer token to the request. `DurableHttpRequest` and
`DurableHttpResponse` are exported from `azure.durable_functions`.

- The `orchestration_trigger` decorator now accepts an `input_type` argument
(v1 parity). When set, a v1-style `context.get_input()` decodes the input to
that type; a call-site `expected_type` on `get_input` takes precedence.
- One-argument (Azure Functions / v1-style) entity functions
(``def entity(context):``) are now supported. The worker detects the entity's
shape and, for single-argument functions, delivers a functional
`DurableEntityContext` that wraps the durabletask `EntityContext` and exposes
the v1 entity API: `entity_name`, `entity_key`, `operation_name`,
`get_input()`, `get_state()` (with `initializer`), `set_state()`,
`set_result()`, and `destruct_on_exit()`. The operation result is taken from
`set_result(...)`, falling back to the function's return value.
durabletask-native two-argument entity functions and class-based
(`DurableEntity`) entities continue to work unchanged.
- One-argument (Azure Functions / v1-style) orchestrator functions
(``def orchestrator(context):``) are now supported. The worker detects the
orchestrator's arity and, for single-argument functions, delivers a
functional `DurableOrchestrationContext` that wraps the durabletask
`OrchestrationContext` and exposes the v1 context API: `get_input()`,
`call_activity`/`call_activity_with_retry`,
`call_sub_orchestrator`/`call_sub_orchestrator_with_retry`, `create_timer`,
`wait_for_external_event`, `continue_as_new`, `set_custom_status`,
`task_all`/`task_any`, `call_entity`/`signal_entity`, and `new_uuid`/`new_guid`.
Two-argument (durabletask-native) orchestrators continue to work unchanged.
`DurableOrchestrationContext.call_http` raises `NotImplementedError` pending a
durabletask durable-HTTP implementation.
- `DurableOrchestrationContext` also exposes `custom_status` (reflecting the
value set via `set_custom_status`) and `will_continue_as_new` (True once
`continue_as_new` has been called). `parent_instance_id`, `function_context`,
and `histories` raise `NotImplementedError` because durabletask does not
surface that information on the orchestration context.

- Backwards-compatible, deprecated aliases on `DurableFunctionsClient` for the
v1 `DurableOrchestrationClient` method names: `start_new`, `get_status`,
`get_status_all`, `get_status_by`, `raise_event`, `terminate`,
`purge_instance_history`, `purge_instance_history_by`, `suspend`, `resume`,
`restart`, `read_entity_state`, `get_client_response_links`, and
`wait_for_completion_or_create_check_status_response`. Each delegates to the
corresponding durabletask method and emits a `DeprecationWarning`; new code
should use the durabletask names (e.g. `schedule_new_orchestration`,
`get_orchestration_state`).
- `DurableFunctionsClient.signal_entity` now also accepts the v1
`operation_input` keyword (alias for `input`); `task_hub_name` and
`connection_name` are accepted for compatibility and ignored.
- `DurableFunctionsClient.rewind` is present as a deprecated stub that raises
`NotImplementedError`, pending a durabletask rewind implementation.
- Deprecated v1 compatibility aliases are now exported from
`azure.durable_functions`: `DurableOrchestrationClient` (alias for
`DurableFunctionsClient`), `DurableOrchestrationContext`, `DurableEntityContext`,
`EntityId`, `ManagedIdentityTokenSource`, `TokenSource`, `Entity`, and
`OrchestrationRuntimeStatus`.
- v1-compatible return-type wrappers `DurableOrchestrationStatus`,
`PurgeHistoryResult`, and `EntityStateResponse` (exported from
`azure.durable_functions`). The deprecated client methods now return these:
`get_status`/`get_status_all`/`get_status_by` return
`DurableOrchestrationStatus` (wrapping durabletask `OrchestrationState`, with
v1 attributes like `runtime_status`, `output`, `input_`, `custom_status`, and
a falsy value for missing instances); `purge_instance_history`/`_by` return
`PurgeHistoryResult` (with `instances_deleted`); and `read_entity_state`
returns `EntityStateResponse` (with `entity_exists`/`entity_state`).
- `DurableOrchestrationContext.call_http` is present as a stub that raises
`NotImplementedError`, documenting the durable-HTTP gap. `TokenSource` /
`ManagedIdentityTokenSource` remain constructible but only apply to
`call_http`, which is not yet supported.
- `RetryOptions`, a deprecated shim that maps the v1 millisecond-based
constructor onto durabletask `RetryPolicy` (which uses `timedelta`).
`RetryPolicy` is now also exported from `azure.durable_functions`.

### Changed

- `DurableFunctionsClient` is now an async client. Its orchestration and entity
management methods (e.g. `schedule_new_orchestration`, `get_orchestration_state`,
`wait_for_orchestration_completion`) are now coroutines and must be awaited.
This aligns the client with the async API surface of the V1
`DurableOrchestrationClient`.
- `create_http_management_payload` now accepts either the durabletask
`(request, instance_id)` signature or the v1 `(instance_id)` signature for
backwards compatibility.
- `HttpManagementPayload` now subclasses `dict`, so it is directly
JSON-serializable via `json.dumps(payload)` and supports mapping-style access
(`payload["statusQueryGetUri"]`, iteration, `in`, `keys()`/`items()`/`values()`)
so v1 code that treated the payload as a `dict` keeps working.

### Fixed

- Registering a `Blueprint` into a `DFApp` (via `register_functions` /
`register_blueprint`) no longer raises a duplicate-function-name error for the
reserved built-in durable-HTTP functions. Both the app and every blueprint
auto-register those built-ins, so the app now de-duplicates them during
registration (leaving the blueprint itself unmodified). This restores the
standard Azure Functions blueprint authoring pattern.
- `durable_client_input` now injects a rich `DurableFunctionsClient` into the
decorated function's client parameter (the binding's JSON string is converted
to a client object). Previously the client parameter received the raw string.
- `DurableFunctionsClient` now applies the host-provided
`maxGrpcMessageSizeInBytes` to the gRPC channel's send/receive message limits
(when provided), allowing large orchestration payloads to be retrieved. When
the host does not supply a value, the gRPC library defaults are left in place.
- `DurableOrchestrationContext.current_utc_datetime` is now timezone-aware
(UTC), matching v1, so comparisons against timezone-aware datetimes (e.g. a
parsed scheduled-start time) no longer raise.
- `DurableOrchestrationStatus.to_json()` now emits orchestration payloads
(`output`, `input`, `customStatus`) as their raw JSON representation instead
of reconstructed Python objects, so the result is always JSON-serializable
even when payloads are custom types.
- Restored v1 members that were missing on the compatibility types, avoiding
`AttributeError`/`TypeError` for existing code that used them:
- `create_http_management_payload(...)` now returns a `dict`-based
`HttpManagementPayload`, so `json.dumps(payload)` works directly again.
- `RetryOptions.to_json()` returns the v1
`firstRetryIntervalInMilliseconds`/`maxNumberOfAttempts` dictionary, and the
`first_retry_interval_in_milliseconds` / `max_number_of_attempts` getters
remain available.
- `DurableOrchestrationStatus.from_json(...)` reconstructs a status from its
`to_json()` representation (or the equivalent v1 JSON schema).
- `PurgeHistoryResult.from_json(...)` reconstructs a result from its v1 JSON
representation.
- `DurableOrchestrationContext.version` returns the orchestration instance
version (or `None`).

## v0.1.0

- Initial implementation
Loading
Loading