Skip to content

Validation

psengine.helpers.validation

validate_list

validate_list(
    model_cls: type[T],
    items: Iterable[Any],
    id_path: str | None = None,
    log: Logger | None = None,
) -> list[T]

Validate a list of dicts against model_cls.

Uses pydantic's TypeAdapter so a single validation pass collects all per-item errors with an index in their loc.

Before re-raising, each failing item is (a) logged individually as a warning and (b) on Python 3.11+ attached to the exception via add_note, so the entity identifier shows up in the traceback too. On Python 3.10 the note step is skipped since BaseException.add_note is not available.

If id_path is provided, a human-readable identifier is extracted from the raw dict (e.g. entity.name); otherwise only the index is reported.

PARAMETER DESCRIPTION
model_cls

Pydantic model each item should validate against.

TYPE: type[T]

items

Raw items to validate, typically dicts from an API response.

TYPE: Iterable[Any]

id_path

Dotted path into each raw item used to build a friendly identifier for log lines and exception notes, e.g. 'entity.name'. If unresolvable for a given item, only the index is reported.

TYPE: str | None DEFAULT: None

log

Logger for the warnings. Defaults to this module's logger.

TYPE: Logger | None DEFAULT: None

RAISES DESCRIPTION
ValidationError

If any item fails validation. Unchanged from [model_cls.model_validate(x) for x in items].

RETURNS DESCRIPTION
list[T]

The list of validated model_cls instances.

Source code in psengine/helpers/validation.py
def validate_list(
    model_cls: Annotated[type[T], Doc('Pydantic model each item should validate against.')],
    items: Annotated[
        Iterable[Any], Doc('Raw items to validate, typically dicts from an API response.')
    ],
    id_path: Annotated[
        str | None,
        Doc(
            'Dotted path into each raw item used to build a friendly identifier for log lines '
            "and exception notes, e.g. `'entity.name'`. If unresolvable for a given item, only "
            'the index is reported.'
        ),
    ] = None,
    log: Annotated[
        logging.Logger | None,
        Doc("Logger for the warnings. Defaults to this module's logger."),
    ] = None,
) -> Annotated[list[T], Doc('The list of validated `model_cls` instances.')]:
    """Validate a list of dicts against `model_cls`.

    Uses pydantic's `TypeAdapter` so a single validation pass collects all per-item errors with an
    index in their `loc`.

    Before re-raising, each failing item is (a) logged individually as a warning and (b) on
    Python 3.11+ attached to the exception via `add_note`, so the entity identifier shows up in
    the traceback too. On Python 3.10 the note step is skipped since `BaseException.add_note`
    is not available.

    If `id_path` is provided, a human-readable identifier is extracted from the raw dict
    (e.g. `entity.name`); otherwise only the index is reported.

    Raises:
        pydantic.ValidationError: If any item fails validation. Unchanged
            from `[model_cls.model_validate(x) for x in items]`.
    """
    if not isinstance(items, list):
        items = list(items)
    try:
        return TypeAdapter(list[model_cls]).validate_python(items)
    except ValidationError as e:
        for msg in _format_failures(e, items, id_path, model_cls):
            (log or LOG).warning(msg)
            if hasattr(e, 'add_note'):
                e.add_note(msg)
        raise