Skip to content

Commit f17ee6f

Browse files
berndverstBernd VerstCopilot
authored
Add pyright strict type-check CI workflow (#146)
* Add pyright strict type-check CI workflow - New .github/workflows/typecheck.yml runs pyright in strict mode on Python 3.10 (lowest supported) across durabletask and durabletask-azuremanaged for PRs and pushes to main. - Add pyrightconfig.json at repo root (strict, Python 3.10, excludes generated protobuf/gRPC files). - Add pyright to dev-requirements.txt. - Clean up 1598 strict-mode type errors across the SDK while preserving runtime behavior. Changes are purely additive type annotations, casts, and targeted `# pyright: ignore` comments scoped to specific rules. - Address related typing issues: - #93: OrchestrationContext.create_timer now returns TimerTask (was CancellableTask). - #94: WhenAnyTask is now generic; when_any(tasks: Sequence[Task[T]]) returns WhenAnyTask[T], so the completing child Task[T] is statically typed. - #92: Broad improvements to generic type-safety hints. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Modernize typing imports to use collections.abc (PEP 585) Move Callable, Sequence, Generator, Iterable, Iterator, and AsyncIterable imports from typing to collections.abc per PEP 585. These typing aliases have been deprecated for runtime use since Python 3.9 in favor of the collections.abc originals, and the project floor is Python 3.10. No behavior change. Affected files: - durabletask/client.py - durabletask/task.py - durabletask/worker.py - durabletask/testing/in_memory_backend.py - durabletask/internal/client_helpers.py - durabletask/internal/grpc_interceptor.py - durabletask/internal/grpc_resiliency.py - durabletask/internal/history_helpers.py - durabletask/internal/orchestration_entity_context.py - durabletask/internal/proto_task_hub_sidecar_service_stub.py Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(changelog): fold Unreleased into v1.5.0 with type-level breaking changes Move the Unreleased changelog entries into the v1.5.0 sections of both the core and azuremanaged changelogs ahead of the 1.5.0 release. Surface the type-level breaking changes (no runtime impact for typical users) explicitly rather than burying them, per PR review feedback, and document the when_any copy semantics change and the EntityInstanceId.__lt__ recursion fix. Remove the CI type-check workflow entry since CI changes are not user-facing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Bernd Verst <beverst@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 1cb30b0 commit f17ee6f

28 files changed

Lines changed: 872 additions & 473 deletions

.github/workflows/typecheck.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Type Check (pyright)
2+
3+
on:
4+
push:
5+
branches:
6+
- "main"
7+
tags:
8+
- "v*"
9+
- "azuremanaged-v*"
10+
pull_request:
11+
branches:
12+
- "main"
13+
14+
permissions:
15+
contents: read
16+
17+
jobs:
18+
pyright:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout repository
22+
uses: actions/checkout@v4
23+
24+
- name: Set up Python 3.10 (lowest supported)
25+
uses: actions/setup-python@v5
26+
with:
27+
python-version: "3.10"
28+
29+
- name: Install packages and dependencies
30+
run: |
31+
python -m pip install --upgrade pip
32+
pip install -r requirements.txt
33+
pip install -e ".[azure-blob-payloads,opentelemetry]"
34+
pip install -e ./durabletask-azuremanaged
35+
pip install pyright
36+
37+
- name: Run pyright (strict, Python 3.10)
38+
run: pyright

CHANGELOG.md

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,36 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project
66
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## Unreleased
8+
## v1.5.0
9+
10+
BREAKING CHANGES (type-level only — no runtime impact for typical users)
11+
12+
These changes do not alter runtime behavior for clients or activity/orchestrator
13+
authors, but because the package ships `py.typed`, consumers running strict type
14+
checkers (pyright/mypy) against their own code — or subclassing the public
15+
abstract types — may see new type-check errors and need to update their
16+
overrides:
17+
18+
- `OrchestrationContext.create_timer` now returns the specific `TimerTask` type
19+
(was `CancellableTask`)
20+
([#93](https://github.com/microsoft/durabletask-python/issues/93)).
21+
- `OrchestrationContext.wait_for_external_event` now returns `CancellableTask[Any]`
22+
(was a bare `CancellableTask`).
23+
- `WhenAnyTask` is now generic; `when_any(tasks: Sequence[Task[T]])` returns
24+
`WhenAnyTask[T]` for better static inference of the completing child task
25+
([#94](https://github.com/microsoft/durabletask-python/issues/94)).
26+
`CompositeTask.on_child_completed` now takes `Task[Any]`.
27+
- `TaskHubGrpcWorker.add_activity` / `add_entity` (and the internal registry
28+
methods) now require `Activity[Any, Any]` / `Entity[Any, Any]` instead of the
29+
bare `Activity` / `Entity` aliases.
30+
- `OrchestrationContext.call_entity` / `signal_entity` `input` parameter widened
31+
from `TInput | None` to `Any` (Liskov-safe for callers; subclass overrides
32+
using the old narrower type will be flagged).
33+
- gRPC client interceptors now use the public `grpc.ClientCallDetails` /
34+
`grpc.aio.ClientCallDetails` types instead of private internal namedtuples;
35+
custom interceptor subclasses should retype their override parameters.
36+
- These changes also broadly improve generic type-safety hints throughout the
37+
SDK ([#92](https://github.com/microsoft/durabletask-python/issues/92)).
938

1039
ADDED
1140

@@ -14,14 +43,9 @@ ADDED
1443
existing `AsyncTaskHubGrpcClient` async-context-manager support and the
1544
`TaskHubGrpcWorker` pattern. `DurableTaskSchedulerClient` inherits this
1645
behavior automatically. `__exit__` delegates to `close()`, so the
17-
resiliency-aware teardown introduced in v1.5.0 (in-flight recreate
18-
thread join, retired-channel timer cancellation, and SDK-owned channel
19-
cleanup) runs unchanged through the new `with` path.
20-
21-
## v1.5.0
22-
23-
ADDED
24-
46+
resiliency-aware teardown (in-flight recreate thread join, retired-channel
47+
timer cancellation, and SDK-owned channel cleanup) runs unchanged through the
48+
new `with` path.
2549
- Added `ReplaySafeLogger` and `OrchestrationContext.create_replay_safe_logger()`
2650
for suppressing duplicate log messages during orchestrator replay
2751
- Added `GrpcChannelOptions` and `GrpcRetryPolicyOptions` for configuring
@@ -42,8 +66,17 @@ ADDED
4266
`ListInstanceIds` so local orchestration tests can retrieve history and page
4367
terminal instance IDs by completion window.
4468

69+
CHANGED
70+
71+
- `when_any` now copies its input into a new list (`WhenAnyTask(list(tasks))`).
72+
Previously the task aliased the caller's list, so mutating it after
73+
construction was visible inside the task; that side effect no longer occurs.
74+
4575
FIXED
4676

77+
- Fixed `EntityInstanceId.__lt__` infinite recursion when compared against a
78+
non-`EntityInstanceId` operand. It now returns `NotImplemented`, so mixed-type
79+
comparisons raise `TypeError` cleanly instead of recursing.
4780
- Improved `TaskHubGrpcWorker` recovery from stale or disconnected gRPC streams
4881
so configured hello timeouts apply on fresh connections, received work resets
4982
failure tracking, SDK-owned channels are refreshed and cleaned up safely, and

dev-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
grpcio-tools
22
pymarkdownlnt
3+
pyright

durabletask-azuremanaged/CHANGELOG.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,20 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project
66
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## Unreleased
9-
108
## v1.5.0
119

1210
- Updates base dependency to durabletask v1.5.0
11+
- Improved type coverage benefits Azure Managed users: `create_timer` now
12+
returns the specific `TimerTask` type and `when_any` is generic so the
13+
completing child task is type-checked through `DurableTaskSchedulerClient`,
14+
`AsyncDurableTaskSchedulerClient`, and `DurableTaskSchedulerWorker` derived
15+
orchestrations.
16+
- gRPC client interceptors in the core SDK now use the public
17+
`grpc.ClientCallDetails` / `grpc.aio.ClientCallDetails` types instead of
18+
private internal namedtuples. Any custom DTS auth interceptor built on the
19+
same pattern as `DTSDefaultClientInterceptorImpl` should retype its
20+
`_intercept_call` override parameter accordingly. This is a type-level change
21+
only and does not alter runtime behavior.
1322
- Added optional `interceptors`, `channel`, and `channel_options` parameters to
1423
`DurableTaskSchedulerClient`, `AsyncDurableTaskSchedulerClient`, and
1524
`DurableTaskSchedulerWorker` to allow combining custom gRPC interceptors with

durabletask-azuremanaged/durabletask/azuremanaged/internal/durabletask_grpc_interceptor.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
from durabletask.internal.grpc_interceptor import (
1515
DefaultAsyncClientInterceptorImpl,
1616
DefaultClientInterceptorImpl,
17-
_AsyncClientCallDetails,
18-
_ClientCallDetails,
1917
)
2018

2119

@@ -62,7 +60,7 @@ def _upsert_authorization_header(self, token: str) -> None:
6260
self._metadata.append(("authorization", f"Bearer {token}"))
6361

6462
def _intercept_call(
65-
self, client_call_details: _ClientCallDetails) -> grpc.ClientCallDetails:
63+
self, client_call_details: grpc.ClientCallDetails) -> grpc.ClientCallDetails:
6664
"""Internal intercept_call implementation which adds metadata to grpc metadata in the RPC
6765
call details."""
6866
# Refresh the auth token if a credential was provided. The call to
@@ -114,7 +112,7 @@ def _upsert_authorization_header(self, token: str) -> None:
114112
self._metadata.append(("authorization", f"Bearer {token}"))
115113

116114
async def _intercept_call(
117-
self, client_call_details: _AsyncClientCallDetails) -> grpc.aio.ClientCallDetails:
115+
self, client_call_details: grpc.aio.ClientCallDetails) -> grpc.aio.ClientCallDetails:
118116
"""Internal intercept_call implementation which adds metadata to grpc metadata in the RPC
119117
call details."""
120118
# Refresh the auth token if a credential was provided. The call to

0 commit comments

Comments
 (0)