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"

๐Ÿ“ Notes on Annotations (PEP 563)

Pico-IoC fully supports postponed evaluation of annotations (from __future__ import annotations, a.k.a. PEP 563) in Python 3.8โ€“3.10.

  • Type hints are evaluated with typing.get_type_hints and safely resolved.
  • Missing dependencies always raise a NameError, never a TypeError.
  • Behavior is consistent across Python 3.8+ and Python 3.11+ (where PEP 563 is no longer default).

This means you can freely use either direct type hints or string-based annotations in your components and factories, without breaking dependency injection.


โšก 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.6.0.tar.gz (31.9 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.6.0-py3-none-any.whl (12.5 kB view details)

Uploaded Python 3

File details

Details for the file pico_ioc-0.6.0.tar.gz.

File metadata

  • Download URL: pico_ioc-0.6.0.tar.gz
  • Upload date:
  • Size: 31.9 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.6.0.tar.gz
Algorithm Hash digest
SHA256 3c3ab1aeac94370aa37f5d65bbc7b6c5fa26ac2bd78632fb0721640dc310493b
MD5 a874633a65af6c18ca8379494daaa3a4
BLAKE2b-256 6bcd118222654a9fd1aa43b77d396c5ac2468a19d082d55c7622a55d4a396500

See more details on using hashes here.

File details

Details for the file pico_ioc-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: pico_ioc-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 12.5 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.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 29eea08b8c72d20b9557e0a2f84a36ad7809c8853c191cd1aaa506780c395f5b
MD5 c73c6392aaf200d3e70e8d6af146bbef
BLAKE2b-256 58e4e564f177241855af5b8dbb855051247bd68444ac4ab69420678af6f8ab0d

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