Add pyright strict type-check CI workflow#146
Conversation
- 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>
There was a problem hiding this comment.
Pull request overview
This PR introduces strict static type-checking to the Durable Task Python SDK by adding a dedicated pyright (strict mode) CI workflow and updating the codebase across both durabletask and durabletask-azuremanaged to reach a clean strict-type baseline (also addressing typing issues #92, #93, #94).
Changes:
- Added pyright strict configuration (
pyrightconfig.json) and a new GitHub Actions workflow (.github/workflows/typecheck.yml) that runs on Python 3.10. - Improved public API type precision (notably
OrchestrationContext.create_timer() -> TimerTaskand genericwhen_any()/WhenAnyTasktyping). - Performed broad strict-mode typing cleanup across core runtime, testing backend, gRPC utilities, tracing, and Azure Blob payload store.
Reviewed changes
Copilot reviewed 27 out of 27 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
pyrightconfig.json |
Adds repo-wide pyright strict configuration and exclusions for generated files/venvs. |
.github/workflows/typecheck.yml |
New CI job to run pyright strict on Python 3.10 for both packages. |
dev-requirements.txt |
Adds pyright to dev dependencies. |
CHANGELOG.md |
Documents the new typecheck workflow and public typing improvements (#92/#93/#94). |
durabletask-azuremanaged/CHANGELOG.md |
Notes downstream typing improvements for Azure Managed consumers. |
durabletask/task.py |
Updates task/orchestration typing, including create_timer return type and generic when_any. |
durabletask/worker.py |
Large strict-typing pass across worker runtime and orchestration execution internals. |
durabletask/client.py |
Tightens typing around stubs/continuation tokens and adds typed stub Protocols. |
durabletask/history.py |
Improves typing for event conversion utilities and serialization helpers. |
durabletask/testing/in_memory_backend.py |
Adds strict typing across backend state, filters, and service method signatures. |
durabletask/grpc_options.py |
Typing adjustments for channel/retry options dataclasses. |
durabletask/internal/grpc_interceptor.py |
Reworks interceptor call-details typing and metadata typing for strict mode. |
durabletask/internal/grpc_resiliency.py |
Adds strict typing to sync/async resiliency interceptors. |
durabletask/internal/tracing.py |
Refactors optional OpenTelemetry imports to be strict-type-safe while remaining optional at runtime. |
durabletask/extensions/azure_blob_payloads/blob_payload_store.py |
Adds strict typing and explicit client types for sync/async blob clients. |
durabletask/extensions/azure_blob_payloads/__init__.py |
Adjusts optional import typing to satisfy strict checks. |
durabletask/payload/helpers.py |
Adds strict typing for protobuf descriptor helpers. |
durabletask/internal/shared.py |
Adds typing to JSON encode/decode helpers and encoder/decoder overrides. |
durabletask/internal/helpers.py |
Broadens is_empty typing to accept None. |
durabletask/internal/history_helpers.py |
Adds typing for history conversion helpers and private-usage annotations. |
durabletask/internal/orchestration_entity_context.py |
Adds typing for entity-context helpers and generator return types. |
durabletask/internal/entity_state_shim.py |
Adds typing for entity state shim methods. |
durabletask/internal/client_helpers.py |
Adds typing for continuation tokens and related helpers. |
durabletask/entities/entity_context.py |
Adds missing return typing and clarifies encoded payload types. |
durabletask/entities/entity_lock.py |
Adds targeted pyright ignore for intentional private call. |
durabletask/entities/entity_instance_id.py |
Tightens comparison method typing and corrects non-instance comparisons. |
durabletask-azuremanaged/durabletask/azuremanaged/internal/durabletask_grpc_interceptor.py |
Removes dependency on internal call-details types; uses public grpc call-details interfaces. |
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>
Versioning + CHANGELOG considerations for 1.5.0Reviewed this through a "what breaks for customers?" lens. Runtime behavior is preserved for typical users (clients, activity/orchestrator authors), but a handful of changes are breaking for anyone who subclasses our public abstract types or runs strict pyright/mypy against their own code. Since the package ships Type-level breaking changes to surface in CHANGELOGThese don't change runtime, but
Runtime behavior changes worth documentingThese are small but observable:
Suggested CHANGELOG structure for 1.5.0## 1.5.0
### ⚠️ Type-level breaking changes (no runtime impact for typical users)
- `OrchestrationContext.create_timer` now returns `TimerTask` (was `CancellableTask`)
- `WhenAnyTask` is now generic; `when_any` returns `WhenAnyTask[T]`
- `CompositeTask.on_child_completed` now takes `Task[Any]`
- `TaskHubGrpcWorker.add_activity` / `add_entity` now require `Activity[Any, Any]` / `Entity[Any, Any]`
- `DefaultClientInterceptorImpl._intercept_call` now takes `grpc.ClientCallDetails`
(subclassers should retype their override parameter)
- Closes #92, #93, #94
### Changed
- `when_any` now copies its input list; mutations after construction no longer affect the task
- `EntityInstanceId.__lt__` returns `NotImplemented` for non-matching types (was infinite recursion)
- Added pyright strict-mode CI on Python 3.10
### Fixed
- Type-check 0 errors across the repo under pyright strictRecommendation1.5.0 (minor) is appropriate provided the CHANGELOG has the explicit breaking-changes header above (not buried under generic "Changed"). A 2.0 would be the strict-SemVer-plus-py.typed call, but in practice no runtime API surface changes for normal usage, and a major bump would over-signal the impact. |
… 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>
|
Thanks for the thorough versioning/CHANGELOG review — addressed in 1dc27cb.
I left the internal-only items out of the CHANGELOG (the |
Summary
Adds a new CI workflow that runs pyright in strict mode on the lowest
supported Python version (3.10) across both
durabletaskanddurabletask-azuremanagedpackages, and cleans up every strict-mode typeerror so the workflow starts at a green baseline. The cleanup also resolves
three related typing issues — #92, #93, and #94.
Note
All changes are purely additive: type annotations,
cast(...)fromtyping, and rule-specific# pyright: ignore[...]comments. Noruntime behavior is intentionally changed.
What changed
New CI workflow + config
.github/workflows/typecheck.yml— runs on pushes tomainand PRs tomain(matches the existingdurabletask.yml/durabletask-azuremanaged.ymltriggers). Installs both packages with
[azure-blob-payloads,opentelemetry]extras + the azuremanaged provider, then runs
pyright.pyrightconfig.jsonat repo root —typeCheckingMode: strict,pythonVersion: 3.10. Excludes generated*_pb2.py,*_pb2.pyi,*_pb2_grpc.py,*_pb2_grpc.pyi, and local.venv*folders.pyrightadded todev-requirements.txt.Type-error cleanup
Started at 1,598 strict-mode errors and brought the repo to
0 errors, 0 warnings, 0 informations. The biggest categories cleaned:
durabletask/worker.pydurabletask/history.pydurabletask/client.pydurabletask/testing/in_memory_backend.pydurabletask/internal/grpc_interceptor.pydurabletask/task.pydurabletask/extensions/azure_blob_payloads/blob_payload_store.pyRelated typing issues
create_timershould return specificTimerTask#93 —create_timershould return specificTimerTask.OrchestrationContext.create_timer(...)now returnsTimerTask(wasCancellableTask). The concrete_RuntimeOrchestrationContextoverridein
worker.pyand the in-memory backend override intesting/in_memory_backend.pymatch the new signature.
when_anyshould specify type for inputtasks#94 —when_anyshould specify type for inputtasks.WhenAnyTaskis now generic:Calling
when_any([t1, t2])now infersWhenAnyTask[T]and.get_result()returnsTask[T]instead ofTask[Unknown].Add generic type safety hints #92 — Generic type-safety hints (umbrella). Broad fan-out from the
workflow cleanup: registries use
Orchestrator[Any, Any]/Activity[Any, Any]/Entity[Any, Any]instead of bare aliases,CompositeTask._tasksislist[Task[Any]],_parent: CompositeTask[Any] | None,RetryableTaskis generic,wait_for_external_event(name) -> CancellableTask[Any],etc.
Notable design notes
durabletask/internal/tracing.py) —reworked the lazy-import block to use a
TYPE_CHECKINGdeclaration as thesource of truth, paired with a runtime
try/except ImportErrorthatrebinds the names. The
exceptbranch injects fallback stubs viaglobals()to avoid type-incompatible reassignment errors.ReplaySafeLogger—logging.LoggerAdapteris generic in stubs butnot subscriptable at runtime before Python 3.11. Used the standard
TYPE_CHECKINGalias trick to satisfy both pyright and runtime.internal members to non-underscore names, otherwise use targeted
# pyright: ignore[reportPrivateUsage]. For thedurabletask-azuremanagedinterceptor, eliminated dependence on theprivate
_ClientCallDetailsnamedtuples and updated overrides to usethe public
grpc.ClientCallDetails/grpc.aio.ClientCallDetails(also fixes a Liskov-substitution violation).
Verification
pyright— 0 errors, 0 warnings, 0 informations (validated withthe exact install commands the new workflow runs).
flake8— clean acrossdurabletask/,durabletask-azuremanaged/,examples/,tests/durabletask/,tests/durabletask-azuremanaged/.pymarkdownlnt— clean for bothCHANGELOG.mdfiles.pytest tests/durabletask/ -m "not dts").Note
Some
*_e2e.pytests fail locally on Windows withFailed to bind to address [::]:50060. This reproduces onmainwithout these changes(verified via a temporary checkout), so it is pre-existing and
unrelated to this PR.
Changelogs
CHANGELOG.md— new workflow + type-coverage improvements with explicitcallouts to Add generic type safety hints #92,
create_timershould return specificTimerTask#93,when_anyshould specify type for inputtasks#94.durabletask-azuremanaged/CHANGELOG.md— derived-benefit note (theTimerTaskand genericwhen_anychanges flow through toDurableTaskSchedulerWorkerorchestrations).Stats
27 files changed, 827 insertions(+), 459 deletions(-)Closes #92
Closes #93
Closes #94