Package: antidote
Antidote is a dependency injection micro-framework for Python 3.7+.It is built on the idea of having adeclarative,explicitanddecentralizeddefinitions of dependencies at the type / function / variable definition which can be easily tracked down.Features are built with a strong focus onmaintainability,simplicityandease of usein mind. Everything is statically typed (mypy & pyright), documented with tested examples, can be easily used in existing code and tested in isolation.InstallationTo install Antidote, simply run this command:pipinstallantidoteHelp & IssuesFeel free to open anissueor adiscussiononGithubfor questions, issues, proposals, etc. !DocumentationTutorial, reference and more can be found in thedocumentation. Some quick links:GuideReferenceChangelogOverviewAccessing dependenciesAntidote works with aCatalogwhich is a sort of collection of dependencies. Multiple ones can co-exist, butworldis used by default. The most common form of a dependency is an instance of a given classfromantidoteimportinjectable@injectableclassService:passworld[Service]# retrieve the instanceworld.get(Service,default='something')# similar to a dictBy default,@injectabledefines a singleton but alternative lifetimes (how long theworldkeeps value alive in its cache) exists such astransientwhere nothing is cached at all. Dependencies can also be injected into a function/method with@inject. With both, Mypy, Pyright and PyCharm will infer the correct types.fromantidoteimportinject@inject#                      ⯆ Infers the dependency from the type hintdeff(service:Service=inject.me())->Service:returnservicef()# service injectedf(Service())# useful for testing: no injection, argument is used@injectsupports a variety of ways to bind arguments to their dependencies if any. This binding isalwaysexplicit. for example:fromantidoteimportInjectMe# recommended with inject.me() for best static-typing experience@injectdeff2(service=inject[Service]):...@inject(kwargs={'service':Service})deff3(service):...@injectdeff4(service:InjectMe[Service]):...Classes can also be fully wired, all methods injected, easily with@wire. It is also possible to
inject the first argument, commonly namedself, of a method with an instance of a class:@injectableclassDummy:@inject.methoddefmethod(self)->'Dummy':returnself# behaves like a class methodassertDummy.method()isworld[Dummy]# useful for testing: when accessed trough an instance, no injectiondummy=Dummy()assertdummy.method()isdummyDefining dependenciesAntidote provides out of the box 4 kinds of dependencies:@injectableclasses for which an instance is provided.fromantidoteimportinjectable#           ⯆ optional: would just call Service() otherwise.@injectable(factory_method='load')classService:@classmethoddefload(cls)->'Service':returncls()world[Service]constfor defining simple constants.fromantidoteimportconst# Used as namespaceclassConf:TMP_DIR=const('/tmp')# From environment variables, lazily retrievedLOCATION=const.env("PWD")USER=const.env()# uses the name of the variablePORT=const.env(convert=int)# convert the environment variable to a given typeUNKNOWN=const.env(default='unknown')world[Conf.TMP_DIR]@injectdeff(tmp_dir:str=inject[Conf.TMP_DIR]):...@lazyfunction calls (taking into account arguments) used for (stateful-)factories, parameterized dependencies, complex constants, etc.fromdataclassesimportdataclassfromantidoteimportlazy@dataclassclassTemplate:name:str# the wrapped template function is only executed when accessed through world/@inject@lazydeftemplate(name:str)->Template:returnTemplate(name=name)# By default a singleton, so it always returns the same instance of Templateworld[template(name="main")]@injectdeff(main_template:Template=inject[template(name="main")]):...@lazywill automatically apply@injectand can also be a value, property or even a method similarly to@inject.method.@interfacefor a function, class or even@lazyfunction call for which one or multiple implementations can be provided.fromantidoteimportinterface,implements@interfaceclassTask:pass@implements(Task)classCustomTask(Task):passworld[Task]# instance of CustomTaskThe interface does not need to be a class. It can also be aProtocol, a function or a@lazyfunction call!@interfacedefcallback(event:str)->bool:...@implements(callback)defon_event(event:str)->bool:# do stuffreturnTrue# returns the on_event functionassertworld[callback]ison_event@implementswill enforce as much as possible that the interface is correctly implemented. Multiple implementations can also be retrieved. Conditions, filters on metadata and weighting implementation are all supported to allow full customization of which implementation should be retrieved in which use case.Each of those have several knobs to adapt them to your needs which are presented in the documentation.Testing & DebuggingInjected functions can typically be tested by passing arguments explicitly but it’s not always enough. Antidote provides test context which full isolate themselves and allow overriding any dependencies:original=world[Service]withworld.test.clone()asoverrides:# dependency value is different, but it's still a singleton Service instanceassertworld[Service]isnotoriginal# override examplesoverrides[Service]='x'assertworld[Service]=='x'deloverrides[Service]assertworld.get(Service)isNone@overrides.factory(Service)defbuild_service()->object:return'z'# Test context can be nested and it wouldn't impact the current test contextwithworld.test.clone()asnested_overrides:...# Outside the test context, nothing changed.assertworld[Service]isoriginalAntidote also provides introspection capabilities withworld.debugwhich returns a nicely formatted tree to show what Antidote actually sees without executing anything like the following:🟉 <lazy> f()
└── ∅ Service
    └── Service.__init__
        └── 🟉 <const> Conf.HOST

 ∅ = transient
 ↻ = bound
 🟉 = singletonGoing FurtherScopes are supported. Defining aScopeGlobalVarand using it as dependency will force any dependents to be updated whenever it changes (a request for example).Multiple catalogs can be used which allow you to expose only a subset of your API (dependencies) to your consumer within a catalog.You can easily define your kind of dependencies with proper typing from bothworldandinject.@injectable,@lazy,inject.me()etc.. all rely on Antidote’s core (Provider,Dependency, etc.) which is part of public API.Check out theGuidewhich goes more in depth or theReferencefor specific features.How to ContributeCheck for open issues or open a fresh issue to start a discussion around a feature or a bug.Fork the repo on GitHub. Run the tests to confirm they all pass on your  machine. If you cannot find why it fails, open an issue.Start making your changes to the master branch.Send a pull request.Be sure to merge the latest from “upstream” before making a pull request!If you have any issue during development or just want some feedback, don’t hesitate to open a pull request and ask for help ! You’re also more than welcome to open a discussion or an issue on any topic!But, no code changes will be merged if they do not pass mypy, pyright, don’t have 100% test coverage or documentation with tested examples if relevant