Security News
PyPI’s New Archival Feature Closes a Major Security Gap
PyPI now allows maintainers to archive projects, improving security and helping users make informed decisions about their dependencies.
Dependency injection the python way, the good way.
v5.*
), 3.5-3.8 (v4.*
) and Python 2.7–3.5 (v3.*
).Python | Inject Version |
---|---|
3.9+ | 5.0+ |
3.6-3.8 | 4.1+, < 5.0 |
3.5 | 4.0 |
< 3.5 | 3.* |
Use pip to install the lastest version:
pip install inject
@inject.autoparams
returns a decorator which automatically injects arguments into a function
that uses type annotations. This is supported only in Python >= 3.5.
@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:
@inject.autoparams('cache', 'db')
def sign_up(name, email, cache: RedisCache, db: DbInterface):
pass
# Import the inject module.
import inject
# `inject.instance` requests dependencies from the injector.
def foo(bar):
cache = inject.instance(Cache)
cache.save('bar', bar)
# `inject.params` injects dependencies as keyword arguments or positional argument.
# Also you can use @inject.autoparams in Python 3.5, see the example above.
@inject.params(cache=Cache, user=CurrentUser)
def baz(foo, cache=None, user=None):
cache.save('foo', foo, user)
# this can be called in different ways:
# with injected arguments
baz('foo')
# with positional arguments
baz('foo', my_cache)
# with keyword arguments
baz('foo', my_cache, user=current_user)
# `inject.param` is deprecated, use `inject.params` instead.
@inject.param('cache', Cache)
def bar(foo, cache=None):
cache.save('foo', foo)
# `inject.attr` creates properties (descriptors) which request dependencies on access.
class User(object):
cache = inject.attr(Cache)
def __init__(self, id):
self.id = id
def save(self):
self.cache.save('users', self)
@classmethod
def load(cls, id):
return cls.cache.load('users', id)
# Create an optional configuration.
def my_config(binder):
binder.bind(Cache, RedisCache('localhost:1234'))
# Configure a shared injector.
inject.configure(my_config)
# Instantiate User as a normal class. Its `cache` dependency is injected when accessed.
user = User(10)
user.save()
# Call the functions, the dependencies are automatically injected.
foo('Hello')
bar('world')
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.
@contextlib.contextmanager
def get_file_sync():
obj = MockFiel()
yield obj
obj.destroy()
@contextlib.asynccontextmanager
async def get_conn_async():
obj = MockConnection()
yield obj
obj.destroy()
def config(binder):
binder.bind_to_provider(MockFile, get_file_sync)
binder.bind(int, 100)
binder.bind_to_provider(str, lambda: "Hello")
binder.bind_to_provider(MockConnection, get_conn_sync)
inject.configure(config)
@inject.autoparams()
def example(conn: MockConnection, file: MockFile):
# Connection and file will be automatically destroyed on exit.
pass
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:
import inject
inject.configure(my_config, once=True)
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:
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()
You can reuse configurations and override already registered dependencies to fit the needs in different environments or specific tests.
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)
After configuration the injector is thread-safe and can be safely reused by multiple threads.
Instance bindings always return the same instance:
redis = RedisCache(address='localhost:1234')
def config(binder):
binder.bind(Cache, redis)
Constructor bindings create a singleton on injection:
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:
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:
class Config(object):
pass
class Cache(object):
config = inject.attr(Config)
class Db(object):
config = inject.attr(Config)
class User(object):
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)
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.
It is possible to use any hashable object as a binding key. For example:
import inject
inject.configure(lambda binder: \
binder.bind('host', 'localhost') \
binder.bind('port', 1234))
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:
import inject
import threading
# Given a user class.
class User(object):
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
Apache License 2.0
FAQs
Python dependency injection framework.
We found that inject demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
PyPI now allows maintainers to archive projects, improving security and helping users make informed decisions about their dependencies.
Research
Security News
Malicious npm package postcss-optimizer delivers BeaverTail malware, targeting developer systems; similarities to past campaigns suggest a North Korean connection.
Security News
CISA's KEV data is now on GitHub, offering easier access, API integration, commit history tracking, and automated updates for security teams and researchers.