Python dependency injection library based on type hints
Project description
Inseminator
(definition from dictionary.com)
a technician who introduces prepared
semendependencies into thegenital tract of breeding animalspython classes, especiallycows and marespure classes with proper IoC, forartificial inseminationwell coupled components and clear classes signatures.
Python library for type-based dependency injection. Write code without global state and noisy boilerplate. Inseminator is meant to be used in an entry-point layer of your application and the only thing it requires is properly type-hinted classes dependencies.
Installation
Install using the pip tool.
pip install inseminator
Usage
You start by defining the container of your dependencies. Whenever you want the container to resolve a dependency, it uses the container to search for existing objects and a resolver automatically creates desired dependencies.
from inseminator import Container
class DomainModel:
def __init__(self):
self.__logic_constant = 1
def domain_logic(self, input_value: int) -> int:
return input_value + self.__logic_constant
class Controller:
def __init__(self, domain_model: DomainModel):
self.__domain_model = domain_model
def handler(self, input_value: int) -> int:
return self.__domain_model.domain_logic(input_value)
# entry-point of your application
container = Container()
# view layer handling
controller = container.resolve(Controller)
result = controller.handler(1)
print(result)
The strategy for resolving Controller
is its constructor signature. The resolver works as follows.
- We ask the
container
to resolve a dependencyController
->container.resolve(Controller)
. - Resolver inside the
container
checks theController
's constructor signature, i.e. type hints of__init__
method and seesdomain_models: DomainModel
. - If an instance of
DomainModel
class is already known by thecontainer
it uses that instance. In the opposite case, the container starts the same resolving machinery forDomainModel
- which is the exact case we are facing now. - Because
DomainModel
doesn't have any dependencies it can construct it directly. - Now the resolver has all the dependencies for
Controller
constructor and can instantiate it.
If we programmed against an interface instead of implementation the example is modified like this.
from inseminator import Container
from typing import Protocol
class DomainModel(Protocol):
def domain_logic(self, input_value: int) -> int:
...
class Controller:
def __init__(self, domain_model: DomainModel):
self.__domain_model = domain_model
def handler(self, input_value: int) -> int:
return self.__domain_model.domain_logic(input_value)
# domain model implementation
class ConcreteDomainModel:
def __init__(self):
self.__logic_constant = 1
def domain_logic(self, input_value: int) -> int:
return input_value + self.__logic_constant
# entry point of your application
container = Container()
container.register(DomainModel, value=ConcreateDomainModel())
# view layer handling
controller = container.resolve(Controller)
result = controller.handler(1)
print(result)
In this situation, protocol DomainModel
doesn't hold implementation details, only interface.
Using
container.register(DomainModel, value=ConcreateDomainModel())
we're guiding the resolver to use instance of ConcreateDomainModel
in case someone asks
for DomainModel
.
Enforced parameters
If it is not desired to provide a single concrete implementation for abstract or protocol dependency
one can enforce the resolver to use concrete types for specified parameters. Simply call container.resolve
also with keywords and tell the resolve how it should resolve some particular parameters.
container = Container()
controller = container.resolve(Controller, domain_model=ConcreteDomainModel())
Moreover, using this approach ConcreteDomainModel
is not evaluated and saved in the container but
rather in a sub-container which exists only during the resolving. Therefore, if we want to create
another instance that depends on DomainModel
we must either use register
or again specify
the parameter during resolving.
Injecting functions
It might be convinient to specify funcion's dependencies in-place. The great example is Flask
handler function. It should live in the same layer the DI container lives because it provides
only infrastructure functionality and desirably the only thing it does it calling domain layer's
functions. For this purpose, there is injector
decorator on the Container
object. You just
tell which dependency to provide using Depends
type constructor.
from inseminator import Container, Depends
class Dependency:
def __init__(self):
self.x = 1
container = Container()
@container.inject
def my_handler(input_value: int, dependency: Dependency = Depends(Dependency)):
return input_value + dependency.x
Used like that, my_handler
takes a single argument and thanks to closure it has dependency
prepared with the right instance of Dependency
.
>>> my_handler(1)
2
Default parameter values
When default parameters are specified the resolver uses them unless we override that value by enforced parameter.
def MyDependency:
def __init__(self, parameter: int = 1) -> None:
self.parameter = parameter
my_dependency = container.resolve(MyDependency)
assert my_dependency.parameter == 1
Docs
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file inseminator-0.4.7.tar.gz
.
File metadata
- Download URL: inseminator-0.4.7.tar.gz
- Upload date:
- Size: 7.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.1.11 CPython/3.10.0 Linux/5.11.0-1020-azure
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | b82b7adf07aff48971b90824d7a5e68e6f89308c2195821fd55300f6ab6031b1 |
|
MD5 | c69c3a4b487b4e1caea67b2d42fddf21 |
|
BLAKE2b-256 | f537faeeaf2730d0e73bbbf5b74b485a206fb8e3234d31f94db3b03548bee6f3 |
File details
Details for the file inseminator-0.4.7-py3-none-any.whl
.
File metadata
- Download URL: inseminator-0.4.7-py3-none-any.whl
- Upload date:
- Size: 8.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.1.11 CPython/3.10.0 Linux/5.11.0-1020-azure
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 221682d4db3f5b92174f0ee1b81dc11e11c71618383a8a03e022fe6dcd991629 |
|
MD5 | c44fde8cbe6b17233a4cb8420585224d |
|
BLAKE2b-256 | 83d4496b5ff885d205a15e4c95b24d25a049abf2b43e83dec447469fa2a9b8e4 |