Package: yadi-framework
YADIYet Another Dependency Injection frameworkYADI is a dependency injection framework. It supports both classes and
function in a declarative fashion.Installationpipinstallyadi-frameworkBasic examplesThis is a simple injection example:fromyadi.context_implimportDEFAULT_CONTEXTfromyadi.decoratorsimportinjectfromyadi.typesimportYadi@inject()classComponent1:pass@inject()classComponent2:def__init__(self,c1:Yadi[Component1]):self.c1=c1@inject()classComponent3:def__init__(self,c1:Yadi[Component1]):self.c1=c1c2=DEFAULT_CONTEXT.get_bean(Component2)# type: Component2c3=DEFAULT_CONTEXT.get_bean(Component3)# type: Component3print(Component1istype(c2.c1))# Trueprint(c2.c1isc3.c1)# TrueHere it is an example of how to inject functions:fromyadi.context_implimportDEFAULT_CONTEXTfromyadi.decoratorsimportinjectfromyadi.typesimportYadi@inject()classComponent:pass@inject(name='another_function')defh(x,y,z=None):assertisinstance(x,Component)print('Function h:',type(x))@inject(name='my_function')deff(a:Yadi[Component],b,c:Yadi['another_function']=None,d:str=None):c(a,b,z=d)DEFAULT_CONTEXT.get_bean('my_function')(23,d=5)# Function h: <class '__main__.Component'>ScopesBy default, all the beans are saved as Singleton. Each singleton is
stored in its context, that is, there is a single instancefor each
context instance.Alternatively, it is possible to save beans as Prototypes, that is, a
different instance is generated whenever the bean is referred to.fromyadiimportcontextfromyadiimporttypesfromyadi.context_implimportDEFAULT_CONTEXTfromyadi.decoratorsimportinject@inject(scope=context.PROTOTYPE,name='a component 1')classComponent1:pass@inject(name='a component 2')classComponent2:def__init__(self,f1:types.Yadi[Component1],f2:types.Yadi['a component 1']):self.f1,self.f2=f1,f2@inject(name='a component 3')classComponent3:def__init__(self,f1:types.Yadi[Component1],f2:types.Yadi['a component 1']):self.f1,self.f2=f1,f2c2=DEFAULT_CONTEXT.get_bean('a component 2')# type: Component2c3=DEFAULT_CONTEXT.get_bean('a component 3')# type: Component3print(isinstance(c2.f1,Component1))# Trueprint(isinstance(c2.f2,Component1))# Trueprint(isinstance(c3.f1,Component1))# Trueprint(isinstance(c3.f2,Component1))# Trueprint(c2.f1==c2.f2)# Falseprint(c3.f1==c3.f2)# Falseprint(c2.f1==c3.f1)# Falseprint(c2.f1==c3.f2)# Falseprint(c2.f2==c3.f1)# Falseprint(c2.f2==c3.f2)# FalseIt is also possible to define custom scopes and add them to a context.Here it is an example of thread-local scope:importthreadingfromyadi.contextimportScopefromyadi.context_implimportDEFAULT_CONTEXTfromyadi.decoratorsimportinjectclassThreadLocalScope(Scope):def__init__(self):self._tl=threading.local()defget(self,key:str):returngetattr(self._tl,key,None)defset(self,key:str,obj:object):setattr(self._tl,key,obj)@propertydefname(self):return'threadlocal'@propertydeflevel(self):return100DEFAULT_CONTEXT.add_scope(ThreadLocalScope())@inject(scope='threadlocal',name='a component 1')classComponent1:passc1=DEFAULT_CONTEXT.get_bean('a component 1')c1_2=DEFAULT_CONTEXT.get_bean('a component 1')thread_c1=[]c1_t=Nonedef_f():globalc1_tc1_t=DEFAULT_CONTEXT.get_bean('a component 1')print(c1_t==DEFAULT_CONTEXT.get_bean('a component 1'))# Truethread_c1.append(c1_t)t=threading.Thread(target=_f)t.start()t.join()print(c1==c1_2)# Trueprint(c1==c1_t)# FalseScoped proxiesLet’s suppose to inject a thread-local scoped bean in a singleton. As a
result, different thread sharing the same singleton should not share the
same thread local bean, which is not possible.In order to solve this issue, YADI creates a proxy around the injected
bean that delegates any access to the current bean in the context.More in general, scopes have alevelattribute: if the injected bean
has a higher scope level than the container bean, the injected bean is
wrapped into a scoped proxy.Here it is an example of scoped proxies (don’t worry, you do not have to
do anything to make it work).importrandomimportthreadingfromyadi.bean_factoriesimport_ScopedProxyfromyadi.contextimportScopefromyadi.context_implimportDEFAULT_CONTEXTfromyadi.decoratorsimportinjectfromyadi.typesimportYadiclassThreadLocalScope(Scope):def__init__(self):self._tl=threading.local()defget(self,key:str):returngetattr(self._tl,key,None)defset(self,key:str,obj:object):setattr(self._tl,key,obj)@propertydefname(self):return'threadlocal'@propertydeflevel(self):return100DEFAULT_CONTEXT.add_scope(ThreadLocalScope())@inject(scope='threadlocal')classComponent1:def__init__(self):self.object_id=random.randint(0,1000000)@inject(name='a component')classComponent2:def__init__(self,f1:Yadi[Component1]):self.f1=f1component=DEFAULT_CONTEXT.get_bean('a component')component_thread_id=[]print('Main thread, scoped proxy type',type(component.f1)==_ScopedProxy)# Main thread, scoped proxy type Truedef_f():component_thread=DEFAULT_CONTEXT.get_bean('a component')print('Subthread, scoped proxy type',type(component_thread.f1)==_ScopedProxy)component_thread_id.append(component_thread.f1.object_id)print('Subthread, bean id',component_thread.f1.object_id==DEFAULT_CONTEXT.get_bean('a component').f1.object_id)t=threading.Thread(target=_f)t.start()t.join()# Subthread, scoped proxy type True# Subthread, bean id Trueprint('Main thread, bean id',component.f1.object_id==component_thread_id[0])# Main thread, bean id FalseContextsAll the components are kept in a context.By default, theinjectdecorator keeps the beans instances inyadi.context_impl.DEFAULT_CONTEXT.You might want to instantiate a new context and pass it as acontextkeyword argument ofinjectdecorator.Life cycleIt is possible to trigger beans whenever one of them is created. In
order to define the method(s) to trigger, it is necessary to decorated
them withpost_create, as follows:fromyadi.context_implimportDEFAULT_CONTEXTfromyadi.decoratorsimportinject,post_createfromyadi.typesimportYadi@inject()classComponent1:pass@inject()classComponent2:def__init__(self,c1:Yadi[Component1]):self.c1=c1self.invoked_post_create=0@post_createdeffinished_creating(self):print('Component 1:',self.c1)# Component 1: <__main__.Component1 object at 0x7f42e90d2e48>self.invoked_post_create+=1component_2=DEFAULT_CONTEXT.get_bean(Component2)# type: Component2print('post_create invokations:',component_2.invoked_post_create)# post_create invokations: 1DEFAULT_CONTEXT.get_bean(Component2)print('post_create invokations:',component_2.invoked_post_create)# post_create invokations: 1