Skip to main content

..a tiny but powerful DI library for Python.

Project description

hazrakah on PyPI hazrakah on readthedocs

hazrakah (הזרקה) is a tiny but powerful DI library for Python.

This README is only a high-level introduction to hazrakah. For more detailed documentation, please view the official docs at https://hazrakah.readthedocs.io.

Features

  • Supports Singleton, Transient, and Instance lifetimes.
  • Hierarchical scoping; Isolate registrations and/or resolves. optionally use a context manager to deterministically tear down a scope and its resolved objects.
  • Protocols, ABCs, and Concretes can be registered against Factory Functions and Concretes.
  • Lifetime Decorators; (OPTIONAL) Types decorated with @singleton, @transient or @instanced can be registered with a single call to register_decorated(), simplifying orchestration.
  • Implicit Multi-Registration; Types decorated with @provides bind to all provided types (unless explicit types are specified during registration.)
  • Fluent chaining; All registration methods return self, enabling method-chained container setup.
  • Built-in mocking framework: Mock with fluent configuration, async-aware interception, an extensible set of matchers (is_any, is_gt, contains, neg, ...), and module-level patching.

Installation

You can install hazrakah from PyPI through usual means, such as pip:

   pip install hazrakah

Usage

Core lifetimes — Transient, Singleton, Instance

hazrakah manages object lifecycles through three registration strategies:

from hazrakah import Container

container = Container()

# TRANSIENT — a new instance for every resolve.
container.register_transient(IFoo, Foo)
assert container.resolve(IFoo) is not container.resolve(IFoo)

# SINGLETON — one shared instance across all resolves in scope.
container.register_singleton(IFooBar, lambda c: c.resolve(FooBarImpl))
assert container.resolve(IFooBar) is container.resolve(IFooBar)

# INSTANCE — your exact object, returned everywhere (including child scopes).
bar_obj = Bar()
container.register_instance(IBar, bar_obj)
assert container.resolve(IBar) is bar_obj

Hierarchical scopes

Scopes provide isolation: parent registrations flow down, but child-only registrations stay local.

parent = Container()
child = parent.create_scope()

parent.register_transient(IFoo, Foo)
child.resolve(IFoo)          # resolves parent's registration

child.register_transient(IBar, Bar)
child.resolve(IBar)          # works — registered in this scope
# parent.resolve(IBar)      # raises KeyError — invisible to parent

Context manager cleanup

Resolve tracked resources and get deterministic teardown when the scope exits.

from hazrakah import Container

class Closeable:
    def __init__(self): self.closed = False
    def close(self): self.closed = True

with Container() as c:
    c.register_transient(Closeable)
    res = c.resolve(Closeable)

assert res.closed               # teardown ran automatically on __exit__

Fluent chaining

All registration methods return self, enabling method-chained setup.

container = (
    Container()
    .register_transient(IFoo, Foo)
    .register_singleton(IBar, Bar)
    .register_instance(IFizz, Fizz())
)

assert isinstance(container.resolve(IFoo), Foo)
assert isinstance(container.resolve(IBar), Bar)

Declarative lifetime decorators

Mark intent at class-definition time with @singleton, @transient, or @instanced, then register everything in one call.

from hazrakah import Container, singleton, transient, instanced

@singleton(types=IFoo)
class FooService: ...

@transient(types=IBar)
class BarService: ...

@instanced  # binds to the class itself
class BuzzService: ...

c = Container()
c.register_decorated()            # discovers all decorated classes

assert c.resolve(IFoo) is c.resolve(IFoo)     # singleton
assert c.resolve(IBar) is not c.resolve(IBar)  # transient

Implicit multi-registration with @provides

Declare which interfaces a class implements; registration binds to all of them simultaneously.

from hazrakah import Container, provides

@provides(IFoo, IBar)
class MultiImpl:
    def foo(self): ...
    def bar(self): ...

c = Container()
c.register_transient(MultiImpl)    # registers under IFoo, IBar, and MultiImpl

a = c.resolve(IFoo)
b = c.resolve(IBar)
assert a is b                       # same cached singleton instance

How @provides works

The @provides decorator is a passive marker -- it stores metadata only, with zero registration logic at decoration time. Activation depends entirely on how the container later registers the decorated class.

@provides activates when you call register_singleton, register_transient, or register_instance with no second argument (no explicit type override):

@provides(IFoo, IBar)
class MultiImpl: ...

c.register_singleton(MultiImpl)  # multi-registers under IFoo + IBar + MultiImpl
c.resolve(IFoo)                  # works -- @provides activated
c.resolve(IBar)                  # works -- @provides activated

@provides does NOT activate when you provide an explicit type argument to a registration method:

@provides(IBar)
class MultiImpl: ...

c.register_singleton(IFoo, MultiImpl)  # only IFoo is registered
c.resolve(IFoo)                        # works -- explicit registration
c.resolve(IBar)                        # raises KeyError -- @provides was ignored

This is intentional. The second positional argument on any register_* method is the explicit type override. When you provide it, you are telling the container exactly which key to register against -- and @provides does not interfere.

Registration call @provides activates? Registered keys
register_singleton(MyClass) YES MyClass + all @provides interfaces
register_singleton(IFoo, MyClass) NO Only IFoo
register_transient(MyClass) YES MyClass + all @provides interfaces
register_transient(IFoo, MyClass) NO Only IFoo
register_instance(my_obj) (no explicit instance) YES type(obj) + all @provides interfaces
register_instance(IFoo, my_obj) (explicit instance) NO Only IFoo

Built-in mocking framework

A lightweight Mock with fluent configuration, call tracking, argument matchers, and module-level patch().

from hazrakah.mocks import Mock, is_gt, contains, neg, is_in

m = Mock()

# Fluent stubbing.
m.get_status.returns("ok")
assert m.get_status() == "ok"

# Side-effects on the child mock (fluent call, not direct assignment).
m.compute.side_effect(lambda x: 10 if x > 5 else 20)
assert m.compute(7) == 10
assert m.compute(2) == 20

# Call tracking.
assert m.compute.was_called()
assert m.compute.call_count == 2

# Composed matchers.
m.filter.returns(True)(neg(contains("blocked")))
assert m.filter("allowed") is True

Contact

You can reach me on Discord or open an Issue on Github.

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

hazrakah-1.7.9.tar.gz (26.3 kB view details)

Uploaded Source

Built Distribution

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

hazrakah-1.7.9-py3-none-any.whl (28.9 kB view details)

Uploaded Python 3

File details

Details for the file hazrakah-1.7.9.tar.gz.

File metadata

  • Download URL: hazrakah-1.7.9.tar.gz
  • Upload date:
  • Size: 26.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15+

File hashes

Hashes for hazrakah-1.7.9.tar.gz
Algorithm Hash digest
SHA256 8f559b1bed8da203d992c5dc17fd39b27a11401d346dc20d7f3da96d73da9b28
MD5 6492f88a4b6deb754e8bc5b7893ace50
BLAKE2b-256 a96e1258c4071e2e0acb5332105477988207f468cd77b0d5825235153227c60b

See more details on using hashes here.

File details

Details for the file hazrakah-1.7.9-py3-none-any.whl.

File metadata

  • Download URL: hazrakah-1.7.9-py3-none-any.whl
  • Upload date:
  • Size: 28.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15+

File hashes

Hashes for hazrakah-1.7.9-py3-none-any.whl
Algorithm Hash digest
SHA256 087e774a13966ae5557f8f517c742b0bd11d50971d621eaf331349eaada3a29a
MD5 95e8b531458ea2b8208b3e81e155d174
BLAKE2b-256 d061fdcfe61f725b84e439a13cfadc84e95186fac33fbfd2ff8c41f7c6889711

See more details on using hashes here.

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