Skip to content

Container Context API

The Container Context API provides runtime context management and lifecycle utilities for PicoContainer instances. It ensures each container has a unique identifier, supports activation and deactivation, exposes a context-manager for setting a “current” container, maintains a global registry of all containers, and reports useful statistics such as container ID, profiles, and resolution metrics.

What is this?

  • Context management: Temporarily mark a container as the current context using a context manager.
  • Lifecycle control: Explicitly activate and deactivate containers to manage their usage window.
  • Identity and registry: Each container gets a unique container ID and is tracked in a global registry.
  • Statistics: Inspect runtime stats about a container, including its ID, profiles, and resolution-related metrics.

These capabilities make it easier to coordinate dependency resolution across different parts of your application, especially when multiple containers or profiles are involved.

Key features

  • Unique container IDs to distinguish instances.
  • activate() and deactivate() lifecycle methods.
  • as_current() context manager to set the current container within a scope.
  • A registry that lists all instantiated containers.
  • stats() reporting container metadata and resolution metrics.

Getting started

Create a container and check its unique ID.

from pico import PicoContainer

# Create a new container (optionally with profiles)
container = PicoContainer(profiles=['dev'])  # profiles are optional

# Each container has a unique ID
print(container.container_id)  # e.g., 'c-7f3a2b1e'

Managing lifecycle: activate and deactivate

Use activate() to mark the container as active. Call deactivate() when done to cleanly release the context.

container.activate()

# Use the container while active...
service = container.resolve('my_service')

# When finished, deactivate to clean up
container.deactivate()

Typical usage patterns:

  • Activate before performing a batch of resolutions or configuration.
  • Deactivate to ensure the container is no longer considered active and any scoped resources are released.

Using the current context (context manager)

The as_current() method returns a context manager that sets the container as the current context for the duration of the with block.

from pico import PicoContainer

container = PicoContainer()

with container.as_current():
    # Inside this block, the 'current' container refers to 'container'
    current = PicoContainer.current()
    assert current is container

# Outside the block, the previous current (if any) is restored
assert PicoContainer.current() is not container

This pattern is useful for code that implicitly depends on the current container without passing instances around explicitly.

Container IDs and the global registry

Every container gets a unique ID, and all containers are tracked by a registry that you can inspect at runtime.

from pico import PicoContainer

c1 = PicoContainer()
c2 = PicoContainer()

assert c1.container_id != c2.container_id  # IDs are unique

# Inspect the registry of all containers
all_containers = PicoContainer.registry()
assert c1 in all_containers and c2 in all_containers
print([c.container_id for c in all_containers])

Use the registry to introspect or manage multiple containers in complex applications.

Statistics

Use stats() to retrieve information about a container. Stats include the container ID, configured profiles, and resolution metrics.

container = PicoContainer(profiles=['dev', 'feature-x'])

# After some resolution activity...
_ = container.resolve('my_service')
_ = container.resolve('another_service')

info = container.stats()
print(info['container_id'])  # Unique ID
print(info['profiles'])      # ['dev', 'feature-x']
print(info['resolutions'])   # e.g., {'success': 2, 'failures': 0} or similar

Stats are useful for monitoring, debugging, and diagnostics, providing insight into how and where the container is being used.

Best practices

  • Prefer with container.as_current(): for code that relies on an ambient “current” container to avoid leaking context across scopes.
  • Pair activate() with deactivate() to clearly define lifecycle and prevent unintended usage.
  • Use profiles to segregate configurations and inspect them via stats() for validation.
  • Leverage the registry when coordinating multiple containers, such as in tests or multi-tenant scenarios.

Auto-generated API

pico_ioc.api

High-level API for bootstrapping the pico-ioc container.

The :func:init function is the primary entry point. It scans modules, validates bindings, and returns a fully wired :class:PicoContainer.

init(modules, *, profiles=(), allowed_profiles=None, environ=None, overrides=None, logger=None, config=None, custom_scopes=None, validate_only=False, container_id=None, observers=None, custom_scanners=None)

Bootstrap the pico-ioc container.

Scans the given modules for decorated components, validates all dependency bindings, eagerly creates non-lazy singletons, and returns a ready-to-use :class:PicoContainer.

Parameters:

Name Type Description Default
modules Union[Any, Iterable[Any]]

One or more Python modules (or packages, or dotted-name strings) to scan for @component, @factory, @provides, and @configured declarations.

required
profiles Tuple[str, ...]

Active profile names for conditional binding.

()
allowed_profiles Optional[Iterable[str]]

If set, unknown profile names raise :class:ConfigurationError.

None
environ Optional[Dict[str, str]]

Custom environment dict (defaults to os.environ).

None
overrides Optional[Dict[KeyT, Any]]

Dict of {key: value_or_callable} that replace scanned providers. Useful for testing.

None
logger Optional[Logger]

Custom logger for framework messages.

None
config Optional[ContextConfig]

An optional :class:ContextConfig created by :func:configuration.

None
custom_scopes Optional[Iterable[str]]

Additional scope names to register (each backed by a new ContextVar).

None
validate_only bool

If True, perform binding validation and cycle detection but skip eager singleton creation.

False
container_id Optional[str]

Explicit container identifier.

None
observers Optional[List[ContainerObserver]]

:class:ContainerObserver instances for monitoring.

None
custom_scanners Optional[List[CustomScanner]]

:class:CustomScanner implementations for extending component discovery.

None

Returns:

Type Description
PicoContainer

A fully wired :class:PicoContainer.

Raises:

Type Description
ConfigurationError

If unknown profiles are used or async singletons lack lazy=True.

InvalidBindingError

If dependency validation or cycle detection fails.

ProviderNotFoundError

If a required dependency cannot be found.

Example

from pico_ioc import init, configuration, DictSource container = init( ... modules=["myapp"], # scans recursively ... config=configuration(DictSource({"db": {"url": "sqlite://"}})), ... ) svc = container.get(MyService)

Source code in src/pico_ioc/api.py
def init(
    modules: Union[Any, Iterable[Any]],
    *,
    profiles: Tuple[str, ...] = (),
    allowed_profiles: Optional[Iterable[str]] = None,
    environ: Optional[Dict[str, str]] = None,
    overrides: Optional[Dict[KeyT, Any]] = None,
    logger: Optional[logging.Logger] = None,
    config: Optional[ContextConfig] = None,
    custom_scopes: Optional[Iterable[str]] = None,
    validate_only: bool = False,
    container_id: Optional[str] = None,
    observers: Optional[List[ContainerObserver]] = None,
    custom_scanners: Optional[List[CustomScanner]] = None,
) -> PicoContainer:
    """Bootstrap the pico-ioc container.

    Scans the given modules for decorated components, validates all
    dependency bindings, eagerly creates non-lazy singletons, and returns a
    ready-to-use :class:`PicoContainer`.

    Args:
        modules: One or more Python modules (or packages, or dotted-name
            strings) to scan for ``@component``, ``@factory``,
            ``@provides``, and ``@configured`` declarations.
        profiles: Active profile names for conditional binding.
        allowed_profiles: If set, unknown profile names raise
            :class:`ConfigurationError`.
        environ: Custom environment dict (defaults to ``os.environ``).
        overrides: Dict of ``{key: value_or_callable}`` that replace
            scanned providers. Useful for testing.
        logger: Custom logger for framework messages.
        config: An optional :class:`ContextConfig` created by
            :func:`configuration`.
        custom_scopes: Additional scope names to register (each backed by
            a new ``ContextVar``).
        validate_only: If ``True``, perform binding validation and cycle
            detection but skip eager singleton creation.
        container_id: Explicit container identifier.
        observers: :class:`ContainerObserver` instances for monitoring.
        custom_scanners: :class:`CustomScanner` implementations for
            extending component discovery.

    Returns:
        A fully wired :class:`PicoContainer`.

    Raises:
        ConfigurationError: If unknown profiles are used or async
            singletons lack ``lazy=True``.
        InvalidBindingError: If dependency validation or cycle detection
            fails.
        ProviderNotFoundError: If a required dependency cannot be found.

    Example:
        >>> from pico_ioc import init, configuration, DictSource
        >>> container = init(
        ...     modules=["myapp"],  # scans recursively
        ...     config=configuration(DictSource({"db": {"url": "sqlite://"}})),
        ... )
        >>> svc = container.get(MyService)
    """
    active = tuple(p.strip() for p in profiles if p)
    _validate_profiles(active, allowed_profiles)

    factory, registrar, pico = _create_container(
        active, environ, logger, config, custom_scopes, custom_scanners, container_id, observers,
    )

    for m in _iter_input_modules(modules):
        registrar.register_module(m)

    _apply_overrides(factory, overrides)
    registrar.finalize(overrides, pico_instance=pico)

    return _wire_and_resolve(pico, registrar, validate_only)