Accessing Dependencies#
Dependencies can be either accessed trough a catalog or inject
. Both ways provide proper
type hints to allow mypy, pyright and PyCharm to properly detect the type of the dependency values
Catalog#
world
is an instance of ReadOnlyCatalog
from which dependencies can be
retrieved with a properly typed dict-like API:
from antidote import injectable, inject
@injectable
class Service:
pass
>>> Service in world
True
>>> world[Service]
<Service object ...>
>>> world.get(Service)
<Service object ...>
And for an unknown dependency:
>>> unknown = object()
>>> unknown in world
False
>>> try:
... world[unknown]
... except KeyError:
... pass
>>> world[unknown]
Traceback (most recent call last):
File "<stdin>", line 1, in ?
DependencyNotFoundError: ...
>>> world.get(unknown) is None
True
>>> world.get(unknown, default='x')
'x'
Injection#
inject
exposes a very similar API to world
, except it won’t retrieve the
dependency immediately, it’s only done at function call. Any (async-)function and (static/class-)methods
can be injected. The wrapped function will then behave like a transparent proxy, as much as possible.
Binding dependencies to arguments#
inject
supports a variety of ways to bind dependencies to arguments, in order:
from antidote import injectable, inject
@injectable
class Service:
pass
args
andkwargs
@inject(args=[Service]) def f1(service) -> Service: return service @inject(kwargs=dict(service=Service)) def f2(service) -> Service: return service
Default arguments and PEP-593
Annotated
type hints.Inject.me()
provides a convenient way to use the argument type hint, but it can be explicitly specified:from antidote import InjectMe @inject def f3(service = inject[Service]) -> Service: return service @inject # ⯆ will inject None if Service is not a dependency def f4(service = inject.get(Service)) -> Service: return service @inject def f5(service: Service = inject.me()) -> Service: return service @inject def f6(service: InjectMe[Service]) -> Service: return service
fallback
behaves likekwargs
except it is last to be consulted@inject(fallback=dict(service=Service)) def f7(service) -> Service: return service
Note
It is recommended to use the default values as it helps static type checker such as pyright and Mypy to see the “real” signature of the function.
Binding ‘self’ for methods#
While inject
can be applied on methods, it’s not convenient to inject self
for
methods of an injectable()
class. So that’s exactly the purpose of Inject.method()
:
from antidote import injectable, inject, world
@injectable
class Dummy:
@inject.method
def method(self) -> 'Dummy':
return self
>>> Dummy.method()
<Dummy object ...>
>>> Dummy.method() is world[Dummy]
True
As shown the wrapped method now behaves like a class method and be called directly on the class. But it can also be called on a instance, in which case the injection will be overridden:
>>> dummy = Dummy()
>>> dummy.method() is dummy
True
Injecting multiple methods / wiring a class#
To apply inject
on all methods, or a subset, wire()
can be used. It won’t
change any existing injection, so custom injection for a method can be easily applied:
from antidote import wire, injectable, inject
@injectable
class Service:
pass
@wire
class Dummy:
def method(self, service: Service = inject.me()) -> Service:
return service
@inject(kwargs=dict(service=Service))
def custom(self, service) -> Service:
return service
>>> dummy = Dummy()
>>> dummy.method()
<Service object ...>
>>> dummy.custom()
<Service object ...>