Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
P

python-inject

> 编程语言
Open source

Python dependency injection

773 stars0 likes1 views
WebsiteGitHub

About

Python dependency injection

python-inject

Dependency injection the python way, the good way.

Key features

  • Fast.
  • Thread-safe.
  • Simple to use.
  • Does not steal class constructors.
  • Does not try to manage your application object graph.
  • Transparently integrates into tests.
  • Autoparams leveraging type annotations.
  • Supports type hinting in Python 3.5+.
  • Supports context managers.

Python Support

Python Inject Version
3.10+ 5.5+
3.9 5.0 - 5.4
3.6-3.8 4.1+, = 3.5.
python
@inject.autoparams
def refresh_cache(cache: RedisCache, db: DbInterface):
    pass

There is an option to specify which arguments we want to inject without attempts of injecting everything:

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

It is also acceptable to use explicit curly braces notation (@inject.autoparams()) for non-parameterized decorations — it will be treated the same as @inject.autoparams.

Step-by-step example

…

Context managers

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 leads to the context manager to be used as is, not via with statement.

…

Usage with Django

Django can load some modules multiple times which can lead to InjectorException: Injector is already configured. You can use configure(once=True) which is guaranteed to run only once when the injector is absent:

python
import inject
inject.configure(my_config, once=True)

Testing

In tests use inject.configure(callable, 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()

Composable configurations

You can reuse configurations and override already registered dependencies to fit the needs in different environments or specific tests.

python
    def base_config(binder):
        # ... more dependencies registered here
        binder.bind(Validator, RealValidator())
        binder.bind(Cache, RedisCache('localhost:1234'))

    def tests_config(binder):
        # reuse existing configuration
        binder.install(base_config)

        # override only certain dependencies
        binder.bind(Validator, TestValidator())
        binder.bind(Cache, MockCache())
    
    inject.configure(tests_config, allow_override=True, clear=True)
        

Thread-safety

After configuration the injector is thread-safe and can be safely reused by multiple threads.

Binding types

Instance bindings always return the same instance:

python
redis = RedisCache(address='localhost:1234')
def config(binder):
    binder.bind(Cache, redis)

Constructor bindings create a singleton on injection:

python
def config(binder):
    # Creates a redis cache singleton on first injection.
    binder.bind_to_constructor(Cache, lambda: RedisCache(address='localhost:1234'))

Provider bindings call the provider on injection:

python
def get_my_thread_local_cache():
    pass

def config(binder):
    # Executes the provider on each injection.
    binder.bind_to_provider(Cache, get_my_thread_local_cache) 

Runtime bindings automatically create singletons on injection, require no configuration. For example, only the Config class binding is present, other bindings are runtime:

python
class Config:
    pass

class Cache:
    config = inject.attr(Config)

class Db:
    config = inject.attr(Config)

class User:
    cache = inject.attr(Cache)
    db = inject.attr(Db)
    
    @classmethod
    def load(cls, user_id):
        return cls.cache.load('users', user_id) or cls.db.load('users', user_id)
    
inject.configure(lambda binder: binder.bind(Config, load_config_file()))
user = User.load(10)

Disabling runtime binding

Sometimes runtime binding leads to unexpected behaviour. Say if you forget to bind an instance to a class, inject will try to implicitly instantiate it.

If an instance is unintentionally created with default arguments it may lead to hard-to-debug bugs. To disable runtime binding and make sure that only explicitly bound instances are injected, pass bind_in_runtime=False to inject.configure.

In this case inject immediately raises InjectorException when the code tries to get an unbound instance.

Keys

It is possible to use any hashable object as a binding key. For example:

python
import inject

inject.configure(lambda binder: \
    binder.bind('host', 'localhost') \
          .bind('port', 1234))

Why no scopes?

I've used Guice and Spring in Java for a lot of years, and I don't like their scopes. python-inject by default creates objects as singletons. It does not need a prototype scope as in Spring or NO_SCOPE as in Guice because python-inject does not steal your class constructors. Create instances the way you like and then inject dependencies into them.

Other scopes such as a request scope or a session scope are fragile, introduce high coupling, and are difficult to test. In python-inject write custom providers which can be thread-local, request-local, etc.

For example, a thread-local current user provider:

python
import inject
import threading

# Given a user class.
class User:
    pass

# Create a thread-local current user storage.
_LOCAL = threading.local()

def get_current_user():
    return getattr(_LOCAL, 'user', None)

def set_current_user(user):
    _LOCAL.user = user

# Bind User to a custom provider.
inject.configure(lambda binder: binder.bind_to_provider(User, get_current_user))

# Inject the current user.
@inject.params(user=User)
def foo(user):
    pass

Links

  • Project: https://github.com/ivankorobkov/python-inject

License

Apache License 2.0

Development

We use uv and just for development. Install both, then run just init to set up the environment.

Useful recipes:

  • just lists all recipes
  • just dev formats, lints, and tests on the default interpreter (the fast loop)
  • just test 3.12 runs the tests against a specific Python version
  • just check runs the full gate before pushing

Contributors

  • Ivan Korobkov @ivankorobkov
  • Jaime Wyant @jaimewyant
  • Sebastian Buczyński @Enforcer
  • Oleksandr Fedorov @Fedorof
  • cselvaraj @cselvaraj
  • 陆雨晴 @SixExtreme
  • Andrew William Borba @andrewborba10
  • jdmeyer3 @jdmeyer3
  • Alex Grover @ajgrover
  • Harro van der Kroft @wisepotato
  • Samiur Rahman @samiur
  • 45deg @45deg
  • Alexander Nicholas Costas @ancostas
  • Dmitry Balabka @dbalabka
  • Dima Burmistrov @pyctrl

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

Python

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言