Package: opyoid
OpyoidDependency injection library using typings, to easily manage large applications.This project is inspired fromGuice.InstallationRunpip install opyoidto install from PyPI.Runpip install .to install from sources.This project follows theSemantic Versioning Specification.
All breaking changes are described in theChangelog.UsageSimple InjectionfromopyoidimportModule,InjectorclassMyClass:passclassMyParentClass:def__init__(self,my_param:MyClass):self.my_param=my_paramclassMyModule(Module):defconfigure(self)->None:self.bind(MyClass)self.bind(MyParentClass)injector=Injector([MyModule])my_instance=injector.inject(MyParentClass)assertisinstance(my_instance,MyParentClass)assertisinstance(my_instance.my_param,MyClass)If they are multiple bindings for the same class, the latest will be used.ModuleThe module is used to group bindings related to a feature.
You can include a module in another withinstall:fromopyoidimportModule,InjectorclassMyClass:passclassMyModule(Module):defconfigure(self)->None:self.bind(MyClass)classMyParentClass:def__init__(self,my_param:MyClass):self.my_param=my_paramclassMyParentModule(Module):defconfigure(self)->None:self.install(MyModule)self.bind(MyParentClass)injector=Injector([MyParentModule])my_instance=injector.inject(MyParentClass)assertisinstance(my_instance,MyParentClass)assertisinstance(my_instance.my_param,MyClass)Binding SubclassesfromopyoidimportModule,InjectorclassMyClass:passclassMySubClass(MyClass):passclassMyModule(Module):defconfigure(self)->None:self.bind(MyClass,to_class=MySubClass)injector=Injector([MyModule])my_instance=injector.inject(MyClass)assertisinstance(my_instance,MySubClass)Binding InstancesfromopyoidimportModule,InjectorclassMyClass:def__init__(self,my_param:str):self.my_param=my_parammy_instance=MyClass("hello")classMyModule(Module):defconfigure(self)->None:self.bind(MyClass,to_instance=my_instance)injector=Injector([MyModule])injected_instance=injector.inject(MyClass)assertmy_instanceisinjected_instanceEnvironment VariablesYou can use environment variables to easily override bindings in your application.Supported typesSupported types arestr,int,float, andbool.Environment variables are only used when loading ClassBindings, ProviderBindings or SelfBindings, not InstanceBindingsIf the corresponding environment variable exists, it will override the existing default value and bindings for the
parameter.Environment variable nameThe environment variable should be namedUPPER_CLASS_NAME_UPPER_PARAMETER_NAMEIn this example, the environment variable to set isMY_CLASS_MY_PARAMETER:@dataclassclassMyClass:my_parameter:intValue conversionFor types other than str, an automatic conversion is made:ints and floats are converted using int() and float()for booleans, authorized values are:"0", "false" and "False", will be converted toFalse"1", "true" and "True", will be converted toTrueBinding scopesWhen binding a class, you can choose the scope in which it will be instantiated.
This will only have an effect when binding classes, not instances.Singleton ScopeBy default, all classes are instantiated in a Singleton scope.
This means that only one instance of each class will be created, and it will be shared between all classes requiring it.fromopyoidimportModule,Injector,SingletonScopeclassMyClass:passclassMyParentClass:def__init__(self,my_param:MyClass):self.my_param=my_paramclassMyModule(Module):defconfigure(self)->None:self.bind(MyClass,scope=SingletonScope)self.bind(MyParentClass,scope=SingletonScope)injector=Injector([MyModule])instance_1=injector.inject(MyClass)instance_2=injector.inject(MyClass)parent_instance=injector.inject(MyParentClass)assertinstance_1isinstance_2assertinstance_1isparent_instance.my_paramPerLookup ScopeIf you use the per lookup scope, a new instance will be created every time each class is injected.fromopyoidimportModule,Injector,PerLookupScopeclassMyClass:passclassMyParentClass:def__init__(self,my_param:MyClass):self.my_param=my_paramclassMyModule(Module):defconfigure(self)->None:self.bind(MyClass,scope=PerLookupScope)self.bind(MyParentClass)injector=Injector([MyModule])instance_1=injector.inject(MyClass)instance_2=injector.inject(MyClass)parent_instance=injector.inject(MyParentClass)assertinstance_1isnotinstance_2assertinstance_1isnotparent_instance.my_paramThread ScopeThis scope only creates a new instance the first time that the class is injected in the current thread.
There will only be one instance of each class in each thread, and two instances injected from different threads will be
different objects.fromthreadingimportThreadfromopyoidimportModule,Injector,ThreadScopeclassMyClass:passclassMyModule(Module):defconfigure(self)->None:self.bind(MyClass,scope=ThreadScope)injector=Injector([MyModule])instance_1=injector.inject(MyClass)instance_2=injector.inject(MyClass)defthread_target():instance_3=injector.inject(MyClass)assertinstance_1isnotinstance_3Thread(target=thread_target).start()assertinstance_1isinstance_2Bindings without ModuleIf you prefer, you can add bindings to your injector without creating a Module class (or using both).fromopyoidimportModule,Injector,SelfBindingclassMyClass:passclassMyParentClass:def__init__(self,my_param:MyClass):self.my_param=my_paramclassMyModule(Module):defconfigure(self)->None:self.bind(MyClass)injector=Injector([MyModule],[SelfBinding(MyParentClass)])my_instance=injector.inject(MyParentClass)assertisinstance(my_instance,MyParentClass)assertisinstance(my_instance.my_param,MyClass)The same options of Module.bind are available when using bindings:fromopyoidimportClassBinding,InstanceBinding,PerLookupScope,SelfBindingclassMyClass:passclassMySubClass(MyClass):passmy_instance=MyClass()SelfBinding(MyClass)# binding a class to itselfClassBinding(MyClass,MySubClass)# binding a class to a subclassSelfBinding(MyClass,scope=PerLookupScope)# specifying scopeInstanceBinding(MyClass,my_instance)# binding an instanceSelfBinding(MyClass,named="my_name")# binding a class to itself with a specific nameInstanceBinding(MyClass,my_instance,named="my_name")# binding an instance with a specific nameInjecting TypeIf no explicit binding is defined, the last class binding will be used to inject a type:fromtypingimportTypefromopyoidimportModule,InjectorclassMyClass:passclassSubClass(MyClass):passclassMyParentClass:def__init__(self,my_param:Type[MyClass]):self.my_param=my_parammy_instance=MyClass()classMyModule(Module):defconfigure(self)->None:self.bind(MyClass)self.bind(MyClass,to_instance=my_instance)self.bind(MyClass,to_class=SubClass)self.bind(MyParentClass)injector=Injector([MyModule])parent_instance=injector.inject(MyParentClass)assertisinstance(parent_instance,MyParentClass)assertparent_instance.my_paramisSubClassDataclassesopyoidcan inject classes and parameters defined with theattrslibrary and python data classes.Notes about GenericsThe supported generic types areList,Set,Tuple,Optional,UnionandType(and any combination of them).
Other generics must be bound explicitly (e.g. you must bind a dict toDict[str, MyClass]if you want to inject it).Be careful when using generics, the bindings will only be used if the type matches exactly. For example, you cannot
implicitly bindMyClass[T]to injectMyClass, orMyClass[str]to injectMyClass[T]. You need to bind something
toMyClass[str]to be able to inject it.Advanced usageMore advanced features and examples are available in the./docsfolder.