# Protocols Reference

This page describes the Python protocols (`typing.Protocol`) used by `pico-ioc` for extension points. You can implement these protocols to customize the container's behavior or integrate with external systems.

---

## `MethodInterceptor`

Used for implementing Aspect-Oriented Programming (AOP). Classes implementing this protocol can be applied to component methods using the `@intercepted_by` decorator.

```python
from typing import Any, Callable, Protocol, Dict

class MethodCtx:
    """Context object passed to the interceptor."""
    instance: object         # The component instance being called
    cls: type                # The class of the component instance
    method: Callable         # The original bound method being intercepted
    name: str                # The name of the method being called
    args: tuple              # Positional arguments passed to the method
    kwargs: dict             # Keyword arguments passed to the method
    container: Any           # The PicoContainer instance
    local: Dict[str, Any]    # A dict for interceptors to share state during one call
    request_key: Any | None  # The current request scope ID, if applicable

class MethodInterceptor(Protocol):
    def invoke(
        self,
        ctx: MethodCtx,
        call_next: Callable[[MethodCtx], Any]
    ) -> Any:
        """
        Wraps the original method call.

        Args:
            ctx: The MethodCtx providing call information.
            call_next: A callable that proceeds to the next interceptor
                       or the original method. You must call this.

        Returns:
            The result of the original method, potentially modified.
        """
        ...
```

Note: The `invoke` method can be `async def` if the intercepted method is `async`. `pico-ioc` will handle awaiting `call_next(ctx)` correctly.

---

## `ContainerObserver`

Used for monitoring container events. Instances are passed to `init(observers=[...])`.

```python
from typing import Protocol, Union

KeyT = Union[str, type]

class ContainerObserver(Protocol):
    def on_resolve(self, key: KeyT, took_ms: float):
        """
        Called after a component is successfully created and cached.
        Not called for prototype scope or cache hits.

        Args:
            key: The Key of the component that was resolved.
            took_ms: The time taken (in milliseconds) to create the component.
        """
        ...

    def on_cache_hit(self, key: KeyT):
        """
        Called when container.get() or .aget() retrieves a component
        from the cache (singleton or scoped).

        Args:
            key: The Key of the component retrieved from the cache.
        """
        ...
```
---

## `CustomScanner`

Used for extending the component scanning process to automatically register components based on custom decorators, base classes, or logic that `pico-ioc`'s built-in scanner doesn't support. Instances are passed to `init(custom_scanners=[...])`.

```python
from typing import Any, Callable, Protocol, Optional, Tuple, Union
from pico_ioc.factory import ProviderMetadata

KeyT = Union[str, type]
Provider = Callable[[], Any]

class CustomScanner(Protocol):
    def should_scan(self, obj: Any) -> bool: ...
    def scan(self, obj: Any) -> Optional[Tuple[KeyT, Provider, ProviderMetadata]]: ...
```

  - `should_scan(self, obj: Any) -> bool`:
        - Called for every object inspected during module scanning (classes, protocols).
        - Should return `True` if this scanner is interested in processing the object.
  - `scan(self, obj: Any) -> Optional[Tuple[KeyT, Provider, ProviderMetadata]]`:
        - Called only if `should_scan` returned `True`.
        - Must return a tuple containing the registration key, a deferred provider, and the provider's metadata, or `None` if it decides not to register it.
        
---

## `ScopeProtocol`

Used for defining custom scopes. Instances are passed to `init(custom_scopes={...})`.

```python
from typing import Any, Protocol, Optional
import contextvars

class ScopeProtocol(Protocol):
    def get_id(self) -> Any | None:
        """
        Returns the current unique ID for this scope in the active context.
        If the scope is not active, should return None.
        Used by ScopedCaches to find the correct cache instance.
        """
        ...

    # Optional methods for contextvar-based scopes:
    # def activate(self, scope_id: Any) -> Optional[contextvars.Token]: ...
    # def deactivate(self, token: Optional[contextvars.Token]) -> None: ...
```

Note: `pico-ioc` provides `ContextVarScope` as a standard implementation based on `contextvars`.

---

## `ConfigSource` (Basic Config - Internal Usage)

Defines the interface for sources providing flat key-value configuration. Implementations (like `EnvSource`, `FlatDictSource`) are passed to the `configuration(...)` builder function, not directly to `init()`.

```python
from typing import Optional, Protocol

class ConfigSource(Protocol):
    def get(self, key: str) -> Optional[str]:
        """
        Retrieves a configuration value for the given key.

        Args:
            key: The configuration key (e.g., "APP_DEBUG").

        Returns:
            The configuration value as a string, or None if not found.
        """
        ...
```

Provided Implementations (for `configuration(...)`): `EnvSource`, `FileSource` (legacy), `FlatDictSource`.

---

## `TreeSource` (Tree Config - Internal Usage)

Defines the interface for sources providing nested tree-based configuration. Implementations (like `JsonTreeSource`, `YamlTreeSource`, `DictSource`) are passed to the `configuration(...)` builder function, not directly to `init()`.

```python
from typing import Mapping, Any, Protocol

class TreeSource(Protocol):
    def get_tree(self) -> Mapping[str, Any]:
        """
        Returns the entire configuration structure as a nested dictionary.
        This method might load from a file, parse YAML/JSON, etc.

        Returns:
            A nested dictionary representing the configuration tree.
        """
        ...
```

Provided Implementations (for `configuration(...)`): `JsonTreeSource`, `YamlTreeSource`, `DictSource`.

<!-- end list -->

