Static, lazily-resolved declarative DI containers built on dependency-injector.
Project description
static-dependency-injector
Static, lazily-resolved declarative DI containers built on top of
dependency-injector.
Declare providers as annotated class attributes and read them back as resolved
values straight from the container class — no instance, no () call.
Overrides are fully type-checked: the provider names and value types are
verified (and autocompleted) by ty, mypy and pyright.
from static_dependency_injector import static_providers as sp
from static_dependency_injector.containers import StaticDeclarativeContainer
class Services(StaticDeclarativeContainer):
config: Config = sp.Singleton(load_config)
db: Database = sp.Singleton(Database, config=config)
db = Services.db # resolved Database (lazy, cached)
with Services.set_overrides(db=fake_db): # scoped — auto-restored on exit
...
Services.set_overrides(db=fake_db) # or permanent
Services.set_overrides(dbb=fake_db) # ❌ unknown provider (type error)
Services.set_overrides(db=123) # ❌ wrong value type (type error)
Providers stay lazy: the underlying callables are not invoked until first access,
and each provider keeps dependency-injector's caching semantics.
Installation
pip install static-dependency-injector
# or
uv add static-dependency-injector
Why
- Read providers as values —
Services.dbreturns the resolved instance, no()call and no container instance. - Fully typed — reads resolve to the dependency's type, and
set_overrideschecks provider names and value types under ty, mypy and pyright (Pylance), with autocompletion on the real fields. - Lazy & cached — keeps
dependency-injector's resolution and per-provider caching semantics; nothing is built until first access. - Test-friendly —
set_overrides(scoped context manager or permanent) for per-test overrides, plusTestContextSingletonauto-reset via a bundled pytest plugin. - Thin — a typed wrapper over
dependency-injector; no vendoring, no magic beyond the container metaclass.
Providers
Typed wrappers over dependency-injector providers (static_providers):
Factory, Singleton, ThreadSafeSingleton, ThreadLocalSingleton,
ContextLocalSingleton, Callable, Coroutine, Object, Resource,
Dependency, Selector, Provider, Container (nested container), and the
test-scoped TestContextSingleton.
Each is generic in its resolved type, so Services.db reads back as the
dependency's type.
Nested containers
Nest a static container with Container; reads chain through it, and overriding
the sub-container flows through:
class Inner(StaticDeclarativeContainer):
db: Database = sp.Singleton(Database)
class Outer(StaticDeclarativeContainer):
inner: type[Inner] = sp.Container(Inner)
db = Outer.inner.db # resolved Database (typed)
with Inner.set_overrides(db=fake): # reflected through Outer.inner.db
...
Test-scoped providers
TestContextSingleton holds a value for the duration of one test and is reset
between tests. Reset is driven by an explicit hook, per test framework:
Run under pytest — auto-cleaned. The bundled plugin (auto-registered on
install) calls reset_test_context() after every test; nothing to wire up. This
covers unittest.TestCase tests too, since pytest can run them — if your test
runner is pytest, you get auto-clean regardless of how the tests are written:
class Services(StaticDeclarativeContainer):
driver: Driver = sp.TestContextSingleton(make_driver)
# after each test the plugin calls Services.reset_test_context() for you
Run under the stock unittest runner (python -m unittest) — there is no
plugin auto-discovery outside pytest, so register the reset yourself with
addCleanup:
class Test(unittest.TestCase):
def setUp(self) -> None:
self.addCleanup(Services.reset_test_context)
You can also reset manually anywhere: Services.reset_test_context().
Subclassing & whole-container override
Subclass a container to inherit its providers, and redeclare any to replace them:
class Base(StaticDeclarativeContainer):
db: Database = sp.Singleton(Database)
class Testing(Base):
db: Database = sp.Singleton(FakeDatabase) # replaces Base.db for Testing
extra: Clock = sp.Singleton(Clock) # adds a provider
Testing.set_overrides(db=other, extra=frozen) # typed over inherited + own
Override a whole container's providers by name with another container's, undone
with reset_override (both reflected in reads):
Base.override(FakeContainer) # Base.db now resolves FakeContainer.db
Base.reset_override()
Type checking
Reads resolve to the field's type, and set_overrides is fully checked under
ty, mypy and pyright (Pylance) — unknown provider names and wrong
value types are compile-time errors, verified by a test matrix that runs all
three. This requires an annotation on each provider (db: Db = Singleton(Db))
so the checker knows the field type. Direct attribute assignment
(Services.db = …) is rejected at runtime — use set_overrides.
Compatibility with dependency-injector
The static, class-level model keeps most of dependency-injector's API, but a
few pieces are intentionally different.
Preserved:
- Introspection —
providers,cls_providers,inherited_providers,traverse(),dependencies. - Overriding —
override(other)/reset_override()/reset_last_overriding()and theoverriddentuple, plus the@containers.override(Container)decorator; all reflected in reads. - Lazy resolution and per-provider caching semantics.
Deliberately different (because there is no container instance):
- Reads return values, not providers —
Services.dbis the resolvedDatabase, soServices.db()and provider methods likeServices.db.override(...)do not apply. Override withset_overrides/ the container-leveloverride; if you need the raw provider, useServices.providers["db"]. - Instantiation is repurposed —
Services(db=fake)applies value overrides and returns a restoring handle (usable as awithblock); it does not build a container instance. - Direct provider assignment (
Services.db = provider) is rejected — useset_overrides. init_resources()/shutdown_resources()are deprecated and raise: they are instance-level and meaningless here. AResourcestill initializes on first access, but its post-yieldteardown is not driven — useResourcefor init-only setup, or manage teardown yourself.
Notes & limitations
Because containers are resolved at the class level (no container instance),
overrides go through set_overrides (scoped with …: restores on exit; a
bare call is permanent), or override(other_container) for a whole-container
swap. Both are reflected in reads and cleared by reset_override.
Requirements
- Python 3.12+ (uses PEP 695 generics)
dependency-injector >= 4.49.1
License
MIT. Uses dependency-injector (BSD-3-Clause) as a runtime dependency; none of
its source is vendored.
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 static_dependency_injector-0.3.1.tar.gz.
File metadata
- Download URL: static_dependency_injector-0.3.1.tar.gz
- Upload date:
- Size: 8.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Artix Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
14a37d609327bd05696cc64558c915a252463d72e9bce377a6b9d4c23cee17c6
|
|
| MD5 |
d1ca27b69f5ccc495f00d14b225ef62f
|
|
| BLAKE2b-256 |
496437e2ddb3265bd6aad9273b2a270bbe2e348c51a8cec6263fba2a38a7b364
|
File details
Details for the file static_dependency_injector-0.3.1-py3-none-any.whl.
File metadata
- Download URL: static_dependency_injector-0.3.1-py3-none-any.whl
- Upload date:
- Size: 12.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Artix Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e255bddabde2ded18442b8a74d97700c655147e1ff2013a76669e2fbe8b8e63
|
|
| MD5 |
64fb951e919f5ebc353665b896bd8e64
|
|
| BLAKE2b-256 |
680367c2092cca45f83b528db77f721d0cef8105c9251d4f2309d6ff127c47c6
|