Skip to content

Decorators Reference

This page provides a quick reference for all decorators provided by pico-ioc.


Core Registration Decorators: @component, @factory, @provides

These decorators register classes and functions as providers within the container. They share common parameters for lifecycle, selection, and conditional registration.

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

Marks a class as a component managed by the container. Typically applied directly on the class.

  • cls: The class being decorated (applied automatically when used as @component).

Example: - Register a service class as a singleton component.

@factory(cls=None, *, ...)

Marks a class as a factory for creating other components. Methods inside the factory should use @provides. The factory itself is a component and can declare dependencies.

  • cls: The factory class being decorated.

Example: - A factory class providing multiple implementations via @provides.

@provides(*args, **kwargs)

Marks a function or method as the provider for a specific Key. Can be applied to module-level functions or methods within a @factory class (instance methods or @staticmethod).

  • key (optional positional): The Key (class type or string) the method provides. If omitted, it is typically inferred from the function’s return type hint.
  • **kwargs: Accepts the common parameters listed below.

Example: - A provider function returning a configured client instance.

Common parameters for @component, @factory, @provides

  • name: str | None (default None)
  • Explicit component name/key. Defaults to class name or inferred key for @provides.
  • qualifiers: Iterable[str] (default ())
  • Qualifier tags for disambiguation, especially when injecting lists (e.g., Annotated[List[Type], Qualifier(...)]).
  • scope: str (default "singleton")
  • Component lifetime: "singleton", "prototype", "request", "session", "transaction", or a custom scope supported by your container configuration.
  • primary: bool (default False)
  • Marks this component as the preferred candidate when multiple providers match a type.
  • lazy: bool (default False)
  • Defers singleton instantiation until first use (get/aget).
  • conditional_profiles: Iterable[str] (default ())
  • Enables the component only if one of the specified profiles is active (as configured via init(profiles=...)).
  • conditional_require_env: Iterable[str] (default ())
  • Enables the component only if all specified environment variables exist and are non-empty.
  • conditional_predicate: Callable[[], bool] | None (default None)
  • Custom function returning True/False to control conditional activation.
  • on_missing_selector: str | type | None (default None)
  • Registers this component only if no other provider for the selector key/type is found. Acts as a fallback.
  • on_missing_priority: int (default 0)
  • Priority for on_missing providers when multiple fallbacks target the same selector (higher wins).

Configuration Decorator

@configured(target: Any = "self", *, prefix: str = "", mapping: str = "auto")

Marks a dataclass as a configuration object to be populated from the unified configuration system defined via init(config=ContextConfig(...)) (created by the configuration(...) builder). Supports both flat (key-value) and tree (nested) mapping.

  • target (optional): The class to be configured. Defaults to "self" (the decorated class itself).
  • prefix:
  • Flat mapping: A prefix prepended to field names when looking up keys (e.g., with prefix="APP_" and field host, look up APP_HOST).
  • Tree mapping: The top-level key in the configuration tree to map from (e.g., prefix="app" maps from an app: section). If empty (""), maps from the root.
  • mapping:
  • "auto" (default):
    • If any field type is a dataclass, list, dict, or Union, treat as tree.
    • If all field types are primitives (str, int, float, bool), treat as flat.
  • "flat": Forces flat mapping. Keys are looked up as PREFIX_FIELDNAME (commonly UPPER_CASE in sources like environment variables).
  • "tree": Forces tree mapping. Expects a nested structure under prefix. Path segments in env-like sources are joined by __ (e.g., APP_DB__HOST).

This decorator works with the configuration(...) builder, which defines sources and precedence rules for populating the dataclass.


Lifecycle Decorators

@configure(fn)

Marks a method on a component to be called immediately after the component instance is created and dependencies are injected (including after __ainit__ if present), but before it is returned by get/aget. The method may be synchronous or async def.

Typical uses: - Validate configuration and dependencies. - Initialize derived state or open connections.

@cleanup(fn)

Marks a method on a component to be called when container.cleanup_all() or container.cleanup_all_async() is invoked. Use this to release resources (e.g., close connections, flush buffers). The method may be synchronous or async def.


Health & AOP Decorators

@health(fn)

Marks a method on a component as a health check. These methods are executed by container.health_check(). The method should take no arguments (besides self) and return a truthy value or raise an exception on failure. Can be synchronous or async def.

Use cases: - Verify connectivity to external systems. - Check internal invariants.

@intercepted_by(*interceptor_classes: type[MethodInterceptor])

Applies one or more AOP interceptors (which must be registered components) to a method. Interceptors run before and/or after the original method, forming a chain.

  • *interceptor_classes: The class types of the MethodInterceptor components to apply.

Common patterns: - Logging and metrics collection. - Transaction or retry policies. - Authorization checks.


Event Bus Decorator

@subscribe(event_type: Type[Event], *, priority: int = 0, policy: ExecPolicy = ExecPolicy.INLINE, once: bool = False)

Marks a method (often within a class using AutoSubscriberMixin) to be called when an event of the specified type is published on the EventBus. The handler can be synchronous or async def.

  • event_type: The specific Event subclass to listen for.
  • priority (default 0): Higher numerical priority handlers run first.
  • policy (default ExecPolicy.INLINE): Controls execution strategy:
  • ExecPolicy.INLINE: Run synchronously in the publisher’s context (awaited if async).
  • ExecPolicy.TASK: Run as a background asyncio.Task (fire-and-forget).
  • ExecPolicy.THREADPOOL: Run synchronous handlers in a thread pool executor.
  • once (default False): If True, the handler runs only once and is then automatically unsubscribed.

Typical uses: - React to domain events. - Fire side effects in response to state changes. - Decouple producers and consumers.


Auto-generated API

pico_ioc.decorators

Decorators for registering components, factories, and providers.

This module contains the core decorator API used to mark classes and functions for discovery by the pico-ioc container: :func:component, :func:factory, :func:provides, :func:configured, :func:configure, and :func:cleanup.

Qualifier

Bases: str

A typed string used in Annotated hints for qualifier-based injection.

Use with typing.Annotated to request a specific qualified implementation::

from typing import Annotated
from pico_ioc import Qualifier

@component
class OrderService:
    def __init__(self, cache: Annotated[Cache, Qualifier("fast")]):
        self.cache = cache
Source code in src/pico_ioc/decorators.py
class Qualifier(str):
    """A typed string used in ``Annotated`` hints for qualifier-based injection.

    Use with ``typing.Annotated`` to request a specific qualified
    implementation::

        from typing import Annotated
        from pico_ioc import Qualifier

        @component
        class OrderService:
            def __init__(self, cache: Annotated[Cache, Qualifier("fast")]):
                self.cache = cache
    """

    __slots__ = ()

component(cls=None, *, name=None, qualifiers=(), scope='singleton', primary=False, lazy=False, conditional_profiles=(), conditional_require_env=(), conditional_predicate=None, on_missing_selector=None, on_missing_priority=0)

Register a class as a container-managed component.

Can be used with or without parentheses::

@component
class MyService: ...

@component(scope="request", qualifiers={"fast"})
class FastService: ...

Parameters:

Name Type Description Default
cls

The class to decorate (populated automatically when used without parentheses).

None
name Any

Explicit registration key. Defaults to the class itself.

None
qualifiers Iterable[str]

String tags for multi-binding and list injection.

()
scope str

Lifecycle scope ('singleton', 'prototype', 'request', 'session', 'transaction', or a custom scope name).

'singleton'
primary bool

If True, this component wins when multiple implementations exist for the same type.

False
lazy bool

If True, the component is wrapped in a proxy and created on first attribute access rather than at container startup.

False
conditional_profiles Iterable[str]

Only register when one of these profiles is active.

()
conditional_require_env Iterable[str]

Only register when all listed environment variables are set and non-empty.

()
conditional_predicate Optional[Callable[[], bool]]

Only register when this callable returns True.

None
on_missing_selector Optional[object]

Register as a fallback for the given key/type if no other provider is bound.

None
on_missing_priority int

Precedence among on_missing fallbacks (higher wins).

0

Returns:

Type Description

The decorated class, unchanged, with pico metadata attached.

Example

@component(scope="prototype", qualifiers={"cache"}) ... class InMemoryCache: ... pass

Source code in src/pico_ioc/decorators.py
def component(
    cls=None,
    *,
    name: Any = None,
    qualifiers: Iterable[str] = (),
    scope: str = "singleton",
    primary: bool = False,
    lazy: bool = False,
    conditional_profiles: Iterable[str] = (),
    conditional_require_env: Iterable[str] = (),
    conditional_predicate: Optional[Callable[[], bool]] = None,
    on_missing_selector: Optional[object] = None,
    on_missing_priority: int = 0,
):
    """Register a class as a container-managed component.

    Can be used with or without parentheses::

        @component
        class MyService: ...

        @component(scope="request", qualifiers={"fast"})
        class FastService: ...

    Args:
        cls: The class to decorate (populated automatically when used without
            parentheses).
        name: Explicit registration key. Defaults to the class itself.
        qualifiers: String tags for multi-binding and list injection.
        scope: Lifecycle scope (``'singleton'``, ``'prototype'``, ``'request'``,
            ``'session'``, ``'transaction'``, or a custom scope name).
        primary: If ``True``, this component wins when multiple implementations
            exist for the same type.
        lazy: If ``True``, the component is wrapped in a proxy and created on
            first attribute access rather than at container startup.
        conditional_profiles: Only register when one of these profiles is active.
        conditional_require_env: Only register when all listed environment
            variables are set and non-empty.
        conditional_predicate: Only register when this callable returns ``True``.
        on_missing_selector: Register as a fallback for the given key/type if
            no other provider is bound.
        on_missing_priority: Precedence among ``on_missing`` fallbacks (higher
            wins).

    Returns:
        The decorated class, unchanged, with pico metadata attached.

    Example:
        >>> @component(scope="prototype", qualifiers={"cache"})
        ... class InMemoryCache:
        ...     pass
    """

    def dec(c):
        setattr(c, PICO_INFRA, "component")
        setattr(c, PICO_NAME, name if name is not None else getattr(c, "__name__", str(c)))
        setattr(c, PICO_KEY, name if name is not None else c)

        _apply_common_metadata(
            c,
            qualifiers=qualifiers,
            scope=scope,
            primary=primary,
            lazy=lazy,
            conditional_profiles=conditional_profiles,
            conditional_require_env=conditional_require_env,
            conditional_predicate=conditional_predicate,
            on_missing_selector=on_missing_selector,
            on_missing_priority=on_missing_priority,
        )
        return c

    return dec(cls) if cls else dec

factory(cls=None, *, name=None, qualifiers=(), scope='singleton', primary=False, lazy=False, conditional_profiles=(), conditional_require_env=(), conditional_predicate=None, on_missing_selector=None, on_missing_priority=0)

Register a class as a factory that produces components via @provides methods.

A factory class groups related provider methods. The factory itself is instantiated by the container (with its own dependencies injected), and each @provides-decorated method becomes a separate provider::

@factory
class InfraFactory:
    @provides(Database)
    def build_db(self) -> Database:
        return Database(url="sqlite://")

Parameters:

Name Type Description Default
cls

The class to decorate (populated automatically when used without parentheses).

None
name Any

Explicit name for the factory. Defaults to the class name.

None
qualifiers Iterable[str]

String tags inherited by the factory's providers unless overridden.

()
scope str

Default scope for provider methods that do not specify their own.

'singleton'
primary bool

Whether the factory's providers are primary by default.

False
lazy bool

If True, the factory is lazily initialised.

False
conditional_profiles Iterable[str]

Only register when one of these profiles is active.

()
conditional_require_env Iterable[str]

Only register when all listed environment variables are set and non-empty.

()
conditional_predicate Optional[Callable[[], bool]]

Only register when this callable returns True.

None
on_missing_selector Optional[object]

Fallback selector key/type.

None
on_missing_priority int

Fallback priority.

0

Returns:

Type Description

The decorated class, unchanged, with pico metadata attached.

Source code in src/pico_ioc/decorators.py
def factory(
    cls=None,
    *,
    name: Any = None,
    qualifiers: Iterable[str] = (),
    scope: str = "singleton",
    primary: bool = False,
    lazy: bool = False,
    conditional_profiles: Iterable[str] = (),
    conditional_require_env: Iterable[str] = (),
    conditional_predicate: Optional[Callable[[], bool]] = None,
    on_missing_selector: Optional[object] = None,
    on_missing_priority: int = 0,
):
    """Register a class as a factory that produces components via ``@provides`` methods.

    A factory class groups related provider methods. The factory itself is
    instantiated by the container (with its own dependencies injected), and
    each ``@provides``-decorated method becomes a separate provider::

        @factory
        class InfraFactory:
            @provides(Database)
            def build_db(self) -> Database:
                return Database(url="sqlite://")

    Args:
        cls: The class to decorate (populated automatically when used without
            parentheses).
        name: Explicit name for the factory. Defaults to the class name.
        qualifiers: String tags inherited by the factory's providers unless
            overridden.
        scope: Default scope for provider methods that do not specify their own.
        primary: Whether the factory's providers are primary by default.
        lazy: If ``True``, the factory is lazily initialised.
        conditional_profiles: Only register when one of these profiles is active.
        conditional_require_env: Only register when all listed environment
            variables are set and non-empty.
        conditional_predicate: Only register when this callable returns ``True``.
        on_missing_selector: Fallback selector key/type.
        on_missing_priority: Fallback priority.

    Returns:
        The decorated class, unchanged, with pico metadata attached.
    """

    def dec(c):
        setattr(c, PICO_INFRA, "factory")
        setattr(c, PICO_NAME, name if name is not None else getattr(c, "__name__", str(c)))

        _apply_common_metadata(
            c,
            qualifiers=qualifiers,
            scope=scope,
            primary=primary,
            lazy=lazy,
            conditional_profiles=conditional_profiles,
            conditional_require_env=conditional_require_env,
            conditional_predicate=conditional_predicate,
            on_missing_selector=on_missing_selector,
            on_missing_priority=on_missing_priority,
        )
        return c

    return dec(cls) if cls else dec

provides(*dargs, **dkwargs)

Mark a function or method as a component provider.

@provides can be used on instance methods inside a @factory class, on @staticmethod / @classmethod methods, or on module-level functions. The registration key is inferred from the return type annotation or can be given explicitly::

@provides(Database)
def build_db() -> Database:
    return Database(url="sqlite://")

@provides  # key inferred from return type
def build_cache() -> RedisCache:
    return RedisCache()

Parameters:

Name Type Description Default
*dargs

Optional positional argument: the explicit registration key (a type or string). When omitted, the key is inferred from the return type annotation.

()
**dkwargs

Keyword arguments forwarded to metadata (name, qualifiers, scope, primary, lazy, conditional_profiles, conditional_require_env, conditional_predicate, on_missing_selector, on_missing_priority).

{}

Returns:

Type Description

The decorated function, unchanged, with pico metadata attached.

Raises:

Type Description
TypeError

If the key cannot be inferred and is not provided.

Source code in src/pico_ioc/decorators.py
def provides(*dargs, **dkwargs):
    """Mark a function or method as a component provider.

    ``@provides`` can be used on instance methods inside a ``@factory`` class,
    on ``@staticmethod`` / ``@classmethod`` methods, or on module-level
    functions.  The registration key is inferred from the return type annotation
    or can be given explicitly::

        @provides(Database)
        def build_db() -> Database:
            return Database(url="sqlite://")

        @provides  # key inferred from return type
        def build_cache() -> RedisCache:
            return RedisCache()

    Args:
        *dargs: Optional positional argument: the explicit registration key
            (a type or string). When omitted, the key is inferred from the
            return type annotation.
        **dkwargs: Keyword arguments forwarded to metadata (``name``,
            ``qualifiers``, ``scope``, ``primary``, ``lazy``,
            ``conditional_profiles``, ``conditional_require_env``,
            ``conditional_predicate``, ``on_missing_selector``,
            ``on_missing_priority``).

    Returns:
        The decorated function, unchanged, with pico metadata attached.

    Raises:
        TypeError: If the key cannot be inferred and is not provided.
    """

    def _apply(
        fn,
        key_hint,
        *,
        name=None,
        qualifiers=(),
        scope="singleton",
        primary=False,
        lazy=False,
        conditional_profiles=(),
        conditional_require_env=(),
        conditional_predicate=None,
        on_missing_selector=None,
        on_missing_priority=0,
    ):
        target = fn.__func__ if isinstance(fn, (staticmethod, classmethod)) else fn

        inferred_key = key_hint
        if inferred_key is MISSING:
            rt = get_return_type(target)
            if isinstance(rt, type):
                inferred_key = rt
            else:
                inferred_key = getattr(target, "__name__", str(target))

        setattr(target, PICO_INFRA, "provides")
        pico_name = (
            name
            if name is not None
            else (inferred_key if isinstance(inferred_key, str) else getattr(target, "__name__", str(target)))
        )
        setattr(target, PICO_NAME, pico_name)
        setattr(target, PICO_KEY, inferred_key)

        _apply_common_metadata(
            target,
            qualifiers=qualifiers,
            scope=scope,
            primary=primary,
            lazy=lazy,
            conditional_profiles=conditional_profiles,
            conditional_require_env=conditional_require_env,
            conditional_predicate=conditional_predicate,
            on_missing_selector=on_missing_selector,
            on_missing_priority=on_missing_priority,
        )
        return fn

    if dargs and len(dargs) == 1 and inspect.isfunction(dargs[0]) and not dkwargs:
        fn = dargs[0]
        return _apply(fn, MISSING)
    else:
        key = dargs[0] if dargs else MISSING

        def _decorator(fn):
            return _apply(fn, key, **dkwargs)

        return _decorator

configure(fn)

Mark a method as a post-construction lifecycle hook.

@configure methods are called after the component is instantiated and all constructor dependencies are injected. They may themselves declare dependencies as parameters, which will be resolved from the container::

@component
class CacheService:
    @configure
    def warm_up(self, db: Database):
        self._data = db.load_all()

Parameters:

Name Type Description Default
fn

The method to decorate.

required

Returns:

Type Description

The same method with lifecycle metadata attached.

Source code in src/pico_ioc/decorators.py
def configure(fn):
    """Mark a method as a post-construction lifecycle hook.

    ``@configure`` methods are called after the component is instantiated and
    all constructor dependencies are injected.  They may themselves declare
    dependencies as parameters, which will be resolved from the container::

        @component
        class CacheService:
            @configure
            def warm_up(self, db: Database):
                self._data = db.load_all()

    Args:
        fn: The method to decorate.

    Returns:
        The same method with lifecycle metadata attached.
    """
    m = _meta_get(fn)
    m["configure"] = True
    return fn

cleanup(fn)

Mark a method as a shutdown lifecycle hook.

@cleanup methods are called when the container shuts down (via container.shutdown() or await container.ashutdown())::

@component
class ConnectionPool:
    @cleanup
    async def close(self):
        await self.pool.close()

Parameters:

Name Type Description Default
fn

The method to decorate.

required

Returns:

Type Description

The same method with lifecycle metadata attached.

Source code in src/pico_ioc/decorators.py
def cleanup(fn):
    """Mark a method as a shutdown lifecycle hook.

    ``@cleanup`` methods are called when the container shuts down (via
    ``container.shutdown()`` or ``await container.ashutdown()``)::

        @component
        class ConnectionPool:
            @cleanup
            async def close(self):
                await self.pool.close()

    Args:
        fn: The method to decorate.

    Returns:
        The same method with lifecycle metadata attached.
    """
    m = _meta_get(fn)
    m["cleanup"] = True
    return fn

configured(target='self', *, prefix='', mapping='auto', **kwargs)

Bind a dataclass to configuration sources.

The decorated dataclass is automatically populated from the active ContextConfig (environment variables, JSON/YAML files, dict sources) at container startup::

@configured(prefix="db")
@dataclass
class DbConfig:
    host: str = "localhost"
    port: int = 5432

Parameters:

Name Type Description Default
target Any

The target type to populate. Use "self" (default) to populate the decorated class itself.

'self'
prefix str

Dot-separated prefix for looking up keys in the configuration tree (e.g., "db" looks under the db subtree).

''
mapping str

Binding strategy: "auto" (default) -- auto-detect from field types, "flat" -- flat key-value lookup (PREFIX_FIELD), "tree" -- recursive tree mapping from nested config.

'auto'
**kwargs

Extra keyword arguments forwarded to :func:_apply_common_metadata (qualifiers, scope, etc.).

{}

Returns:

Type Description

A decorator that attaches configuration metadata to the class.

Raises:

Type Description
ValueError

If mapping is not one of 'auto', 'flat', or 'tree'.

Source code in src/pico_ioc/decorators.py
def configured(target: Any = "self", *, prefix: str = "", mapping: str = "auto", **kwargs):
    """Bind a dataclass to configuration sources.

    The decorated dataclass is automatically populated from the active
    ``ContextConfig`` (environment variables, JSON/YAML files, dict sources)
    at container startup::

        @configured(prefix="db")
        @dataclass
        class DbConfig:
            host: str = "localhost"
            port: int = 5432

    Args:
        target: The target type to populate. Use ``"self"`` (default) to
            populate the decorated class itself.
        prefix: Dot-separated prefix for looking up keys in the configuration
            tree (e.g., ``"db"`` looks under the ``db`` subtree).
        mapping: Binding strategy:
            ``"auto"`` (default) -- auto-detect from field types,
            ``"flat"`` -- flat key-value lookup (``PREFIX_FIELD``),
            ``"tree"`` -- recursive tree mapping from nested config.
        **kwargs: Extra keyword arguments forwarded to
            :func:`_apply_common_metadata` (``qualifiers``, ``scope``, etc.).

    Returns:
        A decorator that attaches configuration metadata to the class.

    Raises:
        ValueError: If *mapping* is not one of ``'auto'``, ``'flat'``, or
            ``'tree'``.
    """
    if mapping not in ("auto", "flat", "tree"):
        raise ValueError("mapping must be one of 'auto', 'flat', or 'tree'")

    def dec(cls):
        setattr(cls, PICO_INFRA, "configured")
        m = _meta_get(cls)
        m["configured"] = {"target": target, "prefix": prefix, "mapping": mapping}
        _apply_common_metadata(cls, **kwargs)
        return cls

    return dec

get_return_type(fn)

Extract the concrete return type from a callable's annotations.

Uses typing.get_type_hints with include_extras=True to resolve PEP 563 deferred annotations, falling back to inspect.signature.

Parameters:

Name Type Description Default
fn Callable[..., Any]

The callable to inspect.

required

Returns:

Type Description
Optional[type]

The return type if it is a concrete class, otherwise None.

Source code in src/pico_ioc/decorators.py
def get_return_type(fn: Callable[..., Any]) -> Optional[type]:
    """Extract the concrete return type from a callable's annotations.

    Uses ``typing.get_type_hints`` with ``include_extras=True`` to resolve
    PEP 563 deferred annotations, falling back to ``inspect.signature``.

    Args:
        fn: The callable to inspect.

    Returns:
        The return type if it is a concrete class, otherwise ``None``.
    """
    try:
        hints = typing.get_type_hints(fn, include_extras=True)
        ra = hints.get("return")
    except Exception:
        try:
            ra = inspect.signature(fn).return_annotation
        except Exception:
            return None
    if ra is None or ra is inspect._empty:
        return None
    return ra if isinstance(ra, type) else None