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 (IoC) container for Python. It helps you manage dependencies in a clean, intuitive, and Pythonic way.

The core idea is to let you build loosely coupled, easily testable applications without manually wiring components. Inspired by the IoC philosophy popularized by the Spring Framework.


✨ Key Features

  • Zero Dependencies: Pure Python, no external libraries.
  • Decorator-Based API: Simple decorators like @component and @provides.
  • Automatic Discovery: Scans your package to auto-register components.
  • Lazy Instantiation: Objects are created on first use.
  • Flexible Factories: Encapsulate complex creation logic.
  • Framework-Agnostic: Works with Flask, FastAPI, CLIs, scripts, etc.
  • Smart Dependency Resolution: Resolves by parameter name, then type annotation, then MRO fallback.
  • Auto-Exclude Caller: init() automatically skips the calling module to avoid double-initialization during scans.

📦 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}"

# Initialize the container scanning the current module
container = init(__name__)

db = container.get(DatabaseService)
print(db.get_data())  # Data from postgresql://user:pass@host/db

🧩 Custom Component Keys

You can register a component with a custom key (string, class, enum…). key= is the preferred syntax. For backwards compatibility, name= still works.

from pico_ioc import component, init

@component(name="config")  # still supported for legacy code
class AppConfig:
    def __init__(self):
        self.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__)
repo = container.get(Repository)
print(repo._url)           # postgresql://user:pass@localhost/db
print(container.get("config").db_url)

🏭 Factory Components and @provides

Factories can provide components under a specific key. Default is lazy creation (via LazyProxy).

from pico_ioc import factory_component, provides, init

CREATION_COUNTER = {"value": 0}

@factory_component
class ServicesFactory:
    @provides(key="heavy_service")  # preferred
    def make_heavy(self):
        CREATION_COUNTER["value"] += 1
        return {"payload": "Hello from heavy service"}

container = init(__name__)
svc = container.get("heavy_service")
print(CREATION_COUNTER["value"])  # 0 (not created yet)

print(svc["payload"])             # triggers creation
print(CREATION_COUNTER["value"])  # 1

📦 Project-Style Scanning

project_root/
└── myapp/
    ├── __init__.py
    ├── services.py
    └── main.py

myapp/services.py

from pico_ioc import component

@component
class Config:
    def __init__(self):
        self.base_url = "https://api.example.com"

@component
class ApiClient:
    def __init__(self, config: Config):
        self.base_url = config.base_url

    def get(self, path: str):
        return f"GET {self.base_url}/{path}"

myapp/main.py

import pico_ioc
from myapp.services import ApiClient

container = pico_ioc.init("myapp")
client = container.get(ApiClient)
print(client.get("status"))  # GET https://api.example.com/status

🧠 Dependency Resolution Order

When Pico-IoC instantiates a component, it tries to resolve each parameter in this order:

  1. Exact parameter name (string key in container)
  2. Exact type annotation (class key in container)
  3. MRO fallback (walk base classes until match)
  4. String version of the parameter name

🛠 API Reference

init(root_package_or_module, *, exclude=None, auto_exclude_caller=True) -> PicoContainer

Scan the given root package (str) or module. By default, excludes the calling module.

@component(cls=None, *, name=None)

Register a class as a component. If name is given, registers under that string; otherwise under the class type.

@factory_component

Register a class as a factory of components.

@provides(key=None, *, name=None, lazy=True)

Declare that a factory method provides a component under key. name is accepted for backwards compatibility. If lazy=True, returns a LazyProxy that instantiates on first real use.


🧪 Testing

pip install tox
tox -e py311

📜 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.0.tar.gz (12.6 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.0-py3-none-any.whl (7.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pico_ioc-0.3.0.tar.gz
  • Upload date:
  • Size: 12.6 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.0.tar.gz
Algorithm Hash digest
SHA256 ccf42e0b909f6689424b799cbecbba9d493097ee18f03747c8116fc157302c62
MD5 8461fefd67ffa3cca77f5d521bd28b7d
BLAKE2b-256 6373bcd16e2d06bb846c88300796d5e5d485f9d8ee182bb2026409b0e1dc30b9

See more details on using hashes here.

Provenance

The following attestation bundles were made for pico_ioc-0.3.0.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.0-py3-none-any.whl.

File metadata

  • Download URL: pico_ioc-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 7.2 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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ce9320ba209655e46b3ecd08d1b6d5360e91c89c4a844809b5d642f6ae5202b6
MD5 a87da04be628fd1a9a4288c89e72cb27
BLAKE2b-256 a11190f5e9df5f7d301fd3b85f65365549ab7d9b273a5766ffcf55b167a230d4

See more details on using hashes here.

Provenance

The following attestation bundles were made for pico_ioc-0.3.0-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