Namespaced Registrations#
Overview#
Namespaces allow multiple namespace-scoped containers within a single container tree. A type can be registered into the main (unnamespaced) container or into one or more named namespace scopes. Resolution with a namespace argument walks the requested namespace scopes before falling back to the standard scope chain.
Namespaces are implemented as scope children created via create_scope(namespace=...),
with the root container maintaining a __namespaces dict mapping namespace strings
to their corresponding scoped containers.
Key Design Decisions#
Namespaces are NOT shared between containers. The __namespaces dict only exists
on the container that owns registrations for that namespace. A child scope with
namespace 'v2' has no visibility into another container’s 'v2' namespaces.
Backwards compatibility. Existing code that does not use namespace parameters
continues to work identically. The namespace parameter has a default of None
on all APIs.
Auto-creation of namespace containers. When registering with a namespace string
(e.g., register_transient(IDb, MySQL, namespace='v2')), hazrakah automatically
creates the namespace scope container if it doesn’t exist.
Basic Usage#
Registering into a namespace:
root.register_transient(IDb, Postgres) # → root's default container
root.register_transient(IDb, MySQL, namespace='mysql') # → root.__namespaces['mysql']
Resolving from a namespace:
root.resolve(IDb) # → Postgres (default container)
root.resolve(IDb, namespace='mysql') # → MySQL
root.resolve(IDb, namespace=['mysql', None]) # → MySQL (or fallback)
Namespace priority chains resolve each scope in order:
root.resolve(IDb, namespace=['v2', 'v1', None])
# Tries 'v2' first, then 'v1', then falls back to standard resolution
The namespace= argument accepts:
String:
namespace='v2'is equivalent tonamespace=['v2', None]List/Tuple:
namespace=['v2', 'v1', None]tries each in orderEmpty list:
namespace=[]treats as no fallback (strict failure)None: Default behavior, no namespace filtering
Decoupled from Scope Chain#
A key design rule applies: when a namespace-scoped container resolves with a
namespace argument, it only checks the explicitly requested namespace scopes
and its own registrations. It never walks the scope chain to parent defaults.
This prevents infinite resolution loops (outer → inner → outer → inner) while keeping namespace resolution predictable:
root.register_transient(IDb, Postgres) # default
v2_scope = root.create_scope(namespace='v2')
v2_scope.register_transient(IDb, MySQL)
# Resolving WITH namespace → checks namespace scopes only
root.resolve(IDb, namespace='v2') # → MySQL
# Resolving WITHOUT namespace on the namespace-scoped container
# checks ONLY its own registrations, not the parent scope chain:
v2_scope.resolve(IDb, namespace=[None]) # → ResolutionError
# Notice: even though v2_scope.__outer_scope is root, it does NOT
# find the Postgres registration from root's default scope.
# To get the default (unnamespaced) registration from a namespace-scoped container,
# resolve directly on the root container instead:
root.resolve(IDb) # → Postgres
Decorator Support#
Lifetime decorators accept a namespace parameter:
from hazrakah import singleton, transient, provides
@singleton(namespace='v2')
class MyService:
pass
@transient(namespace='mysql')
class DBConnection:
pass
@provides(IFoo, IBar, namespace='v2')
class MultiImpl:
pass
When register_decorated() runs, it routes registrations into the specified
namespace scopes based on the decoration info.
Frozen Propagation#
Calling container.freeze() also freezes all directly owned namespace containers:
root = Container()
root.register_transient(IDb, MySQL, namespace='v2')
root.freeze()
root.__namespaces['v2'].register_transient(IDb, SQLite)
# Raises RegistrationError
Transitive Propagation#
Namespace context flows into all constructor parameter sub-resolves:
class MyService:
def __init__(self, db: IDb): ...
root.register_transient(IDb, PostgreSQL)
root.register_transient(IDb, MySQL, namespace='mysql')
root.register_transient(MyService)
root.resolve(MyService) # → MyService(db=PostgreSQL)
root.resolve(MyService, namespace=['mysql']) # → MyService(db=MySQL)