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)

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 โ€” 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 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

  1. parameter name
  2. exact type annotation
  3. MRO fallback (walk base classes)
  4. 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 a ComponentProxy; 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: @component and @provides now default to lazy=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 failing init()), 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


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.3.1.tar.gz (12.1 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.3.1-py3-none-any.whl (6.8 kB view details)

Uploaded Python 3

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

Hashes for pico_ioc-0.3.1.tar.gz
Algorithm Hash digest
SHA256 24b8b06e81c00478c8815c9c16fb25bda88896909e2fe85e2073c747dbefb0ed
MD5 b5bb53b89faf73cdc255f6811c623ceb
BLAKE2b-256 08aeb3bb7579e49d5e36e0a27d79f371c7059da69858dc0df0322f5394329797

See more details on using hashes here.

Provenance

The following attestation bundles were made for pico_ioc-0.3.1.tar.gz:

Publisher: publish-to-pypi.yml on dperezcabrera/pico-ioc

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for pico_ioc-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 4bc60889b8ab976c141e8f61fb3c8ebe64e02745c180b299129ac9924a169f10
MD5 b28ce31db6362e033b39403b61129ff1
BLAKE2b-256 d7a0e7596e82d0d434d67fba0f9a54c231e438bfd104053233b034090f572a33

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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