Support Annotated-style dependency injection#354
Conversation
Dependencies can now be attached as `Annotated` type-hint metadata instead
of only as default parameter values:
async def process(customer_id: Annotated[int, ConcurrencyLimit(1)]): ...
The parameter keeps its real value; the dependency runs as a side-effect.
This is especially nice for ConcurrencyLimit where the old default-param
style required a separate dummy parameter just to carry the dependency.
Changes:
- `resolved_dependencies()` now calls `get_annotation_dependencies()` from
uncalled-for and enters each annotation dep via `bind_to_parameter()`
- `ConcurrencyLimit` gains `bind_to_parameter()` to auto-infer the
argument name from the annotated parameter
- `ConcurrencyLimit(1)` shorthand: passing an int as the first positional
arg sets `max_concurrent` (convenient for the Annotated style)
- `get_single_dependency_parameter_of_type()` now searches annotations too
- Bumps uncalled-for to >=0.2.0 for the annotation extraction API
Includes contract tests for the uncalled-for behaviors we depend on, and
integration tests covering concurrency limits, Depends side-effects, mixed
styles, type aliases, and validation.
Closes #334, closes #163.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8af600a9d1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| bound = dep.bind_to_parameter(param_name, value) | ||
| try: | ||
| await stack.enter_async_context(bound) |
There was a problem hiding this comment.
Preserve annotated Runtime/Completion dependencies
This loop enters annotated dependencies but drops the object returned by __aenter__, so those dependencies never appear in the dependencies map yielded by resolved_dependencies. In worker._execute, runtime and completion behavior is discovered only via get_single_dependency_of_type(dependencies, Runtime) / CompletionHandler, so Annotated[..., Timeout(...)] or Annotated[..., Perpetual(...)] will silently not enforce timeouts or run completion hooks even though they are declared on the task.
Useful? React with 👍 / 👎.
|
|
||
| annotation_deps = get_annotation_dependencies(execution.function) | ||
| for param_name, deps in annotation_deps.items(): | ||
| value = execution.kwargs.get(param_name, arguments.get(param_name)) |
There was a problem hiding this comment.
Bind annotated dependencies using bound arg values
The value passed to bind_to_parameter is taken only from execution.kwargs (or previously resolved dependency args), so annotated dependencies on positional parameters receive None instead of the real argument value when tasks are scheduled positionally (e.g. add(task)(42)). This breaks any dependency that relies on bind_to_parameter(name, value) for per-value behavior; it should use bound argument resolution (e.g. execution.get_argument) rather than raw kwargs lookup.
Useful? React with 👍 / 👎.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #354 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 99 102 +3
Lines 2969 3046 +77
Branches 26 26
=========================================
+ Hits 2969 3046 +77
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
- Add @overload signatures to ConcurrencyLimit.__init__ to clarify the three calling conventions (int shorthand, str argument name, keyword-only) - Rename abbreviated variables (deps, param_name, dep) to full names - Rename test helper to my_side_effect for clarity - Move concurrency-specific tests into tests/concurrency_limits/test_annotated.py alongside the existing concurrency test suite - Remove section header comments from tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Dependencies can now be attached as
Annotatedtype-hint metadata insteadof only as default parameter values:
The parameter keeps its real value; the dependency runs as a side-effect.
This is especially nice for ConcurrencyLimit where the old default-param
style required a separate dummy parameter just to carry the dependency.
Changes:
resolved_dependencies()now callsget_annotation_dependencies()fromuncalled-for and enters each annotation dep via
bind_to_parameter()ConcurrencyLimitgainsbind_to_parameter()to auto-infer theargument name from the annotated parameter
ConcurrencyLimit(1)shorthand: passing an int as the first positionalarg sets
max_concurrent(convenient for the Annotated style)get_single_dependency_parameter_of_type()now searches annotations tooIncludes contract tests for the uncalled-for behaviors we depend on, and
integration tests covering concurrency limits, Depends side-effects, mixed
styles, type aliases, and validation.
Closes #334, closes #163.
🤖 Generated with Claude Code