Validating Lists and Sequences
In pydantic-core, list validation is managed through the ListSchema structure. This schema allows for defining variable-length sequences where every item must conform to a specific sub-schema. The primary way to construct these schemas is via the list_schema helper function found in pydantic_core.core_schema.
Defining List Schemas
A basic list schema requires an items_schema, which defines the validation logic for every element within the list. When a list is validated, pydantic-core iterates through the input, applies the items_schema to each element, and returns a new list containing the validated items.
from pydantic_core import SchemaValidator, core_schema
# A schema for a list of integers
schema = core_schema.list_schema(core_schema.int_schema())
v = SchemaValidator(schema)
assert v.validate_python(['1', 2, 3.0]) == [1, 2, 3]
Length Constraints
The ListSchema supports min_length and max_length constraints. These constraints are evaluated after the individual items have been validated. This is a critical distinction when using features like item omission.
from pydantic_core import SchemaValidator, core_schema
schema = core_schema.list_schema(
core_schema.int_schema(),
min_length=2,
max_length=5
)
v = SchemaValidator(schema)
# Valid: length is 3
assert v.validate_python([1, 2, 3]) == [1, 2, 3]
# Invalid: length is 1
# Raises ValidationError: List should have at least 2 items
Interaction with Item Omission
If the items_schema is configured to omit items on error (using on_error='omit' in a with_default_schema), the length constraints are checked against the final list after the invalid items have been removed.
As seen in pydantic-core/tests/validators/test_list.py:
from pydantic_core import SchemaValidator, core_schema as cs
v = SchemaValidator(
cs.list_schema(
items_schema=cs.with_default_schema(schema=cs.int_schema(), on_error='omit'),
max_length=4
)
)
# Input has 5 items, but 'x' is omitted, resulting in a list of length 4.
# This passes the max_length=4 constraint.
assert v.validate_python([1, 2, 3, 'x', 4]) == [1, 2, 3, 4]
# Input has 5 valid items, resulting in length 5.
# This fails the max_length=4 constraint.
# v.validate_python([1, 2, 3, 4, 5]) -> ValidationError
Strict vs. Lax Validation
The behavior of list validation changes significantly based on the strict setting:
- Lax Mode (Default):
pydantic-coreaccepts various iterable types and coerces them into a list. This includestuple,set,frozenset,deque, generators, and dictionary keys or values. - Strict Mode: Only actual Python
listinstances are accepted.
Example of strict mode from pydantic-core/tests/validators/test_list.py:
from pydantic_core import SchemaValidator, core_schema as cs
import pytest
v = SchemaValidator(cs.list_schema(items_schema=cs.int_schema(), strict=True))
# Lists are still accepted and items are validated/coerced
assert v.validate_python([1, 2, '33']) == [1, 2, 33]
# Tuples are rejected in strict mode
with pytest.raises(ValidationError) as exc_info:
v.validate_python((1, 2, 3))
Validation Control with fail_fast
By default, pydantic-core validates every item in the list and collects all encountered errors. If you prefer to stop validation immediately upon the first error, you can set fail_fast=True.
This is particularly useful for performance when validating very large lists where a single failure renders the entire list invalid.
from pydantic_core import SchemaValidator, core_schema
# fail_fast=True stops at the first error
schema = core_schema.list_schema(core_schema.int_schema(), fail_fast=True)
v = SchemaValidator(schema)
# If [1, 'not-a-number', 'also-bad'] is provided,
# only the error for 'not-a-number' is reported.
Implementation Details
Copying Behavior
In pydantic-core, lists are always copied during validation. Even if the input is already a list and no items are changed or coerced, v.validate_python(data) will return a new list object. This ensures that the original input remains immutable relative to the validation process.
Generator Validation
When a generator is passed to a list validator in lax mode, the validator consumes the generator to create the list. If the generator raises an exception during iteration, pydantic-core wraps this in a ValidationError with a type of iteration_error.