Package: trainspotting
Choose dependency injectionFriendly with MyPySupports lazy injectionsSupports fabrics with environment variablesCan connect/disconnect clients in correct orderInstallpip install trainspottingContactFeel free to ask questions in telegramt.me/PulitzerKennyExamplesfromtypingimportProtocolfromdataclassesimportdataclassfromtrainspottingimportDependencyInjectorfromtrainspotting.factoryimportfactory,EnvFieldclassTransportProtocol(Protocol):defgo(self):...classEngineProtocol(Protocol):defstart(self):...classHeadlightsProtocol(Protocol):deflight(self):...@dataclassclassDriver:transport:TransportProtocoldefdrive(self):self.transport.go()@dataclassclassV8Engine:sound:strdefstart(self):print(self.sound)@dataclassclassXenonHeadlights:brightness:intdeflight(self):print(f'LIGHT!{self.brightness}')@dataclassclassCar:engine:EngineProtocolheadlights:HeadlightsProtocoldefgo(self):self.engine.start()self.headlights.light()defget_config():return{EngineProtocol:factory(V8Engine,sound=EnvField('ENGINE_SOUND')),HeadlightsProtocol:factory(XenonHeadlights,brightness=EnvField('HEADLIGHTS_BRIGHTNESS',int)),TransportProtocol:Car,}injector=DependencyInjector(get_config())injected_driver=injector.inject(Driver)injected_driver().drive()Clients connect/disconnectConnect can be used for async class initializationimportaioredisfromtypingimportProtocolfromtrainspottingimportDependencyInjectorclassClientProtocol(Protocol):asyncdefdo(self):...classRedisClient:def__init__(self):self.pool=Noneasyncdefdo(self):ifnotself.pool:raiseValueErrorprint('did something')asyncdefconnect(self):self.pool=awaitaioredis.create_redis_pool(('127.0.0.1',6379))print('connected')asyncdefdisconnect(self):self.pool.close()awaitself.pool.wait_closed()print('disconnected')asyncdefmain(client:ClientProtocol):awaitclient.do()injector=DependencyInjector({ClientProtocol:RedisClient})injected=injector.inject(main)asyncwithinjector.deps:# connectedawaitinjected()# did something# disconnectedTypes InjectionclassEntityProtocol(Protocol):defdo(self):...classEntity:defdo(self):print('do something')@dataclassclassSomeUsefulClass:entity:Type[EntityProtocol]injector.add_config({EntityProtocol:Entity})injector.inject(SomeUsefulClass)Lazy injections@injector.lazy_injectasyncdefsome_func(client:ClientProtocol):awaitclient.do_something()some_func()# raise TypeError, missing argument clientinjector.do_lazy_injections()some_func()# okExtend or change configinjector.add_config({ClientProtocol:Client})EnvFieldimportosfromtypingimportProtocolfromdataclassesimportdataclassfromtrainspottingimportDependencyInjectorfromtrainspotting.factoryimportfactory,EnvFieldclassClientProtocol(Protocol):defdo(self):...@dataclassclassClient(ClientProtocol):url:strlog_prefix:strtimeout:int=5defdo(self):print(f'{self.log_prefix}: sent request to{self.url}with timeout{self.timeout}')injector=DependencyInjector({ClientProtocol:factory(Client,url=EnvField('SERVICE_URL'),log_prefix=EnvField('LOG_PREFIX',default='CLIENT'),timeout=EnvField('SERVICE_TIMEOUT',int,optional=True),)})defmain(client:ClientProtocol):client.do()os.environ.update({'SERVICE_URL':'some_url'})injected=injector.inject(main)injected()# CLIENT: sent request to some_url with timeout 5Supports type validationfromtypingimportProtocol,runtime_checkablefromtrainspottingimportDependencyInjector@runtime_checkableclassClientProtocol(Protocol):defdo(self):...classClient:defdo(self):print('did something')classBadClient:defwrong_do(self):...defmain(client:ClientProtocol):client.do()injector=DependencyInjector({ClientProtocol:BadClient,})injector.inject(main)()# raise InjectionError: <class 'BadClient'> does not realize <class 'ClientProtocol'> interfaceinjector=DependencyInjector({ClientProtocol:Client,})injector.inject(main)()# prints: did something