Lightweight, thread-safe dependency injection container for Python
Project description
Overview
PS DI is a lightweight, thread-safe dependency injection container for Python. It provides a DI class that manages service registration, resolution, and automatic constructor injection. Registrations support singleton and transient lifetimes, priority-based ordering, and resolution by type or string name.
For working project examples, see the ps-poetry-examples repository.
Installation
pip install ps-dependency-injection
Or with Poetry:
poetry add ps-dependency-injection
Quick Start
from ps.di import DI, Lifetime
di = DI()
di.register(Logger).factory(Logger, "app")
di.register(UserRepository, Lifetime.TRANSIENT).implementation(UserRepository)
repo = di.resolve(UserRepository)
Register Services
The register method accepts a type (or string key), an optional Lifetime, and an optional Priority. It returns a Binding object that configures how the service is created.
.factory(callable, *args, **kwargs)— Registers a callable that produces the service. Typed parameters not covered by explicit arguments are resolved from the container at registration time, using the same injection rules assatisfy. Explicit positional and keyword arguments take precedence over container resolution..implementation(cls)— Registers a class whose constructor is invoked viaspawn, allowing the container to inject known dependencies automatically.
from ps.di import DI, Lifetime
di = DI()
# Singleton (default) — one shared instance
di.register(Logger).factory(Logger, "app")
# Transient — new instance on every resolve
di.register(UserRepository, Lifetime.TRANSIENT).implementation(UserRepository)
Lifetime values:
SINGLETON— The factory is called once; all subsequent resolves return the same instance. This is the default.TRANSIENT— The factory is called on every resolve, producing a new instance each time.
Resolve Services
Use resolve to retrieve the highest-priority registration for a type, or resolve_many to retrieve all registrations ordered by priority.
service = di.resolve(Logger) # Logger | None
all_loggers = di.resolve_many(Logger) # list[Logger]
resolve returns None when no registration exists for the requested type. resolve_many returns an empty list in that case.
Both methods also accept a string type name instead of a class:
service = di.resolve("Logger")
String resolution matches against the __name__ attribute of registered types and raises ValueError when no match is found.
Priority
Each registration carries a Priority that determines its position relative to other registrations for the same type. Higher-priority registrations are resolved first by resolve and appear earlier in the list returned by resolve_many.
from ps.di import DI, Priority
di = DI()
di.register(NotificationService, priority=Priority.LOW).factory(NotificationService, "email")
di.register(NotificationService, priority=Priority.HIGH).factory(NotificationService, "sms")
di.register(NotificationService, priority=Priority.MEDIUM).factory(NotificationService, "push")
primary = di.resolve(NotificationService) # sms (HIGH wins)
Priority values: LOW (default), MEDIUM, HIGH. When multiple registrations share the same priority, the most recently registered one wins.
Spawn Objects
spawn instantiates a class without registering it, injecting constructor dependencies from the container automatically. It inspects type hints on __init__ parameters and resolves them as follows:
- A parameter typed as
DIor a subclass ofDIreceives the container itself. - A parameter typed as
List[T]receives the result ofresolve_many(T). - A parameter typed as
Optional[T]receives the result ofresolve(T), falling back to the default value when nothing is registered. - Any other typed parameter receives the result of
resolve(T). IfresolvereturnsNoneand no default exists,spawnraisesValueError.
Positional and keyword arguments passed to spawn override automatic resolution:
from ps.di import DI
di = DI()
di.register(Logger).factory(Logger, "app")
repo = di.spawn(UserRepository) # Logger injected from container
repo = di.spawn(UserRepository, logger=custom_logger) # explicit override
Satisfy Functions
satisfy binds a callable to dependencies resolved from the container at the time of the call, returning a new callable that accepts any remaining parameters at invocation time.
- Parameters with registered types are resolved from the container automatically.
- Parameters with defaults fall back to their default values when no registration exists.
- Parameters typed as
List[T]receive all registered instances ofT. - Parameters typed as
Optional[T]receiveNonewhen no registration exists. - Parameters marked with
REQUIREDare excluded from DI resolution and must be supplied by the caller.
from ps.di import DI, REQUIRED
log_message = di.satisfy(format_log, message=REQUIRED)
print(log_message(message="Application started"))
print(log_message(message="Low disk space", level="WARNING"))
The returned callable accepts keyword arguments at invocation time. Any keyword argument passed at invocation time overrides the corresponding resolved value, including DI-resolved parameters.
Scopes
scope() creates a child DI instance that inherits all registrations from the parent but maintains its own isolated registry. This is useful for per-request, per-session, or any other short-lived context that needs additional or overriding registrations without affecting the parent container.
Resolution in a scoped container follows these rules:
resolvechecks the scoped registry first; if nothing is registered, it falls through to the parent.resolve_manyreturns scoped registrations followed by parent registrations, with scoped results first.spawnandsatisfyuse the scoped resolver, so injected dependencies prefer scoped registrations.- A parameter typed as
DIreceives the scoped instance, not the parent.
Scopes support the context manager protocol. Exiting the with block clears the scoped registry and releases all singleton instances held by the scope, enabling deterministic cleanup of resources such as database connections or file handles.
with di.scope() as request_scope:
request_scope.register(RequestContext).factory(RequestContext, request_id)
handler = request_scope.spawn(RequestHandler)
handler.handle()
# scoped singletons released here; parent container unaffected
The root container supports the same context manager protocol. Exiting a with di: block clears all registrations and releases every singleton instance held by the container.
Scopes can be nested arbitrarily. Each level sees its own registrations plus all ancestor registrations, with closer scopes taking precedence.
Thread Safety
All registration and resolution operations are protected by internal locks. Singleton creation uses double-checked locking so the factory is called exactly once even under concurrent access. Transient registrations produce independent instances per call with no shared mutable state.
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
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 ps_dependency_injection-0.2.14.tar.gz.
File metadata
- Download URL: ps_dependency_injection-0.2.14.tar.gz
- Upload date:
- Size: 6.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.3.2 CPython/3.13.12 Linux/6.17.0-1010-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
49f49a6f01721bcfc0dbda3cdb3ec9d736935b867349663e6649f6b85bc24c61
|
|
| MD5 |
49f5ed61f62d8db531c29b82f21c5d91
|
|
| BLAKE2b-256 |
ec3dfdd26bfc2569c31e2b61ede48b503c8bc6f7e16400f1aecc5844db4bc830
|
File details
Details for the file ps_dependency_injection-0.2.14-py3-none-any.whl.
File metadata
- Download URL: ps_dependency_injection-0.2.14-py3-none-any.whl
- Upload date:
- Size: 7.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.3.2 CPython/3.13.12 Linux/6.17.0-1010-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c773ee4769a64deebbe5103886125f88e49cf8d37fdf8ab7813059d8e36c4da9
|
|
| MD5 |
2decf9b496dcbf9ce0a935b35f65964b
|
|
| BLAKE2b-256 |
e76c900709d04f6210cf154cb4696249a8f1c83316f54825325d76527b997fc7
|