Python dependency injection

Why build a tiny Python DI container?

Python does not need dependency injection everywhere. Injex exists for a narrower case: service code that should stay explicit, typed, testable, and independent from a web framework.

The problem is not object creation. It is repeated wiring.

Direct constructor calls are a good default. They are readable and they keep dependencies visible. The problem starts when the same graph is recreated in API startup, CLI commands, background workers, tests, and one-off scripts.

At that point the project needs a composition root: one place where repositories, clients, gateways, settings, and use cases are connected. Injex gives that structure without turning the application into a provider-configuration project.

Why not use only framework dependencies?

Framework dependency systems are useful when all entrypoints live inside the framework. Many real projects do not look like that. The same service layer may be called by a web API, a CLI task, a queue worker, and tests. Injex keeps that graph portable.

What makes Injex small?

Where it fits best

Project shapeWhy Injex helps
Service layer + CLIOne graph can be reused outside web handlers.
Background workersScoped lifetimes work well for jobs and messages.
Clean architectureUse cases depend on interfaces, not framework hooks.
Tests with external servicesoverride() replaces gateways with fakes in one block.

The trade-off

A DI container is still another abstraction. If manual wiring is clear, keep manual wiring. Injex is designed for the point where explicit wiring needs a small home, not for replacing simple Python code with ceremony.

Try it when: your Python app has a shared service graph, tests need clean overrides, and startup should validate dependencies before real objects are constructed.