Basic 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. 1) We ask the ``container`` to resolve a dependency ``Controller`` -> ``container.resolve(Controller)``. 2) Resolver inside the ``container`` checks the ``Controller``'s constructor signature, i.e. **type hints** of ``__init__`` method and sees ``domain_models: DomainModel``. 3) If an instance of ``DomainModel`` class is already known by the ``container`` it uses that instance. In the opposite case, the container starts the same resolving machinery for ``DomainModel`` - which is the exact case we are facing now. 4) Because ``DomainModel`` doesn't have any dependencies it can construct it directly. 5) 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``.