Package: wirinj
WirinjA comfy dependency injection library for Python 3.Why choose wirinjMinimal boiler plate code.Injection via__init__or viaattributes.Dependencies automatically detected throughreflection.No naming conventions required.Zero codefactories.Friendly withIDE'scode completion(e.g. withPyCharm).Autowiringoption.Injection reportsto easily debug dependency problems.Simple but powerful wiring configuration.Open and extendable architecture.InstallationPython >=3.6Tested with Python3.6,3.7and3.8.$pipinstallwirinjTable of ContentsHow to use itCode completion on the IDEInjection typesInto attributesIntoinitargumentsFactoriesDependency definitionsDefinition formatdict keysdict valuesWhen to specify the classCreation-context-dependent definitionCustom-built dependenciesCustom-built dependencies with argumentsSplit definitionsAutowiringHeuristic rulesAutowiring for productionAutowiring reportNo singletonsoptionInjection reportsDebugging the injectionMissing dependenciesInstance errorA complete injection exampleHow to use itExample (autowiring.py):...classMyService:...classMyObject:my_service:MyService=INJECTEDmy_config:str=INJECTEDdef__init__(self,param):...config={'my_config':'some conf',}# Use a function to get access to the root dependencies@inject(Definitions(config),Autowiring())defdo(my_service:MyService,my_object_factory:Type[MyObject]):print(my_service)my_object1=my_object_factory(10)print(my_object1)# Inject and run itdo()Output:my_service = <MyService>
my_object1 = <MyObject> -> my_config: "some conf", param: 10, my_service: <MyService>Explanation of the example aboveMyServiceandMyObjectare the two user classes.Attributesmy_serviceandmy_configare set with the constantINJECTEDto indicate that they must be injected.The function nameddo, or any other name you choose, will contain the code inside the injection context with access to any required dependency. This function is decorated with@injectwhich will inject the dependencies into the function parameters.@injecttakes as arguments one or more dependency sources. In this case, aDefinitionfor the static config values and anAutowiringto automatically instantiate the required objects.wirinjwill use this ordered list of sources to locate any required dependency.As the first parameter in the functiondo(my_service) is not defined in the first source (config), the dependency will be requested to the second one (Autowiring) which will lazily instantiate aMyServiceobject. The class is inferred from the type annotation of the parameter. By defaultAutowiringwill make the object a singleton and therefore any subsequent request for this class will get the same unique instance.The second parameter (my_object_factory) is annotated asType[MyObject].Type[]is part of the standardtypinglibrary. It indicates that the parameter is not expected to contain an object of classMyObjectbut the classMyObjectitself or a subclass of it. Therefore you may usemy_object_factoryvariable the same way you would useMyObjectclass. In the body of the functiondo, this parameter is called to instantiate a newMyObject. By callingmy_object_factoryinstead ofMyObjectyou makewirinjto inject all its required dependencies during the instantiation:# This instantiate a MyObject but, as expected, nothing is injected.obj=MyObject(10)# This is the same but its dependencies are automatically injected BEFORE running __init__obj=my_object_factory(10)The last line runs the functiondowith no parameters. The@injectdecorator will inject them from the dependency sources.Code completion on the IDEIf you are using anIDEsuch asPyCharm, you will notice thatcode completionwill work as expected in the previous example even for the factory.Injection typesInto attributesExample (attribute_injection.py):classCat:feeder:Feeder=INJECTEDdef__init__(self,color,weight):...@inject(...)deffn(factory:Type[Cat]):cat=factory('blue',12)When you call the factory:The attributes set toINJECTEDare located and injected.The__init__method is called.Into__init__argumentsExample (init_injection.py):classCat:def__init__(self,color,weight,feeder:Feeder=INJECTED):...@inject(...)deffn(factory:Type[Cat]):cat=factory('blue',12)...colorandweightare passed to__init__.feederis injected.For__init__injections, it is not required to setINJECTEDas a default value. Any missing argument not passed to the constructor will be injected too. This way you can inject into third party classes whose code is not under your control. However, if you can assignINJECTEDas a default value, the IDE won't complain about a missing argument and at the same time you will get aMissingDependenciesErrorif the dependency is missing which is helpful to early detect dependency issues.FactoriesI have already explained in thefirst exampleof this README how factories are used. Now, I'm going to elaborate on that a little more.You can think of a Python class as an object factory because if you call it as a function, you get a new instance of the object.Wirinjallows you to inject classes so that they can be used as factories. Unlike the original class, the injected version will also inject dependencies into the newly created objects.Pay attention to the functionfuncand its argumentcat_classin this example:fromtypingimportTypeclassCat:passclassBlackCat(Cat):passdeffunc(cat_class:Type[Cat]):returncat_class()print('Cat: ',func(Cat))print('BlackCat: ',func(BlackCat))Output:Cat:  <__main__.Cat object at 0x7fc458bc7588>
BlackCat:  <__main__.BlackCat object at 0x7fc458bc7588>Note that you pass a class and not an object as an argument.Type[Cat], as described in thetypinglibrary docs, represents the classCator a subclass of it. A parameter annotated withType[]expects to receive a class and not an object. In the body of the function, the parameter can be used the same way as its subscribed class would be used.Example (factory.py):fromtypingimportTypeclassCat:def__init__(self,sound):...@inject(...)deffn(cat_factory:Type[Cat]):cat=cat_factory('Meow')print('cat:',cat)fn()By using theType[]annotation, the IDE recognizes the parameter as if it was the original class but with the difference that any newly created object will be automatically injected.# This instantiate a Cat object but, as expected, nothing is injected.cat=Cat('Meow')# This is the same but its dependencies are automatically injected BEFORE running __init__cat=cat_factory('Meow')You have both things: the injection is enabled and the IDE completion is fully functional:Dependency definitionsIn previous examples, the wiring configuration has been relegated to the automaticAutowiringclass. However you will gain control by explicity defining how your dependencies have to be met. The general way to do this would be something like this:defs={Cat:Instance(),'dog':Singleton(Dog),}@inject(Definitions(defs))TheDefinitionsclass allows you to configure the wiring of your classes according to one or more definitiondictparameters (defsin the example). The first definitions take precedence.Definition formatOne or severaldictarguments passed to theDefinitionclass define your dependency configuration.
Eachkeyrepresents an argument being injected.
Thevaluerepresents how is it injected:dict keysIf thekeyis aclass, it will match the argument'stypeannotation.
E.g.: the first key in the example above causes any argument of typeCat, no matter its name, to be injected with a newinstanceofCat.If thekeyis astringit will match the argumentname.
E.g.: the second key causes any argument with name'dog'to be injected with a uniqueDoginstance.
Here, as the class can't be inferred from the key, you need to explicitly provide the class as an argument:'dog': Singleton(Dog).dict valuesEachvaluein thedictcan be:Aliteralvalue you want to be injected. E.g.'db_name': 'my-db'.Instance: inject a new instance each time.Singleton: inject the same unique instance every time.Factory: inject a factory object that can be called to create new injected objects dynamically.CustomInstance: similar toInstancebut you provide a custom function to have full control over instantiation.CustomSingleton: similar toSingletonbut you provide a custom function which will create the object.Any other user defined subclasses ofDependencyBuilderorDependency.Example (definition_types.py):classHouse:passclassCat:passclassDog:passdefdog_builder():""" Custom instantiation """dog=Dog()dog.random=randint(50,100)returndogdefs={House:Singleton(),Cat:Instance(),Type[Cat]:Factory(),Dog:CustomInstance(dog_builder),Type[Dog]:Factory(),}@inject(Definitions(defs))deffn(house:House,cat_factory:Type[Cat],dog_factory:Type[Dog]):cat=cat_factory()dog=dog_factory()print('house:',house)print('cat:',cat)print('dog:',dog)fn()When to specify the classInstance,SingletonandFactoryaccept an optional class argument to indicate the class of the object being created.
There are two use cases where you need to pass the class:Thekeyis astringand therefore the dependency class is undefined.The attribute or argument being injected is annotated with abase classbut you want to provide a specificsubclassof it.Example of both use cases (explicit_class.py):classPet:passclassCat(Pet):passclassDog(Pet):passdefs={'cat':Singleton(Cat),Pet:Singleton(Dog),}@inject(Definitions(defs))deffn(cat,pet:Pet):print('cat is a',cat.__class__.__name__)print('pet is a',pet.__class__.__name__)fn()Output:cat is a Cat
pet is a DogCreation-context-dependent definitionclassNail:passclassLeg:def__init__(self,nail:Nail):passclassCat:def__init__(self,leg:Leg):passImaginewirinjis injecting aCatwhich requires aLegwhich requires aNail.
The injector will gather:First, theNailthat has no dependencies.Then, theLegwith theNailas an argument.Finally, theCatwith theLegas an argument.We can think of this process as a path:Cat-> nail:Nail-> leg:Leg.
I call this theinstantiation path.You can explicitly specify ainstantiation pathconstraint in the definitiondict.Example (instantiation_path.py):classAnimal:def__init__(self,sound):self.sound=soundclassDog(Animal):passclassCat(Animal):passclassCow(Animal):passdefs={Dog:Instance(),Cat:Instance(),Cow:Instance(),(Dog,'sound'):'woof',(Cat,'sound'):'meow','sound':'?',}@inject(Definitions(defs))deffn(cat:Cat,dog:Dog,cow:Cow):print('Cat:',cat.sound)print('Dog:',dog.sound)print('Cow:',cow.sound)fn()Output:Cat: meow
Dog: woof
Cow: ?To restrict a definition entry to a particularinstantiation pathwe use atuplein thekeypart.
Thistuplemust match the last entries in theinstantiation path.For eachtupleentry, astringrefers to the argumentnameand aclassrefers to the argumenttypeannotation.If two entries match the required dependency, the more specific one will be chosen.Custom-built dependenciesInstanceandSingletonare used for simple class instantiation.
When a custom process is required to create or locate the dependency, useCustomInstanceorCustomSingleton.
Both take afunctionas an argument.Example (custom_build.py):fromrandomimportrandintfromwirinjimportCustomInstance,inject,DefinitionsclassCat:def__init__(self,color,weight):self.color=colorself.weight=weightdef__str__(self):returnf'A{self.color}pounds{self.weight}cat.'defcreate_cat(color):returnCat(color,randint(4,20))defs={'color':'blue',Cat:CustomInstance(create_cat),}@inject(Definitions(defs))deffn(cat1:Cat,cat2:Cat,cat3:Cat):print(cat1)print(cat2)print(cat3)fn()Output:A 11 pounds blue cat.
A 5 pounds blue cat.
A 14 pounds blue cat.Custom-built dependencies with argumentsIn the previous example, the object is instantiated without arguments,
so all of its__init__arguments are injected from dependencies.If your constructor requires some arguments to be passed (explicit arguments) and others to be injected (injection arguments),
I recommend to follow these rules:In the__init__method, put theexplicit argumentsfirst and then theinjection arguments.
This allow you to use positional arguments when you create the object.Set the default value of theinjection argumentstoINJECTED.
This way the IDEcode completionwill not complain about missing arguments.
Also, this is the only way you can have defaults in yourexplicit argumentswhen they are followed byinjection arguments.About the builder function that you pass toCustomInstance, use the same name and position for theexplicit argumentsas you use in the__init__method.
The rest of the arguments don't have to be related at all to the__init__arguments.
Indeed, you can specify as many dependency arguments as you need to create the object.
The injection process will inspect the function signature and will provide them.Example (custom_build_with_args.py):fromrandomimportrandintfromtypingimportTypefromwirinjimportCustomInstance,Factory,inject,DefinitionsclassCat:def__init__(self,name,color=None,weight=None):self.name=nameself.color=colorself.weight=weightdef__str__(self):returnf'{self.name}is a{self.color}pounds{self.weight}cat.'defcreate_cat(name,color):returnCat(name,color,randint(4,20))defs={'color':'black',Cat:CustomInstance(create_cat),Type[Cat]:Factory(),}@inject(Definitions(defs))deffn(factory:Type[Cat]):cat=factory('Tom')print(cat)cat2=factory('Sam')print(cat2)fn()Output:Tom is a 8 pounds black cat.
Sam is a 14 pounds black cat.About the 3 arguments ofCat.__init__:One comes from calling the factory.Another one from the dependency configuration.The third is generated by the custom creation function.Split definitionsYou can split the dependency configuration in severaldictdefinitions.Example (split_definitions.py):classCat:sound:str=INJECTEDweight:float=INJECTEDconfig={'sound':'meow','weight':5,}wiring={Cat:Instance(),Type[Cat]:Factory()}@inject(Definitions(config,wiring))deffn(...):...Definitionsaccepts any number of definitiondicts.AutowiringYou can add anAutowiringinstance as a last resort to provide a dependency when it is undefined.
The automatically created dependency will be of typeInstance,SingletonorFactorydependening on the context.Example (autowiring.py):classMyService:def__str__(self):return'<MyService>'classMyObject:my_service:MyService=INJECTEDmy_config:str=INJECTED...def__init__(self,param):self.param=paramconfig={'my_config':'some conf',}# Use a function to get access to the root dependencies@inject(Definitions(config),Autowiring())defdo(my_service:MyService,my_object_factory:Type[MyObject]):print(my_service)my_object1=my_object_factory(10)print(my_object1)# Inject and run itdo()Output:<MyService>
<MyObject> -> my_config: "some conf", param: 10, my_service: <MyService>my_configis the only dependency explicitly defined. The 3 others fall back toAutowiringwhich will automatically create:AMyServicesingleton.AType[MyObject]factory.AMyObjectinstance when the factory is called.Heuristic rulesAutowiringworks only for arguments that have atype annotation:If the annotation is aclass, as withdog: Dogin the previous example, asingletonwill be generated.If it isType[class], as withhorse_factory: Type[Horse], afactorywill be provided.If the injection comes from a factory, as whenhorse_factory()is called, aninstancewill be created.Autowiring for productionIn my opinion, this kind ofmagicshould not be used in production environments;
you should not take the risk of leaving such important wiring decisions in the hands of an heuristic algorithm.Fortunately, you can useAutowiringReportclass to easily convert the autowiring configuration into a regular dependency definition:Autowiring reportIt's quite simple to use; just pass anAutowiringReportinstance toAutowiring:Example (autowiring_report.py):report=AutowiringReport()@inject(Definitions(deps),Autowiring(report))deffn(cat:Cat,dog:Dog,horse_factory:Type[Horse]):...fn()print(report.get())Output:...
--------------- wirinj ---------------
Autowiring report:

Definitions({
    Dog: Singleton(),
    Type[Horse]: Factory(),
    Horse: Instance(),
}),
--------------------------------------Callreport.get()to get the report.
Review and copy the definitions to your configuration file, removeAutowiring, and you will be production ready.No singletonsoptionYou may setuse_singletonstoFalseto force all dependencies to be injected as anInstance.Autowiring(use_singletons=False)Injection reportsDuring each injection process, adependency treeis built with all the dependencies that are being gathered.As you change your code, your dependency configuration can get out of sync.wirinjinclude reporting features that can help you to solve this dependency issues:Debugging the injectionThe injection process can be debugged to expose the creation order and thedependency tree.Take this composition of classes (cat_example_classes.py) :classNail:passclassLeg:def__init__(self,nail1:Nail,nail2:Nail,nail3:Nail,nail4:Nail,nail5:Nail):passclassMouth:passclassEar:passclassEye:passclassHead:def__init__(self,mouth:Mouth,ear1:Ear,ear2:Ear,eye1:Eye,eye2:Eye):passclassBody:passclassTail:passclassCat:def__init__(self,head:Head,body:Body,tail:Tail,leg1:Leg,leg2:Leg,leg3:Leg,leg4:Leg):passWe can debug the injection process just by setting the logging level toDEBUGand then, requesting aCatfrom theInjector:Example (injection_debug_report.py):importlogginglogging.basicConfig(level=logging.DEBUG,format='%(message)s')inj=Injector(Autowiring(use_singletons=False))cat=inj.get(Cat)Note that we are replacing all the dependency definitions by a simpleAutowiring().
We pass the argumentuse_singletons=Falseto force all dependencies to be injected as anInstance.
By defaultAutowiringgeneratesSingletondependencies and, in this case, we don't want all the legs of theCatto be the same.The code above returns this:--------------- wirinj ---------------
        mouth:Mouth
        ear1:Ear
        ear2:Ear
        eye1:Eye
        eye2:Eye
    head:Head
    body:Body
    tail:Tail
        nail1:Nail
        nail2:Nail
        nail3:Nail
        nail4:Nail
        nail5:Nail
    leg1:Leg
        nail1:Nail
        nail2:Nail
        nail3:Nail
        nail4:Nail
        nail5:Nail
    leg2:Leg
        nail1:Nail
        nail2:Nail
        nail3:Nail
        nail4:Nail
        nail5:Nail
    leg3:Leg
        nail1:Nail
        nail2:Nail
        nail3:Nail
        nail4:Nail
        nail5:Nail
    leg4:Leg
:Cat
--------------------------------------You can see how all the dependencies are gathered, and in which order.
The final object is the requestedCatobject.Missing dependenciesInjection doesn't stop when a dependency is missing.
It continues building thedependency treeas far as it can.
This makes it possible to fix several dependency issues in one shot.If one ore more dependencies are missing, anERRORlevel report will be logged.
Therefore, you don't need to change thelogginglevel to get it; just look above theerror tracebackafter a dependency exception.Example (missing_dependencies_report.py):fromexamples.report.cat_example_classesimportCat,HeadfromwirinjimportInjector,Definitions,Instanceinj=Injector(Definitions({Cat:Instance(),Head:Instance(),}))cat2=inj.get(Cat)In the example above, we only define the wiring forCatandHead.
All the other dependencies, such asMouth,Ear,Eye, etc, are undefined.After running the example, we get:--------------- wirinj ---------------
Missing dependencies:
        mouth:Mouth *** NotFound ***
        ear1:Ear *** NotFound ***
        ear2:Ear *** NotFound ***
        eye1:Eye *** NotFound ***
        eye2:Eye *** NotFound ***
    head:Head
    body:Body *** NotFound ***
    tail:Tail *** NotFound ***
    leg1:Leg *** NotFound ***
    leg2:Leg *** NotFound ***
    leg3:Leg *** NotFound ***
    leg4:Leg *** NotFound ***
:Cat
--------------------------------------
Traceback (most recent call last):
...
wirinj.errors.MissingDependenciesError: Missing dependencies.Notice that, although the first dependency,Mouth, failed to be satisfied, the injection process continues in order to gather as much information as possible about the missing dependencies.With this report, it becomes clear which classes are undefined, and what needs to be added in the injection configuration.Instance errorIf an exception is raised during the instantiation of any of the dependencies,
you will not get thedependency treelogs as it happens when a dependency is missing.You'll need to track the stack trace to fix the problem.
However, there is a task planned in theTO-DOlist to log thedependency treein these cases too.A complete exampleThis silly example aims to illustrate several aspects of thewirinjlibrary.The two main classes,BobandMike, extendPetDeliveryPerson.
They deliver pets to the client using one or more vehicles.
Whenever aVehicleis needed, it is built in advance.WhileBobuses his only vehicle by repeating the route several times,Mikebuilds a fleet of autonomous vehicles to deliver all the pets in one trip.The classes (pet_delivery/classes.py):classPet:def__deps__(self,sound:str,weight):self.sound=soundself.weight=weight@depsdef__init__(self,gift_wrapped):self.gift_wrapped=gift_wrappeddefcry(self):returnself.sound.lower()ifself.gift_wrappedelseself.sound.upper()classCat(Pet):passclassDog(Pet):passclassBird(Pet):passclassPart:def__deps__(self,mount_sound):self.mount_sound=mount_sound@depsdef__init__(self):passdefmount(self):returnself.mount_soundclassEngine(Part):passclassPlate(Part):passclassWheel(Part):passclassContainer(Part):passclassVehicleBuilder:def__deps__(self,engine_factory:Type[Engine],plate_factory:Type[Plate],wheel_factory:Type[Wheel],container_factory:Type[Container],):self.engine_factory=engine_factoryself.plate_factory=plate_factoryself.wheel_factory=wheel_factoryself.container_factory=container_factory@depsdef__init__(self):passdefbuild(self,recipe:Dict):parts=[]# type: List[Part]parts+=[self.engine_factory()for_inrange(recipe.get('engines',0))]parts+=[self.plate_factory()for_inrange(recipe.get('plates',0))]parts+=[self.wheel_factory()for_inrange(recipe.get('wheels',0))]parts+=[self.container_factory()for_inrange(recipe.get('containers',0))]mounting=''forpartinparts:mounting+=' '+part.mount()returnmountingclassVehicle:def__deps__(self,builder:VehicleBuilder,recipe:Dict,max_load_weight):self.builder=builderself.recipe=recipeself.max_load_weight=max_load_weight@depsdef__init__(self):self.pets=[]self.build()defgo(self,miles):logger.info('{}goes{}miles'.format(self.__class__.__name__,miles))defcome_back(self):logger.info('{}commes back'.format(self.__class__.__name__))defbuild(self):logger.info('{}is built:{}'.format(self.__class__.__name__,self.builder.build(self.recipe)))defget_available_load(self):returnself.max_load_weight-sum(pet.weightforpetinself.pets)classCar(Vehicle):passclassVan(Vehicle):passclassTruck(Vehicle):passclassPetLoader:defupload(self,pets:List[Pet],vehicle:Vehicle):info='Uploading to the{}:'.format(vehicle.__class__.__name__.lower())whilepets:pet=pets.pop()ifvehicle.get_available_load()>=pet.weight:vehicle.pets.append(pet)info+=' '+pet.__class__.__name__else:pets.append(pet)breaklogger.info(info)defdownload(self,vehicle):logger.info('{}pets delivered'.format(len(vehicle.pets)))vehicle.pets=[]classPetPicker:def__deps__(self,pet_store:Type[Pet]):self.pet_store=pet_store@depsdef__init__(self):# raise Exception('HORROR!!!!')passdefpick(self,qty,gift_wrapped):info='Picking pets up: 'pets=[]for_inrange(qty):pet=self.pet_store(gift_wrapped)info+=' '+pet.cry()pets.append(pet)logger.info(info)returnpetsclassPetDeliveryPerson:@depsdef__init__(self):passdefdeliver(self,pet_qty,miles,gift_wrapped):passclassBob(PetDeliveryPerson):"""Bob builds a car and deliver pets in his vehicle repeating the route several times."""def__deps__(self,vehicle:Vehicle,pet_picker:PetPicker,pet_loader:PetLoader):self.vehicle=vehicleself.pet_picker=pet_pickerself.pet_loader=pet_loaderdefdeliver(self,pet_qty,miles,gift_wrapped):# Pick up petspets=self.pet_picker.pick(pet_qty,gift_wrapped)# Bob owns one vehicle onlywhilepets:self.pet_loader.upload(pets,self.vehicle)self.vehicle.go(miles)self.pet_loader.download(self.vehicle)self.vehicle.come_back()classMike(PetDeliveryPerson):"""Mike builds several autonomous vehicles and use them to deliver the pets all together"""def__deps__(self,vehicle_factory:Type[Vehicle],pet_picker:PetPicker,pet_loader:PetLoader):self.vehicle_factory=vehicle_factoryself.pet_picker=pet_pickerself.pet_loader=pet_loader@depsdef__init__(self):super().__init__()self.vehicles=[]# type: List[Vehicle]defget_vehicle(self):ifself.vehicles:returnself.vehicles.pop()else:returnself.vehicle_factory()defpark_vehicles(self,vehicles):self.vehicles+=vehiclesdefdeliver(self,pet_qty,miles,gift_wrapped):# Pick up petspets=self.pet_picker.pick(pet_qty,gift_wrapped)# Get vehicles and upload themvehicles=[]whilepets:vehicle=self.get_vehicle()vehicles.append(vehicle)self.pet_loader.upload(pets,vehicle)# Goforvehicleinvehicles:vehicle.go(miles)# Deliver petsforvehicleinvehicles:self.pet_loader.download(vehicle)# Come backforvehicleinvehicles:vehicle.come_back()# Parkself.park_vehicles(vehicles)Injection definitions (pet_delivery/defs.py):pet_defs={Dog:Instance(),Cat:Instance(),Bird:Instance(),(Dog,'sound'):'Woof',(Dog,'weight'):10,(Cat,'sound'):'Meow',(Cat,'weight'):5,(Bird,'sound'):'Chirp',(Bird,'weight'):0.1,}vehicle_defs={Engine:Instance(),Plate:Instance(),Wheel:Instance(),Container:Instance(),Type[Engine]:Factory(),Type[Plate]:Factory(),Type[Wheel]:Factory(),Type[Container]:Factory(),VehicleBuilder:Singleton(),(Engine,'mount_sound'):'RRRRoarrr',(Plate,'mount_sound'):'plaf',(Wheel,'mount_sound'):'pffff',(Container,'mount_sound'):'BLOOOOM',Car:Instance(),(Car,'max_load_weight'):10,(Car,'recipe'):{'engines':1,'plates':6,'wheels':4,},Van:Instance(),(Van,'max_load_weight'):50,(Van,'recipe'):{'engines':1,'plates':8,'wheels':4,},Truck:Instance(),(Truck,'max_load_weight'):200,(Truck,'recipe'):{'engines':1,'plates':20,'wheels':12,'container':1,},}common_defs={PetPicker:Singleton(),PetLoader:Singleton(),Bob:Singleton(),Mike:Singleton(),}Running the app (pet_delivery/example_1.py):world_one_defs={(Bob,Vehicle):Singleton(Car),(Bob,PetPicker,Type[Pet]):Factory(Bird),(Mike,Type[Vehicle]):Factory(Van),(Mike,PetPicker,Type[Pet]):Factory(Cat),}world_one=Definitions(pet_defs,vehicle_defs,common_defs,world_one_defs,)logging.basicConfig(format='%(message)s',level=logging.INFO)@inject(world_one)defdo(bob:Bob,mike:Mike):bob.deliver(100,5,False)bob.deliver(50,200,True)mike.deliver(20,1000,True)do()Running the same app with another wiring configuration (pet_delivery/example_2.py):world_two_defs={(Bob,Vehicle):Singleton(Van),(Bob,PetPicker,Type[Pet]):Factory(Cat),(Mike,Type[Vehicle]):Factory(Truck),(Mike,PetPicker,Type[Pet]):Factory(Dog),}world_two=Definitions(pet_defs,vehicle_defs,common_defs,world_two_defs,)logging.basicConfig(format='%(message)s',level=logging.INFO)@inject(world_two)defdo(bob:Bob,mike:Mike):bob.deliver(100,5,False)bob.deliver(50,200,True)mike.deliver(20,1000,True)do()