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


¿Quieres que también te prepare un ejemplo completo en el README con fast_model y BaseChatModel para que quede documentado el nuevo orden de resolución? Así quedaría clarísimo para cualquiera que lo use.

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.2.1.tar.gz (12.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.2.1-py3-none-any.whl (7.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pico_ioc-0.2.1.tar.gz
  • Upload date:
  • Size: 12.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.2.1.tar.gz
Algorithm Hash digest
SHA256 3ebd1ef3e5d5acb22beccbb16002ece3874713c69f1dc6f18b6df3f0e5feb24c
MD5 186c7cb3110005274301186cccda9b4b
BLAKE2b-256 b32e0731792649bd03029da01c0b706261fca212b3b680fde8ed41fce2fa0af0

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: pico_ioc-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 7.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.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 16eda968134007e1291e244fd320d72f9fc6af0211584ec76fb845c0ab480b9a
MD5 7b250fa9682518102e981de0d1f0f357
BLAKE2b-256 19dd7e105453b609581df722d76efe88e14f80ca89454ed689cd4b3aa51c0be6

See more details on using hashes here.

Provenance

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