..a tiny but powerful DI library for Python.
Project description
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.
- 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 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)
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hazrakah-1.10.0.tar.gz.
File metadata
- Download URL: hazrakah-1.10.0.tar.gz
- Upload date:
- Size: 27.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15+
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
69f3bf37ac5605fbb93928f252bf6a1d79f08af4e6f50bef8c87209752af79dd
|
|
| MD5 |
42b9b561fa4e146ea77edc06893e2fa3
|
|
| BLAKE2b-256 |
eda10b3102129dc207a46b0708c17882d4c991cdeb45e07509f0173c42b838e8
|
File details
Details for the file hazrakah-1.10.0-py3-none-any.whl.
File metadata
- Download URL: hazrakah-1.10.0-py3-none-any.whl
- Upload date:
- Size: 29.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15+
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e97fcb2cb479a9f0fe89f0362a5d01f0aafb1a1ba997a1f5408d0e226a2bfe4
|
|
| MD5 |
63e4cb1f57cbf4ca1470c4b20a4c779a
|
|
| BLAKE2b-256 |
606aab929e53f6ce0724bff13e077abd81be06dbb3d3c42bc1cb31040508825b
|