Skip to content

API Reference

Complete API reference for Pico-Pydantic, auto-generated from source code docstrings.

Module Overview

Module Description
pico_pydantic Package exports and public API
pico_pydantic.decorators @validate decorator for Pydantic validation
pico_pydantic.interceptor Validation interceptor implementation

pico_pydantic

pico_pydantic

pico-pydantic: Declarative, AOP-based Pydantic validation for pico-ioc.

This package provides automatic argument validation for pico-ioc managed components using Pydantic BaseModel type hints. It intercepts method calls via AOP and validates arguments with TypeAdapter.validate_python() before the method body executes.

Public API

validate: Marker decorator that enables validation on a method. ValidationFailedError: Exception raised when argument validation fails. ValidationInterceptor: Singleton MethodInterceptor that performs validation.

Auto-discovery

Registered via the pico_boot.modules entry point. When using pico-boot, the ValidationInterceptor is auto-discovered and globally registered with the container.

Example

from pydantic import BaseModel, Field from pico_ioc import component from pico_pydantic import validate

class UserCreate(BaseModel): ... username: str = Field(min_length=3) ... email: str

@component ... class UserService: ... @validate ... async def create_user(self, data: UserCreate) -> dict: ... return data.model_dump()

ValidationFailedError

Bases: ValueError

Exception raised when Pydantic validation fails on method arguments.

Wraps the underlying Pydantic ValidationError with the name of the method that triggered the failure. Inherits from ValueError so it can be caught broadly as a value-related error.

Attributes:

Name Type Description
method_name

The name of the method whose arguments failed validation.

pydantic_error

The original Pydantic ValidationError instance.

Message format

"Validation failed for method '<method_name>': <pydantic_error>"

Example

from pico_pydantic import ValidationFailedError try: ... await service.create_user({"username": "ab"}) ... except ValidationFailedError as e: ... print(e.method_name) # "create_user" ... print(e.pydantic_error) # Original ValidationError

Source code in src/pico_pydantic/decorators.py
class ValidationFailedError(ValueError):
    """Exception raised when Pydantic validation fails on method arguments.

    Wraps the underlying Pydantic ``ValidationError`` with the name of the
    method that triggered the failure. Inherits from ``ValueError`` so it
    can be caught broadly as a value-related error.

    Attributes:
        method_name: The name of the method whose arguments failed validation.
        pydantic_error: The original Pydantic ``ValidationError`` instance.

    Message format:
        ``"Validation failed for method '<method_name>': <pydantic_error>"``

    Example:
        >>> from pico_pydantic import ValidationFailedError
        >>> try:
        ...     await service.create_user({"username": "ab"})
        ... except ValidationFailedError as e:
        ...     print(e.method_name)       # "create_user"
        ...     print(e.pydantic_error)    # Original ValidationError
    """

    def __init__(self, method_name: str, pydantic_error: Exception):
        """Initialize ValidationFailedError.

        Args:
            method_name: The name of the method that failed validation.
            pydantic_error: The original Pydantic ``ValidationError`` that
                describes which fields or constraints were violated.
        """
        self.method_name = method_name
        self.pydantic_error = pydantic_error
        super().__init__(f"Validation failed for method '{method_name}': {pydantic_error}")

__init__(method_name, pydantic_error)

Initialize ValidationFailedError.

Parameters:

Name Type Description Default
method_name str

The name of the method that failed validation.

required
pydantic_error Exception

The original Pydantic ValidationError that describes which fields or constraints were violated.

required
Source code in src/pico_pydantic/decorators.py
def __init__(self, method_name: str, pydantic_error: Exception):
    """Initialize ValidationFailedError.

    Args:
        method_name: The name of the method that failed validation.
        pydantic_error: The original Pydantic ``ValidationError`` that
            describes which fields or constraints were violated.
    """
    self.method_name = method_name
    self.pydantic_error = pydantic_error
    super().__init__(f"Validation failed for method '{method_name}': {pydantic_error}")

ValidationInterceptor

Bases: MethodInterceptor

AOP interceptor that validates method arguments against Pydantic schemas.

Registered as a singleton component via pico-ioc. When a method decorated with @validate is called, this interceptor:

  1. Checks for the @validate marker on the target method.
  2. Inspects the method signature for BaseModel type hints.
  3. Validates each matching argument using TypeAdapter(annotation).validate_python(value).
  4. Replaces dict arguments with validated BaseModel instances.
  5. Raises ValidationFailedError if any argument fails validation.

Only parameters with BaseModel type hints (or generic types containing BaseModel, such as List[Model], Optional[Model], Union[Model, ...]) are validated. Parameters without annotations or with non-Pydantic types (str, int, etc.) are passed through.

Auto-discovery

Registered via the pico_boot.modules entry point. No manual registration is needed when using pico-boot.

Example

from pydantic import BaseModel from pico_ioc import component from pico_pydantic import validate

class UserData(BaseModel): ... name: str ... age: int

@component ... class UserService: ... @validate ... async def create(self, data: UserData) -> dict: ... return data.model_dump()

Dicts are automatically converted to UserData instances:

await service.create({"name": "alice", "age": 30})

Source code in src/pico_pydantic/interceptor.py
@component(scope="singleton")
class ValidationInterceptor(MethodInterceptor):
    """AOP interceptor that validates method arguments against Pydantic schemas.

    Registered as a singleton component via pico-ioc. When a method
    decorated with ``@validate`` is called, this interceptor:

    1. Checks for the ``@validate`` marker on the target method.
    2. Inspects the method signature for ``BaseModel`` type hints.
    3. Validates each matching argument using
       ``TypeAdapter(annotation).validate_python(value)``.
    4. Replaces dict arguments with validated ``BaseModel`` instances.
    5. Raises ``ValidationFailedError`` if any argument fails validation.

    Only parameters with ``BaseModel`` type hints (or generic types
    containing ``BaseModel``, such as ``List[Model]``, ``Optional[Model]``,
    ``Union[Model, ...]``) are validated. Parameters without annotations
    or with non-Pydantic types (``str``, ``int``, etc.) are passed through.

    Auto-discovery:
        Registered via the ``pico_boot.modules`` entry point. No manual
        registration is needed when using ``pico-boot``.

    Example:
        >>> from pydantic import BaseModel
        >>> from pico_ioc import component
        >>> from pico_pydantic import validate
        >>>
        >>> class UserData(BaseModel):
        ...     name: str
        ...     age: int
        >>>
        >>> @component
        ... class UserService:
        ...     @validate
        ...     async def create(self, data: UserData) -> dict:
        ...         return data.model_dump()
        >>>
        >>> # Dicts are automatically converted to UserData instances:
        >>> # await service.create({"name": "alice", "age": 30})
    """

    def invoke(self, ctx: MethodCtx, call_next: Callable[[MethodCtx], Any]) -> Any:
        """Validate marked arguments, then proceed. Sync and async methods
        both work: validation is synchronous and happens before the call;
        an async method's awaitable is returned untouched for the proxy to
        await.

        Raises:
            ValidationFailedError: If a ``BaseModel``-typed argument fails
                Pydantic validation.
        """
        original_func = getattr(ctx.cls, ctx.name, None)

        if original_func and getattr(original_func, VALIDATE_META, False):
            try:
                new_args, new_kwargs = self._validate_and_transform(original_func, ctx.args, ctx.kwargs)
                ctx.args = new_args
                ctx.kwargs = new_kwargs
            except ValidationError as e:
                raise ValidationFailedError(ctx.name, e) from e

        return call_next(ctx)

    def _validate_and_transform(self, func: Callable, args: tuple, kwargs: dict) -> tuple[tuple, dict]:
        """Validate and transform method arguments using Pydantic.

        Binds the given positional and keyword arguments to the function
        signature, then iterates over each parameter. For parameters with
        ``BaseModel`` type hints (or generics containing them), the value
        is validated and potentially transformed (e.g., dicts become model
        instances) via ``TypeAdapter(annotation).validate_python(value)``.

        Args:
            func: The original method (used to obtain the signature).
            args: Positional arguments from the method call.
            kwargs: Keyword arguments from the method call.

        Returns:
            A tuple of ``(new_args, new_kwargs)`` with validated and
            transformed values.

        Raises:
            pydantic.ValidationError: If any argument fails validation.
                This is caught by ``invoke()`` and wrapped in
                ``ValidationFailedError``.
        """
        sig = inspect.signature(func)
        # Strip 'self'/'cls' from signature — the AOP proxy doesn't include
        # the instance in ctx.args, but test mocks might.
        params = [v for k, v in sig.parameters.items() if k not in ("self", "cls")]
        sig = sig.replace(parameters=params)
        try:
            bound = sig.bind(*args, **kwargs)
        except TypeError:
            # Fallback: args may still include self (e.g. in test mocks)
            bound = sig.bind(*args[1:], **kwargs)
        bound.apply_defaults()

        validated_args_map = bound.arguments.copy()

        for name, val in bound.arguments.items():
            param = sig.parameters[name]

            if _should_skip_param(name, param.annotation):
                continue

            if self._requires_pydantic_validation(param.annotation):
                validated_args_map[name] = TypeAdapter(param.annotation).validate_python(val)

        bound.arguments.update(validated_args_map)
        return bound.args, bound.kwargs

    def _requires_pydantic_validation(self, annotation: Any) -> bool:
        """Determine whether a type annotation requires Pydantic validation.

        Checks if the annotation is a ``BaseModel`` subclass directly, or
        if it is a generic type (``List``, ``Optional``, ``Union``, etc.)
        whose ``__args__`` contain a ``BaseModel`` subclass. The check is
        recursive, so deeply nested generics are supported.

        An exception during the check (e.g., ``TypeError`` from
        ``issubclass`` on special typing constructs, or broken
        ``__args__`` iterators) is caught and logged as a warning, and
        ``False`` is returned — the argument is NOT validated. The log
        makes the skip observable; silently skipping validation in a
        validation library would hide real annotation bugs.

        Args:
            annotation: The type annotation to inspect.

        Returns:
            ``True`` if the annotation involves a ``BaseModel`` type that
            should be validated, ``False`` otherwise.
        """
        try:
            if _is_basemodel_class(annotation):
                return True
            return _has_pydantic_in_args(annotation, self._requires_pydantic_validation)
        except Exception as exc:
            logger.warning(
                "pico-pydantic: could not inspect annotation %r (%s); skipping validation for it",
                annotation,
                exc,
            )
            return False

invoke(ctx, call_next)

Validate marked arguments, then proceed. Sync and async methods both work: validation is synchronous and happens before the call; an async method's awaitable is returned untouched for the proxy to await.

Raises:

Type Description
ValidationFailedError

If a BaseModel-typed argument fails Pydantic validation.

Source code in src/pico_pydantic/interceptor.py
def invoke(self, ctx: MethodCtx, call_next: Callable[[MethodCtx], Any]) -> Any:
    """Validate marked arguments, then proceed. Sync and async methods
    both work: validation is synchronous and happens before the call;
    an async method's awaitable is returned untouched for the proxy to
    await.

    Raises:
        ValidationFailedError: If a ``BaseModel``-typed argument fails
            Pydantic validation.
    """
    original_func = getattr(ctx.cls, ctx.name, None)

    if original_func and getattr(original_func, VALIDATE_META, False):
        try:
            new_args, new_kwargs = self._validate_and_transform(original_func, ctx.args, ctx.kwargs)
            ctx.args = new_args
            ctx.kwargs = new_kwargs
        except ValidationError as e:
            raise ValidationFailedError(ctx.name, e) from e

    return call_next(ctx)

validate(func)

Marker decorator that enables Pydantic validation on a method.

Marks the decorated function with a hidden metadata attribute (_pico_pydantic_validate_meta) so the ValidationInterceptor knows to inspect and validate its arguments at call time.

This decorator does not contain validation logic itself. It is intentionally lightweight to keep import times fast and to delegate the heavy lifting to the interceptor, which has access to the full IoC context.

Parameters:

Name Type Description Default
func T

The function or method to mark for validation.

required

Returns:

Type Description
T

The same function, unmodified except for the added metadata attribute.

Example

from pico_ioc import component from pico_pydantic import validate from pydantic import BaseModel

class ItemData(BaseModel): ... name: str ... price: float

@component ... class ItemService: ... @validate ... async def add_item(self, data: ItemData) -> dict: ... return data.model_dump()

Source code in src/pico_pydantic/decorators.py
def validate(func: T) -> T:
    """Marker decorator that enables Pydantic validation on a method.

    Marks the decorated function with a hidden metadata attribute
    (``_pico_pydantic_validate_meta``) so the ``ValidationInterceptor``
    knows to inspect and validate its arguments at call time.

    This decorator does **not** contain validation logic itself. It is
    intentionally lightweight to keep import times fast and to delegate
    the heavy lifting to the interceptor, which has access to the full
    IoC context.

    Args:
        func: The function or method to mark for validation.

    Returns:
        The same function, unmodified except for the added metadata attribute.

    Example:
        >>> from pico_ioc import component
        >>> from pico_pydantic import validate
        >>> from pydantic import BaseModel
        >>>
        >>> class ItemData(BaseModel):
        ...     name: str
        ...     price: float
        >>>
        >>> @component
        ... class ItemService:
        ...     @validate
        ...     async def add_item(self, data: ItemData) -> dict:
        ...         return data.model_dump()
    """
    setattr(func, VALIDATE_META, True)
    from pico_ioc import intercepted_by

    from .interceptor import ValidationInterceptor

    return intercepted_by(ValidationInterceptor)(func)

Decorators

pico_pydantic.decorators

Decorators and exceptions for pico-pydantic validation.

This module provides
  • The @validate marker decorator that flags methods for argument validation by the ValidationInterceptor.
  • The ValidationFailedError exception raised when Pydantic validation fails on a method argument.

VALIDATE_META = '_pico_pydantic_validate_meta' module-attribute

str: Hidden attribute name set on functions by @validate.

The ValidationInterceptor checks for this attribute to determine whether a method should undergo argument validation.

ValidationFailedError

Bases: ValueError

Exception raised when Pydantic validation fails on method arguments.

Wraps the underlying Pydantic ValidationError with the name of the method that triggered the failure. Inherits from ValueError so it can be caught broadly as a value-related error.

Attributes:

Name Type Description
method_name

The name of the method whose arguments failed validation.

pydantic_error

The original Pydantic ValidationError instance.

Message format

"Validation failed for method '<method_name>': <pydantic_error>"

Example

from pico_pydantic import ValidationFailedError try: ... await service.create_user({"username": "ab"}) ... except ValidationFailedError as e: ... print(e.method_name) # "create_user" ... print(e.pydantic_error) # Original ValidationError

Source code in src/pico_pydantic/decorators.py
class ValidationFailedError(ValueError):
    """Exception raised when Pydantic validation fails on method arguments.

    Wraps the underlying Pydantic ``ValidationError`` with the name of the
    method that triggered the failure. Inherits from ``ValueError`` so it
    can be caught broadly as a value-related error.

    Attributes:
        method_name: The name of the method whose arguments failed validation.
        pydantic_error: The original Pydantic ``ValidationError`` instance.

    Message format:
        ``"Validation failed for method '<method_name>': <pydantic_error>"``

    Example:
        >>> from pico_pydantic import ValidationFailedError
        >>> try:
        ...     await service.create_user({"username": "ab"})
        ... except ValidationFailedError as e:
        ...     print(e.method_name)       # "create_user"
        ...     print(e.pydantic_error)    # Original ValidationError
    """

    def __init__(self, method_name: str, pydantic_error: Exception):
        """Initialize ValidationFailedError.

        Args:
            method_name: The name of the method that failed validation.
            pydantic_error: The original Pydantic ``ValidationError`` that
                describes which fields or constraints were violated.
        """
        self.method_name = method_name
        self.pydantic_error = pydantic_error
        super().__init__(f"Validation failed for method '{method_name}': {pydantic_error}")

__init__(method_name, pydantic_error)

Initialize ValidationFailedError.

Parameters:

Name Type Description Default
method_name str

The name of the method that failed validation.

required
pydantic_error Exception

The original Pydantic ValidationError that describes which fields or constraints were violated.

required
Source code in src/pico_pydantic/decorators.py
def __init__(self, method_name: str, pydantic_error: Exception):
    """Initialize ValidationFailedError.

    Args:
        method_name: The name of the method that failed validation.
        pydantic_error: The original Pydantic ``ValidationError`` that
            describes which fields or constraints were violated.
    """
    self.method_name = method_name
    self.pydantic_error = pydantic_error
    super().__init__(f"Validation failed for method '{method_name}': {pydantic_error}")

validate(func)

Marker decorator that enables Pydantic validation on a method.

Marks the decorated function with a hidden metadata attribute (_pico_pydantic_validate_meta) so the ValidationInterceptor knows to inspect and validate its arguments at call time.

This decorator does not contain validation logic itself. It is intentionally lightweight to keep import times fast and to delegate the heavy lifting to the interceptor, which has access to the full IoC context.

Parameters:

Name Type Description Default
func T

The function or method to mark for validation.

required

Returns:

Type Description
T

The same function, unmodified except for the added metadata attribute.

Example

from pico_ioc import component from pico_pydantic import validate from pydantic import BaseModel

class ItemData(BaseModel): ... name: str ... price: float

@component ... class ItemService: ... @validate ... async def add_item(self, data: ItemData) -> dict: ... return data.model_dump()

Source code in src/pico_pydantic/decorators.py
def validate(func: T) -> T:
    """Marker decorator that enables Pydantic validation on a method.

    Marks the decorated function with a hidden metadata attribute
    (``_pico_pydantic_validate_meta``) so the ``ValidationInterceptor``
    knows to inspect and validate its arguments at call time.

    This decorator does **not** contain validation logic itself. It is
    intentionally lightweight to keep import times fast and to delegate
    the heavy lifting to the interceptor, which has access to the full
    IoC context.

    Args:
        func: The function or method to mark for validation.

    Returns:
        The same function, unmodified except for the added metadata attribute.

    Example:
        >>> from pico_ioc import component
        >>> from pico_pydantic import validate
        >>> from pydantic import BaseModel
        >>>
        >>> class ItemData(BaseModel):
        ...     name: str
        ...     price: float
        >>>
        >>> @component
        ... class ItemService:
        ...     @validate
        ...     async def add_item(self, data: ItemData) -> dict:
        ...         return data.model_dump()
    """
    setattr(func, VALIDATE_META, True)
    from pico_ioc import intercepted_by

    from .interceptor import ValidationInterceptor

    return intercepted_by(ValidationInterceptor)(func)

Interceptor

pico_pydantic.interceptor

AOP validation interceptor for pico-ioc managed components.

This module implements the ValidationInterceptor, a singleton MethodInterceptor that inspects method signatures for Pydantic BaseModel type hints and validates arguments using TypeAdapter.validate_python() before the method body executes.

Helper functions are extracted at module level to keep cyclomatic complexity low: - _should_skip_param: Determines if a parameter should bypass validation. - _is_basemodel_class: Checks if an annotation is a BaseModel subclass. - _has_pydantic_in_args: Recursively checks generic __args__ for BaseModel types.

ValidationInterceptor

Bases: MethodInterceptor

AOP interceptor that validates method arguments against Pydantic schemas.

Registered as a singleton component via pico-ioc. When a method decorated with @validate is called, this interceptor:

  1. Checks for the @validate marker on the target method.
  2. Inspects the method signature for BaseModel type hints.
  3. Validates each matching argument using TypeAdapter(annotation).validate_python(value).
  4. Replaces dict arguments with validated BaseModel instances.
  5. Raises ValidationFailedError if any argument fails validation.

Only parameters with BaseModel type hints (or generic types containing BaseModel, such as List[Model], Optional[Model], Union[Model, ...]) are validated. Parameters without annotations or with non-Pydantic types (str, int, etc.) are passed through.

Auto-discovery

Registered via the pico_boot.modules entry point. No manual registration is needed when using pico-boot.

Example

from pydantic import BaseModel from pico_ioc import component from pico_pydantic import validate

class UserData(BaseModel): ... name: str ... age: int

@component ... class UserService: ... @validate ... async def create(self, data: UserData) -> dict: ... return data.model_dump()

Dicts are automatically converted to UserData instances:

await service.create({"name": "alice", "age": 30})

Source code in src/pico_pydantic/interceptor.py
@component(scope="singleton")
class ValidationInterceptor(MethodInterceptor):
    """AOP interceptor that validates method arguments against Pydantic schemas.

    Registered as a singleton component via pico-ioc. When a method
    decorated with ``@validate`` is called, this interceptor:

    1. Checks for the ``@validate`` marker on the target method.
    2. Inspects the method signature for ``BaseModel`` type hints.
    3. Validates each matching argument using
       ``TypeAdapter(annotation).validate_python(value)``.
    4. Replaces dict arguments with validated ``BaseModel`` instances.
    5. Raises ``ValidationFailedError`` if any argument fails validation.

    Only parameters with ``BaseModel`` type hints (or generic types
    containing ``BaseModel``, such as ``List[Model]``, ``Optional[Model]``,
    ``Union[Model, ...]``) are validated. Parameters without annotations
    or with non-Pydantic types (``str``, ``int``, etc.) are passed through.

    Auto-discovery:
        Registered via the ``pico_boot.modules`` entry point. No manual
        registration is needed when using ``pico-boot``.

    Example:
        >>> from pydantic import BaseModel
        >>> from pico_ioc import component
        >>> from pico_pydantic import validate
        >>>
        >>> class UserData(BaseModel):
        ...     name: str
        ...     age: int
        >>>
        >>> @component
        ... class UserService:
        ...     @validate
        ...     async def create(self, data: UserData) -> dict:
        ...         return data.model_dump()
        >>>
        >>> # Dicts are automatically converted to UserData instances:
        >>> # await service.create({"name": "alice", "age": 30})
    """

    def invoke(self, ctx: MethodCtx, call_next: Callable[[MethodCtx], Any]) -> Any:
        """Validate marked arguments, then proceed. Sync and async methods
        both work: validation is synchronous and happens before the call;
        an async method's awaitable is returned untouched for the proxy to
        await.

        Raises:
            ValidationFailedError: If a ``BaseModel``-typed argument fails
                Pydantic validation.
        """
        original_func = getattr(ctx.cls, ctx.name, None)

        if original_func and getattr(original_func, VALIDATE_META, False):
            try:
                new_args, new_kwargs = self._validate_and_transform(original_func, ctx.args, ctx.kwargs)
                ctx.args = new_args
                ctx.kwargs = new_kwargs
            except ValidationError as e:
                raise ValidationFailedError(ctx.name, e) from e

        return call_next(ctx)

    def _validate_and_transform(self, func: Callable, args: tuple, kwargs: dict) -> tuple[tuple, dict]:
        """Validate and transform method arguments using Pydantic.

        Binds the given positional and keyword arguments to the function
        signature, then iterates over each parameter. For parameters with
        ``BaseModel`` type hints (or generics containing them), the value
        is validated and potentially transformed (e.g., dicts become model
        instances) via ``TypeAdapter(annotation).validate_python(value)``.

        Args:
            func: The original method (used to obtain the signature).
            args: Positional arguments from the method call.
            kwargs: Keyword arguments from the method call.

        Returns:
            A tuple of ``(new_args, new_kwargs)`` with validated and
            transformed values.

        Raises:
            pydantic.ValidationError: If any argument fails validation.
                This is caught by ``invoke()`` and wrapped in
                ``ValidationFailedError``.
        """
        sig = inspect.signature(func)
        # Strip 'self'/'cls' from signature — the AOP proxy doesn't include
        # the instance in ctx.args, but test mocks might.
        params = [v for k, v in sig.parameters.items() if k not in ("self", "cls")]
        sig = sig.replace(parameters=params)
        try:
            bound = sig.bind(*args, **kwargs)
        except TypeError:
            # Fallback: args may still include self (e.g. in test mocks)
            bound = sig.bind(*args[1:], **kwargs)
        bound.apply_defaults()

        validated_args_map = bound.arguments.copy()

        for name, val in bound.arguments.items():
            param = sig.parameters[name]

            if _should_skip_param(name, param.annotation):
                continue

            if self._requires_pydantic_validation(param.annotation):
                validated_args_map[name] = TypeAdapter(param.annotation).validate_python(val)

        bound.arguments.update(validated_args_map)
        return bound.args, bound.kwargs

    def _requires_pydantic_validation(self, annotation: Any) -> bool:
        """Determine whether a type annotation requires Pydantic validation.

        Checks if the annotation is a ``BaseModel`` subclass directly, or
        if it is a generic type (``List``, ``Optional``, ``Union``, etc.)
        whose ``__args__`` contain a ``BaseModel`` subclass. The check is
        recursive, so deeply nested generics are supported.

        An exception during the check (e.g., ``TypeError`` from
        ``issubclass`` on special typing constructs, or broken
        ``__args__`` iterators) is caught and logged as a warning, and
        ``False`` is returned — the argument is NOT validated. The log
        makes the skip observable; silently skipping validation in a
        validation library would hide real annotation bugs.

        Args:
            annotation: The type annotation to inspect.

        Returns:
            ``True`` if the annotation involves a ``BaseModel`` type that
            should be validated, ``False`` otherwise.
        """
        try:
            if _is_basemodel_class(annotation):
                return True
            return _has_pydantic_in_args(annotation, self._requires_pydantic_validation)
        except Exception as exc:
            logger.warning(
                "pico-pydantic: could not inspect annotation %r (%s); skipping validation for it",
                annotation,
                exc,
            )
            return False

invoke(ctx, call_next)

Validate marked arguments, then proceed. Sync and async methods both work: validation is synchronous and happens before the call; an async method's awaitable is returned untouched for the proxy to await.

Raises:

Type Description
ValidationFailedError

If a BaseModel-typed argument fails Pydantic validation.

Source code in src/pico_pydantic/interceptor.py
def invoke(self, ctx: MethodCtx, call_next: Callable[[MethodCtx], Any]) -> Any:
    """Validate marked arguments, then proceed. Sync and async methods
    both work: validation is synchronous and happens before the call;
    an async method's awaitable is returned untouched for the proxy to
    await.

    Raises:
        ValidationFailedError: If a ``BaseModel``-typed argument fails
            Pydantic validation.
    """
    original_func = getattr(ctx.cls, ctx.name, None)

    if original_func and getattr(original_func, VALIDATE_META, False):
        try:
            new_args, new_kwargs = self._validate_and_transform(original_func, ctx.args, ctx.kwargs)
            ctx.args = new_args
            ctx.kwargs = new_kwargs
        except ValidationError as e:
            raise ValidationFailedError(ctx.name, e) from e

    return call_next(ctx)