Skip to main content

A minimalist, zero-dependency Inversion of Control (IoC) container for Python.

Project description

📦 Pico-IoC: A Robust, Async-Native IoC Container for Python

PyPI Ask DeepWiki License: MIT CI (tox matrix) codecov Quality Gate Status Duplicated Lines (%) Maintainability Rating

Pico-IoC is a lightweight, async-ready, decorator-driven IoC container built for clarity, testability, and performance. It brings Inversion of Control and dependency injection to Python in a deterministic, modern, and framework-agnostic way.

🐍 Requires Python 3.10+


⚖️ Core Principles

  • Single Purpose – Do one thing: dependency management.
  • Declarative – Use simple decorators (@component, @factory, @provides, @configured) instead of complex config files.
  • Deterministic – No hidden scanning or side-effects; everything flows from an explicit init().
  • Async-Native – Fully supports async providers, async lifecycle hooks (__ainit__), and async interceptors.
  • Fail-Fast – Detects missing bindings and circular dependencies at bootstrap (init()).
  • Testable by Design – Use overrides and profiles to swap components instantly.
  • Zero Core Dependencies – Built entirely on the Python standard library. Optional features may require external packages (see Installation).

🚀 Why Pico-IoC?

As Python systems evolve, wiring dependencies by hand becomes fragile and unmaintainable. Pico-IoC eliminates that friction by letting you declare how components relate — not how they’re created.

Feature Manual Wiring With Pico-IoC
Object creation svc = Service(Repo(Config())) svc = container.get(Service)
Replacing deps Monkey-patch overrides={Repo: FakeRepo()}
Coupling Tight Loose
Testing Painful Instant
Async support Manual Built-in (aget, __ainit__, ...)

🧩 Highlights (v2.0+)

  • Unified Configuration: Use @configured to bind both flat (ENV-like) and tree (YAML/JSON) sources via the configuration(...) builder (ADR-0010).
  • Async-aware AOP system: Method interceptors via @intercepted_by.
  • Scoped resolution: singleton, prototype, request, session, transaction, and custom scopes.
  • UnifiedComponentProxy: Transparent lazy=True and AOP proxy supporting serialization.
  • Tree-based configuration runtime: Advanced mapping with reusable adapters and discriminators (Annotated[Union[...], Discriminator(...)]).
  • Observable container context: Built-in stats, health checks (@health), observer hooks (ContainerObserver), dependency graph export (export_graph), and async cleanup.

📦 Installation

pip install pico-ioc

For optional features, you can install extras:

  • YAML Configuration:

    pip install pico-ioc[yaml]
    

    (Requires PyYAML)

  • Dependency Graph Export (Rendering):

    # You still need Graphviz command-line tools installed separately
    # This extra is currently not required by the code,
    # as export_graph generates the .dot file content directly.
    # pip install pico-ioc[graphviz] # Consider removing if not used by code
    

⚙️ Quick Example (Unified Configuration)

import os
from dataclasses import dataclass
from pico_ioc import component, configured, configuration, init, EnvSource

# 1. Define configuration with @configured
@configured(prefix="APP_", mapping="auto") # Auto-detects flat mapping
@dataclass
class Config:
    db_url: str = "sqlite:///demo.db"

# 2. Define components
@component
class Repo:
    def __init__(self, cfg: Config): # Inject config
        self.cfg = cfg
    def fetch(self):
        return f"fetching from {self.cfg.db_url}"

@component
class Service:
    def __init__(self, repo: Repo): # Inject Repo
        self.repo = repo
    def run(self):
        return self.repo.fetch()

# --- Example Setup ---
os.environ['APP_DB_URL'] = 'postgresql://user:pass@host/db'

# 3. Build configuration context
config_ctx = configuration(
    EnvSource(prefix="") # Read APP_DB_URL from environment
)

# 4. Initialize container
container = init(modules=[__name__], config=config_ctx) # Pass context via 'config'

# 5. Get and use the service
svc = container.get(Service)
print(svc.run())

# --- Cleanup ---
del os.environ['APP_DB_URL']

Output:

fetching from postgresql://user:pass@host/db

🧪 Testing with Overrides

class FakeRepo:
    def fetch(self): return "fake-data"

# Build configuration context (might be empty or specific for test)
test_config_ctx = configuration()

# Use overrides during init
container = init(
    modules=[__name__],
    config=test_config_ctx,
    overrides={Repo: FakeRepo()} # Replace Repo with FakeRepo
)

svc = container.get(Service)
assert svc.run() == "fake-data"

🩺 Lifecycle & AOP

import time # For example
from pico_ioc import component, init, intercepted_by, MethodInterceptor, MethodCtx

# Define an interceptor component
@component
class LogInterceptor(MethodInterceptor):
    def invoke(self, ctx: MethodCtx, call_next):
        print(f"→ calling {ctx.cls.__name__}.{ctx.name}")
        start = time.perf_counter()
        try:
            res = call_next(ctx)
            duration = (time.perf_counter() - start) * 1000
            print(f"← {ctx.cls.__name__}.{ctx.name} done ({duration:.2f}ms)")
            return res
        except Exception as e:
            duration = (time.perf_counter() - start) * 1000
            print(f"← {ctx.cls.__name__}.{ctx.name} failed ({duration:.2f}ms): {e}")
            raise

@component
class Demo:
    @intercepted_by(LogInterceptor) # Apply the interceptor
    def work(self):
        print("   Working...")
        time.sleep(0.01)
        return "ok"

# Initialize container (must scan module containing interceptor too)
c = init(modules=[__name__])
result = c.get(Demo).work()
print(f"Result: {result}")

Output:

→ calling Demo.work
   Working...
← Demo.work done (10.xxms)
Result: ok

📖 Documentation

The full documentation is available within the docs/ directory of the project repository. Start with docs/README.md for navigation.

  • Getting Started: docs/getting-started.md
  • User Guide: docs/user-guide/README.md
  • Advanced Features: docs/advanced-features/README.md
  • Observability: docs/observability/README.md
  • Integrations: docs/integrations/README.md
  • Cookbook (Patterns): docs/cookbook/README.md
  • Architecture: docs/architecture/README.md
  • API Reference: docs/api-reference/README.md
  • ADR Index: docs/adr/README.md

🧩 Development

pip install tox
tox

🧾 Changelog

See CHANGELOG.mdSignificant redesigns and features in v2.0+.


📜 License

MIT — 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-2.1.1.tar.gz (160.0 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-2.1.1-py3-none-any.whl (40.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pico_ioc-2.1.1.tar.gz
  • Upload date:
  • Size: 160.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pico_ioc-2.1.1.tar.gz
Algorithm Hash digest
SHA256 3d3713f9a6023498461104229d2e97d8a606692155e79b43e8cc927150e964a5
MD5 1e9a5e61e0e0d7a22db9dc41a7bbe3d9
BLAKE2b-256 7214dcc3606f918614833aaae9fd8208fd08921616b1923bdca2991263fd8c7c

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pico_ioc-2.1.1-py3-none-any.whl
  • Upload date:
  • Size: 40.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pico_ioc-2.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 07b6c28e0c959b58ed7dc1e7dd152bd5b44d83563ec696011739cfa2509ee97e
MD5 582a35ce09e8f85d3083378b91f839e2
BLAKE2b-256 01e825d26f1f94425f5bb744a218dc0ed9de5df60015ecd366bb88d147872dd2

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