A minimalist, zero-dependency Inversion of Control (IoC) container for Python.
Project description
๐ฆ Pico-IoC: A Minimalist IoC Container for Python
Pico-IoC is a tiny, zero-dependency, decorator-based Inversion of Control container for Python.
Build loosely-coupled, testable apps without manual wiring. Inspired by the Spring ecosystem.
โจ Key Features
- Zero dependencies โ pure Python.
- Decorator API โ
@component,@factory_component,@provides. - Auto discovery โ scans a package and registers components.
- Eager by default, fail-fast โ non-lazy bindings are instantiated immediately after
init(). Missing deps fail startup. - Opt-in lazy โ set
lazy=Trueto defer creation (wrapped inComponentProxy). - Factories โ encapsulate complex creation logic.
- Smart resolution order โ parameter name takes precedence over type annotation, then MRO fallback, then string(name).
- Re-entrancy guard โ prevents
get()during scanning. - Auto-exclude caller โ
init()skips the calling module to avoid double scanning.
๐ฆ Installation
pip install pico-ioc
๐ Quick Start
from pico_ioc import component, init
@component
class AppConfig:
def get_db_url(self):
return "postgresql://user:pass@host/db"
@component
class DatabaseService:
def __init__(self, config: AppConfig):
self._cs = config.get_db_url()
def get_data(self):
return f"Data from {self._cs}"
container = init(__name__) # blueprint runs here (eager + fail-fast)
db = container.get(DatabaseService)
print(db.get_data())
๐งฉ Custom Component Keys
from pico_ioc import component, init
@component(name="config") # custom key
class AppConfig:
db_url = "postgresql://user:pass@localhost/db"
@component
class Repository:
def __init__(self, config: "config"): # resolve by NAME
self.url = config.db_url
container = init(__name__)
print(container.get("config").db_url)
๐ญ Factories and @provides
- Default is eager (
lazy=False). Eager bindings are constructed at the end ofinit(). - Use
lazy=Truefor on-first-use creation viaComponentProxy.
from pico_ioc import factory_component, provides, init
COUNTER = {"value": 0}
@factory_component
class ServicesFactory:
@provides(key="heavy_service", lazy=True)
def heavy(self):
COUNTER["value"] += 1
return {"payload": "hello"}
container = init(__name__)
svc = container.get("heavy_service") # not created yet
print(COUNTER["value"]) # 0
print(svc["payload"]) # triggers creation
print(COUNTER["value"]) # 1
๐ง Dependency Resolution Order (Updated in v0.5.0)
Starting with v0.5.0, Pico-IoC enforces name-first resolution:
- Parameter name (highest priority)
- Exact type annotation
- MRO fallback (walk base classes)
- String(name)
This means that if a dependency could match both by name and type, the name match wins.
Example:
from pico_ioc import component, factory_component, provides, init
class BaseType: ...
class Impl(BaseType): ...
@component(name="inject_by_name")
class InjectByName:
def __init__(self):
self.value = "by-name"
@factory_component
class NameVsTypeFactory:
@provides("choose", lazy=True)
def make(self, inject_by_name, hint: BaseType = None):
return inject_by_name.value
container = init(__name__)
assert container.get("choose") == "by-name"
โก Eager vs. Lazy (Blueprint Behavior)
At the end of init(), Pico-IoC performs a blueprint:
- Eager (
lazy=False, default): instantiated immediately; failures stop startup. - Lazy (
lazy=True): returns aComponentProxy; instantiated on first real use.
Lifecycle:
โโโโโโโโโโโโโโโโโโโโโโโโโ
โ init() โ
โโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโ
โ Scan & bind deps โ
โโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Blueprint instantiates all โ
โ non-lazy (eager) beans โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโ
โ Container ready โ
โโโโโโโโโโโโโโโโโโโโโโโโโ
๐ API Reference
init(root, *, exclude=None, auto_exclude_caller=True) -> PicoContainer
Scan and bind components in root (str module name or module).
Skips the calling module if auto_exclude_caller=True.
Runs blueprint (instantiate all lazy=False bindings).
@component(cls=None, *, name=None, lazy=False)
Register a class as a component.
Use name for a custom key.
Set lazy=True to defer creation.
@factory_component
Mark a class as a component factory (its methods can @provides bindings).
@provides(key, *, lazy=False)
Declare that a factory method provides a component under key.
Set lazy=True for deferred creation (ComponentProxy).
๐งช Testing
pip install tox
tox
New in v0.5.0: Additional tests verify:
- Name vs. type precedence.
- Mixed binding key resolution in factories.
- Eager vs. lazy instantiation edge cases.
๐ Extensibility: Plugins, Binder, and Lifecycle Hooks
From v0.4.0 onward, Pico-IoC can be cleanly extended without patching the core.
(plugin API docs unchanged from before)
๐ License
MIT โ see LICENSE
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pico_ioc-0.5.2.tar.gz.
File metadata
- Download URL: pico_ioc-0.5.2.tar.gz
- Upload date:
- Size: 17.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06b722ce977263c74b09112cb4c1546138d6195b6234998c924e902391c9f0a9
|
|
| MD5 |
e4fb8f3eb00d0d5d6ad05125cd84513f
|
|
| BLAKE2b-256 |
a2a183772bf7d619bfb3254b56b55b297930fced145b9a023b7ca7575084f31e
|
File details
Details for the file pico_ioc-0.5.2-py3-none-any.whl.
File metadata
- Download URL: pico_ioc-0.5.2-py3-none-any.whl
- Upload date:
- Size: 9.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6dcc4397fab88dd145c229182631fbf4b5a4436da6bf8f12d266f212934d723e
|
|
| MD5 |
7798cf746fd877627b99a83e92d7be73
|
|
| BLAKE2b-256 |
4dc5cdc29c1d103ac8e4fe2a1028f33cd9bd68c1069ab458d9427c34f31ce113
|