Utils Validation#

validation #

FUNCTION DESCRIPTION
validate_args

A decorator for type validation based on type annotations.

Functions#

validate_args(func: Callable) -> Callable #

A decorator for type validation based on type annotations.

Validates both positional and keyword arguments against their type annotations. Preserves function metadata through functools.wraps.

PARAMETER DESCRIPTION
func

The function to be decorated with type validation.

TYPE: Callable

RETURNS DESCRIPTION
Callable

The decorated function that performs type validation on its arguments before executing the original function.

RAISES DESCRIPTION
TypeError

If any argument does not match its expected type annotation.

Notes

This decorator only validates arguments that have type annotations. Arguments without annotations are not validated. Does not support Union types or Optional parameters - for advanced validation consider using pydantic.

Examples:

>>> @validate_args
... def add_numbers(a: int, b: int) -> int:
...     return a + b
>>> add_numbers(1, 2)
... 3
>>> add_numbers(1, "2")
... TypeError: Argument `b=2` must be of type `<class 'int'>`.
Source code in spectre/utils/validation.py
def validate_args(func: Callable) -> Callable:
    """
    A decorator for type validation based on type annotations.

    Validates both positional and keyword arguments against their type annotations.
    Preserves function metadata through functools.wraps.

    Parameters
    ----------
    func : Callable
        The function to be decorated with type validation.

    Returns
    -------
    Callable
        The decorated function that performs type validation on its arguments
        before executing the original function.

    Raises
    ------
    TypeError
        If any argument does not match its expected type annotation.

    Notes
    -----
    This decorator only validates arguments that have type annotations.
    Arguments without annotations are not validated. Does not support
    Union types or Optional parameters - for advanced validation consider
    using pydantic.

    Examples
    --------
    >>> @validate_args
    ... def add_numbers(a: int, b: int) -> int:
    ...     return a + b
    >>> add_numbers(1, 2)
    ... 3
    >>> add_numbers(1, "2")
    ... TypeError: Argument `b=2` must be of type `<class 'int'>`.
    """

    @functools.wraps(func)
    def validate(*args, **kwargs) -> Any:
        # Get function signature and bind arguments
        sig = inspect.signature(func)
        try:
            bound = sig.bind(*args, **kwargs)
            bound.apply_defaults()
        except TypeError as e:
            # Re-raise binding errors (missing arguments, etc.)
            raise e

        # Validate each argument against its annotation
        for param_name, param_value in bound.arguments.items():
            if param_name in func.__annotations__:
                expected_type = func.__annotations__[param_name]
                # Skip return type annotation
                if param_name == "return":
                    continue
                # Perform type check
                if not isinstance(param_value, expected_type):
                    raise TypeError(
                        f"Argument `{param_name}={param_value}` must be of type `{expected_type}`."
                    )

        return func(*args, **kwargs)

    return validate