YADI - Yet another dependency injection framework
Project description
YADI
Yet Another Dependency Injection framework
YADI is a dependency injection framework. It supports both classes and function in a declarative fashion.
Installation
pip install yadi-framework
Basic examples
This is a simple injection example:
from yadi.context_impl import DEFAULT_CONTEXT from yadi.decorators import inject from yadi.types import Yadi @inject() class Component1: pass @inject() class Component2: def __init__(self, c1: Yadi[Component1]): self.c1 = c1 @inject() class Component3: def __init__(self, c1: Yadi[Component1]): self.c1 = c1 c2 = DEFAULT_CONTEXT.get_bean(Component2) # type: Component2 c3 = DEFAULT_CONTEXT.get_bean(Component3) # type: Component3 print(Component1 is type(c2.c1)) # True print(c2.c1 is c3.c1) # True
Here it is an example of how to inject functions:
from yadi.context_impl import DEFAULT_CONTEXT from yadi.decorators import inject from yadi.types import Yadi @inject() class Component: pass @inject(name='another_function') def h(x, y, z=None): assert isinstance(x, Component) print('Function h:', type(x)) @inject(name='my_function') def f(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'>
Scopes
By default, all the beans are saved as Singleton. Each singleton is stored in its context, that is, there is a single instance for 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.
from yadi import context from yadi import types from yadi.context_impl import DEFAULT_CONTEXT from yadi.decorators import inject @inject(scope=context.PROTOTYPE, name='a component 1') class Component1: pass @inject(name='a component 2') class Component2: def __init__( self, f1: types.Yadi[Component1], f2: types.Yadi['a component 1']): self.f1, self.f2 = f1, f2 @inject(name='a component 3') class Component3: def __init__( self, f1: types.Yadi[Component1], f2: types.Yadi['a component 1']): self.f1, self.f2 = f1, f2 c2 = DEFAULT_CONTEXT.get_bean('a component 2') # type: Component2 c3 = DEFAULT_CONTEXT.get_bean('a component 3') # type: Component3 print(isinstance(c2.f1, Component1)) # True print(isinstance(c2.f2, Component1)) # True print(isinstance(c3.f1, Component1)) # True print(isinstance(c3.f2, Component1)) # True print(c2.f1 == c2.f2) # False print(c3.f1 == c3.f2) # False print(c2.f1 == c3.f1) # False print(c2.f1 == c3.f2) # False print(c2.f2 == c3.f1) # False print(c2.f2 == c3.f2) # False
It is also possible to define custom scopes and add them to a context.
Here it is an example of thread-local scope:
import threading from yadi.context import Scope from yadi.context_impl import DEFAULT_CONTEXT from yadi.decorators import inject class ThreadLocalScope(Scope): def __init__(self): self._tl = threading.local() def get(self, key: str): return getattr(self._tl, key, None) def set(self, key: str, obj: object): setattr(self._tl, key, obj) @property def name(self): return 'threadlocal' @property def level(self): return 100 DEFAULT_CONTEXT.add_scope(ThreadLocalScope()) @inject(scope='threadlocal', name='a component 1') class Component1: pass c1 = DEFAULT_CONTEXT.get_bean('a component 1') c1_2 = DEFAULT_CONTEXT.get_bean('a component 1') thread_c1 = [] c1_t = None def _f(): global c1_t c1_t = DEFAULT_CONTEXT.get_bean('a component 1') print(c1_t == DEFAULT_CONTEXT.get_bean('a component 1')) # True thread_c1.append(c1_t) t = threading.Thread(target=_f) t.start() t.join() print(c1 == c1_2) # True print(c1 == c1_t) # False
Scoped proxies
Let’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 a level attribute: 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).
import random import threading from yadi.bean_factories import _ScopedProxy from yadi.context import Scope from yadi.context_impl import DEFAULT_CONTEXT from yadi.decorators import inject from yadi.types import Yadi class ThreadLocalScope(Scope): def __init__(self): self._tl = threading.local() def get(self, key: str): return getattr(self._tl, key, None) def set(self, key: str, obj: object): setattr(self._tl, key, obj) @property def name(self): return 'threadlocal' @property def level(self): return 100 DEFAULT_CONTEXT.add_scope(ThreadLocalScope()) @inject(scope='threadlocal') class Component1: def __init__(self): self.object_id = random.randint(0, 1000000) @inject(name='a component') class Component2: def __init__(self, f1: Yadi[Component1]): self.f1 = f1 component = DEFAULT_CONTEXT.get_bean('a component') component_thread_id = [] print('Main thread, scoped proxy type', type(component.f1) == _ScopedProxy) # Main thread, scoped proxy type True def _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 True print('Main thread, bean id', component.f1.object_id == component_thread_id[0]) # Main thread, bean id False
Contexts
All the components are kept in a context.
By default, the inject decorator keeps the beans instances in yadi.context_impl.DEFAULT_CONTEXT.
You might want to instantiate a new context and pass it as a context keyword argument of inject decorator.
Life cycle
It 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 with post_create, as follows:
from yadi.context_impl import DEFAULT_CONTEXT from yadi.decorators import inject, post_create from yadi.types import Yadi @inject() class Component1: pass @inject() class Component2: def __init__(self, c1: Yadi[Component1]): self.c1 = c1 self.invoked_post_create = 0 @post_create def finished_creating(self): print('Component 1:', self.c1) # Component 1: <__main__.Component1 object at 0x7f42e90d2e48> self.invoked_post_create += 1 component_2 = DEFAULT_CONTEXT.get_bean(Component2) # type: Component2 print('post_create invokations:', component_2.invoked_post_create) # post_create invokations: 1 DEFAULT_CONTEXT.get_bean(Component2) print('post_create invokations:', component_2.invoked_post_create) # post_create invokations: 1
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Filename, size | File type | Python version | Upload date | Hashes |
---|---|---|---|---|
Filename, size yadi-framework-0.0.6.tar.gz (8.3 kB) | File type Source | Python version None | Upload date | Hashes View |