Package: dishka
Dishka (from russian "cute DI")Cute DI framework with scopes and agreeable API.📚DocumentationPurposeThis library is targeting to provide only an IoC-container but make it really useful. If you are tired manually passing objects to create others objects which are only used to create more object - we have a solution. Not all project require an IoC-container, but check what we have.Unlike other instruments we are not trying to solve tasks not related to dependency injection. We want to keep DI in place, not soiling you code with global variables and additional specifiers in all places.Main ideas:Scopes. Any object can have lifespan of the whole app, single request or even more fractionally. Many frameworks do not have scopes or have only 2 of them. Here you can have as many scopes as you need.Finalization. Some dependencies like database connections must be not only created, but carefully released. Many framework lack this essential featureModular providers. Instead of creating lots of separate functions or contrariwise a big single class, you can split your factories into several classes, which makes them simpler reusable.Clean dependencies. You do not need to add custom markers to the code of dependencies so to allow library to see them. All customization is done within providers code and only borders of scopes have to deal with library API.Simple API. You need minimum of objects to start using library. You can easily integrate it with your task framework, examples provided.Speed. It is fast enough so you not to worry about. It is even faster than many of the analogs.See more intechnical requirementsQuickstartInstall dishkapipinstalldishkaCreateProviderinstance. It is only used co setup all factories providing your objects.fromdishkaimportProviderprovider=Provider()Register functions which provide dependencies. Do not forget to place correct typehints for parameters and result. We usescope=Scope.APPfor dependencies which ar created only once in applicaiton lifetime, andscope=Scope.REQUESTfor those which should be recreated for each processing request/event/etc.fromdishkaimportProvider,Scopedefget_a()->A:returnA()defget_b(a:A)->B:returnB(a)provider=Provider()provider.provide(get_a,scope=Scope.APP)provider.provide(get_b,scope=Scope.REQUEST)This can be also rewritten using classes:fromdishkaimportprovide,Provider,ScopeclassMyProvider(Provider):@provide(scope=Scope.APP)defget_a(self)->A:returnA()@provide(scope=Scope.REQUEST)defget_b(self,a:A)->B:returnB(a)provider=MyProvider()Create Container instance passing providers, and step intoAPPscope. Container holds dependencies cache and is used to retrieve them. Here, you can use.getmethod to access APP-scoped dependencies:fromdishkaimportmake_containercontainer=make_container(provider)# it has Scope.APPa=container.get(A)# `A` has Scope.APP, so it is accessible hereYou can enter and exitREQUESTscope multiple times after that:fromdishkaimportmake_containercontainer=make_container(provider)withcontainer()asrequest_container:b=request_container.get(B)# `B` has Scope.REQUESTa=request_container.get(A)# `A` is accessible here toowithcontainer()asrequest_container:b=request_container.get(B)# another instance of `B`a=request_container.get(A)# the same instance of `A`Close container in the end:container.close()If you are using supported framework add decorators and middleware for it.fromdishka.integrations.fastapiimport(Depends,inject,setup_dishka,)@router.get("/")@injectasyncdefindex(a:Annotated[A,Depends()])->str:......setup_dishka(container,app)ConceptsDependencyis what you need for some part of your code to work. They are just object which you do not create in place and probably want to replace some day. At least for tests.
Some of them can live while you application is running, others are destroyed and created on each request. Dependencies can depend on other objects, which are their dependencies.Scopeis a lifespan of a dependency. Standard scopes are:APP->REQUEST->ACTION->STEP.You decide when to enter and exit them, but it is done one by one. You set a scope for your dependency when you configure how to create it. If the same dependency is requested multiple time within one scope without leaving it, then by default the same instance is returned.If you are developing web application, you would enterAPPscope on startup, and you wouldREQUESTscope in each HTTP-request.You can provide your own Scopes class if you are not satisfied with standard flow.Containeris what you use to get your dependency. You just call.get(SomeType)and it finds a way to get you an instance of that type. It does not create things itself, but manages their lifecycle and caches. It delegates objects creation to providers which are passed during creation.Provideris a collection of functions which really provide some objects.
Provider itself is a class with some attributes and methods. Each of them is either result ofprovide,aliasordecorate. They can be used as provider methods, functions to assign attributes or method decorators.@providecan be used as a decorator for some method. This method will be called when corresponding dependency has to be created. Name of the method is not important: just check that it is different form otherProviderattributes. Type hints do matter: they show what this method creates and what does it require. All method parameters are treated as other dependencies and created using container.Ifprovideis used with some class then that class itself is treated as a factory (__init__is analyzed for parameters). But do not forget to assing that call to some attribute otherwise it will be ignored.Component- is an isolated group of providers within the same container identified by a string. When dependency is requested it is searched only within the same component as its dependant, unless it is declared explicitly.This allows you to have multiple parts of application build separately without need to think if the use same types.TipsAdd method and mark it with@providedecorator. It can be sync or async method returning some value.classMyProvider(Provider):@provide(scope=Scope.REQUEST)defget_a(self)->A:returnA()Want some finalization when exiting the scope? Make that method generator:classMyProvider(Provider):@provide(scope=Scope.REQUEST)defget_a(self)->Iterable[A]:a=A()yieldaa.close()Do not have any specific logic and just want to create class using its__init__? then add a provider attribute usingprovideas function passing that class.classMyProvider(Provider):a=provide(A,scope=Scope.REQUEST)Want to create a child class instance when parent is requested? add asourceattribute toprovidefunction with a parent class while passing child as a first parameterclassMyProvider(Provider):a=provide(source=AChild,scope=Scope.REQUEST,provides=A)Having multiple interfaces which can be created as a same class with defined provider? Use alias:classMyProvider(Provider):p=alias(source=A,provides=AProtocol)it works the same way asclassMyProvider(Provider):@provide(scope=<ScopeofA>)defp(self,a:A)->AProtocol:returnaWant to apply decorator pattern and do not want to alter existing provide method? Usedecorate. It will construct object using earlie defined provider and then pass it to your decorator before returning from the container.classMyProvider(Provider):@decoratedefdecorate_a(self,a:A)->A:returnADecorator(a)Decorator function can also have additional parameters.Want to goasync? Make provide methods asynchronous. Create async container. Useasync withand awaitgetcalls:classMyProvider(Provider):@provide(scope=Scope.APP)asyncdefget_a(self)->A:returnA()container=make_async_container(MyProvider())a=awaitcontainer.get(A)Having some data connected with scope which you want to use when solving dependencies? Set it when entering scope. These classes can be used as parameters of yourprovidemethodscontainer=make_async_container(MyProvider(),context={App:app})withcontainer(context={RequestClass:request_instance})asrequest_container:passHaving to many dependencies? Or maybe want to replace only part of them in tests keeping others? Create multipleProviderclassescontainer=make_container(MyProvider(),OtherProvider())Tired of providingscope==for each depedency? Set it inside yourProviderclass and all dependencies with no scope will use it.classMyProvider(Provider):scope=Scope.APP@provideasyncdefget_a(self)->A:returnA()