Dependency injection tool


Keywords
di, dependency, injection, ioc, inversion, of, control, inject, dependency-injection, python
License
MIT
Install
pip install injectool==2.0.2

Documentation

injectool

example branch parameter Codacy Badge Codacy Badge image image image Downloads

Lightweight dependency injection tool.

Installation

Install using pip:

pip install injectool

How to use

Injecting

https://github.com/eumis/injectool/blob/dev/injectool/injection.py

resolve()

import injectool

instance = injectool.resolve(SomeClass)
function = injectool.resolve(some_function)
value = injectool.resolve('some_value')

inject decorator

import injectool
from typing import Callable

class DependenciesUser:
    @injectool.inject(instance=SomeClass)
    def __init__(self, instance: SomeClass = injectool.In):
        pass

    @injectool.inject(function=some_function)
    def some_method(self, function: Callable = injectool.In):
        pass

@injectool.inject(value='some_value')
def use_some_value(value: int = injectool.In):
    pass

dependency decorator

import injectool

@injectool.dependency
def some_function():
    return 'some_function'

def some_function_implementation():
    return 'some_function implementation'

injectool.add_singleton(some_function, some_function_implementation)

value = some_function() # value: some_function implementation

Dependencies

https://github.com/eumis/injectool/blob/dev/injectool/resolvers.py

Singleton

import injectool

injectool.add_singleton('some_value', 54)
some_value = injectool.resolve('some_value')

injectool.add_singleton(SomeClass, SomeClassImplementation())
instance: SomeClass = injectool.resolve(SomeClass)

Type

New instance is created for every resolving.

import injectool

injectool.add_type(SomeClass, SomeClassImplementation)

instance = injectool.resolve(SomeClass)

Scoped

One instance is created per scope.

import injectool

injectool.add_scoped(SomeClass, SomeClassImplementation)

with injectool.scope():
    instance1: SomeClass = injectool.resolve(SomeClass)

with injectool.scope():
    instance2: SomeClass = injectool.resolve(SomeClass)

Dispose method can be passed to add_type method. The method will be called on closing scope.

import injectool

def dispose(instance: SomeClassImplementation):
    pass

injectool.add_scoped(SomeClass, SomeClassImplementation, dispose)

with injectool.scope():
    injectool.resolve(SomeClass)

Thread

One instance is created per thread.

import injectool
from threading import Thread
from concurrent.futures.thread import ThreadPoolExecutor

injectool.add_per_thread(SomeClass, SomeClassImplementation)

one = injectool.resolve(SomeClass)

def thread_target():
    two = injectool.resolve(SomeClass)

thread = Thread(target=thread_target)
thread.start()


with ThreadPoolExecutor(max_workers=1) as executor:
    future = executor.submit(injectool.resolve, SomeClass)
    three = future.result()

Custom resolver

import injectool

injectool.add('some_value', lambda: 54)
some_value = injectool.resolve('some_value')

injectool.add(SomeClass, lambda: SomeClassImplementation())
instance: SomeClass = injectool.resolve(SomeClass)

How it works

All dependencies are stored in Container.

Basically Container is just a dictionary with Dependency used as a key and Resolver used as a value.

Any object can be used as Dependency.

Resolver is function that returns value for a Dependency.

https://github.com/eumis/injectool/blob/dev/injectool/core.py#L12-37

Dependency = Any
Resolver = Callable[[], Any]


class Container:
    """Container for dependencies"""

    def __init__(self, resolvers: Optional[Dict[Dependency, Resolver]] = None):
        self._resolvers: Dict[Dependency, Resolver] = {} if resolvers is None else resolvers
        self.set(Container, lambda: self)

    def set(self, dependency: Dependency, resolve: Resolver):
        """Sets resolver for dependency"""
        self._resolvers[dependency] = resolve

    def resolve(self, dependency: Dependency) -> Any:
        """Resolve dependency"""
        resolve = self._resolvers.get(dependency)
        if resolve is None:
            dependency_name = dependency.__name__ if hasattr(dependency, '__name__') else str(dependency)
            raise DependencyError(f'Dependency "{dependency_name}" is not found')
        return resolve()

    def copy(self) -> 'Container':
        """returns new container with same dependencies"""
        return Container(self._resolvers.copy())

Default container is stored as global variable and used by default. Default container can be changed.

https://github.com/eumis/injectool/blob/dev/injectool/core.py#L40-45

_DEFAULT_CONTAINER = Container()

def set_default_container(container: Container):
    """Sets default container"""
    global _DEFAULT_CONTAINER
    _DEFAULT_CONTAINER = container

Current container can be set and used temporary. It's stored in ContextVar so it can be used in asynchronous code.

https://github.com/eumis/injectool/blob/dev/injectool/core.py#L48-66

_CURRENT_CONTAINER = ContextVar('dependency_container')

def get_container() -> Container:
    """Returns current container"""
    return _CURRENT_CONTAINER.get(_DEFAULT_CONTAINER)


@contextmanager
def use_container(container: Optional[Container] = None) -> Generator[Container, None, None]:
    """
    Uses passed container for registering and resolving dependencies
    Creates new if container doesn't exist.
    """
    container = container if container else Container()
    reset_token = _CURRENT_CONTAINER.set(container)
    try:
        yield container
    finally:
        _CURRENT_CONTAINER.reset(reset_token)

License

MIT

Copyright (c) 2017-present, eumis (Eugen Misievich)