Skip to main content

A minimalist, zero-dependency Inversion of Control (IoC) container for Python.

Project description

๐Ÿ“ฆ Pico-IoC: A Minimalist IoC Container for Python

PyPI License: MIT CI (tox matrix) codecov Quality Gate Status Duplicated Lines (%) Maintainability Rating

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=True to defer creation (wrapped in ComponentProxy).
  • 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 of init().
  • Use lazy=True for on-first-use creation via ComponentProxy.
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:

  1. Parameter name (highest priority)
  2. Exact type annotation
  3. MRO fallback (walk base classes)
  4. 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 a ComponentProxy; 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pico_ioc-0.5.2.tar.gz (17.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pico_ioc-0.5.2-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

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

Hashes for pico_ioc-0.5.2.tar.gz
Algorithm Hash digest
SHA256 06b722ce977263c74b09112cb4c1546138d6195b6234998c924e902391c9f0a9
MD5 e4fb8f3eb00d0d5d6ad05125cd84513f
BLAKE2b-256 a2a183772bf7d619bfb3254b56b55b297930fced145b9a023b7ca7575084f31e

See more details on using hashes here.

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

Hashes for pico_ioc-0.5.2-py3-none-any.whl
Algorithm Hash digest
SHA256 6dcc4397fab88dd145c229182631fbf4b5a4436da6bf8f12d266f212934d723e
MD5 7798cf746fd877627b99a83e92d7be73
BLAKE2b-256 4dc5cdc29c1d103ac8e4fe2a1028f33cd9bd68c1069ab458d9427c34f31ce113

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page