Collections and Containers
Handling collections like lists, dictionaries, and tuples involves defining schemas that specify the types of items, keys, and values, along with constraints like length and strictness.
Lists and Sequences
To validate a list, use list_schema. In lax mode (the default), it accepts any iterable except for strings, bytes, and dictionaries. In strict mode, it only accepts actual Python list objects.
from pydantic_core import SchemaValidator, core_schema
# Define a list of integers with length constraints
schema = core_schema.list_schema(
items_schema=core_schema.int_schema(),
min_length=1,
max_length=3
)
v = SchemaValidator(schema)
# Validates and converts items
assert v.validate_python([1, '2', 3]) == [1, 2, 3]
# Accepts other iterables in lax mode
assert v.validate_python((1, 2)) == [1, 2]
assert v.validate_python({1, 2}) == [1, 2]
Sets and Frozensets
Validation for set and frozenset works similarly to lists but returns the respective collection type.
# Set validation
set_v = SchemaValidator(core_schema.set_schema(core_schema.int_schema()))
assert set_v.validate_python([1, '2', 1]) == {1, 2}
# Frozenset validation
frozenset_v = SchemaValidator(core_schema.frozenset_schema(core_schema.int_schema()))
assert frozenset_v.validate_python([1, 2]) == frozenset({1, 2})
Dictionaries and Mappings
Use dict_schema to validate mappings. You can specify schemas for both keys and values.
schema = core_schema.dict_schema(
keys_schema=core_schema.str_schema(),
values_schema=core_schema.int_schema()
)
v = SchemaValidator(schema)
# Validates keys and values
assert v.validate_python({'a': 1, 'b': '2'}) == {'a': 1, 'b': 2}
# Accepts custom Mapping types
from collections.abc import Mapping
class MyMapping(Mapping):
def __init__(self, d): self._d = d
def __getitem__(self, k): return self._d[k]
def __iter__(self): return iter(self._d)
def __len__(self): return len(self._d)
assert v.validate_python(MyMapping({'x': '10'})) == {'x': 10}
Tuples
The tuple_schema supports both fixed-position items and variadic items (PEP 646 style).
Fixed-Length Positional Tuples
Define a list of schemas corresponding to each position in the tuple.
# Matches exactly (int, str)
schema = core_schema.tuple_schema([
core_schema.int_schema(),
core_schema.str_schema()
])
v = SchemaValidator(schema)
assert v.validate_python((1, 'hello')) == (1, 'hello')
Variadic Tuples
Use variadic_item_index to indicate which schema in items_schema should repeat for variable-length parts.
# Matches (int, str, str, ..., float)
# The item at index 1 (str_schema) is variadic
schema = core_schema.tuple_schema(
[
core_schema.int_schema(),
core_schema.str_schema(),
core_schema.float_schema()
],
variadic_item_index=1,
)
v = SchemaValidator(schema)
assert v.validate_python((1, 'a', 'b', 'c', 1.5)) == (1, 'a', 'b', 'c', 1.5)
# Matches (int, ...)
schema_all_ints = core_schema.tuple_schema(
[core_schema.int_schema()],
variadic_item_index=0
)
v_ints = SchemaValidator(schema_all_ints)
assert v_ints.validate_python((1, 2, 3)) == (1, 2, 3)
Generators and Lazy Validation
The generator_schema validates iterators and generators. Unlike other collections, validation is lazy: errors are only raised when a violating value is actually yielded from the generator.
from typing import Iterator
def my_generator() -> Iterator[Any]:
yield 1
yield 'not an int'
schema = core_schema.generator_schema(items_schema=core_schema.int_schema())
v = SchemaValidator(schema)
# Validation happens during iteration, not call
gen = v.validate_python(my_generator())
assert next(gen) == 1
import pytest
from pydantic_core import ValidationError
with pytest.raises(ValidationError):
next(gen) # Raises error here because 'not an int' fails int_schema
Common Constraints
Most collection schemas support these shared parameters:
| Parameter | Description |
|---|---|
min_length | Minimum number of items required. |
max_length | Maximum number of items allowed. |
strict | If True, rejects types that require conversion (e.g., rejects tuples for list_schema). |
fail_fast | If True, stops validation and raises an error on the first invalid item found. |
Using Fail Fast
By default, pydantic-core collects all errors in a collection. Use fail_fast=True to stop immediately.
schema = core_schema.list_schema(
items_schema=core_schema.int_schema(),
fail_fast=True
)
v = SchemaValidator(schema)
try:
v.validate_python(['a', 'b', 'c'])
except ValidationError as e:
# Only contains the error for the first item 'a'
assert len(e.errors()) == 1
Troubleshooting
Lax Mode Rejections
Even in lax mode, list_schema and tuple_schema explicitly reject strings and bytes to prevent accidental iteration over characters.
v = SchemaValidator(core_schema.list_schema(core_schema.str_schema()))
# This will raise a ValidationError even though strings are iterable
# v.validate_python("abc")
Dictionary Key Coercion
When validating dictionaries, if a key fails validation but can be represented as a string, the error location will use that string representation.
v = SchemaValidator(core_schema.dict_schema(values_schema=core_schema.int_schema()))
# Error loc will be ('(1, 2)',) if the key is the tuple (1, 2)