Skip to content

Ship type information and enforce strict typing - #15

Draft
hansoutset wants to merge 5 commits into
mainfrom
typing
Draft

hansoutset wants to merge 5 commits into
mainfrom
typing

Conversation

@hansoutset

Copy link
Copy Markdown

Makes dramatiq-workflow a typed library: it now ships py.typed, the package and tests pass mypy --strict, and CI checks it with mypy, pyright, pyrefly and ty across the Python × dramatiq matrix.

Changes

Packaging

  • Add a py.typed marker, included via package_data, and the Typing :: Typed classifier. Without the marker, type checkers ignored this package's annotations for anyone who installed it.

Library typing

  • Annotate the remaining untyped functions and give bare generics their type arguments (dict[str, Any], Message[Any], CallbackStorage[Any]). Public aliases stay compatible for existing users.
  • Fix the DedupWorkflowCallbackStorage generics: WorkflowRefT is now passed through to the CallbackStorage base, and retrieve() no longer reuses one variable for two types.
  • unserialize_workflow / unserialize_workflow_or_none take object instead of Any. The input is decoded broker data that is validated at runtime.
  • WorkflowType keeps Message[Any] rather than Message[object], because Message's type parameter is invariant before dramatiq 1.18. There is a comment in the code explaining this.

Small runtime changes

  • Workflow now stamps message_timestamp=int(time.time() * 1000) instead of a float, matching dramatiq's Message type and its own default.
  • Remove AtMostOnceBarrier's dead *args from __init__ and wait. dramatiq's Barrier accepts no extra positional arguments, so passing any already raised TypeError.

Tests

  • Tests are type-checked under mypy --strict. Test message ids are now strings, matching dramatiq's Message type.
  • New tests/test_typing.py builds workflows from real @dramatiq.actor messages. The other tests use MagicMock actors, so type checkers never saw real Message[R] types. I confirmed this test fails under dramatiq 1.17.1 if WorkflowType is changed to Message[object].

Tooling / CI

  • make typecheck (mypy + pyright) is a blocking CI step.
  • make pyrefly and make ty run as pinned, non-blocking steps (continue-on-error) while those checkers are pre-1.0 or changing quickly.
  • The checkers are pinned in requirements.txt.

Verification (local)

With CI-equivalent installs on Python 3.10 and 3.14 × dramatiq 1.17.1 and 2.2.1:

  • mypy --strict, pyright, pyrefly and ty report 0 errors.
  • All 39 tests pass.
  • The built wheel contains py.typed. A consumer script type-checks clean under mypy --strict against the installed package and reports errors for invalid workflow arguments.

The full CI matrix has not run yet; this PR is its first run.

Notes

  • This predates this PR: CI's dramatiq 1.x run tests 1.17.1, not the latest 1.x. requirements.txt pins dramatiq==1.17.1, and the later pip install "dramatiq~=1.0" is already satisfied by that pin.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Hf4k4mZyJvX9wYNJt9MCRZ

hansoutset and others added 5 commits September 24, 2026 20:53
- Add py.typed marker (packaged via package_data) and Typing :: Typed classifier
- Annotate remaining untyped functions; parametrize bare dict/Message/CallbackStorage generics
- Fix DedupWorkflowCallbackStorage typing: pass WorkflowRefT through to the base class and
  stop reusing one variable for two types in retrieve()
- Configure mypy (strict for the package, default checks for tests) and run it in CI
  across the Python x dramatiq matrix

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hf4k4mZyJvX9wYNJt9MCRZ
Drop the relaxed-flags override for tests so the whole package is held to
strict. Test message ids are now strings, matching dramatiq's Message type.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hf4k4mZyJvX9wYNJt9MCRZ
- Add `-> None` to Chain/Group/WithDelay.__init__ (flagged by pyrefly)
- Workflow now stamps int(time.time() * 1000), matching dramatiq's Message
  type and its own default, instead of a float
- Tighten test annotations for pyrefly; add a ty ignore next to the mypy one
- CI: pyright joins mypy as a blocking check; pyrefly and ty run pinned but
  non-blocking while they're pre-1.0 / fast-moving

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hf4k4mZyJvX9wYNJt9MCRZ
- unserialize_workflow(_or_none) take `object`: the input is decoded broker
  data validated at runtime, so callers must go through the existing checks
- Drop AtMostOnceBarrier's dead *args: dramatiq's Barrier.__init__/wait accept
  no extra positionals, so any value passed already raised TypeError
- Document why WorkflowType keeps Message[Any] (invariant before dramatiq 1.18)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hf4k4mZyJvX9wYNJt9MCRZ
The other tests build messages from MagicMock actors, so type checkers never
saw real Message[R] types flowing into Chain/Group/WithDelay. This caught a
Message[object] regression that only fails on dramatiq < 1.18.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hf4k4mZyJvX9wYNJt9MCRZ
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.

1 participant