Package: botox-di
BotoxBotox Injector is a dependency injection implementation based on Python type annotations.
It helps deliver the configured functional objects, decreasing coupling between a class and its dependencies.DeliveryGlobal variables? Proxy objects? Application context?There should be one (and preferably only one) obvious way to do it.Botox helps you isolate a class from the impact of different design changes and defects.
Meaning that, instead of thinking about interdependence between application modules
you will now find yourself spending your time having to focus a class on the task it is designed for.ConfigurationMonkey-patching? Decorators?Explicit is better than implicit.Botox allows a class the flexibility of being configurable.
The class rely on dependencies interface that he expect.
Explicit configurations can be written separately for different
situations that require different implementations of dependencies.UsageCould we try it in the next project?Now is better than never.Botox doesn’t require any change in code behavior it can be applied to legacy code as a refactoring.InstallationInstall and update usingpip:pipinstall-Ubotox-diExamplesClass InjectionCan be used to reduce boilerplate code in the application classes since
all work to initialize or set up dependencies is handled separately.frombotoximportInjectorclassPaymentService:...classBillingService:...classSalesService:def__init__(self,payment_service:PaymentService,billing_service:BillingService):self.payment_service=payment_serviceself.billing_service=billing_serviceinjector=Injector()injector.prepare(PaymentService)injector.prepare(BillingService)injector.prepare(SalesService)sales=injector.deliver(SalesService)assertisinstance(sales.payment_service,PaymentService)assertisinstance(sales.billing_service,BillingService)The result is class that is easier to unit test in
isolation using stubs or mock objects that simulate other objects.injector.prepare(PaymentService,PaymentServiceStub)Value InjectionCan be used when exactly one object is needed to coordinate actions across the system.frombotoximportInjectorclassAppSettings:...settings=AppSettings()injector=Injector()injector.prepare(AppSettings,settings)assertinjector.deliver(AppSettings)issettingsLambda InjectionCan be used to wrap Proxy objects in legacy code as refactoring.frombotoximportInjectorfromflaskimportgfromsqlalchemy.ormimportSessioninjector=Injector()injector.prepare(Session,lambda:g.session)Function InjectionCan be used to make factory functions with dependencies.frombotoximportInjectordefcreate_api_client(settings:Settings):returnApiClient(settings.base_url,settings.key)injector=Injector()injector.prepare(Settings)injector.prepare(ApiClient,create_api_client)Sequence InjectionCan be used to provide collection of dependencies.classPaymentService(metaclass=ABCMeta):name:strclassGooglePayService(PaymentService):...classApplePayService(PaymentService):...classFlowService:def__init__(self,payment_services:List[PaymentService]):self.payment_services=payment_servicesdefget_available_payment_methods(self):forpayment_serviceinself.payment_services:yieldpayment_service.nameinjector=Injector()injector.prepare(GooglePayService)injector.prepare(ApplePayService)injector.prepare(List[PaymentService],[GooglePayService,ApplePayService])injector.prepare(FlowService)AIOHTTPYou can use a middleware to deliver dependencies into a request handler. Asynchronous functions also supported.fromaiohttpimportwebfrombotoximportInjectorclassHelloService:defget_hello_message(self,name):returnf'Hello,{name}!'asyncdefhandle(request,service:HelloService):name=request.match_info.get('name','Anonymous')text=service.get_hello_message(name)returnweb.Response(text=text)@web.middlewareasyncdefdependency_injection(request,handler):handler=request.app.injector.inject(handler,skip=1)returnawaithandler(request)app=web.Application(middlewares=[dependency_injection])app.injector=Injector()app.injector.prepare(HelloService)app.add_routes([web.get('/',handle),web.get('/{name}',handle)])web.run_app(app)FastAPIYou can use Botox together with the built-in dependency system of FastAPI.frombotoximportInjectorfromfastapiimportDepends,FastAPIclassHelloService:defget_hello_message(self,name):returnf'Hello,{name}!'app=FastAPI(openapi_url=None)app.injector=Injector()app.injector.prepare(HelloService)definject(token:Type[T])->T:asyncdef_get_dependency(request:Request):returnrequest.app.injector.deliver(token)returnDepends(_get_dependency,use_cache=False)@app.get("/{name}")asyncdefhandle(name:str,service=inject(HelloService)):returnservice.get_hello_message(name)CeleryYou can define a different application base task class to deliver dependencies into a task call.fromceleryimportCelery,TaskfrombotoximportInjectorclassCalculator:defadd(self,x,y):returnx+yclassAppTask(Task):def__call__(self,*args,**kwargs):run=self.app.injector.inject(self.run,skip=len(args))returnrun(*args,**kwargs)app=Celery('tasks',broker='pyamqp://guest@localhost//',task_cls=AppTask)app.injector=Injector()app.injector.prepare(Calculator)@app.taskdefadd(x,y,calculator:Calculator):returncalculator.add(x,y)