API Reference
World
world
- get = <antidote._internal.world.WorldGet object>
Used to retrieve a dependency from Antidote. A type hint can be provided and the result will cast to match it. It follows the same philosophy as mypy and will NOT enforce it.
>>> from antidote import world, service >>> @service ... class Dummy: ... pass >>> world.get(Dummy) <Dummy ...> >>> # But Mypy will only an object, not a `Dummy` instance. For this Mypy needs help: >>> world.get[Dummy](Dummy) # Treated by Mypy as a Dummy <Dummy ...> >>> # To avoid repetition, if a type hint is provided but no argument, it ... # will retrieve the type hint itself. ... world.get[Dummy]() <Dummy ...>
- lazy = <antidote._internal.world.WorldLazy object>
Deprecated since version 1.1: If you need lazy behavior, wrap world.get() yourself. This feature barely brings anything.
Used to retrieves lazily a dependency. A type hint can be provided and the retrieved instance will be cast to match it. It follows the same philosophy as mypy and will NOT enforce it.
>>> from antidote import world, Service >>> class Dummy(Service): ... pass >>> dep = world.lazy(Dummy) >>> dep.get() <Dummy ...> >>> # But Mypy will only an object, not a `Dummy` instance. For this Mypy needs help: >>> world.lazy[Dummy](Dummy).get() # Treated by Mypy as a Dummy <Dummy ...> >>> # To avoid repetition, if a type hint is provided but no argument, it ... # will retrieve the type hint itself. ... world.lazy[Dummy]().get() <Dummy ...>
- freeze() None
Freezes Antidote. No additional dependencies or scope can be defined.
Its primary purpose is to state explicitly in your code when all dependencies have been defined and to offer a bit more control on Antidote’s state.
>>> from antidote import world, Service >>> world.freeze() >>> class Dummy(Service): ... pass Traceback (most recent call last): ... FrozenWorldError
- debug(dependency: Hashable, *, depth: int = - 1) str
To help you debug issues you may encounter with your injections, this will provide a tree representing all the dependencies that will be retrieved by Antidote when instantiating the specified dependency. It will also show whether those are singletons or if there are any cyclic dependencies.
>>> from antidote import world, Service, Provide >>> class Dummy(Service): ... pass >>> class Root(Service): ... def __init__(self, dummy: Provide[Dummy]): ... pass >>> print(world.debug(Root)) Root └── Dummy Singletons have no scope markers. <∅> = no scope (new instance each time) <name> = custom scope
Note
The output is not part of the public API. Any variation in it will not be considered as a breaking change. So only rely on it to actively debug issues.
Warning
With
implementation()
, it’ll execute the function to know which dependency to follow.- Parameters
dependency – Root of the dependency tree.
depth – Maximum depth of the result tree. Defaults to -1 which implies not limit.
Returns:
- provider(p: antidote.world._methods.P) antidote.world._methods.P
Class decorator used to register a new
Provider
.>>> from antidote import world >>> from antidote.core import Provider >>> @world.provider ... class MyProvider(Provider): ... '''Implementation missing'''
- Parameters
p – Provider class
- Returns
The same class, unmodified.
world.scopes
- new(*, name: str) antidote.core.container.Scope
Creates a new scope. See
Scope
for more information on scopes.>>> from antidote import world, Service >>> REQUEST_SCOPE = world.scopes.new(name='request') >>> class Dummy(Service): ... __antidote__ = Service.Conf(scope=REQUEST_SCOPE)
- Parameters
name – Friendly identifier used for debugging purposes. It must be unique.
- reset(scope: antidote.core.container.Scope) None
All dependencies values of the specified scope will be discarded, so invalidating the scope. See
Scope
for more information on scopes.>>> from antidote import world >>> REQUEST_SCOPE = world.scopes.new(name='request') >>> # All cached dependencies value with scope=REQUEST_SCOPE will be discarded. ... world.scopes.reset(REQUEST_SCOPE)
- Parameters
scope – Scope to reset.
world.test
Testing utilities with Antidote. Also used by Antidote itself.
- clone(*, keep_singletons: bool = False, keep_scopes: bool = False) Iterator[None]
Clone the current container, namely scopes and providers. Existing singletons and scopes depdency values can also be propagated. Its primary use is to test existing dependencies. Hence the world will already be frozen but overridding dependencies will be possible.
>>> from antidote import world, Service >>> class MyService(Service): ... pass >>> # Declared dependencies are available ... with world.test.clone(): ... world.get[MyService]() <MyService object ...> >>> s = world.get[MyService]() >>> # But by default singletons are NOT kept ... with world.test.clone(): ... world.get[MyService]() is s False >>> # which is of course configurable ... with world.test.clone(keep_singletons=True): ... world.get[MyService]() is s True >>> # You'll keep the same objects though, so be careful ! ... with world.test.clone(keep_singletons=True): ... world.get[MyService]().new_attr = "hello" >>> world.get[MyService]().new_attr 'hello' >>> # You cannot define new dependencies ... with world.test.clone(): ... class NewService(Service): ... pass Traceback (most recent call last): ... FrozenWorldError >>> # but they can be overridden ... with world.test.clone(): ... # dependencies can also be overridden as many times as necessary ... world.test.override.singleton(MyService, "fake service") ... world.get(MyService) 'fake service'
- Parameters
keep_singletons – Whether current singletons should be kept in the new world. Beware that the same objects will be propagated. Defaults to
False
.keep_scopes – Whether current dependency values within the existing scopes should be kept in the new world. Like
keep_singletons
, the same objects will be propagated. Defaults toFalse
.
- Returns
context manager within you can make your tests.
- empty() Iterator[None]
Only used to test providers.
Creates an empty container. No initial providers are set up. This is useful to test one or several providers in isolation. If you’re not testing provider, consider use
new()
instead.
- factory(dependency: Optional[Hashable] = None, *, singleton: Optional[bool] = None, scope: Optional[antidote.core.container.Scope] = Scope(name='__sentinel__')) Callable[[antidote.world.test._methods.F], antidote.world.test._methods.F]
Declare one or multiple singleton dependencies with its associated value.
>>> from antidote import world >>> class Dummy: ... pass >>> with world.test.new(): # or empty() ... @world.test.factory() ... def build_dummy() -> Dummy: ... return Dummy() ... world.get[Dummy]() <Dummy ...> >>> with world.test.new(): ... # You don't need to rely on type hints ... @world.test.factory(Dummy) ... def build_dummy(): ... return Dummy() ... world.get[Dummy]() <Dummy ...>
- Parameters
dependency – Dependency to override.
singleton – Whether the returned dependency is a singleton or not. If yes, the factory will be called at most once and the result re-used. Mutually exclusive with
scope
. Defaults toTrue
.scope – Scope of the returned dependency. Mutually exclusive with
singleton
. The scope defines if and how long the returned dependency will be cached. SeeScope
. Defaults tosingleton()
.
- maybe_provide_from(provider: antidote.core.container.RawProvider, dependency: Hashable) Optional[antidote.core.container.DependencyValue]
Only used to test providers.
Utility function to test _providers that have not been registered in
world
. The current :py:class:~.core.container.DependencyContainer` will be given as if it were registered. This allows to test exactly what has been returned by the provider.- Parameters
provider – provider from which the dependency should be retrieved.
dependency – dependency to retrieve.
- Returns
dependency instance as returned by the provider.
- new() Iterator[None]
Creates a new container with the same kind of providers and scopes. So it doesn’t have any dependencies defined, but any scope or provider added will be present.
>>> from antidote import world >>> with world.test.new(): ... # Current world is empty but has the same providers and scope as the ... # original one. ... # You can define dependencies locally with world.test.singleton or factory ... world.test.singleton("test", 1) ... world.get[int]("test") ... 1 >>> # Anything done within the context manager stays there. ... world.get[int]("test") Traceback (most recent call last): ... DependencyNotFoundError: test
- singleton(dependency: typing.Union[typing.Dict[object, object], object], value: object = <object object>, test_provider: typing.Optional[antidote._providers.world_test.WorldTestProvider] = InjectMeMarker(source=None)) None
Declare one or multiple singleton dependencies with its associated value.
>>> from antidote import world >>> with world.test.new(): # or empty() ... world.test.singleton("test", 1) ... # or ... world.test.singleton({"dummy": 1}) ... world.get[int]("test") 1
- Parameters
test_provider –
dependency – Singleton to declare, must be hashable. If a dict is provided, it’ll be treated as a dictionary of singletons to add.
value – Associated value for the dependency.
world.test.override
Utilities to overrides dependencies in Antidote. Those can only be used in a test world
created by world.test.clone()
with overridable=True
. Dependencies
can be overridden multiple times, it won’t raise any error. Overridden dependencies also
won’t be taken into account when Antidote checks for duplicates. Hence you may do the
following:
>>> from antidote import world
>>> with world.test.clone():
... world.test.override.singleton('test', 1)
... # Override a second time a singleton
... world.test.override.singleton('test', 2)
... world.get[int]("test")
2
- singleton(dependency: typing.Union[typing.Dict[typing.Hashable, object], typing.Hashable], value: object = <object object>) None
Override one or multiple dependencies with one/multiple singletons.
>>> from antidote import world, Service >>> class MyService(Service): ... pass >>> world.get(MyService) <MyService ...> >>> with world.test.clone(): ... # dependencies can also be overridden as many times as necessary ... world.test.override.singleton(MyService, "fake service") ... # Within the cloned world, we changed the dependency value of MyService ... world.get(MyService) 'fake service'
- Parameters
dependency – Singleton to declare, must be hashable. If a dict is provided, it’ll be treated as a dictionary of singletons to add.
value – Associated value for the dependency.
- factory(dependency: Optional[Hashable] = None, *, singleton: Optional[bool] = None, scope: Optional[antidote.core.container.Scope] = Scope(name='__sentinel__')) Callable[[antidote.world.test._override.F], antidote.world.test._override.F]
Override a dependency with the result of a factory. To be used as a function decorator. The result of the underlying function, the factory, will be used as the associated value of the dependency.
>>> from antidote import world, Service >>> class MyService(Service): ... pass >>> world.get(MyService) <MyService ...> >>> with world.test.clone(): ... @world.test.override.factory(MyService) ... def test(): ... return "fake service" ... # Within the cloned world, we changed the dependency value of MyService ... world.get(MyService) 'fake service' >>> with world.test.clone(): ... # If not specified explicitly, the return type hint will be used ... # as the dependency. ... @world.test.override.factory() ... def test() -> MyService: ... return "fake service" ... world.get(MyService) 'fake service'
- Parameters
dependency – Dependency to override.
singleton – Whether the returned dependency is a singleton or not. If yes, the factory will be called at most once and the result re-used. Mutually exclusive with
scope
. Defaults toTrue
.scope – Scope of the returned dependency. Mutually exclusive with
singleton
. The scope defines if and how long the returned dependency will be cached. SeeScope
. Defaults tosingleton()
.
Note
As all override work as a single layer on top of the usual dependencies, if the dependency is already overridden as a singleton, the factory won’t be called. Overrides done with a provider also have a higher priority. See
world.test.override
.- Returns
The decorated function, unchanged.
- provider() Callable[[antidote.world.test._override.P], antidote.world.test._override.P]
Function decorator used to declare a simplified provider to override some dependencies. The function will be called for each dependency and should return
core.DependencyValue
if it can be provided orNone
if not.>>> from antidote import world, Service >>> from antidote.core import DependencyValue, Scope >>> class MyService(Service): ... pass >>> world.get(MyService) <MyService ...> >>> with world.test.clone(): ... @world.test.override.provider() ... def test(dependency): ... if dependency is MyService: ... return DependencyValue('fake service', scope=Scope.singleton()) ... # Within the cloned world, we changed the dependency value of MyService ... world.get(MyService) 'fake service'
Note
As all override work as a single layer on top of the usual dependencies, singleton overrides won’t pass through the provider. See
world.test.override
.Warning
Beware of
provider()
, it can conflict withfactory()
andsingleton()
. Dependencies declared withsingleton()
will hideprovider()
. Andprovider()
will hidefactory()
.Moreover it won’t appear in
world.debug()
.- Returns
Function decorator.
Utilities
- is_compiled() bool
Whether current Antidote implementations is the compiled (Cython) version or not
- validate_injection(dependencies: Union[None, Mapping[str, Hashable], Sequence[Optional[Hashable]], Callable[[antidote.core.injection.Arg], Optional[Hashable]]] = None, auto_provide: Optional[Optional[Union[bool, Iterable[type], Callable[[type], bool]]][Optional[Union[bool, Iterable[type], Callable[[type], bool]]]]] = None) None
Validates that injection parameters are valid. If not, an error is raised.
>>> from antidote.utils import validate_injection >>> validate_injection(dependencies={'name': object()}) >>> validate_injection(dependencies=object()) Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: ...
- validated_scope(scope: Optional[antidote.core.container.Scope] = Scope(name='__sentinel__'), singleton: Optional[bool] = None, *, default: Optional[antidote.core.container.Scope]) Optional[antidote.core.container.Scope]
Validates given arguments and ensures consistency between singleton and scope.
Top-level APIs exposes both singleton and scope arguments. Users can choose either, but not both. This function does all validation necessary in those APIs.
>>> from antidote import Scope, world >>> from antidote.utils import validated_scope >>> custom_scope = world.scopes.new(name='custom') >>> validated_scope(singleton=None, default=custom_scope) Scope(name='custom') >>> validated_scope(singleton=True, default=None) Scope(name='singleton') >>> validated_scope(scope=Scope.singleton(), default=None) Scope(name='singleton')
- Parameters
scope – Scope argument to validate. Neutral value is
Scope.sentinel()
.singleton – Singleton argument to validate. Neutral value is
None
.default – Default value to use if both scope and singleton have neutral values.
- Returns
Scope defined by either
singleton
orscope
.
Dependencies
Service
- service(klass: antidote.service.C, *, singleton: Optional[bool] = 'None', scope: Optional[antidote.core.container.Scope] = 'Scope.sentinel()', wiring: Optional[antidote.core.wiring.Wiring] = 'Wiring()') antidote.service.C
- service(*, singleton: Optional[bool] = 'None', scope: Optional[antidote.core.container.Scope] = 'Scope.sentinel()', wiring: Optional[antidote.core.wiring.Wiring] = 'Wiring()') Callable[[antidote.service.C], antidote.service.C]
Defines the decorated class as a service.
>>> from antidote import service >>> @service ... class MyService: ... pass
Like
Service
it’ll automatically wire the class.>>> from antidote import world, inject >>> @service ... class SecondService: ... def __init__(self, cis: MyService = inject.me()): ... self.cis = cis >>> world.get[SecondService]().cis <MyService object at ...>
Note
If your wish to declare to register an external class to Antidote, prefer using a factory with
factory()
.- Parameters
klass – Class to register as a dependency. It will be instantiated only when requested.
singleton – Whether the service is a singleton or not. A singleton is instantiated only once. Mutually exclusive with
scope
. Defaults toTrue
scope – Scope of the service. Mutually exclusive with
singleton
. The scope defines if and how long the service will be cached. SeeScope
. Defaults tosingleton()
.wiring –
Wiring
to be used on the class. By defaults will apply a simpleinject()
on all methods, so only annotated type hints are taken into account. Can be deactivated by specifyingNone
.
- Returns
The decorated class, unmodified, if specified or the class decorator.
- class Service
Deprecated since version 1.1: Use
service()
instead.Note
If you encounter conflicts with
Service
metaclass, consider using the class decoratorservice()
instead. You may also useABCService
if you only need to be compatible with theabc.ABC
class.Defines subclasses as services:
>>> from antidote import Service, world, inject >>> class Database(Service): ... pass
The service can then be retrieved by its class:
>>> world.get(Database) # treated as `object` by Mypy <Database ...> >>> # With Mypy casting ... world.get[Database]() <Database ...> >>> @inject([Database]) ... def f(db: Database): ... pass
Or with annotated type hints:
>>> from antidote import Provide >>> @inject ... def f(db: Provide[Database]): ... pass
All methods are injected by default and the service itself is a singleton. All of this can be configured with
__antidote__
:>>> # Singleton by default ... world.get[Database]() is world.get[Database]() True >>> class Session(Service): ... __antidote__ = Service.Conf(singleton=False) ... ... def __init__(self, db: Provide[Database]): ... self.db = db ... >>> world.get[Session]() is world.get[Session]() False
You may also parameterize the service. Parameters will be passed on to
__init__()
:>>> class Database(Service): ... __antidote__ = Service.Conf(parameters=['host']) ... ... def __init__(self, host: str): ... self.host = host ... ... @classmethod ... def hosted(cls, host: str) -> object: ... return cls.parameterized(host=host) ... >>> test_db = world.get[Database](Database.hosted('test')) >>> test_db.host 'test' >>> # The factory returns a singleton so our test_session will also be one ... world.get[Database](Database.hosted('test')) is test_db True
- classmethod parameterized(**kwargs: object) object
Deprecated since version 1.1:
parameterized()
is a complex behavior with poor type-safety. Use-cases that really benefit from this behavior are few and would be better implemeneted explicitly in your own code.Creates a new dependency based on the service with the given arguments. The new dependency will have the same scope as the original one.
The recommended usage is to provide a classmethod exposing only parameters that may be changed:
>>> from antidote import Service, world >>> class Database(Service): ... __antidote__ = Service.Conf(parameters=['host']) ... ... def __init__(self, host: str): ... self.host = host ... ... @classmethod ... def with_host(cls, host: str) -> object: ... return cls.parameterized(host=host) >>> db = world.get(Database.with_host(host='remote')) >>> db.host 'remote' >>> # As Database is defined as a singleton, the same is applied: ... world.get(Database.with_host(host='remote')) is db True
- Parameters
**kwargs – Arguments passed on to
__init__()
.- Returns
Dependency to be retrieved from Antidote. You cannot use it directly.
- class Conf(*, wiring: typing.Optional[antidote.core.wiring.Wiring] = Wiring(methods=<Methods.ALL: 1>, auto_provide=False, dependencies=None, raise_on_double_injection=False), singleton: typing.Optional[bool] = None, scope: typing.Optional[antidote.core.container.Scope] = Scope(name='__sentinel__'), parameters: typing.Optional[typing.Iterable[str]] = None)
Deprecated since version 1.1.
Immutable service configuration. To change parameters on a existing instance, use either method
copy()
orcore.wiring.WithWiringMixin.with_wiring()
.- Parameters
wiring – Wiring to be applied on the service. By default only
__init__()
will be wired. Unlessfactory
is class method name, in which case only the latter would be wired. To deactivate any wiring at all useNone
.singleton – Whether the service is a singleton or not. A singleton is instantiated only once. Mutually exclusive with
scope
. Defaults toTrue
scope – Scope of the service. Mutually exclusive with
singleton
. The scope defines if and how long the service will be cached. SeeScope
. Defaults tosingleton()
.
- copy(*, wiring: Union[antidote.core.wiring.Wiring, None, antidote._internal.utils.Copy] = Copy.IDENTICAL, singleton: Union[bool, None, antidote._internal.utils.Copy] = Copy.IDENTICAL, scope: Union[antidote.core.container.Scope, None, antidote._internal.utils.Copy] = Copy.IDENTICAL, parameters: Union[Iterable[str], None, antidote._internal.utils.Copy] = Copy.IDENTICAL) antidote.service.Service.Conf
Deprecated since version 1.1.
Copies current configuration and overrides only specified arguments. Accepts the same arguments as
__init__()
- with_wiring(*, methods: Union[antidote.core.wiring.Methods, Iterable[str], antidote._internal.utils.Copy] = Copy.IDENTICAL, dependencies: Union[None, Mapping[str, Hashable], Sequence[Optional[Hashable]], Callable[[antidote.core.injection.Arg], Optional[Hashable]], antidote._internal.utils.Copy] = Copy.IDENTICAL, auto_provide: Union[bool, Iterable[type], Callable[[type], bool], None, antidote._internal.utils.Copy][Union[bool, Iterable[type], Callable[[type], bool], None, antidote._internal.utils.Copy]] = Copy.IDENTICAL, raise_on_double_injection: Union[bool, antidote._internal.utils.Copy] = Copy.IDENTICAL) antidote.core.wiring.W
Accepts the same arguments as
Wiring
. Its only purpose is to provide a easier way to change the wiring:conf.with_wiring(dependencies={}) # instead of conf.copy(wiring=conf.wiring.copy(dependencies={}))
- class ABCService
Deprecated since version 1.1: Use
service()
instead.This class only purpose is to facilitate the use of a abstract parent class, relying on
abc.ABC
, withService
.>>> from abc import ABC, abstractmethod >>> from antidote import ABCService, world >>> class AbstractClass(ABC): ... @abstractmethod ... def hello(self) -> str: ... pass >>> class MyService(AbstractClass, ABCService): ... def hello(self) -> str: ... return "world" >>> world.get[MyService]().hello() 'world'
Factory
- factory(f: antidote.factory.T, *, singleton: Optional[bool] = 'None', scope: Optional[antidote.core.container.Scope] = 'Scope.sentinel()', wiring: Optional[antidote.core.wiring.Wiring] = 'Wiring()') antidote.factory.T
- factory(*, singleton: Optional[bool] = 'None', scope: Optional[antidote.core.container.Scope] = 'Scope.sentinel()', wiring: Optional[antidote.core.wiring.Wiring] = 'Wiring()') Callable[[antidote.factory.T], antidote.factory.T]
Registers a factory which provides as single dependency, defined through the return type annotation.
>>> from antidote import factory >>> class Database: ... pass >>> @factory ... def load_db() -> Database: ... return Database()
Now to retrieve the dependency:
>>> from antidote import inject, world >>> @inject ... def f(db: Database = inject.me(source=load_db)) -> Database: ... return db >>> f() is world.get(Database, source=load_db) True
inject()
supports two other alternatives:>>> from typing import Annotated >>> from antidote import From, Get >>> @inject ... def f(db: Annotated[Database, From(load_db)]) -> Database: ... return db >>> @inject({'db': Get(Database, source=load_db)}) ... def f(db: Database) -> Database: ... return db
It’s also possible to have a stateful factory using a class. The class will be instantiated only once.
>>> @factory ... class DatabaseFactory: ... def __call__(self) -> Database: ... return Database()
- Parameters
f – Factory function or class which builds the dependency.
singleton – Whether the returned dependency is a singleton or not. If so, the factory will be called at most once and the result re-used. Mutually exclusive with
scope
. Defaults toTrue
.scope – Scope of the returned dependency. Mutually exclusive with
singleton
. The scope defines if and how long the returned dependency will be cached. SeeScope
. Defaults tosingleton()
.wiring –
Wiring
to be used on the class. By defaults will apply a simpleinject()
on all methods, so only annotated type hints are taken into account. Can be deactivated by specifyingNone
. If the factory is a function, it’ll only be injected if notNone
.
- Returns
The factory or the decorator.
- class Factory
Deprecated since version 1.1: Use
factory()
instead.Defines sublcass as a factory to Antidote. The provided dependency is defined through the type annotation of
__call__()
:>>> from antidote import Factory >>> class Database: ... pass ... >>> class DatabaseLoader(Factory): ... def __call__(self) -> Database: ... return Database()
To retrieve the dependency from Antidote you need to use a specific syntax
dependency @ factory
as presented in the following examples. The goal of it is twofold:Ensure that the factory is loaded whenever you require the dependency.
Better maintainability as you know where the dependency comes from.
>>> from antidote import world, inject >>> world.get(Database @ DatabaseLoader) # treated as `object` by Mypy <Database ...> >>> # With Mypy casting ... world.get[Database](Database @ DatabaseLoader) <Database ...> >>> # Concise Mypy casting ... world.get[Database] @ DatabaseLoader <Database ...> >>> @inject([Database @ DatabaseLoader]) ... def f(db: Database): ... pass
Or with annotated type hints:
>>> from typing import Annotated ... # from typing_extensions import Annotated # Python < 3.9 >>> from antidote import From >>> @inject ... def f(db: Annotated[Database, From(DatabaseLoader)]): ... pass
Note
If you only need a simple function, consider using
factory()
instead. It behaves the same way as aboveAll methods are injected by default and the factory returns a singleton by default. All of this can be configured with
__antidote__
:>>> # Singleton by default ... world.get[Database] @ DatabaseLoader is world.get[Database] @ DatabaseLoader True >>> class Session: ... pass >>> # The factory will be called anew each time a `Session` is needed. But the ... # factory itself, `SessionFactory`, will only be created once. ... class SessionFactory(Factory): ... __antidote__ = Factory.Conf(singleton=False) ... ... def __init__(self, db: Annotated[Database, From(DatabaseLoader)]): ... self.db = db ... ... def __call__(self) -> Session: ... return Session() ... >>> world.get[Session] @ SessionFactory is world.get[Session] @ SessionFactory False
You may also parameterize the service. Parameters will be passed on to
__call__()
:>>> class Database: ... def __init__(self, host: str): ... self.host = host ... >>> class DatabaseFactory(Factory): ... __antidote__ = Factory.Conf(parameters=['host']) ... ... def __call__(self, host: str) -> Database: ... return Database(host) ... ... @classmethod ... def hosted(cls, host: str) -> object: ... return cls.parameterized(host=host) ... >>> test_db = world.get[Database] @ DatabaseFactory.hosted('test') >>> test_db.host 'test' >>> # The factory returns a singleton so our test_session will also be one ... world.get[Database] @ DatabaseFactory.hosted('test') is test_db True
- classmethod parameterized(**kwargs: object) antidote._factory.PreBuild
Deprecated since version 1.1:
parameterized()
is a complex behavior with poor type-safety. Use-cases that really benefit from this behavior are few and would be better implemeneted explicitly in your own code.Creates a new dependency based on the factory with the given arguments. The new dependency will have the same scope as the original one.
The recommended usage is to provide a classmethod exposing only parameters that may be changed:
>>> from antidote import Factory, world >>> class Database: ... def __init__(self, host: str): ... self.host = host >>> class DatabaseFactory(Factory): ... __antidote__ = Factory.Conf(parameters=['host']) ... ... def __call__(self, host: str) -> Database: ... return Database(host) ... ... @classmethod ... def with_host(cls, host: str) -> object: ... return cls.parameterized(host=host) >>> db = world.get(Database @ DatabaseFactory.with_host(host='remote')) >>> # or with Mypy type hint ... db = world.get[Database] @ DatabaseFactory.with_host(host='remote') >>> db.host 'remote' >>> # As DatabaseFactory is defined to return a singleton, ... # the same is applied: ... world.get(Database @ DatabaseFactory.with_host(host='remote')) is db True
- Parameters
**kwargs – Arguments passed on to
__call__()
.- Returns
Dependency to be retrieved from Antidote.
- class Conf(*, wiring: typing.Optional[antidote.core.wiring.Wiring] = Wiring(methods=<Methods.ALL: 1>, auto_provide=False, dependencies=None, raise_on_double_injection=False), singleton: typing.Optional[bool] = None, scope: typing.Optional[antidote.core.container.Scope] = Scope(name='__sentinel__'), parameters: typing.Optional[typing.Iterable[str]] = None)
Deprecated since version 1.1.
Immutable factory configuration. To change parameters on a existing instance, use either method
copy()
orcore.wiring.WithWiringMixin.with_wiring()
.- Parameters
wiring – Wiring to be applied on the factory. By default only
__init__()
and__call__()
will be wired. To deactivate any wiring at all useNone
.singleton – Whether the returned dependency is a singleton or not. If yes, the factory will be called at most once and the result re-used. Mutually exclusive with
scope
. Defaults toTrue
scope – Scope of the returned dependency. Mutually exclusive with
singleton
. The scope defines if and how long the returned dependency will be cached. SeeScope
. Defaults tosingleton()
.
- copy(*, wiring: Union[antidote.core.wiring.Wiring, None, antidote._internal.utils.Copy] = Copy.IDENTICAL, singleton: Union[bool, antidote._internal.utils.Copy] = Copy.IDENTICAL, scope: Union[antidote.core.container.Scope, None, antidote._internal.utils.Copy] = Copy.IDENTICAL, parameters: Union[Iterable[str], None, antidote._internal.utils.Copy] = Copy.IDENTICAL) antidote.factory.Factory.Conf
Deprecated since version 1.1.
Copies current configuration and overrides only specified arguments. Accepts the same arguments as
__init__()
- with_wiring(*, methods: Union[antidote.core.wiring.Methods, Iterable[str], antidote._internal.utils.Copy] = Copy.IDENTICAL, dependencies: Union[None, Mapping[str, Hashable], Sequence[Optional[Hashable]], Callable[[antidote.core.injection.Arg], Optional[Hashable]], antidote._internal.utils.Copy] = Copy.IDENTICAL, auto_provide: Union[bool, Iterable[type], Callable[[type], bool], None, antidote._internal.utils.Copy][Union[bool, Iterable[type], Callable[[type], bool], None, antidote._internal.utils.Copy]] = Copy.IDENTICAL, raise_on_double_injection: Union[bool, antidote._internal.utils.Copy] = Copy.IDENTICAL) antidote.core.wiring.W
Accepts the same arguments as
Wiring
. Its only purpose is to provide a easier way to change the wiring:conf.with_wiring(dependencies={}) # instead of conf.copy(wiring=conf.wiring.copy(dependencies={}))
Constants
- class Constants
Used to declare constants, typically configuration which may be retrieved from the environment, a file, etc…
Constants are simply defined with
const()
:>>> from antidote import Constants, const, world >>> class Config(Constants): ... PORT = const(80) ... HOST = const('localhost') >>> world.get[int](Config.PORT) 80 >>> world.get[str](Config.HOST) 'localhost' >>> # Or directly from an instance, for testing typically: ... Config().PORT 80
Constants
really shines when you need to change how you retrieve the constants. For example, let’s suppose we need to load our configuration from a file:>>> class Config(Constants): ... PORT = const[int]('port') ... WRONG_TYPE = const[Constants]('port') ... HOST = const[str]('host') ... ... def __init__(self): ... self._data = {'host': 'localhost', 'port': '80'} ... ... def provide_const(self, name: str, arg: str): ... return self._data[arg] ... >>> # the actual constant will be auto casted to the type hint if present and ... # is one of {str, float, int}. In all cases, the type of the constant value ... # is always checked at runtime. ... world.get(Config.PORT) 80 >>> Config().WRONG_TYPE Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError >>> # The type hint is also used as... a type hint ! ... Config().PORT # will be treated as an int by Mypy 80 >>> world.get(Config.PORT) # will also be treated as an int by Mypy 80 >>> world.get(Config.HOST) 'localhost'
In the previous example we’re also using the
auto_cast
feature. It provides a type hint for Mypy and will be used to cast the result ofConstants.provide_const()
if one ofstr
,int
orfloat
. It can be configured to be either deactivated or extended to support enums for example.Another useful feature is
default
which simply defines the default value to be used if aLookUpError
is raised inConstants.provide_const()
. It must already have the correct type,auto_cast
will not be applied on it.>>> class Config(Constants): ... PORT = const[int]('port', default=80) ... HOST = const[str]('host') ... ... def __init__(self): ... self._data = {'host': 'localhost'} ... ... def provide_const(self, name: str, arg: str): ... return self._data[arg] ... >>> world.get(Config.PORT) 80 >>> world.get(Config.HOST) 'localhost'
- class Conf(*, auto_cast: typing.Union[typing.Iterable[type], bool] = True, wiring: typing.Optional[antidote.core.wiring.Wiring] = Wiring(methods=<Methods.ALL: 1>, auto_provide=False, dependencies=None, raise_on_double_injection=False))
Immutable Constants configuration. To change parameters on a existing instance, use either method
copy()
orwith_wiring()
.- Parameters
wiring –
Wiring
used on the class. Defaults to wire only__init__()
.auto_cast – When the type of the constant is specified with
const()
, the value of the dependency will be cast to its type if it’s one ofstr
,float
orint
by default. You can disable this by specifying code:auto_cast=False or change the types for which it’s done by specifying explicitly those types.
- copy(*, wiring: Union[antidote.core.wiring.Wiring, None, antidote._internal.utils.Copy] = Copy.IDENTICAL, auto_cast: Union[Sequence[type], bool, antidote._internal.utils.Copy] = Copy.IDENTICAL) antidote.constants.Constants.Conf
Copies current configuration and overrides only specified arguments. Accepts the same arguments as
__init__()
- provide_const(*, name: str, arg: Optional[Any]) object
Used to retrieve the value of the constant defined with
const()
. If aLookUpError
is raised, thedefault
value defined withconst()
will be used if any.- Parameters
name – Name of the constant
arg – Argument given to
const()
when creating the constant.
- Returns
Constant value.
Lazy
- class LazyCall(func: Callable[[...], object], *, singleton: Optional[bool] = None, scope: Optional[antidote.core.container.Scope] = Scope(name='__sentinel__'))
Declares the result of a function call as a depdency.
>>> from antidote import LazyCall, world >>> def f(x, y): ... return x + y >>> Computation = LazyCall(f)(2, y=3) >>> world.get(Computation) 5 >>> user = 'John' >>> def hello(): ... return f"Hello {user}" >>> HelloUser = LazyCall(hello, singleton=False) >>> world.get(HelloUser) 'Hello John' >>> user = "Adam" >>> world.get(HelloUser) 'Hello Adam'
- Parameters
func – Function to lazily call, any (keyword-)arguments given by the returned
LazyCall
itself will be propagated.singleton – Whether the lazy dependency is a singleton or not. If yes, the function will be called at most once and the result re-used. Mutually exclusive with
scope
. Defaults toTrue
.scope – Scope of the dependency. Mutually exclusive with
singleton
. The scope defines if and how long the returned dependency will be cached. SeeScope
. Defaults tosingleton()
.
- class LazyMethodCall(method: Callable[[...], object], *, singleton: Optional[bool] = None, scope: antidote.core.container.Scope = Scope(name='__sentinel__'))
Similar to
LazyCall
but adapted to methods within a class definition. It can only be used with aService
subclass.LazyMethodCall
behaves in a similar way as constants inConstants
. When accessed as class attributes, the dependency is returned. But on an instance, the actual method call result is returned.>>> from antidote import LazyMethodCall, Service, world >>> class Api(Service): ... def __init__(self, host: str = 'localhost'): ... self.host = host ... ... def query(self, url: str = '/status'): ... return f"requested {self.host}{url}" ... ... status = LazyMethodCall(query, singleton=False) ... conf = LazyMethodCall(query)('/conf') >>> world.get(Api.conf) 'requested localhost/conf' >>> world.get(Api.status) 'requested localhost/status' >>> # For ease of use, accessing the dependency through a instance will simply ... # call the method without passing through Antidote. Useful for tests typically ... Api(host='example.com').status 'requested example.com/status'
Note
Consider using
Constants
if you only declare constants.- Parameters
method – Method name or the method itself that must be called.
singleton – Whether the lazy dependency is a singleton or not. If yes, the function will be called at most once and the result re-used. Mutually exclusive with
scope
. Defaults toTrue
.scope – Scope of the dependency. Mutually exclusive with
singleton
. The scope defines if and how long the returned dependency will be cached. SeeScope
. Defaults tosingleton()
.
Implementation
- implementation(interface: type, *, permanent: bool = True) Callable[[Callable[[antidote.implementation.P], antidote.implementation.T]], antidote.implementation.ImplementationProtocol[antidote.implementation.P, antidote.implementation.T]]
Defines which dependency should be retrieved when
interface
is requested. Suppose we have to support two different databases and the configuration defines which one should be chosen:>>> from antidote import Constants, const >>> class Config(Constants): ... DB = const('postgres') >>> # The interface ... class Database: ... pass
>>> from antidote import Service, factory >>> class PostgreSQL(Database, Service): ... pass >>> class MySQL(Database): ... pass >>> @factory ... def build_mysql() -> MySQL: ... return MySQL()
The decorated function must return the dependency that should be used for
Database
. It’ll be automatically injected, so you can use annotated type hints on it. Furthermore, the chosen implementation is by default permanent. Meaning that the decorated function will only be called once, at most.>>> from antidote import implementation, Get >>> from typing import Annotated ... # from typing_extensions import Annotated # Python < 3.9 >>> @implementation(Database) ... def local_db(choice: Annotated[str, Get(Config.DB)]) -> object: ... if choice == 'postgres': ... return PostgreSQL ... else: ... return MySQL @ build_mysql
To retrieve the actual implementation from Antidote you need to use a specific syntax
dependency @ implementation
as presented in the following examples. The goal of it is twofold:Ensure that the implementation is loaded whenever you require the interface.
Better maintainability as you know where the dependenciy comes from.
>>> from antidote import world, inject >>> world.get(Database @ local_db) # treated as `object` by Mypy <PostgreSQL ...> >>> # With Mypy casting ... world.get[Database](Database @ local_db) <PostgreSQL ...> >>> # Concise Mypy casting ... world.get[Database] @ local_db <PostgreSQL ...> >>> @inject([Database @ local_db]) ... def f(db: Database): ... pass
Or with annotated type hints:
>>> from typing import Annotated ... # from typing_extensions import Annotated # Python < 3.9 >>> from antidote import From >>> @inject ... def f(db: Annotated[Database, From(local_db)]): ... pass
- Parameters
interface – Interface for which an implementation will be provided
permanent – Whether the function should be called each time the interface is needed or not. Defaults to
True
.
- Returns
The decorated function, unmodified.
Core
Injection
Inject
- class Arg(name: str, type_hint: Any, type_hint_with_extras: Any)
Deprecated since version 1.1: Deprecated as specifying a function to
inject()
is deprecatedRepresents an argument (name and type hint) if you need a very custom injection logic.
- type_hint: Any
Type hint of the argument if any
- type_hint_with_extras: Any
Type hint of the argument if any with include_extras=True, so with annotations.
- class Inject
Use
inject
directly, this class is not meant to instantiated or subclassed.- __call__(__arg: staticmethod[F], *, dependencies: Union[None, Mapping[str, Hashable], Sequence[Optional[Hashable]], Callable[[antidote.core.injection.Arg], Optional[Hashable]]] = 'None', auto_provide: Optional[Union[bool, Iterable[type], Callable[[type], bool]]][Optional[Union[bool, Iterable[type], Callable[[type], bool]]]] = 'None', strict_validation: bool = 'True', ignore_type_hints: bool = 'False') staticmethod[F]
- __call__(__arg: classmethod[F], *, dependencies: Union[None, Mapping[str, Hashable], Sequence[Optional[Hashable]], Callable[[antidote.core.injection.Arg], Optional[Hashable]]] = 'None', auto_provide: Optional[Union[bool, Iterable[type], Callable[[type], bool]]][Optional[Union[bool, Iterable[type], Callable[[type], bool]]]] = 'None', strict_validation: bool = 'True', ignore_type_hints: bool = 'False') classmethod[F]
- __call__(__arg: antidote.core.injection.F, *, dependencies: Union[None, Mapping[str, Hashable], Sequence[Optional[Hashable]], Callable[[antidote.core.injection.Arg], Optional[Hashable]]] = 'None', auto_provide: Optional[Union[bool, Iterable[type], Callable[[type], bool]]][Optional[Union[bool, Iterable[type], Callable[[type], bool]]]] = 'None', strict_validation: bool = 'True', ignore_type_hints: bool = 'False') antidote.core.injection.F
- __call__(*, dependencies: Union[None, Mapping[str, Hashable], Sequence[Optional[Hashable]], Callable[[antidote.core.injection.Arg], Optional[Hashable]]] = 'None', auto_provide: Optional[Union[bool, Iterable[type], Callable[[type], bool]]][Optional[Union[bool, Iterable[type], Callable[[type], bool]]]] = 'None', strict_validation: bool = 'True', ignore_type_hints: bool = 'False') Callable[[antidote.core.injection.F], antidote.core.injection.F]
- __call__(__arg: Sequence[object], *, auto_provide: Optional[Union[bool, Iterable[type], Callable[[type], bool]]][Optional[Union[bool, Iterable[type], Callable[[type], bool]]]] = 'None', strict_validation: bool = 'True', ignore_type_hints: bool = 'False') Callable[[antidote.core.injection.F], antidote.core.injection.F]
- __call__(__arg: Mapping[str, object], *, auto_provide: Optional[Union[bool, Iterable[type], Callable[[type], bool]]][Optional[Union[bool, Iterable[type], Callable[[type], bool]]]] = 'None', strict_validation: bool = 'True', ignore_type_hints: bool = 'False') Callable[[antidote.core.injection.F], antidote.core.injection.F]
Inject the dependencies into the function lazily, they are only retrieved upon execution. As several options can apply to the same argument, the priority is defined as:
Markers
inject.me
… / Annotated type hints (PEP-593)Dependencies declared explicitly if any declared with
dependencies
.Deprecated: Class type hints if specified with
auto_provide
.
>>> from antidote import inject, service, Inject >>> @service ... class A: ... pass >>> @service ... class B: ... pass >>> @inject ... def f(a: A = inject.me()): ... pass # a = world.get(A) >>> # PEP-593 annotation ... @inject ... def f(a: Inject[A]): ... pass # a = world.get(A) >>> # All possibilities for dependency argument. >>> @inject(dict(b='dependency')) # shortcut ... def f(a, b): ... pass >>> @inject([None, 'dependency']) # shortcut ... def f(a, b): ... pass # a, b = <not injected>, world.get('dependency')
- Parameters
ignore_type_hints –
__arg – Callable to be wrapped. Can also be used on static methods or class methods. May also be sequence of dependencies or mapping from argument name to dependencies.
dependencies –
Explicit definition of the dependencies which overrides
auto_provide
. Defaults toNone
. Can be one of:Mapping from argument name to its dependency
Sequence of dependencies which will be mapped with the position of the arguments.
None
can be used as a placeholder.
auto_provide –
Deprecated since version 1.1.
Whether or not class type hints should be used as the arguments dependency. Only classes are taken into account and it works with
Optional
. An iterable of classes may also be supplied to activate this feature only for those. A function may also be provided, in which case it’ll be called to determine whether the class type hint ca be provided or not. Defaults toFalse
.strict_validation – Whether arguments should be strictly validated given the decorated function’s argumnet. For example, a key in the dependencies dict that does not match any argument would raise error. Defaults to
True
.
- Returns
The decorator to be applied or the injected function if the argument
func
was supplied.
- me(*, source: Optional[Union[Source[Any], Callable[..., Any], Type[CallableClass[Any]]]] = None) Any
Injection Marker specifying that the current type hint should be used as dependency.
>>> from antidote import inject, service >>> @service ... class MyService: ... pass >>> @inject ... def f(s: MyService = inject.me()) -> MyService: ... return s >>> f() <MyService object at ...>
It also works with dependency sources such as
factory()
orimplementation()
:>>> from antidote import inject, factory >>> class Dummy: ... pass >>> @factory ... def current_dummy() -> Dummy: ... return Dummy() >>> @inject ... def f(d: Dummy = inject.me(source=current_dummy)) -> Dummy: ... return d >>> f() <Dummy object at ...>
When the type hint is
Optional
inject()
won’t raiseDependencyNotFoundError
but will provideNone
instead:>>> from typing import Optional >>> from antidote import inject >>> class MyService: ... pass >>> @inject ... def f(s: Optional[MyService] = inject.me()) -> MyService: ... return s >>> f() is None True
- get(__dependency: object) Any
- get(__dependency: Type[antidote.core.injection.T], *, source: Union[Source[T], Callable[..., T], Type[CallableClass[T]]]) Any
Injection Marker specifying explicitly which dependency to retrieve.
>>> from antidote import inject, service >>> @service ... class MyService: ... pass >>> @inject ... def f(s = inject.get(MyService)): ... return s >>> f() <MyService object at ...>
Annotations
- Provide
Annotation specifying that the type hint itself is the dependency:
>>> from antidote import world, inject, Inject, service >>> from typing import Annotated ... # from typing_extensions import Annotated # Python < 3.9 >>> @service ... class Database: ... pass >>> @inject ... def load_db(db: Inject[Database]): ... return db >>> load_db() <Database ...>
alias of
antidote.core.annotations.T
[antidote.core.annotations.T
]
- class Get(__dependency: object)
- class Get(__dependency: Type[antidote.core.annotations.T], *, source: Union[antidote.core.typing.Source[antidote.core.annotations.T], Callable[[...], antidote.core.annotations.T], Type[antidote.core.typing.CallableClass[antidote.core.annotations.T]]])
Annotation specifying explicitly which dependency to inject.
>>> from typing import Annotated >>> from antidote import world, inject, Get, Constants, const >>> class Config(Constants): ... DB_HOST = const('localhost') >>> @inject ... def f(host: Annotated[str, Get(Config.DB_HOST)]): ... return host >>> f() == world.get(Config.DB_HOST) True
- class From(_From__source: Union[antidote.core.annotations.SupportsRMatmul, antidote.core.typing.Source[Any], Callable[[...], Any], Type[antidote.core.typing.CallableClass[Any]]])
Annotation specifying from where a dependency must be provided. To be used with
factory()
,Factory
andimplementation()
typically.>>> from typing import Annotated >>> from antidote import factory, world, inject, From >>> class Database: ... def __init__(self, host: str): ... self.host = host >>> @factory ... def build_db(host: str = 'localhost:5432') -> Database: ... return Database(host=host) >>> @inject ... def f(db: Annotated[Database, From(build_db)]) -> Database: ... return db >>> f().host 'localhost:5432'
- class FromArg(_FromArg__function: Callable[[Arg], Optional[Hashable]])
Deprecated since version 1.1: Specifying a callable to
inject()
is deprecated, so is this annotation. If you rely on this behavior, you’ll need to wrap @inject and do the annotation parsing yourself.Annotation specifying which dependency should be provided based on the argument. The function should accept a single argument of type
Arg
and return either a dependency orNone
.>>> from typing import Annotated, TypeVar >>> from antidote import world, inject, FromArg, Constants, const, Arg >>> class Config(Constants): ... PORT = const(5432) ... ... @classmethod ... def from_arg(cls, arg: Arg): ... return getattr(cls, arg.name.upper()) ... >>> T = TypeVar('T') >>> ProvideFromConfig = Annotated[T, FromArg(Config.from_arg)] >>> @inject ... def f(port: ProvideFromConfig[int]) -> int: ... return port >>> f() 5432
Wiring
- class Wiring(*, methods: Union[antidote.core.wiring.Methods, Iterable[str]] = Methods.ALL, dependencies: Union[None, Mapping[str, Hashable], Sequence[Optional[Hashable]], Callable[[antidote.core.injection.Arg], Optional[Hashable]]] = None, auto_provide: Optional[Optional[Union[bool, Iterable[type], Callable[[type], bool]]][Optional[Union[bool, Iterable[type], Callable[[type], bool]]]]] = None, raise_on_double_injection: bool = False)
Defines how a class should be wired, meaning if/how/which methods are injected. This class is intended to be used by configuration objects. If you just want to wire a single class, consider using the class decorator
wire()
instead. There are two purposes:providing a default injection which can be overridden either by changing the wiring or using @inject when using
attempt_methods
.wiring of multiple methods with similar dependencies.
Instances are immutable. If you want to change some parameters, typically defaults defined by Antidote, you’ll need to rely on
copy()
. It accepts the same arguments as__init__()
and overrides existing values.>>> from antidote import Wiring, Service, Provide >>> wiring = Wiring(methods=['my_method']) >>> class Database(Service): ... pass >>> @wiring.wire ... class Dummy: ... def my_method(self, db: Provide[Database]) -> Database: ... return db >>> Dummy().my_method() <Database ...>
- Parameters
- copy(*, methods: Union[antidote.core.wiring.Methods, Iterable[str], antidote._internal.utils.Copy] = Copy.IDENTICAL, dependencies: Union[None, Mapping[str, Hashable], Sequence[Optional[Hashable]], Callable[[antidote.core.injection.Arg], Optional[Hashable]], antidote._internal.utils.Copy] = Copy.IDENTICAL, auto_provide: Union[bool, Iterable[type], Callable[[type], bool], None, antidote._internal.utils.Copy][Union[bool, Iterable[type], Callable[[type], bool], None, antidote._internal.utils.Copy]] = Copy.IDENTICAL, raise_on_double_injection: Union[bool, antidote._internal.utils.Copy] = Copy.IDENTICAL) antidote.core.wiring.Wiring
Copies current wiring and overrides only specified arguments. Accepts the same arguments as
__init__()
- wire(_Wiring__klass: antidote.core.wiring.C) antidote.core.wiring.C
Used to wire a class with specified configuration.
- Parameters
__klass – Class to wired
- Returns
The same class object with specified methods injected.
- wire(__klass: antidote.core.wiring.C, *, methods: Union[antidote.core.wiring.Methods, Iterable[str]] = 'Methods.ALL', dependencies: Union[None, Mapping[str, Hashable], Sequence[Optional[Hashable]], Callable[[antidote.core.injection.Arg], Optional[Hashable]]] = 'None', auto_provide: Optional[Union[bool, Iterable[type], Callable[[type], bool]]][Optional[Union[bool, Iterable[type], Callable[[type], bool]]]] = 'None', raise_on_double_injection: bool = 'False') antidote.core.wiring.C
- wire(*, methods: Union[antidote.core.wiring.Methods, Iterable[str]] = 'Methods.ALL', dependencies: Union[None, Mapping[str, Hashable], Sequence[Optional[Hashable]], Callable[[antidote.core.injection.Arg], Optional[Hashable]]] = 'None', auto_provide: Optional[Union[bool, Iterable[type], Callable[[type], bool]]][Optional[Union[bool, Iterable[type], Callable[[type], bool]]]] = 'None', raise_on_double_injection: bool = 'False') Callable[[antidote.core.wiring.C], antidote.core.wiring.C]
Wire a class by injecting specified methods. This avoids repetition if similar dependencies need to be injected in different methods. Methods will only be wrapped if and only if Antidote may inject a dependency in it, like
inject()
.- Parameters
raise_on_double_injection – Whether an error should be raised if method is already injected. Defaults to
False
.__klass – Class to wire.
methods – Names of methods that must be injected. Defaults to all method
dependencies – Propagated for every method to
inject()
.auto_provide – Propagated for every method to
inject()
. .. deprecated:: 1.1
- Returns
Wired class or a class decorator.
Provider
- does_not_freeze(method: antidote.core.provider.M) antidote.core.provider.M
Decorated method won’t freeze when
freeze()
is called. All others will, with the exceptions of base methods defined in :py:class`.Provider`.Beware that is must be the last decorator if you use multiple ones.
- class Provider
Abstract Base class for a provider.
Consider using
StatelessProvider
if you do not have any state to keep.>>> from typing import Hashable, Optional >>> from antidote import world, Scope >>> from antidote.core import Provider, Container, \ ... DependencyValue, does_not_freeze >>> @world.provider ... class SquareProvider(Provider[int]): ... def __init__(self, registered: set = None): ... super().__init__() ... self._registered = registered or set() ... ... def register_numbers(self, number: int): ... self._assert_not_duplicate(number) ... self._registered |= {number} ... ... def exists(self, dependency: Hashable): ... return isinstance(dependency, int) and dependency in self._registered ... ... def provide(self, dependency: int, container: Container ... ) -> DependencyValue: ... return DependencyValue(self._square(dependency), ... scope=Scope.singleton()) ... ... # we don't want this method to fail when world freezes ... # and it does not modify any state, so we're safe ! ... @does_not_freeze ... def _square(self, n: int) -> int: ... return n ** 2 ... ... def clone(self, keep_singletons_cache: bool) -> 'SquareProvider': ... return SquareProvider(self._registered.copy()) ... >>> # To modify your provider, retrieve it from world ... world.get[SquareProvider]().register_numbers(9) >>> world.get[SquareProvider]().register_numbers(7) >>> world.get[int](9) 81 >>> # If you freeze world, you cannot register any dependencies ... world.freeze() >>> from antidote.exceptions import FrozenWorldError >>> try: ... world.get[SquareProvider]().register_numbers(10) ... except FrozenWorldError: ... print("Frozen world !") Frozen world ! >>> # But you can still retrieve previously defined ones ... world.get[int](9) 81 >>> # even if never called before ... world.get[int](7) 49
Warning
This is most advanced feature of Antidote and allows you to extend Antidote’s behavior. So be careful with it, it will impact the whole library. There are several rules to respect:
Different providers MUST provide strictly different dependencies.
You MUST NOT use
world
. If you need a dependency, rely on the providedContainer
.Methods will automatically freeze except those marked with the decorator
does_not_freeze()
. Methods changing dependencies definitions MUST NOT usedoes_not_freeze()
, others may.You may cache singletons by yourself, but you need to clean your cache when
clone()
is called withkeep_singletons_cache=False
.
- clone(keep_singletons_cache: bool) antidote.core.provider.P
If you have no internal state, consider implementing
StatelessProvider
instead.Used by the different test utilities.
- Parameters
keep_singletons_cache – Whether cached singletons, if any, should be kept or discarded.
- Returns
A deep copy of the current provider. It should always be a new instance.
- exists(dependency: Hashable) bool
Check whether dependency exists in the current provider. It is recommended to be relatively fast as this function will often be called for all _providers. Among others it is used to check for duplicates and ensure that it is providable.
- Parameters
dependency – Dependency to check.
- Returns
Whether dependency has been registered in this provider or not.
- Return type
- provide(dependency: antidote.core.provider.T, container: antidote.core.container.Container) antidote.core.container.DependencyValue
Method called by the
Container
when searching for a dependency. Be sure that the dependency space of your _providers don’t intersect ! This function will only be called ifexists()
succeeded on thedependency
.If you need to access other dependencies, you MUST use the provided
container
argument and NEVERworld
.It is always called within a thread-safe (locked) environment, so you don’t need to worry about it. You also don’t need to worry about handling singletons, the
Container
will handle it for you.Be careful to be consistent with
exists()
.- Parameters
dependency – The dependency to be provided by the provider. It will have passed
exists()
.container – current container which may use to retrieve other dependencies.
- Returns
The requested instance wrapped in a
DependencyValue
. If the dependency is a singleton, you MUST specify it withsingleton=True
.
- debug(dependency: antidote.core.provider.T) antidote.core.utils.DependencyDebug
Optional support for
world.debug
. If not implemented, debug information will not be provided for dependencies.- Parameters
dependency – The dependency for which debug information should be provided. It will have passed
exists()
.- Returns
A short information text on the dependency, whether it’s a singleton and everything that has been wired for the dependency and its respective dependencies if any.
- maybe_provide(dependency: Hashable, container: antidote.core.container.Container) Optional[antidote.core.container.DependencyValue]
Expert feature
maybe_provide()
MUST be consistent withexists()
. It should return- Parameters
dependency – The dependency to be provided by the provider.
container – current container which may use to retrieve other dependencies.
- Returns
The requested instance wrapped in a
DependencyValue
if available orNone
. If the dependency is a singleton, you MUST specify it withsingleton=True
.
- maybe_debug(dependency: Hashable) Optional[antidote.core.utils.DependencyDebug]
Expert feature
maybe_debug()
MUST be consistent withexists()
.- Parameters
dependency – The dependency for which debug information should be provided. It will have passed
exists()
.- Returns
A short information text on the dependency, whether it’s a singleton and everything that has been wired for the dependency and its respective dependencies if any.
- class StatelessProvider
Abstract stateless
Provider
which implementsclone()
.>>> from typing import Hashable, Optional >>> from antidote import world, Scope >>> from antidote.core import StatelessProvider, Container, DependencyValue >>> @world.provider ... class SquareProvider(StatelessProvider[int]): ... def exists(self, dependency: Hashable) -> bool: ... return isinstance(dependency, int) ... ... def provide(self, dependency: int, container: Container ... ) -> Optional[DependencyValue]: ... return DependencyValue(dependency ** 2, scope=Scope.singleton()) >>> world.get(9) 81
- clone(keep_singletons_cache: bool) antidote.core.provider.StatelessProvider[antidote.core.provider.T]
If you have no internal state, consider implementing
StatelessProvider
instead.Used by the different test utilities.
- Parameters
keep_singletons_cache – Whether cached singletons, if any, should be kept or discarded.
- Returns
A deep copy of the current provider. It should always be a new instance.
- class Scope(*args: object, **kwargs: object)
Used to identify a specific scope for dependencies. The scope of a dependency defines when the dependency value is valid or not. Or said differently, for how long it is valid. A singleton is valid forever, and no scope at all means that a new dependency value needs to be retrieved every time.
Scopes can be create through
world.scopes.new()
. The name is only used to have a friendly identifier when debugging.>>> from antidote import world >>> REQUEST_SCOPE = world.scopes.new(name='request')
To use the newly created scope, use
scope
parameters:>>> from antidote import Service >>> class Dummy(Service): ... __antidote__ = Service.Conf(scope=REQUEST_SCOPE)
As
Dummy
has been defined with a custom scope, the dependency value will be kep as long asREQUEST_SCOPE
stays valid. That is to say, until you reset it withworld.scopes.reset()
:>>> dummy = world.get[Dummy]() >>> dummy is world.get(Dummy) True >>> world.scopes.reset(REQUEST_SCOPE) >>> dummy is world.get(Dummy) False
Note
You probably noticed that dependencies supporting scopes always offer both
singleton
andscope
arguments. Those are mutually exclusive. The reason behind this is simply due to the nature of scopes. Scopes are hard to get right ! For example, what should happen with a service in a scope A which required a dependency in scope B when B resets ? What if the service kept the dependency as an attribute ? There are no good answers for this, hence scopes are a pretty advanced feature which can cause inconsistencies. Moreover they do have a performance impact. But at the same time, scopes aren’t uncommon. In a webapp you’ll typically need soon enough a request scope. So it shouldn’t be hard to use it.So to have easy to use scopes while keeping them under the radar until you actually need them, Antidote exposes both
singleton
andscope
arguments.- static singleton() antidote.core.container.Scope
Using this scope or specifying
singleton=True
is equivalent.- Returns
Singleton scope (unique object).
- static sentinel() antidote.core.container.Scope
For functions having both
singleton
andscope
argument, validation of those is done withvalidated_scope()
. To correctly identify if the scope was actually set by the user, we’re using this sentinel scope as the default value.- Returns
Sentinel scope (unique object).
- class DependencyValue(value: object, *, scope: Optional[antidote.core.container.Scope] = None)
Simple wrapper of the dependency value given by a
Provider
.- Parameters
value – Actual dependency value.
scope – Scope of the dependency.
- scope: Optional[antidote.core.container.Scope]
Scope of the dependency.
- class Container
Public interface of the container used by Antidote to handles all dependencies. Used in a
Provider
to access other dependencies.- get(dependency: typing.Hashable, default: object = <object object>) object
Retrieves given dependency or raises a
DependencyNotFoundError
.- Parameters
dependency – Dependency to be retrieved.
default – Returned if specified and no dependency can be retrieved.
- Returns
The dependency instance or the default if specified.
- provide(dependency: Hashable) antidote.core.container.DependencyValue
Similar to
get()
it will retrieve a dependency. However it will be wrapped in aDependencyValue
if found. If notNone
will be returned. This allows to get additional information such as whether the dependency is a singleton or not.- Parameters
dependency – Dependency to be retrieved.
- Returns
wrapped dependency instance.
- class DependencyDebug(_DependencyDebug__info: str, *, scope: Optional[antidote.core.container.Scope] = None, wired: Sequence[object] = (), dependencies: Sequence[Hashable] = ())
Debug information on a dependency. Used by
world.debug
to provide runtime information for debugging.- Parameters
__info – Short and concise information on the dependency, just enough to identify clearly which one it is.
scope – Scope of the dependency.
wired – Every class or function that may have been wired for this dependency.
dependencies – Any transient dependency, so dependencies of this dependency.
Exceptions
- exception AntidoteError
Base class of all errors of antidote.
- exception DependencyInstantiationError(dependency: Hashable, stack: Optional[List[Hashable]] = None)
The dependency could not be instantiated.
- exception DuplicateDependencyError
A dependency already exists with the same id. May be raised by _providers.
- exception FrozenWorldError
An action failed because the world is frozen. Typically happens when trying to register a dependency after having called freeze() on the world.