Skip to content

Interface Injection

You can use abstract classes as interfaces when you need to inject dependencies. This pattern is particularly useful for testing, as it allows you to create mock implementations.

Basic Usage

Register a class as an interface using the @abstract decorator. Then implement and register concrete classes that inherit from it. Note that the class marked with @abstract doesn't actually have to inherit abc.ABC.

from wireup import abstract, container, service


@abstract
class Engine(abc.ABC):
    @abc.abstractmethod
    def get_type(self) -> EngineType:
        raise NotImplementedError


@service
class CombustionEngine(Engine):
    @override
    def get_type(self) -> EngineType:
        return EngineType.COMBUSTION


@wireup.inject_from_container(container)
def target(engine: Engine):
    engine_type = engine.get_type()  # Returns EngineType.COMBUSTION

Multiple Implementations

Use qualifiers to distinguish between different implementations of the same interface:

@service(qualifier="electric")
class ElectricEngine(Engine):
    def get_type(self):
        return EngineType.ELECTRIC


@service(qualifier="combustion")
class CombustionEngine(Engine):
    def get_type() -> EngineType:
        return EngineType.COMBUSTION


@wireup.inject_from_container(container)
def target(
    electric: Annotated[Engine, Inject(qualifier="electric")],
    combustion: Annotated[Engine, Inject(qualifier="combustion")],
):
    ...

Tip

Qualifiers can be any hashable value, including enum members.

Default Implementation

To set a default implementation, register one class without a qualifier:

@service  # Default implementation
class ElectricEngine(Engine):
    pass

@service(qualifier="combustion")
class CombustionEngine(Engine):
    pass

When injecting Engine without a qualifier, the container will use the default implementation (ElectricEngine in this example). Use qualifiers to access other implementations.