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.
- Resolve Types without Registration; Container resolves types even when not explicitly registered, unlike typical pythonic DI containers.
- Hierarchical scoping; Isolate registrations and/or resolves. optionally use a context manager to deterministically tear down a scope and its resolved objects.
- Namespaced Registrations; Register types into named scopes and resolve with priority chains. Allows multiple implementations of the same interface to coexist.
- Protocols, ABCs, and Concretes can be registered against Factory Functions and Concretes.
- Lifetime Decorators; (OPTIONAL) Types decorated with
@singleton,@transientor@instancedcan be registered with a single call toregister_decorated(), simplifying orchestration. - Implicit Multi-Registration; Types decorated with
@providesbind to all provided types (unless explicit types are specified during registration.) - Fluent API; All registration methods return
self, enabling method-chained container setup.
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 lifetimes 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)
Caching with Cached[T]
The Cached[T] class wraps a factory callable so its result is produced once and re-used until the TTL window elapses. The factory receives a resolver argument, matching hazrakah's standard factory contract (see :py:data:hazrakah.DependencyRegistry.Factory). Register it with any container lifetime to combine DI resolution with time-bound caching:
from datetime import timedelta
from hazrakah import Container, Cached
class ConfigSource:
def load(self) -> str:
return 'loaded'
# TTL accepts float (seconds) or timedelta; default is 47.0 seconds.
cache = Cached(lambda c: ConfigSource(), ttl=timedelta(seconds=47))
first = cache(object()) # factory called once (TTL not yet elapsed)
second = cache(object()) # cached value returned; factory not re-invoked
assert first is second # same instance
Every Cached instance exposes a ttl read-only property and a reset() method for manual cache eviction:
cache = Cached(lambda c: ConfigSource(), ttl=timedelta(seconds=0))
# Zero TTL — factory called on every access.
assert cache(object()) is not cache(object()) # two distinct instances
cache.reset() # discard cached value
_ = cache(object()) # re-invokes factory
You can also pass ttl as a plain float (seconds):
cache = Cached(lambda c: ConfigSource(), ttl=120.0) # 120 seconds
assert cache.ttl == timedelta(seconds=120)
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 |
Contact
You can reach me on Discord or open an Issue on Github.
Metadata
Release files for hazrakah 1.11.11
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hazrakah-1.11.11.tar.gz | 30.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hazrakah-1.11.11-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 63.3 kB
Release files / hazrakah-1.11.11.tar.gz
| Download URL | hazrakah-1.11.11.tar.gz |
|---|---|
| Size | 30.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aa59f3d0c7612545a3ba0914fa41fe7916cf544ce8608729e5f8fc3dc0d29ad4
|
|
BLAKE2b-256 checksum How to use checksums |
42992ae103193030f9330bb1543c42c4a79954e8c9ee374d4f268e8afd2f9071
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.15
|
Release files / hazrakah-1.11.11-py3-none-any.whl
| Download URL | hazrakah-1.11.11-py3-none-any.whl |
|---|---|
| Size | 32.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
967c726ae7a5885e2a771e88bfa0b0d52649a4d6d59cdca4bb7b6c6c492f449a
|
|
BLAKE2b-256 checksum How to use checksums |
400394817a5b33e41156ce3bd11cbc21bdd8316c67949b1b5eda687d839212f8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.15
|