Package: inseminator
Inseminator(definition from dictionary.com)a technician who introduces preparedsemendependenciesinto 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.InstallationInstall using the pip tool.pip install inseminatorUsageYou start by defining thecontainerof 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.frominseminatorimportContainerclassDomainModel:def__init__(self):self.__logic_constant=1defdomain_logic(self,input_value:int)->int:returninput_value+self.__logic_constantclassController:def__init__(self,domain_model:DomainModel):self.__domain_model=domain_modeldefhandler(self,input_value:int)->int:returnself.__domain_model.domain_logic(input_value)# entry-point of your applicationcontainer=Container()# view layer handlingcontroller=container.resolve(Controller)result=controller.handler(1)print(result)The strategy for resolvingControlleris its constructor signature. The resolver works as follows.We ask thecontainerto resolve a dependencyController->container.resolve(Controller).Resolver inside thecontainerchecks theController's constructor signature, i.e.type hintsof__init__method and seesdomain_models: DomainModel.If an instance ofDomainModelclass is already known by thecontainerit uses that instance.
In the opposite case, the container starts the same resolving machinery forDomainModel- which
is the exact case we are facing now.BecauseDomainModeldoesn't have any dependencies it can construct it directly.Now the resolver has all the dependencies forControllerconstructor and can instantiate it.If we programmed against an interface instead of implementation the example is modified like this.frominseminatorimportContainerfromtypingimportProtocolclassDomainModel(Protocol):defdomain_logic(self,input_value:int)->int:...classController:def__init__(self,domain_model:DomainModel):self.__domain_model=domain_modeldefhandler(self,input_value:int)->int:returnself.__domain_model.domain_logic(input_value)# domain model implementationclassConcreteDomainModel:def__init__(self):self.__logic_constant=1defdomain_logic(self,input_value:int)->int:returninput_value+self.__logic_constant# entry point of your applicationcontainer=Container()container.register(DomainModel,value=ConcreateDomainModel())# view layer handlingcontroller=container.resolve(Controller)result=controller.handler(1)print(result)In this situation, protocolDomainModeldoesn't hold implementation details, only interface.
Usingcontainer.register(DomainModel, value=ConcreateDomainModel())we're guiding the resolver to use instance ofConcreateDomainModelin case someone asks
forDomainModel.Enforced parametersIf 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 callcontainer.resolvealso 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 approachConcreteDomainModelis 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 onDomainModelwe must either useregisteror again specify
the parameter during resolving.Injecting functionsIt 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 isinjectordecorator on theContainerobject. You just
tell which dependency to provide usingDependstype constructor.frominseminatorimportContainer,DependsclassDependency:def__init__(self):self.x=1container=Container()@container.injectdefmy_handler(input_value:int,dependency:Dependency=Depends(Dependency)):returninput_value+dependency.xUsed like that,my_handlertakes a single argument and thanks to closure it hasdependencyprepared with the right instance ofDependency.>>> my_handler(1)
2Default parameter valuesWhen default parameters are specified the resolver uses them unless we override that value
by enforced parameter.defMyDependency:def__init__(self,parameter:int=1)->None:self.parameter=parametermy_dependency=container.resolve(MyDependency)assertmy_dependency.parameter==1DocsClean architecture introduction (draft version)