Lib#
Injectable#
- antidote_lib_injectable(catalog)#
Adds the necessary for the use of
injectable
into the specified catalog. The function is idempotent, and will not raise an error if it was already applied>>> from antidote import new_catalog, antidote_lib_injectable >>> # Include at catalog creation ... catalog = new_catalog(include=[antidote_lib_injectable]) >>> # Or afterwards ... catalog.include(antidote_lib_injectable)
- injectable(__klass=None, *, lifetime='singleton', wiring=Wiring(), factory_method=None, type_hints_locals=Default.sentinel, catalog=world)#
Defines the decorated class as a dependency and its associated value to be an instance of it. By default, it’s a singleto and the class will be instantiated at most once.
>>> from antidote import injectable, world, inject >>> @injectable ... class Dummy: ... pass >>> world[Dummy] <Dummy object at ...>
By default, it’s a singleton and the class will be instantiated at most once and all methods are automatically injected:
>>> world[Dummy] is world[Dummy] True >>> @injectable ... class MyService: ... def __init__(self, dummy: Dummy = inject.me()): ... self.dummy = dummy >>> world[MyService].dummy is world[Dummy] True >>> @injectable(lifetime='transient') ... class ThrowAwayService: ... pass >>> world[ThrowAwayService] is world[ThrowAwayService] False
It is also possible to use a class or static method instead for the intstantiation:
>>> @injectable(factory_method='build') ... class ComplexService: ... def __init__(self, name: str, dummy: Dummy) -> None: ... self.env = name ... self.dummy = dummy ... ... @classmethod ... def build(cls, dummy: Dummy = inject.me()) -> 'ComplexService': ... return ComplexService('Greetings from build!', dummy) >>> world[ComplexService].env 'Greetings from build!'
Note
The registration of the dependency is thread-safe but the wiring isn’t.
Tip
For external classes which you don’t own, consider using a
lazy
function instead. Usinginjectable()
outside the class definition will make it hard to track how the dependency was defined and thus less maintainable.>>> from antidote import lazy >>> class External: ... pass >>> @lazy.value(lifetime='singleton') ... def external() -> External: ... return External() >>> world[external] # easy to track where and how it's defined <External object at ...>
- Parameters
__klass (
Optional
[TypeVar
(C
, bound=type
)]) – /positional-only/ Class to register as a dependency. It will be instantiated only when necessary.lifetime (
Union
[Literal
[‘singleton’, ‘scoped’, ‘transient’],LifeTime
]) – Defines how long the dependency value will be cached. Defaults to'singleton'
, the class is instantiated at most once.wiring (
Optional
[Wiring
]) – Defines how and if methods should be injected. By defaults, all methods will be injected. Custom injection for specific methods with withinject
will not be overridden. SpecifyingNone
will prevent any wiring.factory_method (
Optional
[str
]) – Class or static method to use to build the class. Defaults toNone
, the class is instantiated normally.type_hints_locals (
Union
[Mapping
[str
,object
],Literal
[‘auto’],Default
,None
]) – Local variables to use fortyping.get_type_hints()
. They can be explicitly defined by passing a dictionary or automatically detected withinspect
and frame manipulation by specifying'auto'
. SpecifyingNone
will deactivate the use of locals. The default behavior depends on theconfig
value ofauto_detect_type_hints_locals
. IfTrue
the default value is equivalent to specifying'auto'
, otherwise toNone
.catalog (
Catalog
) – Defines in which catalog the dependency should be registered. Defaults toworld
.
Interface#
- antidote_lib_interface(catalog)#
Adds the necessary for the use of
interface
into the specified catalog. The function is idempotent, and will not raise an error if it was already applied>>> from antidote import new_catalog, antidote_lib_interface >>> # Include at catalog creation ... catalog = new_catalog(include=[antidote_lib_interface]) >>> # Or afterwards ... catalog.include(antidote_lib_interface)
- interface: antidote.lib.interface_ext.Interface#
Singleton instance of
Interface
- class Interface#
Use the
interface
singleton object.Declares an interface contract for which one or multiple implementations can be registered through
implements
. The interface can be a class/protocol, a function or alazy
call. Implementations won’t be directly accessible unless explicitly defined as such.For a class,
implements
ensures that all implementations are subclasses of it. A single implementation can be retrieved directly with the interface class as a dependency. For additional functionnality useinstanceOf
.>>> from antidote import interface, implements, inject, world, instanceOf >>> @interface ... class Service: ... pass >>> @implements(Service) ... class ServiceImpl(Service): ... pass >>> @inject ... def f(service: Service = inject.me()) -> Service: ... return service >>> f() <ServiceImpl ...> >>> world[Service] <ServiceImpl ...> >>> world[instanceOf(Service)] <ServiceImpl ...> >>> world[instanceOf(Service).single()] <ServiceImpl ...>
With a
Protocol
implementations are only checked if decorated withruntime_checkable()
. While a Protocol can be used with the same syntax as before, it will generate static typing errors withType
. So an alternative syntax exists:>>> from typing import Protocol >>> @interface ... class IService(Protocol): ... pass >>> @implements.protocol[IService]() ... class MyService: ... pass >>> world[instanceOf[IService]] <MyService ...> >>> world[instanceOf[IService]().single()] <MyService ...>
A function can also be declared as an interface acting as protocol.
implements
ensures that the implementation signature matches the interface one.>>> from typing import Callable >>> @interface ... def callback(x: int) -> str: ... ... >>> @implements(callback) ... def callback_impl(x: int) -> str: ... return str(x) >>> world[callback] <function callback_impl ...> >>> world[callback.single()] <function callback_impl ...> >>> @inject ... def f(callback: Callable[[int], str] = inject[callback]) -> Callable[[int], str]: ... return callback >>> f() <function callback_impl ...>
Finally a lazy dependency can also be an interface. Similarly to a function interface, a matching signature is enforced.
>>> from typing import Callable >>> @interface.lazy ... def template(name: str) -> str: ... ... >>> @implements.lazy(template) ... def hello_template(name: str) -> str: ... return f"Hello {name}" >>> world[template("John")] 'Hello John' >>> world[template.single()(name="John")] 'Hello John' >>> @inject ... def f(out: str = inject[template("John")]) -> str: ... return out >>> f() 'Hello John'
An interface can also have multiple implementations and all of them be retrieved with
instanceOf.all()
for a class,FunctionInterface.all()
for a function andLazyInterface.all()
forlazy
call:>>> from antidote import interface, implements, world, instanceOf >>> @interface ... class Service: ... pass >>> @implements(Service) ... class ServiceImpl(Service): ... pass >>> @implements(Service) ... class ServiceImpl2(Service): ... pass >>> world[instanceOf(Service).all()] [<ServiceImpl2 object ...>, <ServiceImpl object ...>] >>> world[Service] Traceback (most recent call last): File "<stdin>", line 1, in ? AmbiguousImplementationChoiceError: ...
Selecting a single implementation is not possible anymore as is though. For this three different mechanisms exist to select one or multiple implementations among many:
conditions with
implements.when()
, defnining whehter an implementation is registered or not.>>> from antidote import const, inject, interface, implements, world >>> CLOUD = const('AWS') # some configuration loader somewhere >>> @inject ... def on_cloud(expected: str, actual: str = inject[CLOUD]) -> bool: ... return expected == actual >>> @interface ... class CloudAPI: ... pass >>> @implements(CloudAPI).when(on_cloud('GCP')) ... class GCPapi(CloudAPI): ... pass >>> @implements(CloudAPI).when(on_cloud('AWS')) ... class AWSapi(CloudAPI): ... pass >>> world[CloudAPI] <AWSapi object ...>
At request, constraints be used to filter out implementations. In the following example we’re using qualifiers which are provided out of the box. But you may also define your own
Predicate
andPredicateConstraint
to customize this. Thequalified_by
parameters are actually implemented throughQualifiedBy
underneath.>>> from antidote import const, inject, interface, implements, world, instanceOf >>> @interface ... class CloudAPI: ... pass >>> @implements(CloudAPI).when(qualified_by='GCP') ... class GCPapi(CloudAPI): ... pass >>> @implements(CloudAPI).when(qualified_by='AWS') ... class AWSapi(CloudAPI): ... pass >>> world[instanceOf(CloudAPI).single(qualified_by='GCP')] <GCPapi object ...>
A tie between matching implementations can also be resolved through a different weight. For the sake of simplicity, it must be defined at declaration time. Out of the box, only
NeutralWeight
is used by Antidote, which as the name implies is always the same. To define your own weight system, seeImplementationWeight
.
It is also possible to define a default implementation which is used whenever no alternative implementation can. You can either define it to be the interface itself with
Interface.as_default()
orimplements.as_default()
:>>> from antidote import interface, implements >>> @interface.as_default ... class Service: ... pass >>> world[Service] <Service object ...> >>> @implements(Service) ... class ServiceImpl(Service): ... pass >>> world[Service] <ServiceImpl object ...> >>> # For a lazy call ... @interface.lazy.as_default ... def template(name: str) -> str: ... return f"Default {name}" >>> world[template(name='x')] 'Default x'
Any implementation, default included, can be overridden as long as the catalog is not frozen with
implements.overriding()
.Note
The registration of the interface is thread-safe.
Tip
For Python ealier than 3.9, before PEP614, you can rely on the following trick for decorators:
>>> from typing import TypeVar >>> T = TypeVar('T') >>> def _(x: T) -> T: ... return x >>> @_(implements(Service).when(qualified_by='yet another')) ... class YetAnotherServiceImpl(Service): ... pass
- property lazy
Used to define
lazy
call interface, seeInterfaceLazy
.
- __call__(_Interface__obj=None, *, catalog=world)#
Declares a class/protocol or a function interface. See
Interface
for an overview.- Parameters
- Returns
A
FunctionInterface
if a function interface, otherwise the original class.
- class InterfaceLazy#
- __call__(_InterfaceLazy__func=None, *, catalog=world)#
Define a
lazy
call interface. For more information on what an interface means, seeInterface
. To declare an implementation useimplements.lazy()
.>>> from antidote import interface, implements, world, inject >>> @interface.lazy ... def template(name: str) -> str: ... ... >>> @implements.lazy(template) ... def template_impl(name: str) -> str: ... return f"My Template {name}" >>> world[template(name="World")] 'My Template World' >>> @inject ... def f(world_template: str = inject[template(name="World")]) -> str: ... return world_template >>> f() 'My Template World'
- Parameters
- Returns
- as_default(_InterfaceLazy__func=None, *, inject=Default.sentinel, type_hints_locals=Default.sentinel, catalog=world)#
Declares the decorated
lazy
call interface to also be its own default implementation. The latter is used if and only if there are no alternative implementations that can be provided.>>> from antidote import interface, world, implements >>> @interface.lazy.as_default ... def template(name: str) -> str: ... return f"Default {name}" >>> world[template(name="World")] 'Default World' >>> # Default implementation also provided for all() ... world[template.all()(name="World")] ['Default World'] >>> # Add a "real" implementation ... @implements.lazy(template) ... def custom_template(name: str) -> str: ... return f"Custom {name}" >>> world[template(name="World")] 'Custom World' >>> world[template.all()(name="World")] ['Custom World'] >>> # As custom_template isn't qualified by 'a' it cannot be provided. So the default ... # implementation is returned instead. ... world[template.single(qualified_by='a')("World")] 'Default World'
You can customize more finely how the default implementation behaves by applying yourself either
inject
orlazy
or deactivate any injection by specifyinginject
to be py:obj:None.- Parameters
__func – /positional-only/ Interface contract for a
lazy
call.inject (None | Default) – /Only a function interface/ Specifying
None
will prevent the use of py:obj:.inject on the function.type_hints_locals (TypeHintsLocals) – Local variables to use for
typing.get_type_hints()
. They can be explicitly defined by passing a dictionary or automatically detected withinspect
and frame manipulation by specifying'auto'
. SpecifyingNone
will deactivate the use of locals. The default behavior depends on theconfig
value ofauto_detect_type_hints_locals
. IfTrue
the default value is equivalent to specifying'auto'
, otherwise toNone
.catalog (Catalog) – Defines in which catalog the dependency should be registered. Defaults to
world
.
- Returns
- class implements#
New in version 1.2.
Declares the decorated class to be a candidate implementation for the specified interface. The interface can also be a
Protocol
. If the interface is a regular class or aruntime_checkable()
Protocol, the implementation will be type checked.>>> from antidote import interface, implements, inject, world >>> @interface ... class Service: ... pass >>> @implements(Service) ... class ServiceImpl(Service): ... pass >>> @implements(Service) ... class BadImpl: ... pass Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: ...
Note
Adding an (default) implementation and overriding one are thread-safe.
- as_default(_implements__impl)#
Define a default implementation used when no alternative was found. It can also be overridden with
overriding()
.>>> from antidote import interface, implements, world >>> @interface ... class Base: ... pass >>> @implements(Base).as_default ... class Default(Base): ... pass >>> world[Base] <Default object at ...> >>> @implements(Base) ... class BaseImpl(Base): ... pass >>> world[Base] <BaseImpl object at ...>
- Parameters
__impl – /positional-only/ default implementation for the interface.
- Returns
decorated class
- overriding(_implements__existing_implementation)#
Override an existing implementation with the same predicates, so in the same conditions as the existing one.
>>> from antidote import interface, implements, world >>> @interface ... class Base: ... pass >>> @implements(Base) ... class BaseImpl(Base): ... pass >>> world[Base] <BaseImpl object at ...> >>> @implements(Base).overriding(BaseImpl) ... class Custom(Base): ... pass >>> world[Base] <Custom object at ...>
Trying to override again the same implementation will raise an error:
>>> @implements(Base).overriding(BaseImpl) ... class CustomV2(Base): ... pass Traceback (most recent call last): File "<stdin>", line 1, in ? ValueError: Implementation <class 'BaseImpl'> does not exist.
- Parameters
__existing_implementation – /positional-only/ Existing implementation to override.
- Returns
class decorator
- protocol#
alias of
ImplementsImpl
- when(*conditions, qualified_by=None)#
Associate
Predicate
with the decorated implementation. The implementation will only be used if all the predicates return a weight. In case multiple implementations are valid candidates, their ordering is determined by the total weight of all predicates.Note
See
interface()
for examples.- Parameters
*_predicates – Objects implementing the
Predicate
protocol.qualified_by (Optional[object | list[object]]) – An object, or a list of it, by which the implementation is qualified. Those qualifiers can then be used at runtime to narrow the possible implementations for an interface. Beware, qualifiers rely on the
id()
of the objects, not their equality.
- Returns
class decorator
- is_interface(__obj)#
Returns
True
if the given object is aFunctionInterface
or aLazyInterface
.>>> from antidote import interface, is_interface >>> @interface ... def f() -> None: ... pass >>> is_interface(f) True >>> is_interface(object()) False
- class instanceOf(_instanceOf__interface=None)#
New in version 1.2.
Used to construct the actual dependency for the provider handling the interfaces.
>>> from antidote import interface, implements, inject, world, instanceOf >>> @interface ... class Service: ... pass >>> @implements(Service) ... class ServiceImpl(Service): ... pass >>> world[Service] <ServiceImpl ...> >>> world[instanceOf[Service]] # useful for protocols <ServiceImpl ...> >>> world[instanceOf(Service)] <ServiceImpl ...> >>> world[instanceOf(Service).single()] <ServiceImpl ...>
- Parameters
__interface – /positional-only/ Interface for which implementations should be retrieved. It must have been decorated with
interface()
.
- all(*constraints, qualified_by=None, qualified_by_one_of=None)#
Construct the dependency to retrieve all implementations matching given constraints for the specified interface. Having no implementation matching the constraints will not raise an error, but instead an empty list will be retrieved.
- Parameters
*constraints –
PredicateConstraint
to evaluate for each implementation.qualified_by – All specified qualifiers must qualify the implementation.
qualified_by_one_of – At least one of the specified qualifiers must qualify the implementation.
- Returns
A dependency for the list of implementations matching the constraints.
- single(*constraints, qualified_by=None, qualified_by_one_of=None)#
Construct the dependency to retrieve a single implementation matching given constraints for the specified interface. If multiple or no implementation is found, an error will be raised upon retrieval.
- Parameters
*constraints –
PredicateConstraint
to evaluate for each implementation.qualified_by – All specified qualifiers must qualify the implementation.
qualified_by_one_of – At least one of the specified qualifiers must qualify the implementation.
- Returns
A dependency for the list of implementations matching the constraints.
- class FunctionInterface#
See
Interface
for an overview and usseinterface
to create a function interface andimplements
to declare an implementation.- property __wrapped__#
Original wrapped function
>>> from antidote import interface >>> @interface ... def callback(name: str) -> bool: ... return len(name) < 10 >>> callback.__wrapped__("short") True
- all(*constraints, qualified_by=None, qualified_by_one_of=None)#
Constructs a dependency to retrieve all dependencies matching specified constraints.
>>> from antidote import interface, world, inject, implements >>> @interface ... def callback(name: str) -> bool: ... ... >>> @implements(callback).when(qualified_by='something') ... def callback_impl(name: str) -> bool: ... return len(name) < 10 >>> world[callback.all(qualified_by='something')] [<function callback_impl ...>]
- Parameters
*constraints –
PredicateConstraint
to evaluate for each implementation.qualified_by – All specified qualifiers must qualify the implementation.
qualified_by_one_of – At least one of the specified qualifiers must qualify the implementation.
- single(*constraints, qualified_by=None, qualified_by_one_of=None)#
Constructs a dependency to retrieve a single dependency matching specified constraints.
>>> from antidote import interface, world, inject, implements >>> @interface ... def callback(name: str) -> bool: ... ... >>> @implements(callback).when(qualified_by='something') ... def callback_impl(name: str) -> bool: ... return len(name) < 10 >>> world[callback.single(qualified_by='something')] <function callback_impl ...>
- Parameters
*constraints –
PredicateConstraint
to evaluate for each implementation.qualified_by – All specified qualifiers must qualify the implementation.
qualified_by_one_of – At least one of the specified qualifiers must qualify the implementation.
- class LazyInterface#
See
Interface
for an overview and usseinterface
to create a function interface andimplements
to declare an implementation.- property __wrapped__#
Original wrapped function
>>> from antidote import interface >>> @interface.lazy ... def callback(name: str) -> bool: ... return len(name) < 10 >>> callback.__wrapped__("short") True
- all(*constraints, qualified_by=None, qualified_by_one_of=None)#
Creates a lazy function with the implementations matching specified constraints. The lazy function will return a sequence of the implementations calls.
>>> from antidote import interface, world, inject, implements >>> @interface.lazy ... def template(name: str) -> str: ... ... >>> @implements.lazy(template).when(qualified_by='something') ... def template_impl(name: str) -> str: ... return f"Template {name}" >>> world[template.all(qualified_by='something')(name='Bob')] ['Template Bob']
- Parameters
*constraints –
PredicateConstraint
to evaluate for each implementation.qualified_by – All specified qualifiers must qualify the implementation.
qualified_by_one_of – At least one of the specified qualifiers must qualify the implementation.
- single(*constraints, qualified_by=None, qualified_by_one_of=None)#
Creates a lazy function with the implementation matching specified constraints.
>>> from antidote import interface, world, inject, implements >>> @interface.lazy ... def template(name: str) -> str: ... ... >>> @implements.lazy(template).when(qualified_by='something') ... def template_impl(name: str) -> str: ... return f"Template {name}" >>> world[template.single(qualified_by='something')(name="Bob")] 'Template Bob'
- Parameters
*constraints –
PredicateConstraint
to evaluate for each implementation.qualified_by – All specified qualifiers must qualify the implementation.
qualified_by_one_of – At least one of the specified qualifiers must qualify the implementation.
- exception AmbiguousImplementationChoiceError(*, query, a, b)#
Raised when at least two implementations matches the specified constraints and have the same weight.
- exception HeterogeneousWeightError(a, b)#
- class ImplementationWeight#
The weight defines the ordering of the implementations. When requesting all implementations, their ordering is the one defined by their weight. For a single implementation, it’s the one with the highest weight. If multiple implementations have the highest weight, an exception will be raised when requesting a single implementation.
A weight must define the operator
<
for the ordering,+
to sum the weights of multiple predicates and the methodof_neutral
to handle predicates with a neutralNeutralWeight
.Mixing predicates and/or implementations with
NeutralWeight
and your custom weight is supported as long as your methodof_neutral
can provide a weight. However, multiple custom weights are not.All methods are only called at import time, when declaring dependencies.
>>> from typing import Optional, Any >>> from antidote import Predicate, QualifiedBy >>> class Weight: ... def __init__(self, value: int) -> None: ... self.__value = value ... ... @classmethod ... def of_neutral(cls, predicate: Optional[Predicate[Any]]) -> 'Weight': ... if isinstance(predicate, QualifiedBy): ... return Weight(len(predicate.qualifiers)) ... return Weight(0) ... ... def __lt__(self, other: 'Weight') -> bool: ... return self.__value < other.__value ... ... def __add__(self, other: 'Weight') -> 'Weight': ... return Weight(self.__value + other.__value)
- classmethod of_neutral_predicate(predicate)#
Called when a predicate has a
NeutralWeight
or when an implementation has no weight at all. In which caseNone
is given as argument.
- class MergeablePredicate#
A
Predicate
implementingmerge
can be specified multiple times. All instances will be merged at import time, ensuring only one instance exists for a given implementation.>>> from typing import Optional >>> from antidote import NeutralWeight >>> class UseMe: ... @classmethod ... def merge(cls, a: 'UseMe', b: 'UseMe') -> 'UseMe': ... return UseMe(a.condition and b.condition) ... ... def __init__(self, condition: bool) -> None: ... self.condition = condition ... ... def weight(self) -> Optional[NeutralWeight]: ... return NeutralWeight() if self.condition else None
- class MergeablePredicateConstraint#
A
PredicateConstraint
implementingmerge
allows for runtime optimization by merging all constraints into one.>>> from typing import Optional >>> from antidote import NeutralWeight, QualifiedBy >>> class NotQualified: ... @classmethod ... def merge(cls, a: 'NotQualified', b: 'NotQualified') -> 'NotQualified': ... return a ... ... def evaluate(self, predicate: Optional[QualifiedBy]) -> bool: ... return predicate is None
- class NeutralWeight#
Simple
ImplementationWeight
implementation where the weight always stays the same, neutral. All implementations are treated equally.
- class Predicate#
A predicate can be used to define in which conditions an implementations should be used. A single method must be implemented
weight()
which should return an optionalImplementationWeight
. It is called immediately at import time. The weight is used to determine the ordering of the implementations. IfNone
is returned, the implementation will not be used at all, which allows one to customize which implementations are available at import time.Antidote only provides a single weight system out of the box,
NeutralWeight
which as its name implies does not provide any ordering. All implementations are treated equally. You’re free to use your own weight though, seeImplementationWeight
for more details.>>> from typing import Optional >>> import os >>> from antidote import const, inject, interface, implements, world, NeutralWeight, instanceOf >>> class Conf: ... CLOUD = const('aws') >>> class InCloud: ... def __init__(self, cloud: str) -> None: ... self.cloud = cloud ... ... @inject ... def weight(self, cloud: str = Conf.CLOUD) -> Optional[NeutralWeight]: ... if cloud == self.cloud: ... return NeutralWeight() >>> @interface ... class ObjectStorage: ... def put(self, name: str, data: bytes) -> None: ... pass >>> @implements(ObjectStorage).when(InCloud('aws')) ... class AwsStorage(ObjectStorage): ... pass >>> @implements(ObjectStorage).when(InCloud('gcp')) ... class GCPStorage(ObjectStorage): ... pass >>> world[instanceOf(ObjectStorage).all()] [<AwsStorage ...>]
Tip
Consider using
predicate()
for simple predicates.Predicates can share mother classes, but only one predicate of a specific type can be applied on a implementation:
>>> @implements(ObjectStorage).when(InCloud('gcp'), InCloud('gcp')) ... class GCPStorage(ObjectStorage): ... pass Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: Cannot have multiple predicates ...
To provide some flexibility
Predicate
can be merged if they implementMergeablePredicate
.
- class PredicateConstraint#
A constraint can be used to define at runtime whether a given predicate matches a specific criteria or not. Antidote will evaluate the constraint on all predicate that matches the argument type hint. If not predicate of this type is present on an implementation, it is evaluated with
None
instead.>>> from typing import Optional, Protocol >>> from antidote import QualifiedBy, interface, implements, world, instanceOf >>> # Equivalent to QualifiedBy.nothing ... def not_qualified(predicate: Optional[QualifiedBy]) -> bool: ... return predicate is None >>> def at_least_two_qualifiers(predicate: Optional[QualifiedBy]) -> bool: ... return predicate is not None and len(predicate.qualifiers) >= 2 >>> @interface ... class Dummy(Protocol): ... pass >>> @implements(Dummy) ... class NoQualifiers: ... pass >>> @implements(Dummy).when(qualified_by=object()) ... class OneQualifier: ... pass >>> @implements(Dummy).when(qualified_by=[object(), object()]) ... class TwoQualifiers: ... pass >>> world[instanceOf(Dummy).single(not_qualified)] <NoQualifiers ...> >>> world[instanceOf(Dummy).single(at_least_two_qualifiers)] <TwoQualifiers ...>
Contrary to
Predicate
you can use multiple instances of a single constraint class. But, you can still implementMergeablePredicateConstraint
which allows for runtime optimization by merging them.
- class QualifiedBy(*qualifiers)#
Qualifiers for
interface()
/implements
. Implementations can be qualified with one or multiple objects. One can then impose some constraints on those qualifiers to retrieve only a selection of implementations.Qualifiers are identified by their
id()
and not their equality. Moreover builtins types, such asint
orstr
, cannot be used. This ensures that usage of a specific qualifiers is easy to track.>>> from antidote import QualifiedBy, interface, implements, world, inject, instanceOf >>> V1, V2, V3 = object(), object(), object() >>> @interface ... class Alert: ... pass >>> @implements(Alert).when(qualified_by=V1) ... class AlertV1(Alert): ... pass >>> @implements(Alert).when(qualified_by=[V2, V3]) ... class AlertV2andV3(Alert): ... pass >>> world[instanceOf(Alert).single(qualified_by=V1)] <AlertV1 object at ...> >>> @inject ... def v1alert(alert: Alert = inject.me(qualified_by=V1)) -> Alert: ... return alert >>> v1alert() <AlertV1 object at ...>
QualifiedBy
can also be used directly:>>> @implements(Alert).when(QualifiedBy(V3)) ... class AlertV3(Alert): ... pass >>> world[instanceOf(Alert).single(qualified_by=V1)] <AlertV1 object at ...>
Multiple constraints can be used to query the exact implementation one needs,
qualified_by
enforces that the specified qualifiers are present:>>> world[instanceOf(Alert).single(qualified_by=[V2, V3])] <AlertV2andV3 object at ...> >>> world[instanceOf(Alert).all(qualified_by=V3)] [<AlertV3 object at ...>, <AlertV2andV3 object at ...>]
One can also require that at least one qualifier of a list must be present with
QualifiedBy.one_of()
orqualified_by_one_of
.All of those constraints can be used together or multiple times without any issues.
- Parameters
*qualifiers – Qualifiers to use for an implementation.
- classmethod one_of(*qualifiers)#
Constraints enforcing the presence of a least one qualifier on a implementation.
>>> from antidote import QualifiedBy, interface, implements, world, instanceOf >>> V1, V2 = object(), object() >>> @interface ... class Alert: ... pass >>> @implements(Alert).when(qualified_by=V1) ... class AlertV1(Alert): ... pass >>> @implements(Alert).when(qualified_by=V2) ... class AlertV2(Alert): ... pass >>> world[instanceOf(Alert).all(qualified_by_one_of=[V1, V2])] [<AlertV2 object at ...>, <AlertV1 object at ...>] >>> world[instanceOf(Alert).all(QualifiedBy.one_of(V1, V2))] [<AlertV2 object at ...>, <AlertV1 object at ...>]
- Parameters
*qualifiers – All potential qualifiers.
Lazy#
- antidote_lib_lazy(catalog)#
Adds the necessary for the use of
lazy
andconst
into the specified catalog. The function is idempotent, and will not raise an error if it was already applied>>> from antidote import new_catalog, antidote_lib_lazy >>> # Include at catalog creation ... catalog = new_catalog(include=[antidote_lib_lazy]) >>> # Or afterwards ... catalog.include(antidote_lib_lazy)
- lazy: antidote.lib.lazy_ext.Lazy#
Singleton instance of
Lazy
- class Lazy#
Used to define function calls as dependencies. Those can either be functions or methods and accept arguments or not. Use it through the singleton object
lazy
.- __call__(_Lazy__func=None, *, lifetime='singleton', inject=Default.sentinel, type_hints_locals=Default.sentinel, catalog=world)#
Wraps a function defining a new dependency which is the function call.
>>> from antidote import inject, lazy, world >>> @lazy ... def template(name: str) -> str: ... print("# Called main_template()") ... return f"Template {name}" >>> main_template = template("main") >>> world[main_template] # Called main_template() 'Template main' >>> @inject ... def f(t: str = inject[template("main")]) -> str: ... return t >>> f() # got the same instance as it's a singleton 'Template main' >>> # the function itself is not a dependency ... template in world False
By default, the dependency value has a
singleton
LifeTime
. Using the same arguments will return the same dependency value.>>> world[template("main")] is world[template(name="main")] True
Warning
While it does positional and keyword arguments are properly handled it does NOT take into account default values. For this to work, arguments need to be hashable. It can only be avoided if the
LifeTime
is defined to betransient
.- Parameters
__func – /positional-only/ Function to wrap, which will be called lazily for dependencies.
lifetime (LifetimeType) – Defines how long the dependency value will be cached. Defaults to
'singleton'
, the function is called at most once per group of arguments.inject (None | Default) – Specifying
None
will prevent the use of py:obj:.inject on the function.type_hints_locals (TypeHintsLocals) – Local variables to use for
typing.get_type_hints()
. They can be explicitly defined by passing a dictionary or automatically detected withinspect
and frame manipulation by specifying'auto'
. SpecifyingNone
will deactivate the use of locals. The default behavior depends on theconfig
value ofauto_detect_type_hints_locals
. IfTrue
the default value is equivalent to specifying'auto'
, otherwise toNone
.catalog (Catalog) – Defines in which catalog the dependency should be registered. Defaults to
world
.
- Returns
The function will be wrapped in a
LazyFunction
.
- method(_Lazy__func=None, *, lifetime='transient', inject=Default.sentinel, type_hints_locals=Default.sentinel, catalog=world)#
Wraps a method defining a new dependency which is the method call. Similar to
Inject.method()
, the first argument commonly namedself
will be injected with the class instance.>>> from antidote import injectable, lazy, world, inject >>> @injectable ... class Templates: ... def __init__(self) -> None: ... self.__templates = {'greeting': 'Hello {name}!', 'farewell': 'Bye {name}!'} ... ... @lazy.method ... def render(self, template: str, name: str) -> str: ... return self.__templates[template].format(name=name) >>> world[Templates.render('greeting', name='John')] 'Hello John!' >>> @inject ... def f(t: str = inject[Templates.render('greeting', name='Alice')]) -> str: ... return t >>> f() 'Hello Alice!' >>> # the function itself is not a dependency ... Templates.render in world False
By default, the dependency value has a
singleton
LifeTime
. Using the same arguments will return the same dependency value.>>> world[Templates.render('greeting', name='Alice')] is f() True
Warning
While it does positional and keyword arguments are properly handled it does NOT take into account default values. For this to work, arguments need to be hashable. It can only be avoided if the
LifeTime
is defined to betransient
.- Parameters
__func – /positional-only/ Function to wrap, which will be called lazily for dependencies.
lifetime (LifetimeType) – Defines how long the dependency value will be cached. Defaults to
'singleton'
, the method is called at most once per group of arguments.inject (None | Default) – Specifying
None
will prevent the use of py:obj:.inject on the function.type_hints_locals (TypeHintsLocals) – Local variables to use for
typing.get_type_hints()
. They can be explicitly defined by passing a dictionary or automatically detected withinspect
and frame manipulation by specifying'auto'
. SpecifyingNone
will deactivate the use of locals. The default behavior depends on theconfig
value ofauto_detect_type_hints_locals
. IfTrue
the default value is equivalent to specifying'auto'
, otherwise toNone
.catalog (Catalog) – Defines in which catalog the dependency should be registered. Defaults to
world
.
- Returns
The function will be wrapped in a
LazyMethod
.
- property(_Lazy__func=None, *, lifetime='transient', inject=Default.sentinel, type_hints_locals=Default.sentinel, catalog=world)#
Wraps a function defining a new dependency which is the function call which does not accept any arguments. Similar to
Inject.method()
, the first argument commonly namedself
will be injected with the class instance.>>> from antidote import inject, lazy, world, injectable >>> @injectable ... class Status: ... def __init__(self) -> None: ... self.healthy_code = 200 ... ... @lazy.property ... def code(self) -> int: ... return self.healthy_code >>> world[Status.code] 200 >>> @inject ... def f(t: int = inject[Status.code]) -> int: ... return t >>> f() 200
However, not accepting any argumnets does not mean that nothing can be injected:
>>> @injectable ... class HealthCheck: ... @lazy.property ... def current(self, status_code: int = inject[Status.code]) -> dict[str, object]: ... return {'status': status_code} >>> world[HealthCheck.current] {'status': 200}
By default, the dependency value has a
singleton
LifeTime
>>> world[HealthCheck.current] is world[HealthCheck.current] True
- Parameters
__func – /positional-only/ Function to wrap, which will be called lazily for dependencies.
lifetime (LifetimeType) – Defines how long the dependency value will be cached. Defaults to
'singleton'
, the method is called at most once.inject (None | Default) – Specifying
None
will prevent the use of py:obj:.inject on the function.type_hints_locals (TypeHintsLocals) – Local variables to use for
typing.get_type_hints()
. They can be explicitly defined by passing a dictionary or automatically detected withinspect
and frame manipulation by specifying'auto'
. SpecifyingNone
will deactivate the use of locals. The default behavior depends on theconfig
value ofauto_detect_type_hints_locals
. IfTrue
the default value is equivalent to specifying'auto'
, otherwise toNone
.catalog (Catalog) – Defines in which catalog the dependency should be registered. Defaults to
world
.
- Returns
The function will be wrapped in a
LazyProperty
.
- value(_Lazy__func=None, *, lifetime='transient', inject=Default.sentinel, type_hints_locals=Default.sentinel, catalog=world)#
Wraps a function defining a new dependency which is the function call which does not accept any arguments.
>>> from antidote import inject, lazy, world >>> @lazy.value ... def status() -> int: ... return 200 >>> world[status] 200 >>> @inject ... def f(t: int = inject[status]) -> int: ... return t >>> f() 200
However, not accepting any argumnets does not mean that nothing can be injected:
>>> @lazy.value ... def healthcheck(status: int = inject[status]) -> dict[str, object]: ... return {'status': status} >>> world[healthcheck] {'status': 200}
By default, the dependency value has a
singleton
LifeTime
>>> world[healthcheck] is world[healthcheck] True
- Parameters
__func – /positional-only/ Function to wrap, which will be called lazily for dependencies.
lifetime (LifetimeType) – Defines how long the dependency value will be cached. Defaults to
'singleton'
, the function is called at most once.inject (None | Default) – Specifying
None
will prevent the use of py:obj:.inject on the function.type_hints_locals (TypeHintsLocals) – Local variables to use for
typing.get_type_hints()
. They can be explicitly defined by passing a dictionary or automatically detected withinspect
and frame manipulation by specifying'auto'
. SpecifyingNone
will deactivate the use of locals. The default behavior depends on theconfig
value ofauto_detect_type_hints_locals
. IfTrue
the default value is equivalent to specifying'auto'
, otherwise toNone
.catalog (Catalog) – Defines in which catalog the dependency should be registered. Defaults to
world
.
- Returns
The function will be wrapped in a
LazyValue
.
- is_lazy(__obj)#
Returns
True
if the given object is a lazy function, method, value or property.>>> from antidote import lazy, is_lazy >>> @lazy ... def f() -> None: ... pass >>> is_lazy(f) True >>> is_lazy(object()) False
- const: antidote.lib.lazy_ext.Const#
Singleton instance of
Const
- class Const#
Used to define constants, singleton dependencies. Use it through the singleton
const
.- __call__(_Const__value, *, catalog=world)#
Create a static constant with a pre-defined value.
>>> from antidote import const, world, inject >>> HOST = const('localhost') >>> @inject ... def f(host: str = inject[HOST]) -> str: ... return host >>> f() 'localhost' >>> world[HOST] 'localhost'
- Parameters
__value – Value of the constant.
- env(_Const__var_name=Default.sentinel, *, default=Default.sentinel, convert=None, catalog=world)#
Declares a constant loaded from an environment variables. By default, it relies on the constant name to infer the environement variable, but it can be explicitely specified.
>>> from antidote import const, world, inject >>> class Conf: ... # Specifying explicitly the environment variable name ... HOST = const.env('HOSTNAME') ... # environment value will be converted to an int ... PORT = const.env(convert=int) ... UNKNOWN = const.env(default='not found!') >>> import os >>> os.environ['HOSTNAME'] = 'localhost' >>> os.environ['PORT'] = '80' >>> @inject ... def f(port: int = inject[Conf.PORT]) -> int: ... return port >>> f() 80 >>> world[Conf.HOST] 'localhost' >>> world[Conf.UNKNOWN] 'not found!'
Note
Using a class as a namespace is not required, but it’s convenient.