Python really needed a modern reflection based dependency injection container that "just works". Alas, welcome to AutoContainer for python. The dependency injection service container that just works.
- Direct class as service
- Separate provider functions
- Service Behaviors:
- Singleton
- Factory
- Assembler
- Instance
- Naming Services
- Container Bound Functions
- Dependency Graph
- Automatic Injection
- Service registration checking
- Inject typehint by name
- Primitive types by name
pip install autocontainer
Requirements:
- Python >= 3.5
It's all about types and hints, but first create the container
from autocontainer import Container
container = Container()
# Party Time
We'll use singleton as an example.
class A:
pass
class B:
def __init__(self, obj_a: A):
assert isinstance(obj_a, A)
# Order does not matter.
container.singleton(B)
container.singleton(A)
obj_b = container.get(B)
assert isinstance(obj_b, B)
Note: The container actually contains itself as a singleton service (it's the first registered service), hence if you typehint
the Container
class or any derivateives, the container can and will inject itself.
class A:
pass
container.singleton(A, 'ayy')
obj_a = container.get(A)
obj_b = container.get('ayy')
assert obj_a is obj_b
obj_a = container.get(A) # <--- Best IDE Support due to type hints.
obj_b = container.get('ayy')
obj_c = container.ayy # <--- the most concise way.
obj_d = container('ayy')
You won't always put raw classes into the service container sometimes, it's necessary to write a function that custom initializes a class or object.
class A:
pass
class B:
def __init__(self):
self.fruit = 'tomato'
def makeB(obj_a: A) -> B: # Return type MUST be annotated
b = B()
b.fruit = 'mango'
return b
container.singleton(makeB)
obj_b = container(B)
assert obj_b.fruit == 'mango'
Factories can also take builder function as well as classes. The container returns a new instance every time.
class A:
pass
container.factory(A)
aa = container.get(A)
ab = container.get(A)
assert aa is not ab
assert isinstance(aa, A)
assert isinstance(ab, A)
This is the coolest feature, trust me. Imagine you have a function that needs both classes out of a container and vanilla arguments like int and str, this would be a pain to do manually. Unless...
class A:
pass
class B:
pass
container.singleton(A)
container.singleton(B)
def crazy_function(a: A, repeating: str, b: B, times: int):
assert isinstance(a, A)
assert isinstance(b, B)
return repeating * times
less_crazy_function = container.bind(crazy_function)
result = less_crazy_function("pew", 3)
assert result == 'pewpewpew'
This works for both classes (class constructors) and for functions.
Same as binding but for simpler times.
class A:
pass
class B:
pass
container.singleton(A)
container.singleton(B)
def crazy_function(a: A, b: B):
assert isinstance(a, A)
assert isinstance(b, B)
return 'potato'
assert container.inject(crazy_function) == 'potato'
The container maintains an internal graph of dependencies that allows it to efficiently push instances of ancestor classes.
class A:
pass
class B:
pass
class C(A):
pass
class D(C, B):
pass
container.factory(A)
container.singleton(B)
container.factory(C)
container.singleton(D)
obj = container.get(A)
assert isinstance(obj, D)
assert isinstance(obj, A)
This is completely valid with the container
class A:
pass
container.singleton(A, 'apple')
def magic(ap: 'apple'):
assert isinstance(ap, A)
container.inject(magic)
-
get(service: Union[Type, str])
Retreives a service -
has(service: Union[Type, str])
Returns if a service exists -
singleton(service: Union[Type, Callable[..., Instance]], name?: str)
Adds a service as a singleton into container. (Returns the same object on everyget
) -
factory(service: Union[Type, Callable[..., Instance]], name?: str)
Adds a service as a factory into container. (Returns a fresh object on everyget
) -
instance(service: object, name?: str)
Adds a service as an instance into container. (Returns the same object on everyget
, but does not try to instantiate) -
assembler(service: Union[Type, Callable[..., Instance]], name?: str)
Adds a service such that on every get, the container returns a bound callable that produces a fresh object everytime. -
bind(func: Callable)
Returns a new callable but in which the arguments recognized by the container are automatically pushed when calling. (see examples below) -
inject(func: Callable)
Takes a callable and calls it by injecting all the services it requires and then returns the return value.
python -m unittest discover -s ./
MIT. Go crazy.