@@ -524,6 +524,8 @@ class ContainerProtocol:
524524 """
525525```
526526
527+ ### Using Punq instead of Rodi
528+
527529The following example demonstrates how to use
528530[ ` punq ` ] ( https://github.com/bobthemighty/punq ) for dependency injection as an
529531alternative to ` rodi ` .
@@ -628,3 +630,184 @@ because not all libraries for dependency injection implement the notion of
628630` transient ` ).
629631
630632///
633+
634+ ### Using Dependency Injector instead of Rodi
635+
636+ The following example illustrates how to use [ Dependency Injector] ( https://python-dependency-injector.ets-labs.org/ ) instead of Rodi.
637+
638+ ``` python {linenums="1" hl_lines="3 19-24 31 40-41 43 75 84 95-100"}
639+ from typing import Type, TypeVar, get_type_hints
640+
641+ from dependency_injector import containers, providers
642+
643+ from blacksheep import Application, get
644+
645+ T = TypeVar(" T" )
646+
647+
648+ class APIClient : ...
649+
650+
651+ class SomeService :
652+
653+ def __init__ (self , api_client : APIClient) -> None :
654+ self .api_client = api_client
655+
656+
657+ # Define the Dependency Injector container
658+ class AppContainer (containers .DeclarativeContainer ):
659+ APIClient = providers.Singleton(APIClient)
660+ SomeService = providers.Factory(
661+ SomeService, api_client = APIClient
662+ )
663+
664+
665+ # Create the container instance
666+ container = AppContainer()
667+
668+
669+ class DependencyInjectorConnector :
670+ """
671+ This class connects a Dependency Injector container with a
672+ BlackSheep application.
673+ Dependencies are registered using the code API offered by
674+ Dependency Injector. The BlackSheep application activates services
675+ using the container when needed.
676+ """
677+
678+ def __init__ (self , container : containers.Container) -> None :
679+ self ._container = container
680+
681+ def register (self , obj_type : Type[T]) -> None :
682+ """
683+ Registers a type with the container.
684+ The code below inspects the object's constructor's types annotations to
685+ automatically configure the provider to activate the type.
686+
687+ It is not necessary to use @inject or Provide core on the __init__ method. This
688+ helps reducing code verbosity and keeping the source code not polluted by DI
689+ specific code.
690+ """
691+ constructor = getattr (obj_type, " __init__" , None )
692+
693+ if not constructor:
694+ raise ValueError (
695+ f " Type { obj_type.__name__ } does not have an __init__ method. "
696+ )
697+
698+ # Get the type hints for the constructor parameters
699+ type_hints = get_type_hints(constructor)
700+
701+ # Exclude 'self' from the parameters
702+ dependencies = {
703+ param_name: getattr (self ._container, param_type.__name__ )
704+ for param_name, param_type in type_hints.items()
705+ if param_name not in {" self" , " return" }
706+ and hasattr (self ._container, param_type.__name__ )
707+ }
708+
709+ # Create a provider for the type with its dependencies
710+ provider = providers.Factory(obj_type, ** dependencies)
711+ setattr (self ._container, obj_type.__name__ , provider)
712+
713+ def resolve (self , obj_type : Type[T], _ ) -> T:
714+ """ Resolves an instance of the given type."""
715+ provider = getattr (self ._container, obj_type.__name__ , None )
716+ if provider is None :
717+ raise TypeError (
718+ f " Type { obj_type.__name__ } is not registered in the container. "
719+ )
720+ return provider()
721+
722+ def __contains__ (self , item : Type[T]) -> bool :
723+ """ Checks if a type is registered in the container."""
724+ return hasattr (self ._container, item.__name__ )
725+
726+
727+ app = Application(
728+ services = DependencyInjectorConnector(container), show_error_details = True
729+ )
730+
731+
732+ @get (" /" )
733+ def home (service : SomeService):
734+ print (service)
735+ # DependencyInjector resolved the dependencies
736+ assert isinstance (service, SomeService)
737+ assert isinstance (service.api_client, APIClient)
738+ return id (service)
739+
740+ ```
741+
742+ ** Notes:**
743+
744+ - By using ** composition** , we can integrate a third-party dependency injection
745+ library like ` dependency_injector ` into BlackSheep without tightly coupling
746+ the framework to the library.
747+ - We need a class like ` DependencyInjectorConnector ` that acts as a
748+ bridge between ` dependency_injector ` and BlackSheep.
749+ - When wiring dependencies for your application, you use the code API offered
750+ by ** Dependency Injector** .
751+ - BlackSheep remains agnostic about the specific dependency injection library
752+ being used, but it needs the interface provided by the connector.
753+ - In this case, ** Dependency Injector** _ Provide_ and _ @inject_ constructs are
754+ not needed on request handlers because BlackSheep handles the injection of
755+ parameters into request handlers and infers when it needs to resolve a type
756+ using the provided _ connector_ .
757+
758+ In the example above, the name of the properties must match the type names
759+ simply because ` DependencyInjectorConnector ` is obtaining ` providers ` by exact
760+ type names. We could easily follow the convention of using ** snake_case** or
761+ a more robust approach of obtaining providers by types by changing the
762+ connector's logic. Expand the sections below to show different examples.
763+
764+ The connector can resolve types for controllers' ` __init__ ` methods:
765+
766+ ``` python
767+ class APIClient : ...
768+
769+
770+ class SomeService :
771+
772+ def __init__ (self , api_client : APIClient) -> None :
773+ self .api_client = api_client
774+
775+
776+ class AnotherService : ...
777+
778+
779+ # Define the Dependency Injector container
780+ class AppContainer (containers .DeclarativeContainer ):
781+ APIClient = providers.Singleton(APIClient)
782+ SomeService = providers.Factory(SomeService, api_client = APIClient)
783+ AnotherService = providers.Factory(AnotherService)
784+
785+
786+ class TestController (Controller ):
787+
788+ def __init__ (self , another_dep : AnotherService) -> None :
789+ super ().__init__ ()
790+ self ._another_dep = (
791+ another_dep # another_dep is resolved by Dependency Injector
792+ )
793+
794+ @app.controllers_router.get (" /controller-test" )
795+ def controller_test (self , service : SomeService):
796+ # DependencyInjector resolved the dependencies
797+ assert isinstance (self ._another_dep, AnotherService)
798+
799+ assert isinstance (service, SomeService)
800+ assert isinstance (service.api_client, APIClient)
801+ return id (service)
802+ ```
803+
804+ _ [ Full example] ( https://github.com/Neoteroi/BlackSheep-Examples/blob/main/dependency-injector/main.py ) ._
805+
806+ /// admonition | :snake : Examples.
807+ type: hint
808+
809+ The [ _ BlackSheep-Examples_ ] ( https://github.com/Neoteroi/BlackSheep-Examples/blob/main/dependency-injector/ ) . repository contains examples for integrating with
810+ _ Dependency Injector_ , including an example illustrating how to use ` snake_case ` for providers in
811+ the Dependency Injector's container: [ _ BlackSheep-Examples_ ] ( https://github.com/Neoteroi/BlackSheep-Examples/blob/main/dependency-injector/docs/example2.py ) .
812+
813+ ///
0 commit comments