Antidote

Latest version: v2.0.0

Safety actively analyzes 621924 Python packages for vulnerabilities to keep your Python projects secure.

Scan your dependencies

Page 1 of 5

2.0.0

====================

Antidote core has been entirely reworked to be simpler and provide better static typing in addition
of several features. The cython had to be dropped though for now by lack of time. It may eventually
come back.


Breaking Changes
----------------

Important
^^^^^^^^^
- All previously deprecated changes have been removed.
- The previous :code:`Scope` concept has been replaced by :py:class:`.LifeTime` and :py:class:`.ScopeGlobalVar`.
- :code:`world.test` environments API have been reworked. Creating one has a similar API and guarantees, but :code:`world.test.factory`, :code:`world.test.singleton` and all of :code:`world.test.override` have been replaced by a better alternative. See :py:class:`.TestContextBuilder`.
- Dependencies cannot be specified through :code:`inject({...})` and :code:`inject([...])` anymore.
- :py:class:`.QualifiedBy`/:code:`qualified_by` for interface/implementation now relies on equality instead of the :code:`id()`.
- :py:obj:`.const` API has been reworked. :code:`const()` and :code:`cont.env()` have API changes and :code:`const.provider` has been removed.
- Thread-safety guarantees from Antidote are now simplified. It now only ensures lifetime consistency and some decorators such as :py:func:`.injectable` & :py:obj:`.interface` provide some thread-safety guarantees.
- :py:class:`.Provider` has been entirely reworked. It keeps the same name and purpose but has a different API and guarantees.

Core
^^^^
- :py:obj:`.inject`
- removed :code:`dependencies`, :code:`strict_validation` and :code:`auto_provide` parameters.
- removed :code:`source` parameter from :py:meth:`.Inject.me`
- :py:class:`.Wiring`
- removed :code:`dependencies` parameter.
- renamed :code:`class_in_localns` parameter to :code:`class_in_locals` in :py:meth:`.Wiring.wire`.
- :py:func:`.wire`: removed :code:`dependencies` parameter
- renamed :code:`Get` to :code:`dependencyOf`. Usage of :code:`inject[]`/:code:`inject.get` is recommended instead for annotations.
- :py:obj:`.world`
- Providers are not dependencies anymore. Use :py:attr:`.Catalog.providers`.
- Providers do not check anymore that a dependency wasn't defined by another one before. They're expected to be independent.
- Exception during dependency retrieval are not wrapped in :code:`DependencyInstantiationError` anymore
- :code:`FrozenWorldError` has been renamed :code:`FrozenCatalogError`.
- :code:`world.test.new()` now generates a test environment equivalent to a freshly created Catalog with :code:`new_catalog`. It only impacts those using a custom :code:`Provider`.
- Removed dependency cycle detection and :code:`DependencyCycleError`. It wasn't perfectly accurate and it's not really worth it. :code:`world.debug` does a better job at detecting and presenting those cycles.
- :code:`validate_injection()` and :code:`validated_scope()` functions have been removed.
- :code:`DependencyGetter`, :code:`TypedDependencyGetter` are not part of the API anymore.

Injectable
^^^^^^^^^^
- The first argument :code:`klass` of :py:func:`.injectable` is now positional-only.
- :code:`singleton` and :code:`scope` parameters have been replaced by :code:`lifetime`.

Interface
^^^^^^^^^
- :code:`ImplementationsOf` has been renamed to :py:class:`.instanceOf`.
- :py:class:`.PredicateConstraint` protocol is now a callable instead of having an :code:`evaluate()` method.
- Classes wrapped by :py:class:`.implements` are now part of the private catalog by default, if you want them to be available, you'll need to apply :py:func:`.injectable` explicitly.
- :py:meth:`.implements.overriding` raises a :py:exc:`ValueError` instead of :py:exc:`RuntimeError` if the implementation does not exist.
- The default implementation is now only provided if no other implementations matched. It wasn't the case with :code:`all()` before.
- :code:`implements.by_default` has been renamed to :py:meth:`.implements.as_default` to be symmetrical with :py:obj:`.interface`.

Lazy
^^^^
- :code:`singleton` and :code:`scope` parameters have been replaced by :code:`lifetime`.
- :code:`call()` function was removed from lazy functions, use the :code:`__wrapped__` attribute instead.
- In test contexts such as :code:`world.test.empty()` and :code:`world.test.new()`, previously defined lazy/const dependencies will not be available anymore.

Const
^^^^^
- To specify a type for :py:meth:`.Const.env` use the :code:`convert` argument.
- When defining static constant values such as :code:`HOST = const('localhost')`, it's NOT possible to:

- define the type (:code:`const[str]('localhost)`)
- define a default value
- not provide value at all anymore

- :code:`const.provider` has been removed. Use :py:meth:`.Lazy.method` instead. The only difference is that the const provider would return different objects even with the same arguments, while the lazy method won't.


Features
--------

Core
^^^^
- AEP1: Instead of hack of module/functions :py:obj:`.world` is now a proper instance of :py:obj:`.PublicCatalog`. Alternative catalogs can be created and included in one another. Dependencies can also now be private or public. The main goal is for now to expose a whole group of dependencies through a custom catalog.

.. code-block:: python

from antidote import new_catalog, inject, injectable, world

Includes by default all of Antidote
catalog = new_catalog()


Only accessible from providers by default.
injectable(catalog=catalog.private)
class PrivateDummy:
...


injectable(catalog=catalog) if catalog is not specified, world is used.
class Dummy:
def __init__(self, private_dummy: PrivateDummy = inject.me()) -> None:
self.private_dummy = private_dummy


Not directly accessible
assert PrivateDummy not in catalog
assert isinstance(catalog[Dummy], Dummy)


app_catalog is propagated downwards for all inject that don't specify it.
inject(app_catalog=catalog)
def f(dummy: Dummy = inject.me()) -> Dummy:
return dummy


assert f() is catalog[Dummy]

Not inside world yet
assert Dummy not in world
world.include(catalog)
assert world[Dummy] is catalog[Dummy]

- AEP2 (reworked): Antidote now defines a :py:class:`.ScopeGlobalVar` which has a similar interface to :py:class:`ContextVar` and three kind of lifetimes to replace scopes:

- :code:`'singleton'`: instantiated only once
- :code:`'transient'`: instantiated on every request
- :code:`'scoped'`: used by dependencies depending on one or multiple :py:class:`.ScopeGlobalVar`. When any of them changes, the value is re-computed otherwise it's cached.

:py:class:`.ScopeGlobalVar` isn't a :py:class:`ContextVar` though, it's a global variable. It's planned to add a :py:class:`.ScopeContextVar`.

.. code-block:: python

from antidote import inject, lazy, ScopeGlobalVar, world

counter = ScopeGlobalVar(default=0)

Until update, the value stays the same.
assert world[counter] == 0
assert world[counter] == 0
token = counter.set(1)
assert world[counter] == 1


lazy(lifetime='scoped')
def dummy(count: int = inject[counter]) -> str:
return f"Version {count}"


dummy will not be re-computed until counter changes.
assert world[dummy()] == 'Version 1'
assert world[dummy()] == 'Version 1'
counter.reset(token) same interface as ContextVar
assert world[dummy()] == 'Version 0'

- Catalogs, such as :py:obj:`.world` and :py:obj:`.inject`, expose a dict-like read-only API. Typing has also been improved:

.. code-block:: python

from typing import Optional

from antidote import const, inject, injectable, world


class Conf:
HOST = const('localhost')
STATIC = 1


assert Conf.HOST in world
assert Conf.STATIC not in world
assert world[Conf.HOST] == 'localhost'
assert world.get(Conf.HOST) == 'localhost'
assert world.get(Conf.STATIC) is None
assert world.get(Conf.STATIC, default=12) == 12

try:
world[Conf.STATIC]
except KeyError:
pass


injectable
class Dummy:
pass


assert isinstance(world[Dummy], Dummy)
assert isinstance(world.get(Dummy), Dummy)


inject
def f(host: str = inject[Conf.HOST]) -> str:
return host


inject
def g(host: Optional[int] = inject.get(Conf.STATIC)) -> Optional[int]:
return host


assert f() == 'localhost'
assert g() is None

- Testing has a simplified dict-like write-only API:

.. code-block:: python

from antidote import world

with world.test.new() as overrides:
add a singleton / override existing dependency
overrides['hello'] = 'world'
add multiple singletons
overrides.update({'second': object()})
delete a dependency
del overrides['x']


add a factory
overrides.factory('greeting')
def build() -> str:
return "Hello!"

- Added :py:meth:`.Inject.method` which will inject the first argument, commonly :code:`self` of a method with the dependency defined by the class. It won't inject when used as instance method though.

.. code-block:: python

from antidote import inject, injectable, world


injectable
class Dummy:
inject.method
def method(self) -> 'Dummy':
return self


assert Dummy.method() is world[Dummy]
dummy = Dummy()
assert dummy.method() is dummy

- :py:obj:`.inject` now supports wrapping function with :code:`*args`.
- :py:obj:`.inject` has now :code:`kwargs` and :code:`fallback` keywords to replace the old :code:`dependencies`. :code:`kwargs` takes priority over alternative injections styles and :code:`fallback` is used in the same way as :code:`dependencies`, after defaults and type hints.


Interface
^^^^^^^^^
- :py:obj:`.interface` now supports function and :py:obj:`.lazy` calls. It also supports defining the interface as the default function with :py:meth:`.Interface.as_default`:

.. code-block:: python

from antidote import interface, world, implements


interface
def callback(x: int) -> int:
...


implements(callback)
def callback_impl(x: int) -> int:
return x * 2


assert world[callback] is callback_impl
assert world[callback.single()] is callback_impl


interface.lazy.as_default
def template(name: str) -> str:
return f"Template {name!r}"


assert world[template(name='test')] == "Template 'test'"


implements.lazy(template)
def template_impl(name: str) -> str:
return f"Alternative template {name!r}"


assert world[template.all()(name='root')] == ["Alternative template 'root'"]

- Better API for :py:class:`~typing.Protocol` static typing:

.. code-block:: python

from typing import Protocol

from antidote import implements, instanceOf, interface, world


interface
class Dummy(Protocol):
...


implements.protocol[Dummy]()
class MyDummy:
...


assert isinstance(world[instanceOf[Dummy]()], MyDummy)
assert isinstance(world[instanceOf[Dummy]().single()], MyDummy)

- :py:class:`.QualifiedBy` relies on equality instead of the id of the objects now. Limitations on the type of qualifiers has also been removed.

.. code-block:: python

from antidote import implements, interface


interface
class Dummy:
...


implements(Dummy).when(qualified_by='a')
class A(Dummy):
...


implements(Dummy).when(qualified_by='b')
class B(Dummy):
...

- :py:class:`.implements` has a :code:`wiring` argument to prevent any wiring.

Lazy
^^^^
- :py:obj:`.lazy` can now wrap (static-)methods and define values/properties:

.. code-block:: python

from antidote import injectable, lazy, world


lazy.value
def name() -> str:
return "John"


injectable required for lazy.property & lazy.method
class Templates:
lazy.property
def main(self) -> str:
return "Lazy Main Template"

lazy.method
def load(self, name: str) -> name: has access to self
return f"Lazy Method Template {name}"

staticmethod
lazy
def static_load(name: str) -> str:
return f"Lazy Static Template {name}"


world[name]
world[Templates.main]
world[Templates.load(name='Alice')]
world[Templates.static_load(name='Bob')]

- :py:obj:`.lazy` has now an :code:`inject` argument which can be used to prevent any injection.

1.4.2

==================


Bug fix
-------

- Fix injection error for some union type hints such as :code:`str | List[str]`.

1.4.1

==================


Bug fix
-------

- Fix type error for :py:meth:`.implements.overriding`.

1.4.0

==================


Deprecation
-----------

- :py:class:`.Constants` is deprecated as not necessary anymore with the new :py:obj:`.const`.
- :py:func:`~.factory.factory` is deprecated in favor of :py:func:`.lazy`.


Features
--------

- :py:func:`.lazy` has been added to replace :py:func:`~.factory.factory` and the
:code:`parameterized()` methods of both :py:class:`.Factory` and :py:class:`.Service`.

.. code-block:: python

from antidote import lazy, inject

class Redis:
pass

lazy singleton by default
def load_redis() -> Redis:
return Redis()

inject
def task(redis = load_redis()):
...

- :py:obj:`.const` has been entirely reworked for better typing and ease of use:

- it doesn't require :py:class:`.Constants` anymore.
- environment variables are supported out of the box with :py:meth:`.Const.env`.
- custom logic for retrieval can be defined with :py:meth:`.Const.provider`.

Here's a rough overview:

.. code-block:: python

from typing import Optional, TypeVar, Type

from antidote import const, injectable

T = TypeVar('T')

class Conf:
THREADS = const(12) static const
PORT = const.env[int]() converted to int automatically
HOST = const.env("HOSTNAME") define environment variable name explicitly,


injectable
class Conf2:
stateful factory. It can also be stateless outside of Conf2.
const.provider
def get(self, name: str, arg: Optional[str]) -> str:
return arg or name

DUMMY = get.const()
NUMBER = get.const[int]("90") value will be 90

- :py:meth:`.implements.overriding` overrides an existing implementation, and will be used in
exactly the same conditions as the overridden one: default or not, predicates...
- :py:meth:`.implements.by_default` defines a default implementation for an interface outside of
the weight system.


Experimental
------------

- :py:meth:`.ConstantValueProvider.converter` provides a similar to feature to the legacy
:code:`auto_cast` from :py:class:`.Constants`.


Bug fix
-------

- Better behavior of :py:obj:`.inject` and :py:func:`.world.debug` with function wrappers, having a
:code:`__wrapped__` attribute.

1.3.0

==================


Deprecation
-----------

- :py:func:`.service` is deprecated in favor of :py:func:`.injectable` which is a drop-in
replacement.
- :py:func:`.inject` used to raise a :py:exc:`RuntimeError` when specifying
:code:`ignore_type_hints=True` and no injections were found. It now raises
:py:exc:`.NoInjectionsFoundError`
- :py:meth:`.Wiring.wire` used to return the wired class, it won't be the case anymore.


Features
--------

- Add local type hint support with :code:`type_hints_locals` argument for :py:func:`.inject`,
:py:func:`.injectable`, :py:class:`.implements` and :py:func:`.wire`. The default behavior can
be configured globally with :py:obj:`.config`. Auto-detection is done through :py:mod:`inspect`
and frame manipulation. It's mostly helpful inside tests.

.. code-block:: python

from __future__ import annotations

from antidote import config, inject, injectable, world


def function() -> None:
injectable
class Dummy:
pass

inject(type_hints_locals='auto')
def f(dummy: Dummy = inject.me()) -> Dummy:
return dummy

assert f() is world.get(Dummy)


function()

config.auto_detect_type_hints_locals = True


def function2() -> None:
injectable
class Dummy:
pass

inject
def f(dummy: Dummy = inject.me()) -> Dummy:
return dummy

assert f() is world.get(Dummy)


function2()

- Add :code:`factory_method` to :py:func:`.injectable` (previous :py:func:`.service`)

.. code-block:: python

from __future__ import annotations

from antidote import injectable


injectable(factory_method='build')
class Dummy:
classmethod
def build(cls) -> Dummy:
return cls()

- Added :code:`ignore_type_hints` argument to :py:class:`.Wiring` and :py:func:`.wire`.
- Added :code:`type_hints_locals` and :code:`class_in_localns` argument to :py:class:`.Wiring.wire`.


Bug fix
-------

- Fix :code:`Optional` detection in predicate constraints.

1.2.0

==================


Bug fix
-------

- Fix injection error when using the :code:`Klass | None` notation instead of :code:`Optional[Klass]`
in Python 3.10.


Features
--------

- :code:`frozen` keyword argument to :py:func:`.world.test.clone` which allows one to control
whether the cloned world is already frozen or not.
- Both :code:`inject.get` and :code:`world.get` now strictly follow the same API.
- :py:func:`.interface` and py:class:`implements` which provide a cleaner way to separate
implementations from the public interface. Qualifiers are also supported out of the box. They
can be added with :code:`qualified_by` keyword and requested with either :code:`qualified_by` or
:code:`qualified_by_one_of`.

.. code-block:: python

from antidote import implements, inject, interface, world, QualifiedBy

V1 = object()
V2 = object()


interface
class Service:
pass


implements(Service).when(qualified_by=V1)
class ServiceImpl(Service):
pass


implements(Service).when(QualifiedBy(V2))
class ServiceImplV2(Service):
pass


world.get[Service].single(qualified_by=V1)
world.get[Service].all()


inject
def f(service: Service = inject.me(QualifiedBy(V2))) -> Service:
return service


inject
def f(services: list[Service] = inject.me(qualified_by=[V1, V2])) -> list[Service]:
return services



Experimental
------------

- :py:class:`.Predicate` API is experimental allows you to define your custom logic
for selecting the right implementation for a given interface. Qualifiers are implemented with
the :py:class:`.QualifiedBy` predicate which is part of the public API.

Page 1 of 5

© 2024 Safety CLI Cybersecurity Inc. All Rights Reserved.