Skip to main content

Python IOC Container

Project description

pyioc3

Python Inversion of Control (IoC) Container

About

pyioc3 is a lightweight and versatile Inversion of Control (IoC) container for Python. It simplifies the management of dependencies and enables cleaner, more modular code by promoting the use of Dependency Injection (DI) patterns. With pyioc3, you can effortlessly wire up your application's components, specify their lifecycles, and inject dependencies without the hassle of manual instantiation.

Key Features

Multiple APIs

pyioc3 offers multiple configuration interfaces including two manual configuration APIs and a decorator-based autowiring API. These APIs are flexible, yet simple to use.

Fluent Interface

pyioc3 manual APIs support method chaining to minimize boiler-plate code and intermediate variables.

Scoped Lifecycles

pyioc3 supports various scopes, including Singleton, Transient, and Requested. This provides fine-grained control of the lifecycle of your objects.

Predictability

pyioc3 is an immutable ioc container that guarentees predictability of the dependency tree.

Constuctor Based DI

pyioc3 implements constructor based dependency injection making it easy to drop into existing applications.

Performance

pyioc3 pre-computes the dependency tree, resulting in fast instantiations to keep your code fast.

OOP Principles

pyioc3 is designed to improve the maintainability, testability, and flexibility of your Python applications.

Quick Start

Install the package

pip install --user pyioc3

StaticContainerBuilder API

from pyioc3 import StaticContainerBuilder

container = (
    StaticContainerBuilder()
    .bind(Duck)
    .bind(QuackProvider, Squeak)
    .build()
)

duck = container.get(Duck)
duck.quack()

BuilderBase API

from pyioc3.builder import BuilderBase, ProviderBinding

class DuckBuilder(Duck):
    def __init__(self):
        super().__init__(target_t=Duck)

    def with_quack_noise(self, noise: str) -> "DuckBuilder":
        self.using_provider(
            annotation=QuackProvider,
            implementation=DynamicQuackProvider,
            on_activate=lambda x: x.set_quack_noise(noise)
        )
        return self

rubber_duck = (
    DuckBuilder()
    .with_quack_noise("Squeak")
    .build()
)

rubber_duck.quack()

Annotation API

from pyioc3.autowire import bind, AutoWireContainerBuilder


class QuackProvider:
    def quack(self):
        raise NotImplementedError()


@bind()
class Duck:
    def __init__(self, quack: QuackProvider):
        self._quack = quack

    def quack(self):
        self._quack.quack()


@bind(QuackProvider)
class Squeak(QuackProvider):

    def quack(self):
        print("Squeak")


duck = AutoWireContainerBuilder("my_package").build().get(Duck)
duck.quack()

API Documentation

Terms

Provider

A class that provides an implementation of some contract.

Scope

A rule that defines when a provider is instanciated.

Transient: A instanciation rule that causes a new instance of a provider to be created for each dependent reference. Requested: A instanciation rule that causes a new instance of a provider to be created once for each request recieved by the IOC container. Singleton: A instanciation rule that causes a new instance of a provider to be created only once.

Scope Examples:

Consider the given dependency tree.

  • Class A depends on Class B and Class C.
  • Class B depends on Class C.
  • Class C has no dependents.

Transient Scope

If class C is bound with transient scope, class A and B will have a unique instance of C each time they are constructed.

a = ioc.get(A)
assert a.c is not a.b.c

a_again = ioc.get(A)
assert a_again.c is not a.c
assert a_again.b.c is not a.b.c

Requested Scope

If class C is bound with requested scope, class A and B will share an instance of C each time they are constructed.

a = ioc.get(A)
assert a.c is a.b.c

a_again = ioc.get(A)
assert a_again.c is not a.c
assert a_again.b.c is not a.b.c

Singleton Scope

If class C is bound with singleton scope, class A and B will always recieve the same instance of C.

a = ioc.get(A)
assert a.c is a.b.c

a_again = ioc.get(A)
assert a_again.c is a.c
assert a_again.b.c is a.b.c

References

pyioc3.interface

PROVIDER_T

A Generic TypeVar used to identify the type, concrete or abstract, of an injectable.

FACTORY_T

A Generic TypeVar used to identify a factory method.

FactoryBinding

Represents a binding for providing instances created by factory functions.

FactoryBinding.factory:

Any callable that accepts a Container and returns another callable.

FactoryBinding.annotion:

Any Type or forward reference used to uniquely identify this injectable.

ProviderBinding

Represents a binding for providing instances through dependency injection.

ProviderBinding.annotation:

Any Type or forward reference used to uniquely identify this injectable.

ProviderBinding.implementation:

Any Class or Class-Like reference used as the injectable.

ProviderBinding.scope:

Any ScopeEnum or str (one of "singleton", "transient", "requested") that identifies the instantiation strategy.

ProviderBinding.on_activate:

Any callable that accepts an instance of ProviderBinding.implementation and returns the same instance.

ConstantBinding

ConstantBinding.annotation:

Any Type or forward reference used to uniquely identify this injectable.

ConstantBinding.value:

Any value used as the injectable.

Binding

A type union of FactoryBinding, ProviderBinding, and ConstantBinding.

Container

An IOC Container

Container.get:

Retrieve an instance of the specified annotation from the container.

Args:

  • annotation: The annotation (provider) for which an instance is requested.

Returns:

  • PROVIDER_T: An instance of the specified annotation.

Raises:

  • MemberNotBoundError: If the requested annotation is not bound in the container.

ContainerBuilder

Bind classes, values, functions, and factories to a container.

ContainerBuilder.bind:

Bind a class.

Bind any callable type to an annotation. Dependencies will be injected into this object as needed when created.

Scoping can be set to control reuse.

Arguments:

  • annotation: The hint used to inject an instance of implementation
  • implementation: (Optional) A callable type who's result will be stored return and stored according to the scope. If implementation is not inlcuded Annotation will be used in it's place.
  • scope: (Optional) Identifies how the object should be cached. Options are Transient, Requested, Singleton Default: Transient.
  • on_activate: (Optional) A function that will be called with the constructed implementation before it is used as a dep or given as the return in container.get() Default: None.

Scopes:

  • Transient scopes and not cached.
  • Requested scopes are cached during the current execution of a container.get call.
  • Singleton scopes are only instanced once and cached for the lifetime of the container.

Returns:

  • An instance of the ContainerBuilder

ContainerBuilder.bind_constant:

Bind a constant value

This allows you to bind any object to an annotation in a singleton scope.

Arguments:

  • annotation: The hint used to inject the constant
  • value: Any value. Object, function, type, anything.

Returns:

  • An instance of the ContainerBuilder

ContainerBuilder.bind_factory:

Bind a higher order function

This approach allows you to control the creation of objects and gives you access to the container. This lets you make runtime decision about how to create an instance.

Arguments:

  • annotation: The hint used to inject the factory
  • factory: A higher order function that accepts the StackContainer as an arugment.

Returns:

  • An instance of the ContainerBuilder

ContainerBuidler.build:

Compute dependency graph and return the container

This call will roll over all the objects and compute the dependants of each member. The container itself is also added to the graph and can thus be injected using it's Type as the annotation.

Returns:

  • A Container

pyioc3.scope_enums

ScopeEnum

ScopeEnum is an enumeration class representing different dependency scopes.

ScopeEnum.TRANSIENT:

Indicates a transient scope where a new instance is created for each request.

ScopeEnum.REQUESTED:

Indicates a requested scope where a single instance is created for the duration of a request (a single call to Container.get), typically used in web applications.

ScopeEnum.SINGLETON:

Indicates a singleton scope where a single instance is created and shared across the entire application.

pyioc3.static_container_builder

StaticContainerBuilder

Implements ContainerBuilder to allows the caller to staticly bind classes, values, functions, and factories to a container.

StaticContainerBuilder.__init__:

Create a StaticContainerBuilder.

Arguments:

  • bindings: (Optional) A list of default binidngs.

Returns:

  • A new ContainerBuilder

pyioc3.builder

BuilderBase

Base class for building instances with dependency injection.

This class creates a new dependency tree each time build() it is run. This means that there is no distinction between SINGLETON and REQUESTED scope as the the underlaying container will only be activated once.

BuilderBase.__init__:

Initialize the builder with optional bindings.

The provided target_t will be automatically bound as a default. If included in bindings, the bindings entry will be used.

Arguments:

  • target_t: Type produced by this builder.
  • bindings: (Optional) A list of bindings.

BuilderBase.using_provider:

Register a provider for dependency injection.

Arguments:

  • annotation: The annotion target.
  • implementation: The optional implementation class. If not provided, The annotation is used as the implementation.
  • scope: The optional scope of the provider. Defaults to Requested.
  • on_activate: An optional activation callback.

Returns:

  • BuilderBase: The builder instance.

BuilderBase.using_constant:

Register a constant for dependency injection.

Arguments:

  • annotation: The annotion target.
  • value: The constant value.

Returns:

  • BuilderBase: The builder instance.

BuilderBase.using_factory:

Register a factory function for dependency injection.

Arguments:

  • annotation: The annotion target.
  • factory: The factory function.

Returns:

  • BuilderBase: The builder instance.

BuilderBase.build:

Build an instance of the specified target type using the registered dependencies.

Returns:

  • TARGET_T: The built instance.

...

pyioc3.autowire

AutoWireContainerBuilder

Implements ContainerBuilder to allows the caller to automatically and staticaly bind classes, values, functions, and factories to a container.

This class facilitates the creation of an IoC container by automatically scanning and binding dependencies from the provided modules.

AutoWireContainerBuilder.__init__:

Initialize a new AutoWireContainerBuilder.

Arguments:

  • modules: A list of module references (either module names or module objects) to be scanned for dependencies. If a single module reference is provided, it will be treated as a list with a single element.
  • excludes: A list of module names to be excluded from scanning. Modules listed here will not be considered for dependency binding. If a single module name is provided, it will be treated as a list with a single element. If not specified or set to an empty list, no modules will be excluded.

bind:

Decorator for binding a class for use in dependency injection.

Arguments:

  • annotation: The interface or annotation to which the class should be bound as a provider. If not provided, the implementation class itself will be used as the annotation.
  • scope: The scope in which the provider instances should be created and managed. It can be one of the values from the ScopeEnum enumeration, such as "singleton," "transient," or "requested." If not specified, the default scope, "transient" will be used.
  • on_activate: An optional callback function to be executed when instances of the provider class are activated or retrieved from the container. This function can perform additional initialization or configuration on the provider instance.

Returns:

  • A decorator function that can be used to annotate a class as a provider for the specified interface.

bind_factory:

Decorator for binding a function factory for use in dependency injection.

This decorator allows you to bind a function factory to an interface or annotation, indicating that the factory should be used to create instances of the specified type. Function factories are used to construct instances with custom initialization or configuration.

Arguments:

  • annotation: The interface or annotation to which the factory should be bound.

Returns:

  • A decorator function that can be used to annotate a function as a factory for the specified interface or annotation.

pyioc3.errors

PyIOC3Error

Base class for all custom PyIOC3 exceptions.

CircularDependencyError

Raised if the dependency tree contains cycles.

ScopeError

Raised if a string-based scope is not valid.

AutoWireError

Raised if the autowire api detects duplicate annotations.

MemberNotBoundError

Raised if a member is requested but not bound.

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

pyioc3-1.6.3.tar.gz (24.6 kB view details)

Uploaded Source

Built Distribution

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

pyioc3-1.6.3-py3-none-any.whl (23.3 kB view details)

Uploaded Python 3

File details

Details for the file pyioc3-1.6.3.tar.gz.

File metadata

  • Download URL: pyioc3-1.6.3.tar.gz
  • Upload date:
  • Size: 24.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for pyioc3-1.6.3.tar.gz
Algorithm Hash digest
SHA256 b1419437f030ac7d5a655484d3b0d6ba8eecfd7542fb127c89c02ed15a954152
MD5 88a105123eab0348da83744e1d65945a
BLAKE2b-256 1c51cacb711bf840f0125f2236186244c501b852ea4ef249f74349094e92bf70

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyioc3-1.6.3.tar.gz:

Publisher: pypi.yaml on en0/pyioc3

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

File details

Details for the file pyioc3-1.6.3-py3-none-any.whl.

File metadata

  • Download URL: pyioc3-1.6.3-py3-none-any.whl
  • Upload date:
  • Size: 23.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for pyioc3-1.6.3-py3-none-any.whl
Algorithm Hash digest
SHA256 2d07a8231c6e6fcbcad622c061c26f1e946c215942287bca2aaff6e98e472893
MD5 d7a5826e2fb4e7f00a9c7ee504d717c8
BLAKE2b-256 fdd198e8b2b7e1429a86a4d9f5da83bad8182c5612a94c9b9380dae741fc53ed

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyioc3-1.6.3-py3-none-any.whl:

Publisher: pypi.yaml on en0/pyioc3

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