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. Using injectable() 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 with inject will not be overridden. Specifying None will prevent any wiring.

  • factory_method (Optional[str]) – Class or static method to use to build the class. Defaults to None, the class is instantiated normally.

  • type_hints_locals (Union[Mapping[str, object], Literal[‘auto’], Default, None]) – Local variables to use for typing.get_type_hints(). They can be explicitly defined by passing a dictionary or automatically detected with inspect and frame manipulation by specifying 'auto'. Specifying None will deactivate the use of locals. The default behavior depends on the config value of auto_detect_type_hints_locals. If True the default value is equivalent to specifying 'auto', otherwise to None.

  • catalog (Catalog) – Defines in which catalog the dependency should be registered. Defaults to world.

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 a lazy call. Implementations won’t be directly accessible unless explicitly defined as such.

  1. 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 use instanceOf.

    >>> 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 ...>
    
  2. With a Protocol implementations are only checked if decorated with runtime_checkable(). While a Protocol can be used with the same syntax as before, it will generate static typing errors with Type. 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 ...>
    
  3. 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 ...>
    
  4. 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 and LazyInterface.all() for lazy 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:

  1. 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 ...>
    
  2. 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 and PredicateConstraint to customize this. The qualified_by parameters are actually implemented through QualifiedBy 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 ...>
    
  3. 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, see ImplementationWeight.

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() or implements.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, see InterfaceLazy.

__call__(_Interface__obj=None, *, catalog=world)#

Declares a class/protocol or a function interface. See Interface for an overview.

Parameters
  • __obj/positional-only/ Interface contract which can be a class, protocol or a function.

  • catalog (Catalog) – Defines in which catalog the dependency should be registered. Defaults to world.

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, see Interface. To declare an implementation use implements.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
  • __obj/positional-only/ Interface contract for a lazy call.

  • catalog (Catalog) – Defines in which catalog the dependency should be registered. Defaults to world.

Returns

A LazyInterface.

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 or lazy or deactivate any injection by specifying inject 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 with inspect and frame manipulation by specifying 'auto'. Specifying None will deactivate the use of locals. The default behavior depends on the config value of auto_detect_type_hints_locals. If True the default value is equivalent to specifying 'auto', otherwise to None.

  • catalog (Catalog) – Defines in which catalog the dependency should be registered. Defaults to world.

Returns

A LazyInterface.

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 a runtime_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 a FunctionInterface or a LazyInterface.

>>> 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
  • *constraintsPredicateConstraint 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
  • *constraintsPredicateConstraint 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 usse interface to create a function interface and implements 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
  • *constraintsPredicateConstraint 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
  • *constraintsPredicateConstraint 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 usse interface to create a function interface and implements 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
  • *constraintsPredicateConstraint 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
  • *constraintsPredicateConstraint 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 method of_neutral to handle predicates with a neutral NeutralWeight.

Mixing predicates and/or implementations with NeutralWeight and your custom weight is supported as long as your method of_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 case None is given as argument.

Parameters

predicate (Predicate[Any]) – Neutral weighted predicate or None for an implementation without predicates.

Returns

Weight of the predicate or implementation.

class MergeablePredicate#

A Predicate implementing merge 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 implementing merge 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 optional ImplementationWeight. It is called immediately at import time. The weight is used to determine the ordering of the implementations. If None 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, see ImplementationWeight 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 implement MergeablePredicate.

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 implement MergeablePredicateConstraint 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 as int or str, 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() or qualified_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 and const 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 be transient.

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 with inspect and frame manipulation by specifying 'auto'. Specifying None will deactivate the use of locals. The default behavior depends on the config value of auto_detect_type_hints_locals. If True the default value is equivalent to specifying 'auto', otherwise to None.

  • 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 named self 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 be transient.

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 with inspect and frame manipulation by specifying 'auto'. Specifying None will deactivate the use of locals. The default behavior depends on the config value of auto_detect_type_hints_locals. If True the default value is equivalent to specifying 'auto', otherwise to None.

  • 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 named self 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 with inspect and frame manipulation by specifying 'auto'. Specifying None will deactivate the use of locals. The default behavior depends on the config value of auto_detect_type_hints_locals. If True the default value is equivalent to specifying 'auto', otherwise to None.

  • 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 with inspect and frame manipulation by specifying 'auto'. Specifying None will deactivate the use of locals. The default behavior depends on the config value of auto_detect_type_hints_locals. If True the default value is equivalent to specifying 'auto', otherwise to None.

  • 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.

class LazyFunction#
property __wrapped__#

Original wrapped function.

class LazyMethod#
property __wrapped__#

Original wrapped function.

class LazyProperty#
property __wrapped__#

Original wrapped function.

class LazyValue#
property __wrapped__#

Original wrapped function.