Package: zanna
zannaSimple Dependency Injection library.
Supports python 3.5+ and makes full use of the typing annotations.
The design is pythonic but inspired by Guice in many aspects.Free software: BSD licenseDocumentation:https://zanna.readthedocs.io.MotivationZanna is meant to be a modern (3.5+), well maintained injection library for Python.FeaturesSupport for typing annotationsDecorators are not mandatory: all the injection logic can be outside your modulesSupports injection by nameInstances can be bound directly, useful when testing (i.e. by override bindings with mocks)No autodiscover for performance reasons and to avoid running into annoying bugsUsageInjecting by variable nameThe basic form of injection is performed by variable name.
The injector expects a list of modules (any callable that takes a Binder as argument).
You can get the bound instance by calling get_instancefromzannaimportInjector,Binderdefmymodule(binder:Binder)->None:binder.bind_to("value",3)injector=Injector(mymodule)assertinjector.get_instance("value")==3Zanna will automatically inject the value into arguments with the same name:fromzannaimportInjector,Binderdefmymodule(binder:Binder)->None:binder.bind_to("value",3)classValueConsumer:def__init__(self,value):self.value=valueinjector=Injector(mymodule)assertinjector.get_instance(ValueConsumer).value==3Injecting by type annotationZanna also makes use of python typing annotations to find the right instance to inject.fromzannaimportInjector,BinderclassValueClass:def__init__(self,the_value:int):self.the_value=the_valueclassValueConsumer:def__init__(self,value_class_instance:ValueClass):self.value_class_instance=value_class_instancedefmymodule(binder:Binder)->None:binder.bind_to("the_value",3)binder.bind(ValueClass)injector=Injector(mymodule)assertinjector.get_instance(ValueConsumer).value_class_instance.the_value==3Singleton or not singleton?Instances provided by the injector are always singletons, meaning that the __init__ method of
the class will be called only the first time, and every subsequent call of get_instance will
return the same instance:fromzannaimportInjectorclassMyClass:passinjector=Injector(lambdabinder:binder.bind(MyClass))assertinjector.get_instance(MyClass)==injector.get_instance(MyClass)Use providers for more complex use casesBinder instances can be used to bind providers. A provider is any callable that takes
any number of arguments and returns any type. The injector will try to inject all the necessary
arguments. Providers can be bound explicitely or implicitely (in which case zanna will use the
return annotation to bind by type).fromzannaimportInjector,BinderclassAValueConsumer:def__init__(self,value:int):self.value=valuedefexplicit_provider(a_value:int)->int:returna_value+100defimplicit_provider(value_plus_100:int)->AValueConsumer:returnAValueConsumer(value_plus_100)defmymodule(binder:Binder)->None:binder.bind_to("a_value",3)binder.bind_provider("value_plus_100",explicit_provider)binder.bind_provider(implicit_provider)injector=Injector(mymodule)assertinjector.get_instance(AValueConsumer).value==103Override existing bindingsBindings can be overridden. Overriding a non-existent binding will result in a ValueError being raised.Override bindings is extremely useful when testing, as any part of your stack can be replaced with a mock.fromzannaimportInjector,Binderfromunittest.mockimportMagicMockclassValueClass:def__init__(self):passdefretrieve_something(self):return['some','thing']classValueConsumer:def__init__(self,value:ValueClass):self.value=valuedefmymodule(binder:Binder)->None:binder.bind(ValueClass)injector=Injector(mymodule)assertinjector.get_instance(ValueConsumer).value.retrieve_something()==['some','thing']defmodule_overriding_value_class(binder:Binder)->None:mock_value_class=MagicMock(ValueClass)mock_value_class.retrieve_something.return_value=['mock']binder.override_binding(ValueClass,mock_value_class)injector=Injector(mymodule,module_overriding_value_class)assertinjector.get_instance(ValueConsumer).value.retrieve_something()==['mock']Using the decoratorsOne of the advantages of using Zanna over other solutions is that it doesn’t force you
to pollute your code by mixing in the injection logic.If you are working on a small project and would like to handle part (or all) of the
injection logic using decorators instead of modules, Zanna supports that as well.Internally, Zanna creates a module that sets up the bindings as indicated by the decorators
(in a random order).All Injectors initialized with use_decorators=True will run that module first on their Binder.Zanna supports the following decorators:decorators.provider, which takes a provided annotated with an appropriate return typedecorators.provider_for, which can be given the name or the class of the instance provideddecorators.inject, to annotate class to be bound/injectedHere’s an example:fromzannaimportInjectorfromzannaimportdecoratorsclassThing:pass@decorators.provider_for("value")defprovide_value():return3@decorators.providerdefprovide_thing()->Thing:returnThing()@decorators.injectclassOtherThing:def__init__(self,value,thing:Thing):self.value=valueself.thing=thinginj=Injector(use_decorators=True)otherthing=inj.get_instance(OtherThing)assertotherthing.value==3assertisinstance(otherthing.thing,Thing)assertisinstance(otherthing,OtherThing)CreditsThis package was created withCookiecutterand theaudreyr/cookiecutter-pypackageproject template.History0.1.0 (2016-10-13)0.3.1 (2017-01-21)