Changelog#
For any given version N, all releases N.X.X guarantee:
API stability: Python code that used to work will continue work.
Namespace stability for
antidoteandantidote.core. Other namespaces are susceptible to changes.best effort for static type hints stability. Meaning that code relying on Antidote that used to pass MyPy or any other static type checker should continue working, but it’s best effort.
Most, if not all, of the API is annotated with decorators such as @API.public specifying whether
the given functionality can be relied upon.
2.0.0 (2022-08-31)#
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
Scopeconcept has been replaced byLifeTimeandScopeGlobalVar.world.testenvironments API have been reworked. Creating one has a similar API and guarantees, butworld.test.factory,world.test.singletonand all ofworld.test.overridehave been replaced by a better alternative. SeeTestContextBuilder.Dependencies cannot be specified through
inject({...})andinject([...])anymore.QualifiedBy/qualified_byfor interface/implementation now relies on equality instead of theid().constAPI has been reworked.const()andcont.env()have API changes andconst.providerhas been removed.Thread-safety guarantees from Antidote are now simplified. It now only ensures lifetime consistency and some decorators such as
injectable()&interfaceprovide some thread-safety guarantees.Providerhas been entirely reworked. It keeps the same name and purpose but has a different API and guarantees.
Core#
injectremoved
dependencies,strict_validationandauto_provideparameters.removed
sourceparameter fromInject.me()
Wiringremoved
dependenciesparameter.renamed
class_in_localnsparameter toclass_in_localsinWiring.wire().
wire(): removeddependenciesparameterrenamed
GettodependencyOf. Usage ofinject[]/inject.getis recommended instead for annotations.worldProviders are not dependencies anymore. Use
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
DependencyInstantiationErroranymoreFrozenWorldErrorhas been renamedFrozenCatalogError.world.test.new()now generates a test environment equivalent to a freshly created Catalog withnew_catalog. It only impacts those using a customProvider.Removed dependency cycle detection and
DependencyCycleError. It wasn’t perfectly accurate and it’s not really worth it.world.debugdoes a better job at detecting and presenting those cycles.
validate_injection()andvalidated_scope()functions have been removed.DependencyGetter,TypedDependencyGetterare not part of the API anymore.
Injectable#
The first argument
klassofinjectable()is now positional-only.singletonandscopeparameters have been replaced bylifetime.
Interface#
ImplementationsOfhas been renamed toinstanceOf.PredicateConstraintprotocol is now a callable instead of having anevaluate()method.Classes wrapped by
implementsare now part of the private catalog by default, if you want them to be available, you’ll need to applyinjectable()explicitly.implements.overriding()raises aValueErrorinstead ofRuntimeErrorif the implementation does not exist.The default implementation is now only provided if no other implementations matched. It wasn’t the case with
all()before.implements.by_defaulthas been renamed toimplements.as_default()to be symmetrical withinterface.
Lazy#
singletonandscopeparameters have been replaced bylifetime.call()function was removed from lazy functions, use the__wrapped__attribute instead.In test contexts such as
world.test.empty()andworld.test.new(), previously defined lazy/const dependencies will not be available anymore.
Const#
To specify a type for
Const.env()use theconvertargument.When defining static constant values such as
HOST = const('localhost'), it’s NOT possible to:define the type (
const[str]('localhost))define a default value
not provide value at all anymore
const.providerhas been removed. UseLazy.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
worldis now a proper instance ofPublicCatalog. 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.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
ScopeGlobalVarwhich has a similar interface toContextVarand three kind of lifetimes to replace scopes:'singleton': instantiated only once'transient': instantiated on every request'scoped': used by dependencies depending on one or multipleScopeGlobalVar. When any of them changes, the value is re-computed otherwise it’s cached.
ScopeGlobalVarisn’t aContextVarthough, it’s a global variable. It’s planned to add aScopeContextVar.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
worldandinject, expose a dict-like read-only API. Typing has also been improved: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:
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
Inject.method()which will inject the first argument, commonlyselfof a method with the dependency defined by the class. It won’t inject when used as instance method though.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
injectnow supports wrapping function with*args.injecthas nowkwargsandfallbackkeywords to replace the olddependencies.kwargstakes priority over alternative injections styles andfallbackis used in the same way asdependencies, after defaults and type hints.
Interface#
interfacenow supports function andlazycalls. It also supports defining the interface as the default function withInterface.as_default():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
Protocolstatic typing: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)
QualifiedByrelies on equality instead of the id of the objects now. Limitations on the type of qualifiers has also been removed.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): ...
implementshas awiringargument to prevent any wiring.
Lazy#
lazycan now wrap (static-)methods and define values/properties: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')]
lazyhas now aninjectargument which can be used to prevent any injection.
1.4.2 (2022-06-26)#
Bug fix#
Fix injection error for some union type hints such as
str | List[str].
1.4.1 (2022-06-01)#
Bug fix#
Fix type error for
implements.overriding().
1.4.0 (2022-05-22)#
Deprecation#
Constantsis deprecated as not necessary anymore with the newconst.factory()is deprecated in favor oflazy().
Features#
lazy()has been added to replacefactory()and theparameterized()methods of bothFactoryandService.from antidote import lazy, inject class Redis: pass @lazy # singleton by default def load_redis() -> Redis: return Redis() @inject def task(redis = load_redis()): ...
consthas been entirely reworked for better typing and ease of use:it doesn’t require
Constantsanymore.environment variables are supported out of the box with
Const.env().custom logic for retrieval can be defined with
Const.provider().
Here’s a rough overview:
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
implements.overriding()overrides an existing implementation, and will be used in exactly the same conditions as the overridden one: default or not, predicates…implements.by_default()defines a default implementation for an interface outside of the weight system.
Experimental#
ConstantValueProvider.converter()provides a similar to feature to the legacyauto_castfromConstants.
Bug fix#
Better behavior of
injectandworld.debug()with function wrappers, having a__wrapped__attribute.
1.3.0 (2022-04-26)#
Deprecation#
service()is deprecated in favor ofinjectable()which is a drop-in replacement.inject()used to raise aRuntimeErrorwhen specifyingignore_type_hints=Trueand no injections were found. It now raisesNoInjectionsFoundErrorWiring.wire()used to return the wired class, it won’t be the case anymore.
Features#
Add local type hint support with
type_hints_localsargument forinject(),injectable(),implementsandwire(). The default behavior can be configured globally withconfig. Auto-detection is done throughinspectand frame manipulation. It’s mostly helpful inside tests.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
factory_methodtoinjectable()(previousservice())from __future__ import annotations from antidote import injectable @injectable(factory_method='build') class Dummy: @classmethod def build(cls) -> Dummy: return cls()
Added
type_hints_localsandclass_in_localnsargument toWiring.wire.
Bug fix#
Fix
Optionaldetection in predicate constraints.
1.2.0 (2022-04-19)#
Bug fix#
Fix injection error when using the
Klass | Nonenotation instead ofOptional[Klass]in Python 3.10.
Features#
frozenkeyword argument toworld.test.clone()which allows one to control whether the cloned world is already frozen or not.Both
inject.getandworld.getnow strictly follow the same API.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 withqualified_bykeyword and requested with eitherqualified_byorqualified_by_one_of.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#
PredicateAPI is experimental allows you to define your custom logic for selecting the right implementation for a given interface. Qualifiers are implemented with theQualifiedBypredicate which is part of the public API.
1.1.1 (2022-03-25)#
Bug fix#
Injected functions/methods with
inject()did not behave correctly withinspect.isfunction,inspect.ismethod,inspect.iscoroutinefunctionandinspect.iscoroutine.
1.1.0 (2022-03-19)#
Breaking static typing change#
A function decorated with
factory()will not have the@operator anymore from a static typing perspective. It’s unfortunately not possible with the addition of the class support for the decorator.
Deprecation#
ServiceandABCServiceare deprecated in favor ofservice().Passing a function to the argument
dependenciesofinject()is deprecated. If you want to customize how Antidote injects dependencies, just wrapinject()instead.inject()’sauto_provideargument is deprecated. If you rely on this behavior, wrapinject().world.lazyis deprecated. It never brought a lot of value, one can easily write it oneself.dependency @ factoryanddependency @ implementationare replaced by the more explicit notation:world.get(dependency, source=factory) @inject(dependencies={'db': Get(dependency, source=factory)}) def (db): ...
Annotation
Providehas been renamedInject.world.getwill not support extracting annotated dependencies anymore.Omitting the dependency when a type is specified in
world.getis deprecated.world.getprovides now better type information.from antidote import world, service @service class Dummy: pass # this will expose the correct type: world.get(Dummy) # so this is deprecated world.get[Dummy]() # you can still specify the type explicitly world.get[Dummy](Dummy)
Change#
Both
world.getandconsthave better type checking behavior, doing it only when the specified type is an actual instance oftype. For protocols, type check will only be done with those decorated with@typing.runtime_checkable.Dropped Python 3.6 support.
Features#
Add
ignore_type_hintstoinject()to support cases when type hints cannot be evaluated, typically in circular imports.Adding Markers for
inject()used as default arguments to declare injections:from antidote import const, Constants, factory, inject, service class Config(Constants): HOST = const[str]("host") @service class Dummy: value: str @factory def dummy_factory() -> Dummy: return Dummy() # inject type hint @inject def f(dummy: Dummy = inject.me()) -> Dummy: return dummy # inject type hint with factory @inject def f2(dummy: Dummy = inject.me(source=dummy_factory)) -> Dummy: return dummy # inject constants @inject def f3(host: str = Config.HOST) -> str: return host # inject a dependency explicitly @inject def f4(x=inject.get(Dummy)) -> Dummy: return x # inject a dependency with a factory explicitly @inject def f5(x=inject.get(Dummy, source=dummy_factory)) -> Dummy: return x
1.0.1 (2021-11-06)#
Change#
Update
fastrlockdependency to>=0.7,<0.9to support Python 3.10 for the compiled version.
1.0.0 (2021-04-29)#
No changes. From now on breaking changes will be avoided as much as possible.
0.14.2 (2021-04-28)#
Features#
Added
wiringargument toservice()and auto-wiring likeService.
0.14.1 (2021-04-25)#
Features#
Added
ABCServicefor services to be easier to work with ABC abstract classes.Added support for a function in
auto_provide
0.14.0 (2021-03-30)#
Breaking Change#
LazyDependencyandWithWiringMixinare not part of the public API anymore. For the first just useworld.lazyinstead, and the later was experimental.world.scopes.new()argumentnameis keyword-only now.
0.13.0 (2021-03-24)#
Breaking Change#
_with_kwargs()class method has been replaced byService.parameterized()andFactory.parameterized()with a cleaner design. Now parameters must be explicitly defined in their respective configuration. Those will be verified to ensure they don’t have any injections or default values, as sanity checks. Otherwise passing the default value as a parameter or relying on the actual default would not point to the same dependency value.
0.12.1 (2021-03-07)#
Change#
Improved
world.test.clone()performance to be as fast as possible to avoid any overhead in tests in the compiled version.
0.12.0 (2021-02-06)#
Feature / Breaking Change#
Add runtime type checks when a type is explicitly defined with
world.get,world.lazyorConstants.
0.11.0 (2021-02-05)#
Features#
Add scope support.
Add annotated type hints support (PEP-593).
Add async injection support.
Multiple factories can be defined for the same class.
Cleaner testing support, by separating explicitly the case where test existing dependencies or want to create new ones.
All methods of
Service,FactoryandConstantsare automatically wired to support annotated type hints anywhere.
Breaking changes#
Remove
publicconfiguration forFactoryandConstants. They didn’t really bring any value, you hardly hide anything in Python.Removed tags. They didn’t bring enough value.
Reworked
inject(): it will only inject annotated type, nothing else anymore.use_type_hinthas been replaced byauto_provideanduse_nameshas been removed.Reworked
Constantsto be more flexible.Removed
world.singletons. There was no way to track back where a singleton was defined.Reworked
Wiringto be simpler, not super class wiring
0.10.0 (2020-12-24)#
Breaking change#
In
Wiring,ignore_missing_methodshas been replaced byattempt_methods.
Bug fix#
Using
inject()on__init__()of aService, or any methods injected by default by Antidote, will not raise a double injection error anymore.
0.9.0 (2020-12-23)#
Features#
Antidote exposes its type information (PEP 561) and passes strict Mypy (with implicit optionals).
Breaking changes#
Antidote exceptions have no public attributes anymore.
Injecting twice the same function/method will raise an error.
Constantshas been simplified,constis now simply always required to define a constant.
Changes#
Better, simpler
DependencyInstantiationErrorwhen a deeply nested dependency fails.Cleaner packaging: Antidote will only try to compile Cython when the environment variable
ANTIDOTE_COMPILEDis set totrueand doesn’t require Cython to be pre-installed to do so. Antidote’s version is also hardcoded at publish time.Added a Scope example in the documentation. It is a bit more complicated than I would like, but scopes are hard
0.8.0 (2020-12-09)#
Features#
- Reworked entirely
world: Cleaner singletons declarations in
world.singletonsTest utilities in
world.test. Those allow you to change locally, withing a context manager, dependencies declarations. Hence you can replace an existing dependency by a mock for example.Override utilities in
world.test.overrideto be used in tests.Debug utility
world.debug()which returns a tree of all the dependencies that will/may be retrieved by Antidote.Add type hints to
world.getwhich can now be used likeworld.get[<class>]("x")Add
world.lazyfor dependencies to retrieve dependencies lazily.
- Reworked entirely
implementation()is more flexible than@implementsand supports changing the implementation at runtime for example.ServiceandFactoryexpose a handy class methodwith_kwargs()which allows you to specify some key word argument to customize the service you’re retrieving. Typically you would have only one database service class but use this feature to have two different dependencies which each point to different database.Constants, formerlyLazyConstantsMeta, supports a new of defining constants:const. It has two purposes, explicitly define constants and optionally specify the actual type.Added
world.freeze()which will prevent any new dependencies to be added.
Breaking changes#
Drop support of Python 3.5.
Singletons do check for duplicates now. Hence one cannot redefine an existing singleton through
world.world.update_singletonsdoes not exists anymore, useworld.test.singleton_all()orworld.test.singleton()instead.@registeris now replaced by the classServiceand provides mostly the same features. The only corner cases are service that used factories, those should now really use a factory, namelyFactoryorfactory. If you cannot inherit the super class for some reason, you may fallback to the class decoratorservice().@factoryfor functions behaves the same way, however for factory classes the super classFactorymust be used. The dependency identifier has also been to changed, the factory must now be specified likedependency @ factoryinstead ofdependency.LazyConstantsMetahas been replaced by the classConstants. One cannot choose the lazy method anymore, but it is more flexible regarding definition of constants.@implementshas been entirely reworked and split intoimplementation()andImplementation. The latter can be used for straightforward cases where only one implementation exists. The first lets you handle all other cases with multiple implementations which can vary during runtime or not.@providerhas been replaced by the class decoratorworld.provider().Everything related to the container management has been removed for the public interface.
Changes#
Add Python 3.9 support.
public APIs are clearly defined as such, marked by
@API.public. Overall public API is also better defined.Improved Cython performance
0.7.2 (2020-04-21)#
Bug fixes#
The wrapper of the injection function didn’t behave exactly like a proxy for the all of the wrapped functions attributes. Furthermore the Cython version didn’t support setting dynamically attributes at all.
0.7.0 (2020-01-15)#
Breaking changes#
@registerdoes not wire__init__()anymore if a function is provided as a factory. This didn’t make a lot of sense,__init__()is wrapped automatically if and only if it is treated as the “factory” that creates the object.Now when using
dependenciesargument with a sequence (matching dependencies with arguments through their position), the first argument will be ignored for methods (self) and classmethod (cls). So now you can write:from antidote import inject, service class Service: @inject(dependencies=('dependency',)) def method(self, arg1): ... @inject(dependencies=('dependency',)) @classmethod def method(cls, arg1): ... @service(dependencies=('dependency',)) class Service2: def __init__(self, arg1): ...
Hence all other decorators profit from this. No need anymore to explicitly ignore
self.
Bug fixes#
Prevent double
LazyMethodCallwrapping inLazyConstantsMeta(Thanks @keelerm84)@injectcannot be applied on classes. This was never intended as it would not return a class. Use@wireinstead if you relied on this.@injectreturnedValueErrorinstead ofTypeErrorin with erroneous types.@registernow raises an error when using a method as a factory that is neither a classmethod nor a staticmethod. It was never intended to use methods, as it would not make sense.
Changes#
When wrapping multiple methods,
@wireused to raise an error if a sequence was provided fordependencies. This limitation has been removed.
0.6.1 (2019-12-01)#
Add support for Python 3.8
0.6.0 (2019-05-06)#
Features#
Add
@implementsto define service implementing an interface.Add
IndirectProvider()which supports@implements.Add
Container.safe_provide()which does the same asContainer.provide()except that it raises an error if the dependency cannot be found instead of returning None.
Breaking changes#
Container.provide()returns aDependencyInstancenot the instance itself anymore.Rename
LazyConfigurationMetatoLazyConstantsMeta.LazyConfigurationMetadefault method isget().ServiceProviderrenamed toFactoryProviderand reworkedServiceProvider.register()with is split intoregister_factory(),register_class,register_providable_factory.
Changes#
Moved
is_compiledtoantidote.utils.Add better type hints.
0.5.1 (2019-04-27)#
Features#
Add
is_compiled()to check whether the current version is compiled or pure python.
0.5.0 (2019-04-27)#
Breaking changes#
@resourcehas been removed an replaced byLazyConfigurationMetato handle configuration.
Features#
Add
LazyMethodCallandLazyCallto support output of functions as dependencies.
Changes#
Add better type hints for helper decorators.
0.4.0 (2019-02-03)#
A lot of internals have changed, but it can roughly be resumed as the following:
Breaking changes#
The
DependencyManagerdoes not exist anymore and has been replaced by multiple helpers which accepts acontainerargument. By default the global container of Antidote is used. Thus one can easily replacefrom antidote import antidotetoimport antidoteto adapt existing code.The global container of Antidote, previously named
container, has been renamedworld.Dependencydoes not take additional arguments anymore, for custom dependenciesBuild,Taggedmust be used instead.Custom providers must inherit
Provider.register_parameters()has been replaced by a more general function,resource(). See the documentation to imitate its functionality.factory()is more strict. Subclasses are not handled anymore, one should useregister()with itsfactoryargument instead.
Features#
Dependencies can be tagged at registration. Those can then be retrieved as a dependency. This allows one to extend an app by registering a service in special way just by adding a tag.
Type hints usage can now be finely controlled or disabled with
use_type_hints.Add
resource()to support custom resources, such as configuration.Dependency providers are more strict for more maintainable code.
Use of Cython for better injection performance.
0.3.0 (2018-04-29)#
Initial release