Package: inject2
python-injectDependency injection the python way, the good way.Key featuresFast.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 Python 3.9+ (v5.*), 3.5-3.8 (v4.*) and Python 2.7–3.5 (v3.*).Python SupportPythonInject Version3.9+5.0+3.6-3.84.1+, < 5.03.54.0< 3.53.*InstallationUse pip to install the lastest version:pipinstallinjectAutoparams example@inject.autoparamsreturns a decorator which automatically injects arguments into a function
that uses type annotations. This is supported only in Python >= 3.5.@inject.autoparams()defrefresh_cache(cache:RedisCache,db:DbInterface):passThere is an option to specify which arguments we want to inject without attempts of
injecting everything:@inject.autoparams('cache','db')defsign_up(name,email,cache:RedisCache,db:DbInterface):passStep-by-step example# Import the inject module.importinject# `inject.instance` requests dependencies from the injector.deffoo(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)defbaz(foo,cache=None,user=None):cache.save('foo',foo,user)# this can be called in different ways:# with injected argumentsbaz('foo')# with positional argumentsbaz('foo',my_cache)# with keyword argumentsbaz('foo',my_cache,user=current_user)# `inject.param` is deprecated, use `inject.params` instead.@inject.param('cache',Cache)defbar(foo,cache=None):cache.save('foo',foo)# `inject.attr` creates properties (descriptors) which request dependencies on access.classUser(object):cache=inject.attr(Cache)def__init__(self,id):self.id=iddefsave(self):self.cache.save('users',self)@classmethoddefload(cls,id):returncls.cache.load('users',id)# Create an optional configuration.defmy_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')Usage with DjangoDjango can load some modules multiple times which can lead toInjectorException: Injector is already configured. You can useconfigure(once=True)which
is guaranteed to run only once when the injector is absent:importinjectinject.configure(my_config,once=True)TestingIn tests useinject.configure(callable, clear=True)to create a new injector on setup,
and optionallyinject.clear()to clean up on tear down:classMyTest(unittest.TestCase):defsetUp(self):inject.configure(lambdabinder:binder.bind(Cache,MockCache())\.bind(Validator,TestValidator()),clear=True)deftearDown(self):inject.clear()Composable configurationsYou can reuse configurations and override already registered dependencies to fit the needs in different environments or specific tests.defbase_config(binder):# ... more dependencies registered herebinder.bind(Validator,RealValidator())binder.bind(Cache,RedisCache('localhost:1234'))deftests_config(binder):# reuse existing configurationbinder.install(base_config)# override only certain dependenciesbinder.bind(Validator,TestValidator())binder.bind(Cache,MockCache())inject.configure(tests_config,allow_override=True,clear=True)Thread-safetyAfter configuration the injector is thread-safe and can be safely reused by multiple threads.Binding typesInstancebindings always return the same instance:redis=RedisCache(address='localhost:1234')defconfig(binder):binder.bind(Cache,redis)Constructorbindings create a singleton on injection:defconfig(binder):# Creates a redis cache singleton on first injection.binder.bind_to_constructor(Cache,lambda:RedisCache(address='localhost:1234'))Providerbindings call the provider on injection:defget_my_thread_local_cache():passdefconfig(binder):# Executes the provider on each injection.binder.bind_to_provider(Cache,get_my_thread_local_cache)Runtimebindings automatically create singletons on injection, require no configuration.
For example, only theConfigclass binding is present, other bindings are runtime:classConfig(object):passclassCache(object):config=inject.attr(Config)classDb(object):config=inject.attr(Config)classUser(object):cache=inject.attr(Cache)db=inject.attr(Db)@classmethoddefload(cls,user_id):returncls.cache.load('users',user_id)orcls.db.load('users',user_id)inject.configure(lambdabinder:binder.bind(Config,load_config_file()))user=User.load(10)Disabling runtime bindingSometimes runtime binding leads to unexpected behaviour.  Say if you forget
to bind an instance to a class,injectwill 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, passbind_in_runtime=Falsetoinject.configure.In this caseinjectimmediately raisesInjectorExceptionwhen the code
tries to get an unbound instance.KeysIt is possible to use any hashable object as a binding key. For example:importinjectinject.configure(lambdabinder:\binder.bind('host','localhost')\binder.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-injectby default creates objects as singletons. It does not need a prototype scope
as in Spring or NO_SCOPE as in Guice becausepython-injectdoes 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. Inpython-injectwrite custom providers which can be thread-local,
request-local, etc.For example, a thread-local current user provider:importinjectimportthreading# Given a user class.classUser(object):pass# Create a thread-local current user storage._LOCAL=threading.local()defget_current_user():returngetattr(_LOCAL,'user',None)defset_current_user(user):_LOCAL.user=user# Bind User to a custom provider.inject.configure(lambdabinder:binder.bind_to_provider(User,get_current_user))# Inject the current user.@inject.params(user=User)deffoo(user):passLinksProject:https://github.com/ivankorobkov/python-injectLicenseApache License 2.0ContributorsIvan Korobkov@ivankorobkovJaime Wyant@jaimewyantSebastian Buczyński@EnforcerOleksandr Fedorov@Fedorofcselvaraj@cselvaraj陆雨晴@SixExtremeAndrew William Borba@andrewborba10jdmeyer3@jdmeyer3Alex Grover@ajgroverHarro van der Kroft@wisepotatoSamiur Rahman@samiur45deg@45degAlexander Nicholas Costas@ancostasDmitry Balabka@dbalabka