https://github.com/fcracker79/yadi
Yet Another Dependency Injection framework
https://github.com/fcracker79/yadi
Last synced: 5 months ago
JSON representation
Yet Another Dependency Injection framework
- Host: GitHub
- URL: https://github.com/fcracker79/yadi
- Owner: fcracker79
- License: mit
- Created: 2018-03-08T19:39:30.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2018-12-16T17:50:43.000Z (over 7 years ago)
- Last Synced: 2025-08-24T23:19:50.447Z (10 months ago)
- Language: Python
- Size: 29.3 KB
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://travis-ci.org/fcracker79/yadi)
[](https://img.shields.io/pypi/v/yadi-framework.svg)
YADI
=================================================
Yet Another Dependency Injection framework
YADI is a dependency injection framework.
It supports both classes and function in a declarative fashion.
Installation
------------
```sh
pip install yadi-framework
```
Basic examples
--------------
This is a simple injection example:
[//]: # (tmp/readme_md_1.py)
```python
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:
[//]: # (tmp/readme_md_2.py)
```python
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:
```
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.
[//]: # (tmp/readme_md_3.py)
```python
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:
[//]: # (tmp/readme_md_3.py)
```python
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).
[//]: # (tmp/readme_md_5.py)
```python
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:
[//]: # (tmp/readme_md_5.py)
```python
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
```