Package: autoinject
AutoinjectA clean, simple framework for automatically injecting dependencies into objects and functions
based around Python's type-hinting system. The framework provides caching of injectable objects,
though this may be disabled on a class-by-class basis. It also supports managing independent
caches for different contexts.Define Injectable Classes# Easy modefromautoinjectimportinjector@injector.injectableclassMyInjectableClass:# __init__() should have no additional required argumentsdef__init__(self):pass# Hard mode, must specify the fully-qualified name of the class,# but gain control over the arguments@injector.register("example.MyInjectableClass",os.environ("MY_CONFIG_FILE"))classMyInjectableClass:def__init__(self,config_file):# we receive os.environ("MY_CONFIG_FILE") as config_file here# positional and keyword arguments to @injector.register() are supportedpassInject Objects With Decorators# Decorate with @injector.inject for functions/methods:@injector.injectdefinject_me(param1,param2,injected_param:MyInjectableClass):# injected_param is set to an instance of MyInjectableClasspass# Omit the injected parameters when calling it:inject_me("arg1","arg2")# For classes, use @injector.construct to set instance attributes# based on the class attributesclassInjectMe:injected_attribute:MyInjectableClass=None@injector.constructdef__init__(self):# self.injected_attribute is set to an instance of MyInjectableClasspass# No need to do anything special here:obj=InjectMe()# obj.injected_attribute is set by the decorator before __init__() is called.Specifying injected classes in testsYou can override injected classes in your unit tests using the@injector.test_case()decorator. This provides an
independent global context within the test case function and allows you to pass a map of objects to inject. For example,fromautoinjectimportinjector# Your injectable original class@injector.injectable_globalclassServiceConnection:defexecute(self)->int:# Real connection code here, returns HTTP status codepass# The class you want to write a test case for that uses the injectable class.classUsesServiceConnection:connection:ServiceConnection=None@injector.constructdef__init__(self):passdeftest_me(self)->bool:# Super simple, check if response code is under 400resp_code=self.connection.execute()returnresp_code<400# Testing stuffimportunittest# Stub for testingclass_StubServiceFixture:def__init__(self,response_code):self.response_code=response_codedefexecute(self)->int:returnself.response_code# Test caseclassTestUsesServiceConnection(unittest.TestCase):@injector.test_case({ServiceConnection:_StubServiceFixture(200)})deftest_success_200(self):test_obj=UsesServiceConnection()# this will use the injected objects nowself.assertTrue(test_obj.test_me())@injector.test_case({ServiceConnection:_StubServiceFixture(400)})deftest_failure_400(self):test_obj=UsesServiceConnection()self.assertFalse(test_obj.test_me())Read thefull documentationfor more details.Changelogv1.3.3Member lists of objects are now cached to prevent multiple calls toinspect.getmembers()when the
same class is created many times. This results in significant speed increases.v1.3.0The new@injector.test_case()decorator is available for use with unit testing frameworks. It executes the decorated
function with a different global and non-global context to ensure the independence of test functions. In addition, one
can override the injected classes to provide specific test fixtures. These are passed as a dict of eithertypeobjects
or fully qualified class names as strings as keys and either thetypeor class name as string (to create the object),
or an object or function to use as the injected object.A bug was fixed where exceptions within a context caused issues with the new contextvars integration.v1.2.0Contextvar-driven contexts are now respected by defaultSeveral wrappers exist to better support using contextvars. All of them provide for a separate set of injected
CONTEXT_CACHE dependencies. In addition, each is a wrapper around@injector.inject, so both are not needed.@injector.with_contextvars: Creates a new context that is a copy of the current one@injector.with_same_contextvars: Uses the current context@injector.with_empty_contextvars: Creates a new empty contextWhen using awith_contextvarswrapper, you can inject the context object using type-hinting (e.g.ctx: contextvars.Context). Note that this is actually an instance ofContextVarsManagerwhich is a context manager
that delegates most functionality to the currentcontextvars.Contextobject with a few modifications:It provides the methodset(context_var, value) -> tokenand the complementaryreset(context_var, token)to
handle variable setting and resetting within the context manager.If the "same" context is used, these methods are equivalent to calling the methods directly on thecontext_varIn all other cases, they are equivalent to callingctx.run(context_var.METHOD, *args, **kwargs).In essence, this makes sure theset()andreset()operations are performed in the context that the manager is
managing (since the manager doesn't run the inner block in the context).If the "same" context is used:run()will just directly call the function (it is in the current context essentially)copy()is an alias forcontextvars.copy_context()Other functions besidesset()andreset()make a copy of the current context and return the results of its
method. This copy is transient and remade each time, so modules making extensive use of it can callcopy()and
check the copy.Note that, unlike the context manager, the decorators also RUN the inner code in the given context.Thread-handling was improved significantly and now also includes a wrapper function forthreading.Thread.run()methods to
ensure clean-up (@injector.as_thread_run()). This also is a wrapper around@injector.injectso you can inject
variables into yourrun()method directly.v1.1.0Injectable objects may now define a__cleanup__()method which will be invoked when the global cache or context
cache is cleared.Note that__cleanup__()IS NOT INVOKED for one-time use objects at the moment, but this is planned as a feature.v1.0.1Inherited injectable class members are now supported properlyv1.0.0Official initial releaseAdded support for @injector.injectable_global which registers with GLOBAL cache instead of context-specific cacheAdded support for @injector.injectable_nocache which registers with NO_CACHE insteadAdded support for injector.override() as a helper function to replace one constructor with another.Added support for any constructor argument (e.g. via override() or register_constructor()) to be specified
by fully-qualified Python name (e.g. package.module.MyInjectableClass) to better support systems where injected
classes are specified by name.Fixed a bug whereby the cache wasn't clearedv0.2.2Fixed a bug for injection when a non-truthy default value needed to be used.v0.2.1Fixed a bug in Python 3.8 and 3.9 whereentry_points(group=?)was not supportedv0.2.0Objects with a cache strategy ofCONTEXT_CACHEwill now have separate instances within threadsAddedinjector.get()as a fast way to get the object that would be injected (useful if operating outside of
a function or method)Addedinjector.register_constructor()as a wrapper to register a class in a non-decorated fashionAdded the entry pointautoinject.injectablesto directly register injectable classesAdded the entry pointautoinject.registrarsSupport for overriding injectables and for injecting functionsAdded aweightkeyword argument toregister()andregister_construct()to control overriding orderThere is now acleanup()function in theContextManager()class which triggers informant objects to check for
old items that are no longer needed. This was added mostly to support the thread-based context informant, since it
has no easy way of callingdestroy()whenever the thread ends (unless one manually calls it). It is the best
practice if you can calldestroy()directly whenever a context ceases to exist instead of relying oncleanup().