Skip to main content

Botox

Botox 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.

Delivery

Global 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.

Configuration

Monkey-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.

Usage

Could 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.

Installation

Install and update using pip:

pip install -U botox-di

Examples

Class Injection

Can be used to reduce boilerplate code in the application classes since all work to initialize or set up dependencies is handled separately.

from botox import Injector

class PaymentService:
    ...
    
class BillingService:
    ...
    
class SalesService:

    def __init__(self, payment_service: PaymentService, billing_service: BillingService):
        self.payment_service = payment_service
        self.billing_service = billing_service
   
injector = Injector()
injector.prepare(PaymentService)
injector.prepare(BillingService)
injector.prepare(SalesService)

sales = injector.deliver(SalesService)

assert isinstance(sales.payment_service, PaymentService)
assert isinstance(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 Injection

Can be used when exactly one object is needed to coordinate actions across the system.

from botox import Injector

class AppSettings:
    ...
    
settings = AppSettings()

injector = Injector()
injector.prepare(AppSettings, settings)

assert injector.deliver(AppSettings) is settings

Lambda Injection

Can be used to wrap Proxy objects in legacy code as refactoring.

from botox import Injector
from flask import g
from sqlalchemy.orm import Session

injector = Injector()
injector.prepare(Session, lambda: g.session)

Function Injection

Can be used to make factory functions with dependencies.

from botox import Injector

def create_api_client(settings: Settings):
    return ApiClient(settings.base_url, settings.key)
    
injector = Injector()
injector.prepare(Settings)
injector.prepare(ApiClient, create_api_client)

Sequence Injection

Can be used to provide collection of dependencies.

class PaymentService(metaclass=ABCMeta):
    name: str
    
class GooglePayService(PaymentService):
    ...
    
class ApplePayService(PaymentService):
    ...

class FlowService:

    def __init__(self, payment_services: List[PaymentService]):
        self.payment_services = payment_services
        
    def get_available_payment_methods(self):
        for payment_service in self.payment_services:
            yield payment_service.name
    
injector = Injector()
injector.prepare(GooglePayService)
injector.prepare(ApplePayService)
injector.prepare(List[PaymentService], [GooglePayService, ApplePayService])
injector.prepare(FlowService)

AIOHTTP

You can use a middleware to deliver dependencies into a request handler. Asynchronous functions also supported.

from aiohttp import web
from botox import Injector

class HelloService:
    def get_hello_message(self, name):
        return f'Hello, {name}!'

async def handle(request, service: HelloService):
    name = request.match_info.get('name', 'Anonymous')
    text = service.get_hello_message(name)
    return web.Response(text=text)

@web.middleware
async def dependency_injection(request, handler):
    handler = request.app.injector.inject(handler, skip=1)
    return await handler(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)

FastAPI

You can use Botox together with the built-in dependency system of FastAPI.

from botox import Injector
from fastapi import Depends, FastAPI

class HelloService:
    def get_hello_message(self, name):
        return f'Hello, {name}!'

app = FastAPI(openapi_url=None)
app.injector = Injector()
app.injector.prepare(HelloService)

def inject(token: Type[T]) -> T:
    async def _get_dependency(request: Request):
        return request.app.injector.deliver(token)

    return Depends(_get_dependency, use_cache=False)

@app.get("/{name}")
async def handle(name: str, service=inject(HelloService)):
    return service.get_hello_message(name)

Celery

You can define a different application base task class to deliver dependencies into a task call.

from celery import Celery, Task
from botox import Injector

class Calculator:
    def add(self, x, y):
        return x + y

class AppTask(Task):
    def __call__(self, *args, **kwargs):
        run = self.app.injector.inject(self.run, skip=len(args))
        return run(*args, **kwargs)

app = Celery('tasks', broker='pyamqp://guest@localhost//', task_cls=AppTask)
app.injector = Injector()
app.injector.prepare(Calculator)

@app.task
def add(x, y, calculator: Calculator):
    return calculator.add(x, y)

Metadata

Release files for botox-di 1.5.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for botox-di 1.5.2
File Size Uploaded
botox-di-1.5.2.tar.gz 6.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for botox-di 1.5.2
File Interpreter ABI Platform
botox_di-1.5.2-py3-none-any.whl Python 3 none any Details

Total release size: 12.7 kB

Release files / botox-di-1.5.2.tar.gz

Download URL botox-di-1.5.2.tar.gz
Size 6.2 kB
Tags Source
SHA-256 checksum
How to use checksums
01b7421d8b72999d5c7a1a150851d05e308769782dc7b3482faba2f8525e436f
BLAKE2b-256 checksum
How to use checksums
eacaf0ae257887c872bcc1d9bbd0664db5cdca281a0a707a50a78c2070c593c3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.8.0 pkginfo/1.8.2 readme-renderer/32.0 requests/2.26.0 requests-toolbelt/0.9.1 urllib3/1.26.6 tqdm/4.62.3 importlib-metadata/4.11.1 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.9.9

Release files / botox_di-1.5.2-py3-none-any.whl

Download URL botox_di-1.5.2-py3-none-any.whl
Size 6.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2e8c4f1188958d72d2b59b98b1427e4090e6e2f760737b446a9691f8225cdf33
BLAKE2b-256 checksum
How to use checksums
f9bbb799e6f605e5e0d58f2b2e1e588e8e1916b17599a76cf39ecdb358e8d10b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.8.0 pkginfo/1.8.2 readme-renderer/32.0 requests/2.26.0 requests-toolbelt/0.9.1 urllib3/1.26.6 tqdm/4.62.3 importlib-metadata/4.11.1 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.9.9

Release history Release notifications | RSS feed

This release

1.5.2 This release

2 release files

1.5.1

2 release files

1.5.0

2 release files

1.4.4

2 release files

1.4.3

2 release files

1.4.2

2 release files

1.4.1

2 release files

1.4.0

2 release files

1.3.0

1 release file

1.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page