Package: applipy-inject
Applipy Injectpip install applipy_injectLibrary that provides a dependency injector that works with type hinting.Basic usagefromapplipy_injectimportInjectorinjector=Injector()# bind dictionary instanceinjector.bind(dict,{'v':143,'k':'bar'})classA:def__init__(self,v:int):self.v=vclassB(A):def__init__(self,d:dict):super().__init__(d['v'])self.d=d# bind subtype B to type Ainjector.bind(A,B)classC:def__init__(self,a:A):self.a=a# bind type Cinjector.bind(C)# get an instace of type Cc=injector.get(C)# the instance is initialized correctly, resolving the dependecy chainprint(c.a.v)Output:143bind(...)Thebind()function lets bind an object to a type. The meaning on the binding
depends on the type of the object. In general the object can be one of:instance: an instance of a typetype: a typeprovider: a callable object that has type annotations for its arguments and
a return type annotation.The bind function can be used in multiple ways:bind(type, instance)Bind an instance to a type. The instance must be an instance of the type or of
a subtype of the type.injector.bind(int,5)bind((typeA, typeB, ...), instance)Bind an instance to multiple types. The instance must be an instance of all the
types or of a subtype of all the types.injector.bind((str,object),'hello')bind(type)The type is bound as a "provider" of its own type and dependecy annotations are
taken from the__init__function.injector.bind(A)bind(type, subtype)Similar tobind(type)but subtype is bound to the specified type.injector.bind(A,B)bind((typeA, typeB, ...), subtype)Similar tobind(type, subtype)but subtype is bound to the specified types.injector.bind((A,B),B)# orinjector.bind((A,B),C)bind(provider)The provider will be bound to the type it returns (as per the type annotation).defprovide_A(s:str)->A:returnA(int(s))injector.bind(provide_A)bind(type, provider)Similar tobind(provider)but the provider is bound to the specified type.
The annotated return type of the provider must be a subtype of the specified
type.defprovide_B(v:int,k:str)->B:returnB({'v':v,'k':k})injector.bind(A,provide_B)bind((typeA, typeB, ...), provider)Similar tobind(type, provider)but the provider is bound to all the
specified types. The annotated return type of the provider must be a subtype
of all the specified types.defprovide_B(v:int,k:str)->B:returnB({'v':v,'k':k})injector.bind((A,B),provide_B)Common optional parameters:name: defaults toNone. Lets the user give a name to the binding. This
allows to make multiple bindings to the same type and be able to select
which one you want to get by using the name.singleton: defaults toTrue. Define whether the injector should
instantiate or call the provider only once and inject always the same
instance or return a new result every time. It does not applipy to bound
instances.injector.bind(provide_A,name='foo',singleton=False)get(...)Get an instance registered to a given type.Similar tobind(), there is an optionalnameparameter that tells the
injector the name of the instance to get for that type.injector.get(A, name='foo')get_all(...)TheInjectorallows to bind multiple objects to the same type and name.get_all()retrieves all instances for a given type and name combination as a
list, instead of just one asget()does.Similar tobind(), there is an optionalnameparameter that tells the
injector the name of the instance to get for that type.injector.get_all(A, name='foo')Named dependenciesDependencies can be given names so that different providers can depend on
different instances of the same type. This can be achieved by annotating the
dependency type with the dependency name:fromtypingimportAnnotatedfromapplipy_injectimportnamedefprovide_A(s:Annotated[str,name('foo_num')])->A:returnA(int(s))injector.bind(str,'13',name='foo_num')injector.bind(provide_A)Utility functionswith_names(provider, names)Give names to the arguments of an existing provider or class. The injector will
use this to set the value fornamewith doingget().namescan be:dict, where the keys are the names of the arguments and the values are the
names for their type.str, all arguments will be named with the valueinjector.bind(with_names(provide_B,{'k':'name_for_k'}))Note thatvwon't have a nameinjector.bind(with_names(provide_B,'name_for_all'))Bothvandkwill have namename_for_allIt can also be used on classes:injector.bind(with_names(C,'app'))named(...)Similar towith_names()but it is a decorator that is used when defining a
provider or class__init__.classZ:@named({'a':'conf'})def__init__(self,a:dict,b:str):...@named('foo')defprovide_int(x:int,b:str)->int:...This is equivalent to usingtyping.Annotatedas follows:fromtypingimportAnnotatedfromapplipy_injectimportnameclassZ:def__init__(self,a:Annotated[dict,name('conf')],b:str):...defprovide_int(x:Annotated[int,name('foo')],b:Annotated[str,name('foo')])->int:...