Picobox Dependency Injection
Picobox is an opinionated, lightweight (around 500 LOC) dependency injection framework for Python, designed to be clean and pragmatic. It focuses on explicit demands without complex graphs, implicit injections, or XML configurations. The current version is 4.0.0, and it maintains an active development with regular major and minor releases, including support for new Python versions and deprecations of older ones.
Warnings
- breaking The `picobox.contrib` package was renamed to `picobox.ext` in version 4.0.0. Any imports or references to `picobox.contrib` will now fail.
- breaking Picobox 4.0.0 dropped support for Python 3.7. Picobox 3.0.0 dropped support for Python 2.7, 3.4, 3.5, and 3.6. Ensure your Python environment meets the `requires_python>=3.8` specification.
- breaking In version 2.0.0, `picobox.push()` was changed to push a box onto the stack immediately when called as a function, rather than waiting for the `__enter__()` method if used as a context manager. This can alter the timing of when a box becomes active.
- gotcha Picobox scopes (`singleton`, `threadlocal`, `contextvars`, `factory`) determine the lifespan and sharing of injected dependencies. Misunderstanding or misapplying scopes can lead to unexpected state sharing or excessive object creation. For example, `contextvars` is critical for asyncio applications.
- gotcha The `@picobox.pass_()` decorator internally modifies the function signature. While fixed in 4.0.0 to prevent shadowing return types, on older versions or if not configured carefully, it could interfere with LSP servers or static analysis tools that rely on precise type hints.
Install
-
pip install picobox
Imports
- picobox
import picobox
- Box
from picobox import Box
- push
from picobox import push
- pass_
from picobox import pass_
- singleton
from picobox import singleton
- threadlocal
from picobox import threadlocal
- contextvars
from picobox.contrib import flaskscopes
from picobox import contextvars
Quickstart
import picobox
class MyService:
def __init__(self, config_value: int):
self.config_value = config_value
def do_work(self) -> str:
return f"Working with config: {self.config_value}"
# 1. Create a Box instance
box = picobox.Box()
# 2. Put dependencies into the box
# 'my_config' is a simple value
box.put('my_config', 123)
# 'my_service' is a factory, instantiated once per injection or scope
box.put('my_service', factory=MyService, scope=picobox.singleton, depends=['my_config'])
# 3. Use picobox.push to make the box active (often as a context manager)
with picobox.push(box):
# 4. Define a function that needs dependencies, using @picobox.pass_
@picobox.pass_('my_service')
@picobox.pass_('my_config', as_='cfg_val')
def run_application(my_service: MyService, cfg_val: int):
print(f"Retrieved config value: {cfg_val}")
print(my_service.do_work())
run_application()