Skip to main content

Type based python dependency injection framework.

Project description

imbue

tests PyPi python

Type based python dependency injection framework.

Why another dependency injection library?

There are many out there, including some that comes directly with (web) frameworks. I wanted one that was explicitly contextualized in the different layers of the application and that did not leak in the domain layer. In many cases, dependencies are simply parameters that are passed to constructors, and ideally, the domain layer should not know anything how they are injected.

Main objects

  • Container: a singleton containing all dependencies and the links between them
  • Dependency can be either:
    • an interface (class or function)
    • or a structured dependency linking an implementation to an interface
  • Context: contexts in which dependencies will live:
    • APPLICATION: equivalent to singletons across the app lifetime -- must be thread and async safe
    • THREAD: singleton per thread -- must be async safe
    • TASK: one instance will be provided for the entire task -- could be async safe
    • FACTORY: one instance will be provided every time it is requested
  • ContextualizedDependency: enriched dependency with context related attributes
  • ContextualizedContainer: one per context, handles their dependencies lifecycle
  • Provider: wraps injection for different interfaces (classes, functions, methods)
  • Package: provides for more dependency declaration and add grouped dependencies to the container

Usage

Summary

The container should be initialized somewhere near the entrypoint of the application.

from imbue import Container, Context, ContextualizedDependency

from myapp import APkg, ADep, BDep

container = Container(
    APkg(...),  # Packages would typically be passed config.
    ContextualizedDependency(ADep, Context.THREAD, eager=True),
)

...

# The application context should be entered through the framework initialization.
async with container.application_context() as app_container:
    a_dep = await app_container.get(ADep)
    ...
    # The task context should be entered in each request.
    async with app_container.task_context() as task_container:
        b_dep = await task_container.get(BDep)

[!TIP] A dependency can require other dependencies, the container will resolve the graph and raise errors if circular dependencies are found.

[!NOTE] For non async code, use container.sync_application_context.

In the details

The packages provide the dependencies for the container, there are two ways of doing that.

Complex dependencies

This might be because dependencies depend on config and other reasons. You can use the Package class:

from typing import AsyncIterator

from imbue import Package, application_context

from myapp import LoggingConfig, MyLoggerTransport, LoggerTransport, MyLogger, Logger

class LoggingPackage(Package):
    def __init__(self, config: LoggingConfig):
        self._config = config
        
    @application_context(eager=True)  # You can autoload dependencies.
    # The method should expose the interface as the type and return the implementation.
    def logger_transport(self) -> LoggerTransport:
        return MyLoggerTransport(self._config.transport)

    @application_context
    # Sub dependencies should be required through their interface.
    async def logger(self, transport: LoggerTransport) -> AsyncIterator[Logger]:
        # Package methods also allow you to handle context managers.
        async with MyLogger(transport, self._config.logger) as logger:
            # Using generators allow cleaning up resources when the context closes.
            yield logger
Declaring dependencies

It's done via methods that can be:

  • functions
  • async functions
  • generators
  • async generators

To declare the method returns a dependency, you simply choose the appropriate context and add the context decorator on the method.

[!TIP] Eager dependencies are instantiated as soon as we enter their context. This is useful if we want to make sure a dependency will use the main thread's event loop.

Cleaning resources

When you need to close resources you can do so via a generator. The generator should yield the dependency.

[!WARNING] You should watch out for errors, if the context is closed handling an error, it will be raised in the generator.

Simple dependencies

You can directly pass them to the container:

Container(
    # Directly expose the implementation.
    ARepo,
    # Or interfaced implementations.
    Interfaced(BRepoInterface, BRepoImplementation),
    # You can also this to control context and eagerness.
    ContextualizedDependency(CDep, Context.THREAD, eager=True),
    ...,
)

or if you want to include them in a Package, add them in EXTRA_DEPENDENCIES.

[!NOTE] Whatever the method of adding dependencies, non-contextualized ones will have the context automatically set based on sub-dependencies. The lowest possible context will be used.

Integrations

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

imbue-2.2.0.tar.gz (13.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

imbue-2.2.0-py3-none-any.whl (21.7 kB view details)

Uploaded Python 3

File details

Details for the file imbue-2.2.0.tar.gz.

File metadata

  • Download URL: imbue-2.2.0.tar.gz
  • Upload date:
  • Size: 13.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for imbue-2.2.0.tar.gz
Algorithm Hash digest
SHA256 7e9748644d930ca8b535cf608fb873cee10d7a95f32a9c2efdea9d52da43a35e
MD5 5d0c7c8ebee4d63319b3cbe000a8357a
BLAKE2b-256 a866f22beb5be800dfee11864fedd50cc925201450afede42e4db499bc2d60fe

See more details on using hashes here.

Provenance

The following attestation bundles were made for imbue-2.2.0.tar.gz:

Publisher: publish.yml on gpajot/imbue

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file imbue-2.2.0-py3-none-any.whl.

File metadata

  • Download URL: imbue-2.2.0-py3-none-any.whl
  • Upload date:
  • Size: 21.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for imbue-2.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4f8dff7e42f92b7758d86aeabc8ca042a465432da91ce317cfd6d85033f113a3
MD5 45e0b945e7199fdbb61ba3756cf5d180
BLAKE2b-256 6419e4306c000b7c5c2e01121105329b9b5daf6a2d779e09d37544f96cf99f5b

See more details on using hashes here.

Provenance

The following attestation bundles were made for imbue-2.2.0-py3-none-any.whl:

Publisher: publish.yml on gpajot/imbue

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page