Injection#
The decorator, the parameter markers, and the sentinel default.
inject#
- @nodrill.inject#
- @nodrill.inject(*, from_=None)
Fill missing parameters from the current context at call time.
- Parameters:
from_ – Opt into by-name mode. A string name or a class, where every parameter whose name matches an attribute of
use(from_)is filled from it.- Raises:
TypeError –
from_is neither a string nor a class, or the decorated object is not callable, or it is a class, a generator or an async-generator function, or a marker annotates*argsor**kwargs.
Two styles, usable together on one function.
Marker style annotates individual parameters with
FromCtxorfrom_ctx()and defaults them toinjected.By-name style,
@inject(from_="app"), fills every eligible parameter from the attributes of one context object.selfandclsare skipped, as are parameters that already carry a marker. In this mode a matching attribute overrides the parameter’s default. A required parameter that neither the caller nor the context supplied raisesTypeErrornaming the parameter and the key.An argument passed by the caller is never overridden, an explicit
Noneincluded.Applies to plain functions,
async defcoroutines, instance methods, classmethods and staticmethods, in either decorator order. A function with no injectable parameters is returned unwrapped.The injection plan is built once, at decoration time, from
inspect.signature()andtyping.get_type_hints(), and compiles into a wrapper that mirrors the function’s own signature. The interpreter binds every call shape natively, whichever way an injected parameter is passed, and a call that misuses the signature fails before any resolution runs, in the interpreter’s own wording. Only the arity range in those messages differs, counting injectable parameters as optional. If hints cannot be resolved at decoration, because a string annotation names something defined later, the plan is deferred to the first call and cached. A name that never resolves raisesNameErrorat call time, naming the function, unless no annotation on it asks for injection at all. A function nodrill has nothing to do for is called through untouched rather than broken over hints only a checker reads. Annotations that do carry a marker have to name things that exist at runtime, so the import cannot sit behindTYPE_CHECKING.@inject def report(cfg: FromCtx[Config] = injected) -> str: return cfg.url @inject(from_="app") def render(user_id: int, theme: str = "light") -> str: ...
FromCtx#
- nodrill.FromCtx#
The parameter marker, in subscript form.
FromCtx[SomeClass]marks a parameter asuse(SomeClass). At runtime it evaluates toAnnotated[SomeClass, FromCtx()], while to a type checker it isSomeClass, so the body and any explicit argument check against the real type.A union around the marker,
FromCtx[SomeClass] | None, still injects, since the union only widens what an explicit argument may be. The same holds for theOptionalthattyping.get_type_hints()adds by itself on Python 3.10 when a marked parameter defaults toNone.The call form,
FromCtx("app"), builds the same marker asfrom_ctx()but is rejected by pyright, which statically sees anAnnotatedalias and refuses to call it. Usefrom_ctx()in pyright-checked code.
from_ctx#
- nodrill.from_ctx(key=None, attr=None)#
Build a parameter marker for use inside
typing.Annotated.- Parameters:
key – A string name, a class, or a
ref()naming one.Nonemeans the annotated type is the key, which requires that type to be a plain class.attr – The attribute to read off the context object. Defaults to the parameter’s own name for string keys, and for a ref that turns out to name one, which is settled at the first call rather than at decoration.
- Raises:
TypeError –
keyis neither a string, a class, a ref, norNone, or a bare marker annotates something that is not a plain class.
Three shapes.
Annotated[Config, from_ctx()] # use(Config) Annotated[Engine, from_ctx("app")] # use("app").<param name> Annotated[Engine, from_ctx("app", attr="db")] # use("app").db
FromCtx[Config]is shorthand for the first. Clean under both mypy and pyright, which is why it is the spelling used throughout these docs.
injected#
- nodrill.injected: Any#
The default for an injectable parameter.
Typed
Any, so a signature likedef f(db: FromCtx[Db] = injected)stays satisfiable for type checkers when callers omit the argument.inject()treats a parameter still bound to it as one to resolve, the same as an omitted one.If it ever reaches a function body it fails loudly. Attribute access, truthiness, calls, and the protocols a stray value tends to land in next, iteration,
len, indexing,in, the orderings, arithmetic,with,awaitandasync for, all raise with a message pointing at a missing@injector a missing provider.==is left at the default, so containers and identity checks behave normally.
Rejected shapes#
Generator functions, sync and async, raise TypeError at decoration time.
A generator body runs after the call, at the first next(), possibly under different providers, so anything resolved at call time would be silently stale.
Call use() inside the body instead, and iterate while the provider block is open.
Variadic parameters cannot be injected.
A marker on *args or **kwargs raises TypeError, and in by-name mode variadics are skipped.
Classes raise TypeError too.
A class is callable, so it would decorate cleanly and then be replaced by a function, breaking isinstance() and subclassing.
Decorate __init__ instead.