hazrakah#

class Container(outer_scope=None, frozen=False)#

Bases: DependencyRegistry, ScopedDependencyResolver, DependencyResolver

A dependency-injection container that supports hierarchical scopes and deterministic destruction.

Containers track every object they directly instantiate (via __create_instance) in an internal set. When a container is used as a context manager (either directly or via create_scope(), all tracked objects are torn down by calling their close() method at the end of the scope.

Example (Basic)#

container = Container()
container.register_transient(Foo)
foo = container.resolve(Foo)

# supports scopes:
scoped_container = container.create_scope()
scoped_container.register_transient(Foo)
foo = scoped_container.resolve(Foo)

Example (Fluent)#

Registrations return self, so they can be chained. The following demonstrates this:

container = (
    Container()
    .register_singleton(Greeter, GreeterImpl)
    .register_transient(Formatter)
)
greeter = c.resolve(Greeter)

Example (Context Manager)#

The container can be used as a context manager for both direct scoping and nested scopes:

with Container() as container:
    container.register_transient(Foo)
    with Container() as scoped:
        scoped.register_transient(Bar)
        foo = scoped.resolve(Foo)
        assert foo is not None
    bar = container.resolve(Bar)  # raises Error (not in scope)
    assert foo.is_closed is True  # context manager scope closes
ivar outer_scope:

The parent container, or None for the root.

vartype outer_scope:

Optional[Container]

__enter__()#

Return self to enable context manager usage.

Return type:

Container

__exit__(exc_type, exc_val, exc_tb)#

Destroy all tracked instances.

Every object that was directly instantiated by this container (via __create_instance) is torn down by calling its close() method, if present. The tracked set is then cleared so no object is double-cleaned.

Parameters:
  • exc_type (Optional[type[BaseException]]) – (OPTIONAL) Exception type, if one was raised inside the with block.

  • exc_val (Optional[BaseException]) – (OPTIONAL) The exception instance, if one was raised.

  • exc_tb (Optional[TracebackType]) – (OPTIONAL) Traceback object, if one was raised.

Return type:

None

is_registered(t)#

Check if type t has a registration already.

Parameters:

t (Type[Any]) – The type to check for.

Return type:

bool

Returns:

True if a registration exists.

register_instance(t, instance=None)#

Register a pre-existing instance for type t.

Parameters:
  • t (Union[Type[Any], object]) – The type to bind the instance to.

  • instance (Optional[Any]) – (OPTIONAL) An object that must be an instance of t. Omit to construct a t instance automatically.

Return type:

Container

Returns:

self for method chaining.

Raises:

TypeError – When instance is not an instance of t.

Note on @provides#

The @provides decorator on the target type is only discovered when *instance* is omitted (no explicit second argument). In that case, the container discovers __hazrakah_provides metadata and multi-registers the target under every provided interface.

When instance IS provided (explicit registration), @provides metadata on the target class is completely ignored. Only the type specified by t is registered. This applies regardless of whether the concrete class implements additional interfaces via @provides.

register_singleton(t, target=None)#

Create a SINGLETON type registration for type t.

Every resolve of t will result in a single, shared instance of t.

Parameters:
  • t (Type[Any]) – The type to register for.

  • target (Union[Type[Any], Callable[[DependencyResolver], Any], None]) – The type or factory to be used when resolving type t. Omit to use t as the target (requires t to be a concrete type.)

Return type:

Container

Returns:

self for method chaining.

Note on @provides#

The @provides decorator on target is only discovered when *target* is omitted (no explicit second argument). In that case, the container discovers __hazrakah_provides metadata and multi-registers t under every provided interface.

When target IS provided (explicit registration), @provides metadata on the target class is completely ignored. Only the type specified by t is registered. This applies regardless of whether the concrete class implements additional interfaces via @provides.

Examples#

# @provides activates – multi-registers under IFoo, IBar, and MyImpl:

@provides(IFoo, IBar) class MyImpl: … c.register_singleton(MyImpl) # no second arg

# @provides does NOT activate – only IFoo is registered:

c.register_singleton(IFoo, MyImpl) # explicit type override

register_transient(t, target=None)#

Create a TRANSIENT type registration for type t.

Every resolve of t will result in a new instance of t.

Parameters:
  • t (Type[Any]) – The type to register for.

  • target (Union[Type[Any], Callable[[DependencyResolver], Any], None]) – The type or factory to be used when resolving type t. Omit to use t as the target (requires t to be a concrete type.)

Return type:

Container

Returns:

self for method chaining.

Note on @provides#

The @provides decorator on target is only discovered when *target* is omitted (no explicit second argument). In that case, the container discovers __hazrakah_provides metadata and multi-registers t under every provided interface.

When target IS provided (explicit registration), @provides metadata on the target class is completely ignored. Only the type specified by t is registered. This applies regardless of whether the concrete class implements additional interfaces via @provides.

Examples#

# @provides activates – multi-registers under IFoo, IBar, and MyImpl:

@provides(IFoo, IBar) class MyImpl: … c.register_transient(MyImpl) # no second arg

# @provides does NOT activate – only IFoo is registered:

c.register_transient(IFoo, MyImpl) # explicit type override

resolve(t)#
Overloads:
  • self, t (Type[T]) → T

  • self, t (Type[Any]) → Any

Resolve a type to its registered implementation.

For union types (e.g. IFoo | IBar):

  • If the union is Optional[T] (contains NoneType), unwraps to T and resolves normally; returns None if T is unresolvable.

  • For non-Optional unions (e.g. IFoo | IBar): finds the single registered implementation among the union members. If multiple distinct targets are registered, raises ResolutionError. Unregistered concrete classes are auto-registered as transient.

For non-union types, looks up the registration and dispatches by lifetime:

  • INSTANCE – returns the stored instance.

  • SINGLETON – creates or returns a shared instance per container scope.

  • TRANSIENT – creates and returns a new instance each time.

register_decorated()#

Create registrations based on discovered decorators @singleton, @transient, and @instanced.

This method is idempotent – repeated calls overwrite registrations (last-in-wins).

Return type:

Container

Returns:

self for method chaining.

create_scope(frozen=False)#

Create a new scope as a Container instance.

Return type:

Container

Returns:

A Container instance to use for the the scope.

freeze()#

Freeze the Container.

Any attempt to create registrations after the container has been frozen will result in a RegistrationError.

Return type:

None

class DependencyRegistry(*args, **kwargs)#

Bases: DependencyResolver, Protocol

A protocol for dependency registration (and resolution) tasks.

is_registered(t)#

Check if type t has a registration already.

Parameters:

t (Type[Any]) – The type to check for.

Return type:

bool

Returns:

True if a registration exists.

register_instance(t, instance)#

Create an INSTANCE type registration for type t.

Every resolve of t will result in the specified object instance.

Parameters:
  • t (Type[Any]) – The type to register for.

  • instance (Any) – The instance to register.

Return type:

DependencyRegistry

Returns:

self for method chaining (on Container implementations).

Raises:

TypeError – When the provided instance is not an instance of type t (type mismatch.)

register_singleton(t, target=None)#

Create a SINGLETON type registration for type t.

Every resolve of t will result in a single, shared instance of t.

Parameters:
  • t (Type[Any]) – The type to register for.

  • target (Union[Type[Any], Callable[[DependencyResolver], Any], None]) – The type or factory to be used when resolving type t. Omit to use t as the target (requires t to be a concrete type.)

Return type:

DependencyRegistry

Returns:

self for method chaining (on Container implementations).

register_transient(t, target=None)#

Create a TRANSIENT type registration for type t.

Every resolve of t will result in a new instance of t.

Parameters:
  • t (Type[Any]) – The type to register for.

  • target (Union[Type[Any], Callable[[DependencyResolver], Any], None]) – The type or factory to be used when resolving type t. Omit to use t as the target (requires t to be a concrete type.)

Return type:

DependencyRegistry

Returns:

self for method chaining (on Container implementations).

class DependencyResolver(*args, **kwargs)#

Bases: Protocol

A protocol for dependency resolution without exposing registration methods.

resolve(t)#

Resolve type t using available registrations.

If t has no explicit registration but is a concrete class, create a TRANSIENT instance.

Parameters:

t (Type[Any]) – The type to resolve.

Raises:
  • KeyError – When type t is not a concrete class and has no registration.

  • RuntimeError – When a registration is malformed.

Return type:

Any

Returns:

The object instance resolved for type t.

class Lifetime(*values)#

Bases: IntEnum

TRANSIENT = 1#
SINGLETON = 2#
INSTANCE = 3#
exception ResolutionError(message, *, cause=None, matched=None)#

Bases: KeyError

Raised when a type cannot be resolved.

Typical situations that trigger this error:

  • No registration found for a concrete or abstract type during resolve().

  • Multiple registrations for different implementations of a union type alias, making the target ambiguous (e.g. IFoo | IBar where both are registered to different targets).

The matched attribute contains all pre-deduplication matches when available, allowing callers to inspect which types were considered during resolution.

Create the exception.

Parameters:
  • message (str) – Human-readable description of the problem.

  • cause (BaseException | None) – Optional original exception that led to this error. It is stored as __cause__ so that traceback chaining works automatically.

  • matched (list[tuple[Type[Any], Any, Lifetime, Any]] | None) – Pre-deduplication list of (type, scope, lifetime, registration) tuples for all matches found during resolution.

matched: list[tuple[Type[Any], Any, Lifetime, Any]] | None#
exception RegistrationError(message, *, cause=None)#

Bases: RuntimeError

Raised when a registration cannot be processed.

Typical situations that trigger this error:

  • Attempting to create an instance from a registration that has no target (e.g. a Lifetime.INSTANCE registration without an associated object).

  • Supplying a factory that does not conform to the expected signature Callable[[Container], T].

  • Providing an instance to Container.register_instance() that is not an instance of the registration type.

Create the exception.

Parameters:
  • message (str) – Human-readable description of the problem.

  • cause (BaseException | None) – Optional original exception that led to this error. It is stored as __cause__ so that traceback chaining works automatically.

class ScopedDependencyResolver(*args, **kwargs)#

Bases: DependencyResolver, Protocol

create_scope()#

Create a new scope as a Container instance.

Return type:

ScopedDependencyResolver

Returns:

A Container instance to use for the the scope.

provides(*types)#

Passive marker decorator: declares which protocols cls implements.

Stores metadata on the decorated class via __hazrakah_provides; zero registration logic at decoration time. Activation depends on how the container registers the class:

  • No explicit type arg on register_* –> @provides triggers multi-registration.

  • Explicit type arg on register_* –> @provides is ignored.

Usage:

@provides(IFoo, IBar)           # stores metadata only
class MyClass: ...

c.register_singleton(MyClass)   # activates: registers IFoo, IBar, MyClass
c.register_transient(IBaz, ...)  # ignores @provides on other classes

@provides()                     # marker-only, no interfaces (backward compatible)
Parameters:

types (Type[Any]) – Protocol types the decorated class implements. Variadic – no tuple wrapping.

Return type:

Callable[[TypeVar(T)], TypeVar(T)]

singleton(target=None, *, types=None, depends_on=())#

Register target as a singleton.

Usage:

@singleton                  →  self-as (classes only)
@singleton(types=IFoo)         →  explicit interface
@singleton(types=(IFoo, IBar)) →  multiple interfaces
Raises:

RegistrationError – If target is already decorated with @provides — the two decorators are incompatible.

Return type:

Any

transient(target=None, *, types=None, depends_on=())#

Register target as a transient.

Return type:

Any

instanced(target=None, *, types=None, depends_on=())#

Register target as an instance (created once at decoration time).

Return type:

Any