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.0.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.0-py3-none-any.whl (6.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pico_ioc-0.2.0.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.0.tar.gz
Algorithm Hash digest
SHA256 6c9f84d8358e1d4d258a3f9d41eaef205fb3b5741f8332162f7d766aaac86bad
MD5 a7ccbb4acbc3b7fd942a1fb88053f92b
BLAKE2b-256 e5774c36d2e18333a92fb87e026c57c99d7cda69fad8bad1a499c77fccdd432c

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: pico_ioc-0.2.0-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.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f7a2a834c9c2de7ad7a8b3db3cc3f5f5f82b2d921c69a1c92c63b5a22a56fb45
MD5 5cb46ec8406d759eba586c6bfc62e355
BLAKE2b-256 6aee8fed3d5c5974cf7577e46d7e8d4a459d526ab19e5855f02bc17d1de6f519

See more details on using hashes here.

Provenance

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