Autowiring¶
Added in version 26.1.0.
Autowiring is an optional technique that automatically resolves dependencies based on type annotations, eliminating the need to manually specify each dependency when invoking a function or instantiating a class. This reduces boilerplate and makes your code more declarative.
The autowiring functions inject dependencies from a svcs.Container directly into function or class parameters based on their type annotations.
This is particularly useful when you want to build factories or handlers that consume multiple services without explicitly passing them through each layer:
>>> from dataclasses import dataclass
>>> from typing import NewType
>>> import svcs
>>> @dataclass
... class Database: ...
>>> @dataclass
... class Cache: ...
>>> registry = svcs.Registry()
>>> registry.register_factory(Database, Database)
>>> registry.register_factory(Cache, Cache)
>>> @dataclass
... class AppServices:
... db: Database
... cache: Cache
>>> # BEFORE: wire the dependencies by hand.
>>> BeforeAppServices = NewType("BeforeAppServices", AppServices)
>>> registry.register_factory(
... BeforeAppServices,
... lambda svcs_container: AppServices(
... db=svcs_container.get(Database),
... cache=svcs_container.get(Cache),
... ),
... )
>>> # AFTER: the type annotations are enough.
>>> AfterAppServices = NewType("AfterAppServices", AppServices)
>>> registry.register_factory(AfterAppServices, svcs.autowire(AppServices))
>>> with svcs.Container(registry) as container:
... before = container.get(BeforeAppServices)
... after = container.get(AfterAppServices)
>>> # The resulting services look the same...
>>> before
AppServices(db=Database(), cache=Cache())
>>> after
AppServices(db=Database(), cache=Cache())
>>> before == after
True
>>> # but they are different instances...
>>> before is after
False
>>> # ... although their dependencies are shared.
>>> before.db is after.db
True
>>> before.cache is after.cache
True
Autowiring handles regular, positional-only, and keyword-only parameters, and ignores variadic ones (*args and **kwargs).
If a parameter cannot be resolved because the service has not been registered,
the default value is injected instead.
A parameter annotated as svcs.Container receives the current container itself, so a factory can look up further services dynamically.
Autowiring only looks at type annotations, therefore injecting the container by naming an argument svcs_container does not work.
Factories that return context managers are entered and cleaned up as usual.
Bare generator factories, however, are rejected with a TypeError, because their cleanup would be lost.
Decorate them with contextmanager() or asynccontextmanager() instead.
Note
For asynchronous resolution, svcs.aautowire() exists
and uses svcs.Container.aget() instead of svcs.Container.get().
It supports both synchronous and asynchronous callables; therefore in async contexts you can always use svcs.aautowire().
Pros:
Less Boilerplate: Dependencies are resolved automatically from type annotations.
Clear Intent: Signatures still document required services.
Sensible Defaults: Missing optional services fall back to parameter defaults.
Cons:
More Implicit: Dependency lookup is less explicit than direct
svcs.Container.get()calls.Extra Indirection: Wrapping and introspection add small runtime and debugging overhead.
Caveats¶
Annotations are looked up verbatim: a parameter annotated with, for example,
Annotated[Database, "primary"]or aNewTypeis resolved only if the service is registered under exactly that type – not under the plainDatabase.Annotations are resolved when the returned factory is called for the first time. Forward references that are resolvable by then work fine, but types that only exist under
typing.TYPE_CHECKINGcan never be resolved and fail with asvcs.exceptions.ServiceNotFoundError.Some objects (for example, HTTP client responses) are both awaitable and a context manager.
svcs.aautowire()does not await such objects: they are treated as context managers and left to the container, which by default enters them and takes care of their cleanup. If you’ve registered the factory with enter set toFalse, you get the object as-is and have to enter – or await – it yourself.
Autowiring a class¶
Best used as a wrapper during registration:
from svcs import Container, Registry, autowire
class Service:
def __init__(self, name: str) -> None:
self.name = name
class Handler:
def __init__(self, svc: Service, prefix: str = "svc:") -> None:
self.svc = svc
self.prefix = prefix
registry = Registry()
registry.register_value(Service, Service("api"))
registry.register_factory(Handler, autowire(Handler))
with Container(registry) as container:
handler = container.get(Handler)
print(handler.svc.name, handler.prefix) # Output: "api svc:"
Autowiring a function¶
Best used as a decorator on top of your factories:
from svcs import Container, Registry, autowire
class Service:
def __init__(self, name: str) -> None:
self.name = name
@autowire
def build_label(svc: Service, suffix: int = 42) -> str:
return f"{svc.name}{suffix}"
registry = Registry()
registry.register_value(Service, Service("api"))
registry.register_factory(str, build_label)
with Container(registry) as container:
result = container.get(str)
print(result) # Output: "api42"
API reference¶
- svcs.autowire(fn_or_cls)[source]¶
Return a factory that resolves the dependencies of fn_or_cls from a container, based on its type annotations.
The returned factory takes a
svcs.Container, resolves every annotated parameter of fn_or_cls from it, and calls fn_or_cls with the resolved services.If a service is not found in the container and the parameter has a default value, the default is used instead of raising an error.
Variadic parameters (
*argsand**kwargs) are ignored anddataclasses.InitVarannotations are unwrapped.A parameter annotated as
svcs.Containerreceives the current container itself.Factories that return context managers are entered and cleaned up as usual. Bare generator factories are rejected, because their cleanup would be lost. Decorate them with
contextmanager()instead.- Parameters:
fn_or_cls (Callable[[...], _T]) – A callable (function or class) whose parameters will be autowired based on their type annotations.
- Returns:
A factory that takes a container and returns the result of calling fn_or_cls with the resolved dependencies.
- Raises:
- Return type:
Warning
Do not decorate classes at definition time! Decorating a class using
@autowirenotation replaces the class with the autowire factory, so its name suddenly refers to a function, not a type.Wrap the class only at register time.
Added in version 26.1.0.
- svcs.aautowire(fn_or_cls: Callable[[...], Awaitable[_T]]) Callable[[Container], Awaitable[_T]][source]¶
- svcs.aautowire(fn_or_cls: Callable[[...], _T]) Callable[[Container], Awaitable[_T]]
Like
autowire(), but dependencies are resolved withsvcs.Container.aget(), the returned factory is async, and the result of fn_or_cls is awaited if it’s awaitable.It also works with synchronous callables and services, so in an async application, just use this.
Added in version 26.1.0.