Skip to main content

ophiDIan


PyPI version Python Version from PEP 621 TOML Code style: black Imports: isort License GitHub pull requests pre-commit Checked with mypy codecov


⚠️ ATTENTION ⚠️

The ophidian-di package has been deprecated. Please use ophidian instead.

Description

ophiDIan is a Dependency Injection (DI) container for Python. Unlike other DI containers that utilize decorators or configuration files to accomplish their goal, ophiDIan uses type hints to identify and resolve dependencies. In other words, by using type hinting to resolve dependencies, ophiDIan avoids making your components dependent upon the DI framework.

Ophidian follows the Register, Resolve, Release (RRR) pattern.

Installation

Install with pip install ophidian-di

Tutorial

Dependency definitions

For this tutorial, we'll use the following class definitions:

from abc import ABC, abstractmethod


class AbstractA(ABC):
    @abstractmethod
    def do_something(self):
        pass


class AbstractB(ABC):
    @abstractmethod
    def do_something_else(self):
        pass


class DependencyA(AbstractA):
    def do_something(self):
        print(f"{id(self)}: I am dependency A")


class DependencyB(AbstractB):
    def __init__(self, name: str):
        self._name = name
    def do_something_else(self):
        print(f"{id(self)} My name is {self._name}")


class TestClass1:
    def __init__(self, a: AbstractA):
        self.a = a


class TestClass2:
    __test__ = False

    def __init__(self, b: AbstractB):
        self.b = b


class TestClass3:
    def __init__(self, a: AbstractA, b: AbstractB):
        self.a = a
        self.b = b

RRR

The first step in the RRR pattern is to Register dependencies. This can be performed using either the register() or register_instance() methods. Next, objects can be built by Resolving. Finally, when the dependency is no longer needed, it can be Released.

from ophidian import DIContainer

dependency_b_instance = DependencyB("Mike")

di_container = DIContainer()
di_container.register(AbstractA, DependencyA)
di_container.register_instance(AbstractB, dependency_b_instance)

test_class_1 = di_container.resolve(TestClass1)
test_class_2 = di_container.resolve(TestClass2)
test_class_3 = di_container.resolve(TestClass3)

assert id(test_class_1.a) != id(test_class_3.a)
assert id(test_class_2.b) == id(dependency_b_instance)
assert id(test_class_2.b) == id(test_class_3.b)
assert isinstance(test_class_1.a, AbstractA)
assert isinstance(test_class_1.a, DependencyA)
assert isinstance(test_class_2.b, AbstractB)
assert isinstance(test_class_2.b, DependencyB)

# Note: All dependencies will automatically be released when the `di_container`
#       instance is cleaned up by the garbage collector.
di_container.release(AbstractA)
di_container.release(AbstractB)

In the above example, the DIContainer.register() method is used tell the DI container that components that depend upon AbstractA should be supplied a new instance of the concrete class DependencyA. The DiContainer.register_instance() method is used to tell the DI container that all components that depend upon AbstractB should be supplied with the same instance of DependencyB.

Conventions

Conventions are mainly used to resolve primitive dependencies. For example, if a component depends upon a file path for a configuration file passed as a string, a convention can be registered to provide the dependency.

from ophidian import DIContainer

class Configuration:
    def __init__(self, a: DependencyA, config_file_path: str):
        ...

di_container = DIContainer()
di_container.register(AbstractA, DependencyA)
di_container.register_convention(str, "config_file_path", "/tmp/my_config_file")

configuration = di_container.resolve(Configuration)

di_container.release(AbstractA)
di_container.release_convention(str, "config_file_path")

We wouldn't want to use register() or register_instance(), as we wouldn't want all string dependencies to be resolved with the configuration file path. Instead, the DI container is informed about an established "convention" within the code: components that depend upon the configuration file path expect a string parameter named "config_file_path". See Primitive Dependencies by Mark Seemann for more information about conventions.

Documentation

DIContainer

class DIContainer()

A dependency injection (DI) container that uses type annotations to resolve and inject dependencies.

__init__

def __init__()

register

@no_type_check
def register(interface: Type[T], concrete_type: Type[T])

Register a concrete type that satisfies a given interface.

:param interface: An interface or abstract base class that other classes depend upon
:param concrete_type: A type (class) that implements interface
:raises TypeError: If concrete_type is not a class, or not a subclass of interface

register_instance

@no_type_check
def register_instance(interface: Type[T], instance: T)

Register a concrete instance that satisfies a given interface.

:param interface: An interface or abstract base class that other classes depend upon
:param instance: An instance (object) of a type that implements interface
:raises TypeError: If instance is not an instance of interface

register_convention

@no_type_check
def register_convention(type_: Type[T], name: str, instance: T)

Register an instance as a convention

At times — particularly when dealing with primitive types — it can be useful to define a convention for how dependencies should be resolved. For example, you might want any class that specifies hostname: str in its constructor to receive the hostname of the system it's running on. Registering a convention allows you to assign an object instance to a type, name pair.

Example:

    class TestClass:
        def __init__(self, hostname: str):
            self.hostname = hostname

    di_container = DIContainer()
    di_container.register_convention(str, "hostname", "my_hostname.domain")

    test = di_container.resolve(TestClass)
    assert test.hostname == "my_hostname.domain"

:param type_: The type (class) of the dependency
:param name: The name of the dependency parameter
:param instance: An instance (object) of type_ that will be injected into constructors that specify [name]: [type_] as parameters

Errors

UnresolvableDependencyError

class UnresolvableDependencyError(ValueError):

Raised when one or more dependencies cannot be successfully resolved.

Running the tests

Running the tests is as simple as poetry install && poetry run pytest

Release files for ophidian-di 0.1.0.post4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ophidian-di 0.1.0.post4
File Size Uploaded
ophidian_di-0.1.0.post4.tar.gz 18.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ophidian-di 0.1.0.post4
File Interpreter ABI Platform
ophidian_di-0.1.0.post4-py3-none-any.whl Python 3 none any Details

Total release size: 41.1 kB

Release files / ophidian_di-0.1.0.post4.tar.gz

Download URL ophidian_di-0.1.0.post4.tar.gz
Size 18.6 kB
Tags Source
SHA-256 checksum
How to use checksums
0342b9082ff7868a9909238a76e9190325b104917f84e2a66ec67bfea8048094
BLAKE2b-256 checksum
How to use checksums
b2392bb024ef13f21b3a5390194897bf158313f06fdce97fe40f2e9fa44e40b7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.5.1 CPython/3.10.12 Linux/5.15.0-87-generic

Release files / ophidian_di-0.1.0.post4-py3-none-any.whl

Download URL ophidian_di-0.1.0.post4-py3-none-any.whl
Size 22.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
22463be42142d12784eeb97367a86201be2654d067134c24c0d30ad058194127
BLAKE2b-256 checksum
How to use checksums
8a09d8dfd6d8c2a1960e61334c691098bfef286adf06883fc87ae4fbe0f9fcc2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.5.1 CPython/3.10.12 Linux/5.15.0-87-generic

Release history Release notifications | RSS feed

This release

0.1.0.post4 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page