Skip to content

Commit f5ee0c8

Browse files
author
Bernd Verst
committed
Merge remote-tracking branch 'origin/main' into berndverst-entity-method-signature-cache
2 parents 03339f2 + 58f99b2 commit f5ee0c8

9 files changed

Lines changed: 461 additions & 59 deletions

File tree

.github/copilot-instructions.md

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@
22

33
## Project Overview
44

5-
This is the Durable Task Python SDK, providing a client and worker for
6-
building durable orchestrations. The repo contains two packages:
5+
This repository provides the Durable Task Python SDK and Azure Functions
6+
provider implementations for building durable orchestrations. It contains
7+
three packages:
78

89
- `durabletask` — core SDK (in `durabletask/`)
910
- `durabletask.azuremanaged` — Azure Durable Task Scheduler provider (in `durabletask-azuremanaged/`)
11+
- `azure-functions-durable` — Azure Durable Functions provider (in
12+
`azure-functions-durable/`)
1013

1114
## Changelog Requirements
1215

13-
- ALWAYS document user-facing changes in the appropriate changelog under
14-
`## Unreleased`.
15-
- Update `CHANGELOG.md` for core SDK changes and
16-
`durabletask-azuremanaged/CHANGELOG.md` for provider changes.
17-
- If a change affects both packages, update both changelogs.
16+
- ALWAYS document user-facing changes in the applicable changelog under
17+
`## Unreleased`. Create that section if the changelog does not yet have one.
18+
- Update `CHANGELOG.md` for core SDK changes,
19+
`durabletask-azuremanaged/CHANGELOG.md` for Durable Task Scheduler provider
20+
changes, and `azure-functions-durable/CHANGELOG.md` for Azure Functions
21+
provider changes.
22+
- If a change affects multiple packages, update each affected package's
23+
changelog.
1824
- Include changelog entries for externally observable outcomes only, such as
1925
new public APIs, behavior changes, bug fixes users can notice, breaking
2026
changes, and new configuration capabilities.
@@ -24,8 +30,14 @@ building durable orchestrations. The repo contains two packages:
2430
- When in doubt, write the changelog entry in terms of user impact (what users
2531
can now do or what behavior changed), not implementation mechanism (how it
2632
was implemented internally).
33+
- Changelogs are not covered by the CI Markdown lint step. Review changes to
34+
them manually.
35+
- Use the current unindented changelog style: category labels such as `ADDED`,
36+
`CHANGED`, and `FIXED` are plain, unindented lines, and wrapped entry text
37+
remains unindented rather than being aligned beneath the bullet.
2738

2839
Examples:
40+
2941
- Include: "Added `get_orchestration_history()` to retrieve orchestration history from the client."
3042
- Exclude: "Added internal helper functions to aggregate streamed history chunks."
3143

@@ -61,10 +73,16 @@ priority over style.
6173
## Python Linting
6274

6375
This repository uses [flake8](https://flake8.pycqa.org/) for Python
64-
linting. Run it after making changes to verify there are no issues:
76+
linting. Run it after making changes to verify there are no issues. Lint
77+
package source and its tests separately, matching CI:
6578

6679
```bash
67-
python -m flake8 path/to/changed/file.py
80+
python -m flake8 durabletask
81+
python -m flake8 tests/durabletask
82+
python -m flake8 durabletask-azuremanaged
83+
python -m flake8 tests/durabletask-azuremanaged
84+
python -m flake8 azure-functions-durable
85+
python -m flake8 tests/azure-functions-durable
6886
```
6987

7088
## Markdown Style
@@ -117,18 +135,42 @@ python -m pip install -r dev-requirements.txt
117135

118136
## Building and Testing
119137

120-
Install the packages locally in editable mode:
138+
Use the repository-root `.venv` for core and Azure Managed development, and
139+
for Azure Functions Durable linting, type checking, and unit tests. The Azure
140+
Functions Durable package requires Python 3.13+, so use a 3.13+ root virtual
141+
environment for that work. Install packages locally in editable mode:
121142

122143
```bash
123-
python -m pip install -e . -e ./durabletask-azuremanaged
144+
python -m pip install -e . -e ./durabletask-azuremanaged \
145+
-e ./azure-functions-durable
124146
```
125147

126-
Run tests with pytest:
148+
Run the applicable unit tests with pytest. Azure Functions Durable unit tests
149+
exclude tests that require an Azure Functions host or external services:
127150

128151
```bash
129152
python -m pytest
153+
python -m pytest tests/azure-functions-durable \
154+
-m "not dts and not azurite and not functions_e2e"
155+
```
156+
157+
Run Azure Functions Durable E2E tests through Nox, not directly from the root
158+
virtual environment. Nox creates an isolated Python 3.13 session environment,
159+
installs the local packages editable, and links it into each sample Function
160+
app so the Functions worker loads the app's grpc/protobuf dependencies. The
161+
suite requires Azure Functions Core Tools (`func`) on `PATH` and a running
162+
Azurite instance with blob storage on port 10000:
163+
164+
```bash
165+
nox -s functions_e2e
130166
```
131167

168+
After the first successful run, use `nox -R -s functions_e2e` for E2E reruns.
169+
`-R` reuses the Nox environment and skips reinstalls; because the packages are
170+
editable, source changes are still picked up. Pass pytest selectors after `--`,
171+
for example `nox -R -s functions_e2e -- -k "dtask_client"`. Do not manually
172+
activate or modify the per-app `.venv` directories created by Nox.
173+
132174
## Project Structure
133175

134176
- `durabletask/` — core SDK source
@@ -140,10 +182,23 @@ python -m pytest
140182
- `testing/` — in-memory backend for testing without a sidecar
141183
- `internal/` — protobuf definitions, gRPC helpers, tracing (not public API)
142184
- `durabletask-azuremanaged/` — Azure managed provider source
185+
- `azure-functions-durable/` — Azure Durable Functions provider source
143186
- `examples/` — example orchestrations (see `examples/README.md`)
144187
- `tests/` — test suite
145188
- `dev-requirements.txt` — development dependencies
146189

190+
## External Dependencies
191+
192+
The Azure Functions Durable provider integrates with APIs and runtime behavior
193+
owned by these repositories. Consult their current source when changing
194+
decorators, bindings, converters, or worker integration behavior:
195+
196+
- [Azure Functions Python library](https://github.com/Azure/azure-functions-python-library)
197+
— application, decorator, and binding APIs.
198+
- [Azure Functions Python worker](https://github.com/Azure/azure-functions-python-worker)
199+
— function loading, binding conversion, dependency isolation, and invocation
200+
behavior.
201+
147202
## Cross-Package Compatibility
148203

149204
The `durabletask-azuremanaged` package extends the core `durabletask`

.github/skills/release-prep/SKILL.md

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
11
---
22
name: release-prep
33
description: >-
4-
Prepare a release for durabletask and durabletask.azuremanaged. Use when the
5-
user asks for release prep, version bumping, changelog updates, or release
6-
body drafting. Trigger phrases include: release prep, prepare vX.Y.Z,
7-
changelog for release, and draft GitHub release notes.
4+
Prepare a release for durabletask, durabletask.azuremanaged, or
5+
azure-functions-durable. Use when the user asks for release prep, version
6+
bumping, changelog updates, or release body drafting. Trigger phrases include:
7+
release prep, prepare vX.Y.Z, changelog for release, and draft GitHub release
8+
notes.
89
---
910

1011
# Release Prep
1112

12-
This skill prepares a coordinated release for both packages in this repository:
13+
This skill prepares a coordinated release for all packages in this repository:
1314

1415
- `durabletask`
1516
- `durabletask.azuremanaged`
17+
- `azure-functions-durable`
1618

1719
The skill accepts a target version (for example `1.4.0`) and performs the
18-
required changes consistently.
20+
required changes consistently. A single-package release is an exception: only
21+
release one package when the user explicitly requests it, and record that
22+
exception in the release-preparation instructions for that release.
1923

2024
## Inputs
2125

2226
- `version`: Target semantic version (for example `1.4.0`)
27+
- Optional: `packages` limits the release to named packages only when the user
28+
explicitly requests a single-package release.
2329
- Optional: `baseTag` overrides for comparison if tags are non-standard
2430

2531
If `version` is not provided, ask the user before continuing.
@@ -30,31 +36,43 @@ If `version` is not provided, ask the user before continuing.
3036

3137
- Root package range: `v<previousVersion>..HEAD`
3238
- Azure managed package range: `azuremanaged-v<previousVersion>..HEAD`
39+
- Azure Functions package range: `azurefunctions-v<previousVersion>..HEAD`
3340
- Use commit subjects and touched files to classify each change as:
3441
- `durabletask` only
3542
- `durabletask.azuremanaged` only
43+
- `azure-functions-durable` only
3644
- shared/infra/docs changes
3745

3846
### 2. Update package versions
3947

40-
Update both project versions:
48+
Update all project versions:
4149

4250
- `pyproject.toml` -> `version = "<version>"`
4351
- `durabletask-azuremanaged/pyproject.toml` -> `version = "<version>"`
52+
- `azure-functions-durable/pyproject.toml` -> `version = "<version>"`
4453

45-
Update azuremanaged dependency floors:
54+
Update Azure Managed dependency floors:
4655

4756
- `durabletask>=<version>`
4857
- `durabletask[azure-blob-payloads]>=<version>`
4958

59+
Update the `azure-functions-durable` `durabletask` dependency floor when the
60+
coordinated release establishes a new compatible core SDK minimum. For an
61+
explicit single-package Azure Functions release, do not change that floor
62+
solely because the provider package version changes.
63+
5064
### 3. Update changelogs
5165

52-
- Add a new `## v<version>` section directly under `## Unreleased` in:
53-
- `CHANGELOG.md`
54-
- `durabletask-azuremanaged/CHANGELOG.md`
66+
- Add a new `## v<version>` section directly under `## Unreleased` in every
67+
package's changelog. Create `## Unreleased` if it is absent:
68+
- `CHANGELOG.md` for `durabletask`
69+
- `durabletask-azuremanaged/CHANGELOG.md` for `durabletask.azuremanaged`
70+
- `azure-functions-durable/CHANGELOG.md` for `azure-functions-durable`
5571
- Ensure user-facing changes since the previous release tags are represented.
5672
- Keep entries concise and grouped by type (`ADDED`, `CHANGED`, `FIXED`, `REMOVED`) where
5773
applicable.
74+
- Follow the repository's unindented changelog style. Changelogs are not
75+
covered by CI Markdown linting, so review their formatting manually.
5876
- Exclude internal-only changes from changelogs (for example CI/workflow-only
5977
updates, test-only changes, and implementation refactors with no public
6078
behavior or API impact).
@@ -71,23 +89,26 @@ Before creating draft releases in GitHub UI, require explicit user
7189
confirmation of both conditions:
7290

7391
- The version-bump/release-prep PR is merged
74-
- Tags `v<version>` and `azuremanaged-v<version>` already exist in the target
75-
repository
92+
- Tags for every package already exist in the target repository:
93+
`v<version>` for `durabletask`, `azuremanaged-v<version>` for
94+
`durabletask.azuremanaged`, and `azurefunctions-v<version>` for
95+
`azure-functions-durable`
7696

7797
If either condition is not met, stop after preparing release body text and ask
7898
the user to confirm once merge and tags are complete.
7999

80100
### 6. Draft GitHub release bodies
81101

82-
Draft two release body texts for the GitHub Releases UI (do not add files to
102+
Draft three release body texts for the GitHub Releases UI (do not add files to
83103
the repository):
84104

85-
- Tag: `v<version>`
86-
- Tag: `azuremanaged-v<version>`
105+
- `durabletask`: `v<version>`
106+
- `durabletask.azuremanaged`: `azuremanaged-v<version>`
107+
- `azure-functions-durable`: `azurefunctions-v<version>`
87108

88109
Match existing release structure:
89110

90-
- Title (`# v<version>` or `# azuremanaged-v<version>`)
111+
- Title matching the package tag
91112
- `## What's Changed`
92113
- `## External Links`
93114
- `### Contributors`

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,23 @@ MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/
66
Validation](https://github.com/microsoft/durabletask-python/actions/workflows/pr-validation.yml/badge.svg)](https://github.com/microsoft/durabletask-python/actions/workflows/pr-validation.yml)
77
[![PyPI version](https://badge.fury.io/py/durabletask.svg)](https://badge.fury.io/py/durabletask)
88

9-
This repo contains a Python SDK for use with the [Azure Durable Task
10-
Scheduler](https://github.com/Azure/Durable-Task-Scheduler). With this SDK, you can define,
11-
schedule, and manage durable orchestrations using ordinary Python code.
9+
This repository contains Python SDKs for building durable orchestrations with
10+
[Azure Durable Task Scheduler](https://github.com/Azure/Durable-Task-Scheduler)
11+
and [Azure Durable Functions](https://learn.microsoft.com/azure/azure-functions/durable/):
1212

13-
> Note that this SDK is **not** currently compatible with [Azure Durable
14-
Functions](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview). If
15-
you are looking for a Python SDK for Azure Durable Functions, please see [this
16-
repo](https://github.com/Azure/azure-functions-durable-python).
13+
- [`durabletask`](./durabletask/) is the core orchestration SDK.
14+
- [`durabletask.azuremanaged`](./durabletask-azuremanaged/) is the Azure Durable
15+
Task Scheduler provider.
16+
- [`azure-functions-durable`](./azure-functions-durable/) is a preview Azure
17+
Durable Functions provider built on `durabletask`.
1718

1819
## References
1920

2021
- [Supported Patterns](./docs/supported-patterns.md)
2122
- [Available Features](./docs/features.md)
2223
- [Getting Started](./docs/getting-started.md)
2324
- [Development Guide](./docs/development.md)
25+
- [Azure Functions Durable 2.x](./azure-functions-durable/README.md)
2426
- [Contributing Guide](./CONTRIBUTING.md)
2527

2628
## Optional Features

azure-functions-durable/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ calls (`context.call_http(...)`), recurring scheduled tasks, and history export.
3838
- [Changelog](CHANGELOG.md)
3939
- [Durable Functions documentation](https://learn.microsoft.com/azure/azure-functions/durable/)
4040
- [`durabletask` on PyPI](https://pypi.org/project/durabletask/)
41+
- [Azure Functions Durable 1.x source](https://github.com/Azure/azure-functions-durable-python)
42+
- [Azure Functions Python library](https://github.com/Azure/azure-functions-python-library)
43+
- [Azure Functions Python worker](https://github.com/Azure/azure-functions-python-worker)
4144
- [Repository](https://github.com/microsoft/durabletask-python)
4245

4346
## License

durabletask-azuremanaged/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1414
`DefaultAzureCredential`) now surface from the first request rather than from the constructor.
1515
The exception type and message are unchanged; only the timing differs.
1616
- `FailureDetails.error_type` now carries the fully-qualified type name (e.g. `durabletask.task.TaskFailedError`) instead of the bare class name, and the new `FailureDetails.is_caused_by()` helper is available (both inherited from durabletask). See the core `durabletask` changelog for details, including the breaking-change notes.
17+
- Improved async access token refresh concurrency handling to avoid duplicate
18+
refresh operations under concurrent access, matching the existing sync
19+
behavior.
1720

1821
## v1.8.0
1922

durabletask-azuremanaged/durabletask/azuremanaged/internal/access_token_manager.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT License.
3+
import asyncio
34
from datetime import datetime, timedelta, timezone
45
from threading import Lock
56

@@ -71,9 +72,37 @@ def __init__(self, token_credential: AsyncTokenCredential | None,
7172
self._token = None
7273
self.expiry_time = None
7374

75+
# An asyncio.Lock binds itself to the event loop it is first used on, and this
76+
# manager may outlive a loop or be shared across loops. Locks are therefore
77+
# created lazily per running loop, guarded by a plain threading lock because
78+
# different loops may run on different threads.
79+
self._refresh_locks: dict[asyncio.AbstractEventLoop, asyncio.Lock] = {}
80+
self._refresh_locks_guard = Lock()
81+
82+
def _get_refresh_lock(self) -> asyncio.Lock:
83+
loop = asyncio.get_running_loop()
84+
with self._refresh_locks_guard:
85+
lock = self._refresh_locks.get(loop)
86+
if lock is None:
87+
# Discard locks belonging to loops that are no longer usable so the
88+
# mapping does not grow without bound.
89+
stale_loops = [
90+
existing for existing in self._refresh_locks if existing.is_closed()
91+
]
92+
for stale_loop in stale_loops:
93+
del self._refresh_locks[stale_loop]
94+
lock = asyncio.Lock()
95+
self._refresh_locks[loop] = lock
96+
return lock
97+
7498
async def get_access_token(self) -> AccessToken | None:
7599
if self._token is None or self.is_token_expired():
76-
await self.refresh_token()
100+
async with self._get_refresh_lock():
101+
# Re-check under the lock: a concurrent caller may have already
102+
# refreshed the token while this one was waiting, so only a single
103+
# credential request is made per refresh window.
104+
if self._token is None or self.is_token_expired():
105+
await self.refresh_token()
77106
return self._token
78107

79108
def is_token_expired(self) -> bool:

0 commit comments

Comments
 (0)