Skip to content

Add pyright strict type-check CI workflow#146

Open
berndverst wants to merge 2 commits into
mainfrom
berndverst/pyright-typecheck-ci
Open

Add pyright strict type-check CI workflow#146
berndverst wants to merge 2 commits into
mainfrom
berndverst/pyright-typecheck-ci

Conversation

@berndverst
Copy link
Copy Markdown
Member

Summary

Adds a new CI workflow that runs pyright in strict mode on the lowest
supported Python version (3.10) across both durabletask and
durabletask-azuremanaged packages, and cleans up every strict-mode type
error 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(...) from
typing, and rule-specific # pyright: ignore[...] comments. No
runtime behavior is intentionally changed.

What changed

New CI workflow + config

  • .github/workflows/typecheck.yml — runs on pushes to main and PRs to
    main (matches the existing durabletask.yml / durabletask-azuremanaged.yml
    triggers). Installs both packages with [azure-blob-payloads,opentelemetry]
    extras + the azuremanaged provider, then runs pyright.
  • pyrightconfig.json at repo root — typeCheckingMode: strict,
    pythonVersion: 3.10. Excludes generated *_pb2.py, *_pb2.pyi,
    *_pb2_grpc.py, *_pb2_grpc.pyi, and local .venv* folders.
  • pyright added to dev-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:

File Errors fixed
durabletask/worker.py 610
durabletask/history.py 261
durabletask/client.py 203
durabletask/testing/in_memory_backend.py 179
durabletask/internal/grpc_interceptor.py 82
durabletask/task.py 45
durabletask/extensions/azure_blob_payloads/blob_payload_store.py 44
17 other files (small fixes) ~174

Related typing issues

  • create_timer should return specific TimerTask #93create_timer should return specific TimerTask.
    OrchestrationContext.create_timer(...) now returns TimerTask (was
    CancellableTask). The concrete _RuntimeOrchestrationContext override
    in worker.py and the in-memory backend override in testing/in_memory_backend.py
    match the new signature.

  • when_any should specify type for input tasks #94when_any should specify type for input tasks.
    WhenAnyTask is now generic:

    class WhenAnyTask(CompositeTask[Task[T]], Generic[T]): ...
    
    def when_any(tasks: Sequence[Task[T]]) -> WhenAnyTask[T]: ...

    Calling when_any([t1, t2]) now infers WhenAnyTask[T] and
    .get_result() returns Task[T] instead of Task[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._tasks is
    list[Task[Any]], _parent: CompositeTask[Any] | None,
    RetryableTask is generic, wait_for_external_event(name) -> CancellableTask[Any],
    etc.

Notable design notes

  • Optional opentelemetry imports (durabletask/internal/tracing.py)
    reworked the lazy-import block to use a TYPE_CHECKING declaration as the
    source of truth, paired with a runtime try/except ImportError that
    rebinds the names. The except branch injects fallback stubs via
    globals() to avoid type-incompatible reassignment errors.
  • ReplaySafeLoggerlogging.LoggerAdapter is generic in stubs but
    not subscriptable at runtime before Python 3.11. Used the standard
    TYPE_CHECKING alias trick to satisfy both pyright and runtime.
  • Private-usage policy — followed the project guidance: prefer renaming
    internal members to non-underscore names, otherwise use targeted
    # pyright: ignore[reportPrivateUsage]. For the
    durabletask-azuremanaged interceptor, eliminated dependence on the
    private _ClientCallDetails namedtuples and updated overrides to use
    the public grpc.ClientCallDetails / grpc.aio.ClientCallDetails
    (also fixes a Liskov-substitution violation).

Verification

  • pyright0 errors, 0 warnings, 0 informations (validated with
    the exact install commands the new workflow runs).
  • flake8 — clean across durabletask/, durabletask-azuremanaged/,
    examples/, tests/durabletask/, tests/durabletask-azuremanaged/.
  • pymarkdownlnt — clean for both CHANGELOG.md files.
  • ✅ Unit tests — 362 passed, 11 skipped (pytest tests/durabletask/ -m "not dts").
  • ✅ Runtime imports — every modified module imports successfully.

Note

Some *_e2e.py tests fail locally on Windows with Failed to bind to address [::]:50060. This reproduces on main without these changes
(verified via a temporary checkout), so it is pre-existing and
unrelated to this PR.

Changelogs

Stats

27 files changed, 827 insertions(+), 459 deletions(-)

Closes #92
Closes #93
Closes #94

- 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>
Copilot AI review requested due to automatic review settings May 26, 2026 21:26
Comment thread durabletask/worker.py Fixed
Comment thread durabletask/worker.py Fixed
Comment thread durabletask/worker.py Fixed
Comment thread durabletask/client.py

class _SyncTaskHubSidecarServiceStub(Protocol):
def StartInstance(self, request: pb.CreateInstanceRequest) -> pb.CreateInstanceResponse:
...
Comment thread durabletask/client.py
...

def GetInstance(self, request: pb.GetInstanceRequest) -> pb.GetInstanceResponse:
...
Comment thread durabletask/client.py
...

def StreamInstanceHistory(self, request: pb.StreamInstanceHistoryRequest) -> Iterable[pb.HistoryChunk]:
...
Comment thread durabletask/client.py
...

def ListInstanceIds(self, request: pb.ListInstanceIdsRequest) -> pb.ListInstanceIdsResponse:
...
Comment thread durabletask/client.py
...

def QueryInstances(self, request: pb.QueryInstancesRequest) -> pb.QueryInstancesResponse:
...
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() -> TimerTask and generic when_any() / WhenAnyTask typing).
  • 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>
Comment thread durabletask/client.py
request: pb.GetInstanceRequest,
*,
timeout: float | None = None) -> pb.GetInstanceResponse:
...
Comment thread durabletask/client.py
request: pb.GetInstanceRequest,
*,
timeout: float | None = None) -> pb.GetInstanceResponse:
...
Comment thread durabletask/client.py
...

def RaiseEvent(self, request: pb.RaiseEventRequest) -> pb.RaiseEventResponse:
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

when_any should specify type for input tasks create_timer should return specific TimerTask Add generic type safety hints

2 participants