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 โ by parameter name, then 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
- parameter name
- exact type annotation
- MRO fallback (walk base classes)
str(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 โ
โโโโโโโโโโโโโโโโโโโโโโโโโ
Best practice: keep eager+fail-fast for production parity with Spring; use lazy only for heavy/optional deps or to support negative tests.
๐ Migration Guide (v0.2.1 โ v0.3.0)
- Defaults changed:
@componentand@providesnow default tolazy=False(eager). - Proxy renamed:
LazyProxyโComponentProxy(only relevant if referenced directly). - Tests/fixtures: components intentionally missing deps should be marked
@component(lazy=True)(to avoid failinginit()), or excluded from the scan.
Example fix for an intentional failure case:
@component(lazy=True)
class MissingDep:
def __init__(self, missing):
self.missing = missing
๐ 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 -e py311
Tip: for โmissing dependencyโ tests, mark those components as lazy=True so init() remains fail-fast for real components while your test still asserts failure on resolution.
โ FAQ
Q: Can I make the container lenient at startup?
A: By design itโs strict. Prefer lazy=True on specific bindings or exclude problem modules from the scan.
Q: Thread safety?
A: Container uses ContextVar to guard re-entrancy during scanning. Singletons are created once per container; typical usage is in single-threaded app startup, then read-mostly.
Q: Frameworks? A: Framework-agnostic. Works with Flask, FastAPI, CLIs, scripts, etc.
๐ 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.3.1.tar.gz.
File metadata
- Download URL: pico_ioc-0.3.1.tar.gz
- Upload date:
- Size: 12.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
24b8b06e81c00478c8815c9c16fb25bda88896909e2fe85e2073c747dbefb0ed
|
|
| MD5 |
b5bb53b89faf73cdc255f6811c623ceb
|
|
| BLAKE2b-256 |
08aeb3bb7579e49d5e36e0a27d79f371c7059da69858dc0df0322f5394329797
|
Provenance
The following attestation bundles were made for pico_ioc-0.3.1.tar.gz:
Publisher:
publish-to-pypi.yml on dperezcabrera/pico-ioc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pico_ioc-0.3.1.tar.gz -
Subject digest:
24b8b06e81c00478c8815c9c16fb25bda88896909e2fe85e2073c747dbefb0ed - Sigstore transparency entry: 372349921
- Sigstore integration time:
-
Permalink:
dperezcabrera/pico-ioc@11d74eec48bf305c9b1841c16828323505c868e0 -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/dperezcabrera
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@11d74eec48bf305c9b1841c16828323505c868e0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pico_ioc-0.3.1-py3-none-any.whl.
File metadata
- Download URL: pico_ioc-0.3.1-py3-none-any.whl
- Upload date:
- Size: 6.8 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 |
4bc60889b8ab976c141e8f61fb3c8ebe64e02745c180b299129ac9924a169f10
|
|
| MD5 |
b28ce31db6362e033b39403b61129ff1
|
|
| BLAKE2b-256 |
d7a0e7596e82d0d434d67fba0f9a54c231e438bfd104053233b034090f572a33
|
Provenance
The following attestation bundles were made for pico_ioc-0.3.1-py3-none-any.whl:
Publisher:
publish-to-pypi.yml on dperezcabrera/pico-ioc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pico_ioc-0.3.1-py3-none-any.whl -
Subject digest:
4bc60889b8ab976c141e8f61fb3c8ebe64e02745c180b299129ac9924a169f10 - Sigstore transparency entry: 372349937
- Sigstore integration time:
-
Permalink:
dperezcabrera/pico-ioc@11d74eec48bf305c9b1841c16828323505c868e0 -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/dperezcabrera
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@11d74eec48bf305c9b1841c16828323505c868e0 -
Trigger Event:
release
-
Statement type: