Package: gean
geanA minimal IOC container inspired by Spring.Installpython3 -m pip install geanRequirementsgean, like Spring, relies on types and signatures to build and resolve the dependency graph.Required language features:PEP 484 - Type Hints(Python 3.5+)PEP 526 - Syntax for Variable Annotations(Python 3.6+)FeaturesType hierarchiesA dependency of a given typeXis exposed not only asXbut also all of its super types, including generic interfaces. Variance is supported on generic types.Regular inheritancefromabcimportABCfromgeanimportContainer# Works with or without ABCclassWorker(ABC):passclassWorkerImpl(Worker):passcontainer=Container()container.register_class(WorkerImpl)# All of these return the same instancec1=container.resolve(WorkerImpl)c2=container.resolve(Worker)assertc1isc2assertisinstance(c1,WorkerImpl)CovariancefromgeanimportContainerfromtypingimportGeneric,TypeVar_Tco=TypeVar('_Tco',covariant=True)classPerson:passclassStudent(Person):passclassFactory(Generic[_Tco]):passclassStudentFactory(Factory[Student]):passcontainer=Container()container.register_class(StudentFactory)# All of these return the same instancec1=container.resolve(StudentFactory)c2=container.resolve(Factory[Student])c3=container.resolve(Factory[Person])assertc1isc2isc3assertisinstance(c1,StudentFactory)ContravariancefromgeanimportContainerfromtypingimportGeneric,TypeVar_Tcontra=TypeVar('_Tcontra',contravariant=True)classPerson:passclassStudent(Person):passclassValidator(Generic[_Tcontra]):passclassPersonValidator(Validator[Person]):passcontainer=Container()container.register_class(PersonValidator)# All of these return the same instancec1=container.resolve(PersonValidator)c2=container.resolve(Validator[Student])c3=container.resolve(Validator[Person])assertc1isc2isc3assertisinstance(c1,PersonValidator)CachingAll dependencies are cached as they are constructed.fromgeanimportContainerclassA:passcontainer=Container()container.register_class(A)a1=container.resolve(A)a2=container.resolve(A)asserta1isa2AutowiringDependencies can be autowired if a class does not declare an explicit constructor.Field names can disambiguate same-type dependencies.fromgeanimportContainerclassSubject:defwork(self):print('working')classManager:subject:Subject# will be autowireddefrun(self):self.subject.work()container=Container()# Order of registration does not mattercontainer.register_class(Manager)container.register_class(Subject)# This prints 'working'container.resolve(Manager).run()Constructor wiringIf a class defines an explicit constructor, dependencies will be passed as arguments.Parameter names can disambiguate same-type dependencies.fromgeanimportContainerclassSubject:defwork(self):print('working')classManager:def__init__(self,subject:Subject):self.subject=subjectdefrun(self):self.subject.work()container=Container()# Order of registration does not mattercontainer.register_class(Manager)container.register_class(Subject)# This prints 'working'container.resolve(Manager).run()ModulesAmoduleis a class whose name ends inModule.A module may use@includesto declaratively register other classes or modules.A module may use public methods to create dependencies programmatically.fromgeanimportContainer,includesclassPingService:defping(self,addr):...classDNSService:defresolve(self,name):...@includes(DNSService,PingService,)classNetworkModule:passclassAppConfig:passclassApplication:config:AppConfigdns_service:DNSServiceping_service:PingServicedefrun(self):print(self.config)self.ping_service.ping(self.dns_service.resolve('garciat.com'))defload_configuration()->AppConfig:...@includes(NetworkModule,Application,)classApplicationModule:# Create config programmaticallydefconfig(self)->AppConfig:returnload_configuration()container=Container()# No other dependencies need to be declared manually# Because the modules do so declarativelycontainer.register_module(ApplicationModule)container.resolve(Application).run()SingletonsDependencies can be explicitly named so that disambiguation is possible.fromgeanimportContainercontainer=Container()# Both dependencies are of type `str`container.register_instance('/tmp',name='tmp_dir')container.register_instance('/home/garciat',name='user_dir')# Disambiguate with namecontainer.resolve(str,name='tmp_dir')AlternativesAs of June 1 2020, this is a non-exhaustive list of alternative solutions that also leverage Type Hints.injectorDoes not support hierarchy with generic interfacesfrom typing import Generic, TypeVar

from injector import Injector, Module, inject, provider, singleton


_T = TypeVar('_T')

class A(Generic[_T]): pass
class B(A[int]): pass

class C:
  @inject
  def __init__(self, a: A[int]):
    self.a = a


class MyModule(Module):
  @singleton
  @provider
  def provide_b(self) -> B:  # works if return type is A[int] explicitly
    return B()


i = Injector([MyModule])

# injector.UnknownProvider: couldn't determine provider for __main__.A[int] to None
i.get(C)bobthemighty/punqDoes not support type hierarchiesjbasko/auto-initPerforms default initialization. E.g.0forint,Nonefor objectsDoes not support generic interfacesasyncee/wintGlobal stateHistorygeanstarted off asa gistI created to show @alexpizarroj how my team leverages Spring in our projects.