Library / SDK
ivankorobkov/python-inject avatar
ivankorobkov/python-inject

inject wires Python dependencies without taking over your constructors

Python dependency injection

776 stars86 forksPythonApache-2.0

At a glance

What is it?
The inject package is a dependency injection framework that stays out of the way: it does not steal class constructors, does not manage your object graph, and offers four ways to ask for a dependency, from explicit instances to implicit runtime construction.
Who is it for?
inject suits a Python codebase that has outgrown module level globals but does not want a framework, since it resolves dependencies at the call site or on attribute access and needs no configuration for simple cases, and it suits a team that writes plain unittest classes and wants to swap implementations with a two line change in setUp.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 25 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The design commitment is what it refuses to do

The key features list is unusually short, and three of its entries are about what the library does not do. It does not steal class constructors, it does not try to manage your application object graph, and it transparently integrates into tests. The remaining entries are that it is fast, thread-safe and simple to use, that it supports context managers, that it supports type hinting in Python 3.5 and later, and that it has autoparams leveraging type annotations. Read together, those are a coherent position. A dependency injection container that owns your constructors forces you to route every object through it, and this one declines to. What it gives you instead is a way to ask for a dependency at the point of use, which means the code you write looks like ordinary Python with a decorator or a property on it, and a reader who does not know the library can still follow the call.

Installation is one pip command, and the support matrix is old and new at once

The install is the standard one.

bash
pip install inject

The Python support table in the README shows how the library has tracked the language over years, with inject 5.5 and newer requiring Python 3.10 or newer, 5.0 to 5.4 requiring Python 3.9, 4.1 and newer but below 5.0 covering Python 3.6 to 3.8, 4.0 covering 3.5, and the 3.x line covering anything older. The current package metadata matches the modern end of that: it requires Python 3.10 or newer and lists classifiers for 3.10 through 3.14, is marked as Development Status 5, Production/Stable, and has no runtime dependencies at all. The build uses hatchling with hatch-vcs, so the version is derived from the repository tags rather than written in the metadata, which is why there are no release tags listed here yet the README can refer to version numbers of the 5.x line.

Four ways to ask for a dependency, with different lifetimes

The binding types section is the core of the library. An instance binding always returns the same instance, so a config function that binds a Cache to an already constructed RedisCache gives every caller the same object. A constructor binding creates a singleton on injection, so binding to a lambda that builds a RedisCache means the first injection constructs it and later ones reuse it. A provider binding calls the provider on each injection, which is how you get a thread local cache or a fresh connection per use. And a runtime binding automatically creates singletons on injection with no configuration at all, which is the case where only the Config class is bound and the rest of the graph is built implicitly. The runtime example shows the shape, with classes that declare a dependency through inject.attr and a User class that depends on two of them, with the only explicit binding being the Config class itself.

autoparams reads the annotation and injects the argument

The autoparams decorator returns a decorator that automatically injects arguments into a function that uses type annotations, and it is supported on Python 3.5 and later.

python
@inject.autoparams
def refresh_cache(cache: RedisCache, db: DbInterface):
    pass

The annotation is the only input, so the function body never mentions how the cache got there. If you want to inject only some arguments, you pass their names explicitly, and the unannotated ones are left alone.

python
@inject.autoparams('cache', 'db')
def sign_up(name, email, cache: RedisCache, db: DbInterface):
    pass

The curly braces form, @inject.autoparams(), is also accepted for non-parameterised decoration and is treated the same as @inject.autoparams. The older @inject.param decorator is deprecated in favour of @inject.params, which injects dependencies as keyword arguments or as positional arguments, and the README shows it being called three ways, with injected arguments only, with positional arguments supplied by the caller, and with keyword arguments. The @inject.attr form creates properties, that is descriptors, which request the dependency at the moment of access rather than at call time, which is what lets an ordinary class be instantiated with no arguments and still reach a cache when it needs one.

Tests are two lines, and that is the point

The testing section describes the intended workflow directly. In tests you use inject.configure with a callable and clear=True to create a new injector on setup, and optionally inject.clear() to clean up on tear down.

python
class MyTest(unittest.TestCase):
    def setUp(self):
        inject.configure(lambda binder: binder
            .bind(Cache, MockCache()) \
            .bind(Validator, TestValidator()),
            clear=True)
    
    def tearDown(self):
        inject.clear()

That is the transparent test integration the feature list claims, and it is what makes the library usable with plain unittest rather than requiring a plugin or a fixture. The composable configuration section extends the same idea to environments. A base config registers the real dependencies, a test config installs the base with binder.install and then overrides only the two it cares about, and the configure call passes allow_override=True and clear=True. So a test environment is a real configuration with two substitutions, which is a cleaner model than monkeypatching module attributes, and the Django note covers the same case in a specific framework, where a module can be loaded more than once and cause an InjectorException saying the injector is already configured, which the once=True option on configure is guaranteed to run only once when the injector is absent.

Context managers are bound as providers, and the lifetime follows

The context manager section describes a subtlety. Binding a class to an instance of a context manager, through bind or bind_to_constructor, or to a function decorated as a context manager, means the context manager is used as is rather than through a with statement. The example binds a synchronous generator function that yields a MockFile and destroys it, and an asynchronous one that yields a MockConnection, then binds the classes with bind_to_provider so that the connection and the file are destroyed on exit when the function that receives them returns. This is a real difference from the other frameworks, where a scoped binding usually means a with block in the consuming code. Here the provider function is the scope, and the consumer does not have to know about it, which is tidy for a dependency injection library and also means you lose the explicit entry and exit points in the call site. The README also notes that after configuration the injector is thread-safe and can be reused by multiple threads, and there is a section on disabling runtime binding for the cases where implicit instantiation leads to unexpected behaviour.

Editorial conclusion

inject suits a Python codebase that has outgrown module level globals but does not want a framework, since it resolves dependencies at the call site or on attribute access and needs no configuration for simple cases, and it suits a team that writes plain unittest classes and wants to swap implementations with a two line change in setUp. It is the wrong tool if you want a full application container with lifecycle management, since the README explicitly says it does not try to manage your application object graph, and it is the wrong tool for a codebase that has already chosen a DI approach. Before adopting it, decide which of the four binding kinds matches how your dependencies actually behave, since instance, constructor, provider and runtime bindings have different lifetime semantics, and read the section on disabling runtime binding before you rely on implicit construction. The last push was on 2026-09-28 and the package version is managed from version control.

Frequently asked questions

How do I install the inject dependency injection package?

Run pip install inject. The current release line requires Python 3.10 or newer, and the package metadata lists no runtime dependencies at all, with the build handled by hatchling and hatch-vcs.

What are the different binding types in inject?

Instance bindings always return the same instance, constructor bindings create a singleton on injection, provider bindings call the provider on each injection, and runtime bindings automatically create singletons on injection with no configuration. The README says runtime binding can lead to unexpected behaviour and documents how to disable it.

How do I use inject in tests?

Use inject.configure with a callable and clear=True in setUp to create a new injector, and optionally inject.clear() in tearDown. Configurations can also be composed, with a test config installing a base config and overriding only the dependencies it needs, using allow_override=True and clear=True.

Official sources

  1. Issues
  2. ivankorobkov/python-inject on GitHub
  3. License: Apache-2.0
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ivankorobkov-python-inject.svg)](https://hysenlabs.com/projects/ivankorobkov-python-inject)

Was this useful?