Модели (Models)
Документация API: pydantic.main.BaseModel
Один из основных способов задать схему в Pydantic — модели. Модель — это класс, наследующий BaseModel, с полями в виде аннотированных атрибутов.
Модели можно воспринимать как аналог структур в языках вроде C или как описание контракта одного эндпоинта API.
Модели во многом похожи на dataclasses в Python, но спроектированы с отличиями, которые упрощают валидацию, сериализацию и генерацию JSON Schema. Подробнее в разделе Dataclasses.
Ненадёжные данные можно передать в модель; после разбора и валидации Pydantic гарантирует, что поля экземпляра модели будут соответствовать объявленным типам.
Валидация — намерленная неточность термина
Кратко
Под «валидацией» мы понимаем процесс создания экземпляра модели (или другого типа), соответствующего заданным типам и ограничениям. В быту это обычно и называют валидацией, хотя в других контекстах «валидация» может означать более узкую проверку.
Подробнее
Путаница возникает из‑за того, что по смыслу Pydantic занимается не совсем тем, что в словаре называется «валидацией»:
validation (сущ.) — проверка или доказательство правильности/точности чего‑либо.
В Pydantic «валидация» — это создание экземпляра модели (или типа), соответствующего заданным типам и ограничениям. Pydantic гарантирует типы и ограничения результата, а не входных данных. Это видно по тому, что ValidationError выбрасывается, когда данные не удаётся успешно разобрать в экземпляр модели.
Различие может казаться мелким, но оно важно на практике. «Валидация» может включать копирование и приведение данных (в т.ч. копирование аргументов конструктора для приведения типов без изменения исходных данных). Подробнее в разделах Data Conversion и Attribute Copies ниже.
Итог: цель Pydantic — чтобы структура после обработки («валидации») точно соответствовала аннотациям типов. Поскольку в обиходе этот процесс называют валидацией, в документации мы используем тот же термин.
Раньше «parse» и «validation» использовались как синонимы; далее мы будем использовать только «validate», а «parse» — только в контексте разбора JSON.
Базовое использование моделей
Pydantic сильно опирается на типизацию Python. Полезные ссылки: mypy, Type System Guides, раздел Attribute copies ниже.
Примечание
from pydantic import BaseModel, ConfigDict
class User(BaseModel):
id: int
name: str = 'Jane Doe'
model_config = ConfigDict(str_max_length=10) # (1)!
- Модели Pydantic поддерживают разные параметры конфигурации (см. здесь).
В примере у User два поля:
Поддерживаемые типы описаны в разделе types. Поля можно настраивать через Field(), см. документацию по полям.
Создание экземпляра:
user = User(id='123')
user — экземпляр User. При инициализации выполняются разбор и валидация. Если не выброшено ValidationError, экземпляр валиден.
К полям обращаются как к атрибутам:
assert user.name == 'Jane Doe' # (1)!
assert user.id == 123 # (2)!
assert isinstance(user.id, int)
- Строка
'123'была приведена к целому123. Подробнее о приведении в разделе Data conversion. nameне задавался при созданииuser, использовано значение по умолчанию. Набор явно заданных при создании полей доступен в model_fields_set.
Сериализация — метод model_dump():
assert user.model_dump() == {'id': 123, 'name': 'Jane Doe'}
Вызов dict от экземпляра тоже даёт словарь, но вложенные поля не рекурсивно превращаются в словари. У model_dump() есть аргументы для настройки сериализации.
По умолчанию модели изменяемы, поля можно менять через присваивание:
user.id = 321
assert user.id == 321
Избегайте совпадения имён полей с аннотацией типа. Например, следующий код ведёт себя неверно и даёт ошибку валидации:
Предупреждение
from typing import Optional
from pydantic import BaseModel
class Boo(BaseModel):
int: Optional[int] = None
m = Boo(int=123) # Валидация не пройдёт.
Из‑за того, как Python обрабатывает аннотированные присваивания, это эквивалентно int: None = None, что и вызывает ошибку.
Методы и свойства моделей
У классов моделей есть методы и атрибуты:
- model_rebuild(): пересборка схемы модели (в т.ч. для рекурсивных дженериков). См. Rebuilding model schema.
- model_post_init(): действия после создания модели и применения валидаторов полей.
- model_parametrized_name(): имя класса для параметризованных дженериков.
- model_computed_fields: соответствие вычисляемых полей и их описаний (экземпляры ComputedFieldInfo).
- model_fields: соответствие имён полей и их описаний (FieldInfo).
- model_json_schema(): JSON Schema модели. См. JSON Schema.
- model_copy(): копия модели (по умолчанию поверхностная). См. Model copy.
- model_dump_json(): JSON-строка из model_dump(). См. Serialization.
- model_dump(): словарь полей и значений. См. Serialization.
- model_construct(): создание модели без валидации. См. Creating models without validation.
- model_validate_json(): валидация переданных JSON-данных. См. Validating data.
- model_validate(): валидация переданного объекта. См. Validating data.
У экземпляров моделей есть атрибуты:
- model_fields_set: множество полей, явно переданных при инициализации.
- model_extra: дополнительные поля, установленные при валидации.
Полный список методов и атрибутов см. в API BaseModel.
Примечание
Изменения по сравнению с Pydantic V1: Migration Guide → Changes to pydantic.BaseModel.
Совет
Преобразование данных (Data conversion)
Pydantic может приводить входные данные к типам полей модели; иногда это ведёт к потере информации. Пример:
from pydantic import BaseModel
class Model(BaseModel):
a: int
b: float
c: str
print(Model(a=3.000, b='2.72', c=b'binary data').model_dump())
#> {'a': 3, 'b': 2.72, 'c': 'binary data'}
Это осознанное решение Pydantic, часто наиболее удобное. Подробнее: обсуждение.
Есть строгий режим, в котором преобразование не выполняется: значения должны уже иметь объявленный тип.
То же касается коллекций. Обычно лучше использовать конкретный тип (например, list), а не абстрактный:
from pydantic import BaseModel
class Model(BaseModel):
items: list[int] # (1)!
print(Model(items=(1, 2, 3)))
#> items=[1, 2, 3]
- Можно было бы взять абстрактный Sequence, чтобы допускать и списки, и кортежи. Pydantic сам приведёт кортеж к списку, так что в большинстве случаев в этом нет нужды. Абстрактные типы также могут ухудшать производительность валидации; конкретные типы коллекций позволяют избежать лишних проверок.
Дополнительные данные (Extra data)
По умолчанию лишние данные не вызывают ошибки и просто игнорируются:
from pydantic import BaseModel
class Model(BaseModel):
x: int
m = Model(x=1, y='a')
assert m.model_dump() == {'x': 1}
Поведение задаётся параметром конфигурации extra:
from pydantic import BaseModel, ConfigDict
class Model(BaseModel):
x: int
model_config = ConfigDict(extra='allow')
m = Model(x=1, y='a') # (1)!
assert m.model_dump() == {'x': 1, 'y': 'a'}
assert m.__pydantic_extra__ == {'y': 'a'}
- При
extra='forbid'это бы завершилось ошибкой.
Возможные значения:
'allow'— лишние данные разрешены и сохраняются в__pydantic_extra__. Для них можно задать аннотацию и валидацию.'forbid'— лишние данные запрещены.'ignore'— лишние данные игнорируются (по умолчанию).
У методов валидации (например, model_validate()) есть опциональный аргумент extra, переопределяющий настройку модели для этого вызова. Подробнее: документация extra. Pydantic dataclasses тоже поддерживают extra (см. dataclass configuration).
Вложенные модели (Nested models)
Иерархические структуры задают, используя модели в аннотациях типов.
Python 3.9+ (с Optional):
from typing import Optional
from pydantic import BaseModel
class Foo(BaseModel):
count: int
size: Optional[float] = None
class Bar(BaseModel):
apple: str = 'x'
banana: str = 'y'
class Spam(BaseModel):
foo: Foo
bars: list[Bar]
m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
print(m)
print(m.model_dump())
Python 3.10+ (синтаксис | None):
from pydantic import BaseModel
class Foo(BaseModel):
count: int
size: float | None = None
class Bar(BaseModel):
apple: str = 'x'
banana: str = 'y'
class Spam(BaseModel):
foo: Foo
bars: list[Bar]
m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
print(m)
# foo=Foo(count=4, size=None) bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y')]
print(m.model_dump())
# {'foo': {'count': 4, 'size': None}, 'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}]}
Поддерживаются самоссылающиеся модели. См. forward annotations.
Пересборка схемы модели (Rebuilding model schema)
При определении класса модели Pydantic анализирует его тело и строит core schema. Аннотации типов вычисляются при создании класса. Если в аннотациях используются символы, ещё не определённые в момент создания класса, используйте model_rebuild():
from pydantic import BaseModel, PydanticUserError
class Foo(BaseModel):
x: 'Bar' # (1)!
try:
Foo.model_json_schema()
except PydanticUserError as e:
print(e)
# `Foo` is not fully defined; you should define `Bar`, then call `Foo.model_rebuild()`.
class Bar(BaseModel):
pass
Foo.model_rebuild()
print(Foo.model_json_schema())
# {'$defs': {'Bar': {...}}, 'properties': {'x': {'$ref': '#/$defs/Bar'}}, 'required': ['x'], ...}
Barещё не определён при созданииFoo, поэтому используется forward annotation.
Pydantic пытается определять необходимость пересборки автоматически, но при рекурсивных или дженерик-моделях лучше вызывать model_rebuild() явно.
В V2 model_rebuild() заменил update_forward_refs() из V1. При вызове на внешней модели пересобирается схема всей иерархии, поэтому все типы на всех уровнях должны быть определены до вызова.
Валидация данных (Validating data)
Pydantic может валидировать данные в трёх режимах: Python, JSON и strings.
Режим Python используется при:
- model_validate(): данные — словарь или экземпляр модели (по умолчанию экземпляры считаются валидными; см. revalidate_instances). При явном включении можно передавать произвольные объекты.
- Конструкторе
__init__()модели. Значения полей передаются только именованными аргументами.
Режимы JSON и strings — отдельные методы:
- model_validate_strings(): данные — словарь (возможно вложенный) со строковыми ключами и значениями; строки приводятся к нужным типам как в JSON-режиме.
- model_validate_json(): данные — JSON-строка или
bytes. Для JSON-нагрузки обычно быстрее, чем ручной разбор в словарь. См. JSON.
У методов model_validate_*() можно задавать параметры валидации (строгость, лишние данные, контекст валидации и т.д.).
Поведение режимов Python и JSON может различаться (например, по строгости). Если данные не из JSON, но нужна та же логика, что в JSON-режиме: сериализуйте данные в JSON (json.dumps()) или используйте model_validate_strings() для словарей со строковыми ключами и значениями. Прогресс: issue.
Примечание
Python 3.10+:
from datetime import datetime
from pydantic import BaseModel, ValidationError
class User(BaseModel):
id: int
name: str = 'John Doe'
signup_ts: datetime | None = None
m = User.model_validate({'id': 123, 'name': 'James'})
print(m)
#> id=123 name='James' signup_ts=None
try:
m = User.model_validate_json('{"id": 123, "name": 123}')
except ValidationError as e:
print(e)
"""
1 validation error for User
name
Input should be a valid string [type=string_type, input_value=123, input_type=int]
"""
m = User.model_validate_strings({'id': '123', 'name': 'James'})
print(m)
#> id=123 name='James' signup_ts=None
m = User.model_validate_strings(
{'id': '123', 'name': 'James', 'signup_ts': '2024-04-01T12:00:00'}
)
print(m)
#> id=123 name='James' signup_ts=datetime.datetime(2024, 4, 1, 12, 0)
try:
m = User.model_validate_strings(
{'id': '123', 'name': 'James', 'signup_ts': '2024-04-01'}, strict=True
)
except ValidationError as e:
print(e)
"""
1 validation error for User
signup_ts
Input should be a valid datetime, invalid datetime separator, expected `T`, `t`, `_` or space [type=datetime_parsing, input_value='2024-04-01', input_type=str]
"""
Создание моделей без валидации
model_construct() создаёт модель без валидации. Полезно, когда:
- валидаторы имеют побочные эффекты, которые не нужны;
- валидаторы неидемпотентны;
- данные уже заведомо валидны (ради производительности).
model_construct() не выполняет валидации и может создать невалидную модель. Используйте его только с уже провалидированными или доверенными данными.
Предупреждение
В Pydantic V2 разница в скорости между обычной валидацией и model_construct() уменьшилась. Для простых моделей валидация может быть быстрее. При использовании
model_construct()ради скорости стоит замерить свой сценарий.Примечание
Для root-моделей корневое значение в model_construct() можно передать позиционно.
Поведение model_construct():
__init__модели и родительских классов не вызывается.- Для моделей с приватными атрибутами
__pydantic_private__заполняется так же, как при валидации. - Для полей с значениями по умолчанию они подставляются, если аргумент не передан.
- Словари во вложенные модели не преобразуются — при необходимости делайте это сами.
Поведение при лишних данных:
- При
extra='forbid'вызовmodel_construct()с лишними данными не вызывает ошибку, данные игнорируются. - При
extra='ignore'лишние данные не попадают в__pydantic_extra__и__dict__. - При
extra='allow'лишние данные сохраняются в__pydantic_extra__и в__dict__.
Собственный init()
У моделей Pydantic есть реализация __init__() по умолчанию (вызывается только из конструктора, не из model_validate_*()), которая делегирует валидацию в pydantic-core.
Можно задать свой __init__(). Тогда он будет вызываться из всех методов валидации без выполнения валидации (в реализации нужно вызывать super().__init__(**kwargs)).
Собственный __init__() не рекомендуется: теряются параметры валидации (строгость, лишние данные, контекст). Для действий после инициализации используйте after field или model валидаторы либо model_post_init():
import logging
from typing import Any
from pydantic import BaseModel
class MyModel(BaseModel):
id: int
def model_post_init(self, context: Any) -> None:
logging.info("Model initialized with id %d", self.id)
Обработка ошибок (Error handling)
При ошибках в данных Pydantic выбрасывает ValidationError. Одно исключение содержит информацию обо всех найденных ошибках. Подробнее: Error Handling.
Пример:
from pydantic import BaseModel, ValidationError
class Model(BaseModel):
list_of_ints: list[int]
a_float: float
data = {'list_of_ints': ['1', 2, 'bad'], 'a_float': 'not a float'}
try:
Model(**data)
except ValidationError as e:
print(e)
"""
2 validation errors for Model
list_of_ints.2
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='bad', input_type=str]
a_float
Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='not a float', input_type=str]
"""
Произвольные экземпляры классов (Arbitrary class instances)
(Ранее «ORM Mode» / from_orm().)
При использовании model_validate() Pydantic может валидировать произвольные объекты, читая атрибуты по именам полей. Часто это используют для интеграции с ORM.
Включение: конфиг from_attributes или параметр from_attributes в model_validate(). Пример с SQLAlchemy:
from typing import Annotated
from sqlalchemy import ARRAY, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from pydantic import BaseModel, ConfigDict, StringConstraints
class Base(DeclarativeBase):
pass
class CompanyOrm(Base):
__tablename__ = 'companies'
id: Mapped[int] = mapped_column(primary_key=True, nullable=False)
public_key: Mapped[str] = mapped_column(String(20), index=True, nullable=False, unique=True)
domains: Mapped[list[str]] = mapped_column(ARRAY(String(255)))
class CompanyModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
public_key: Annotated[str, StringConstraints(max_length=20)]
domains: list[Annotated[str, StringConstraints(max_length=255)]]
co_orm = CompanyOrm(id=123, public_key='foobar', domains=['example.com', 'foobar.com'])
co_model = CompanyModel.model_validate(co_orm)
# id=123 public_key='foobar' domains=['example.com', 'foobar.com']
Вложенные атрибуты
Экземпляры моделей создаются и из верхнеуровневых, и из вложенных атрибутов. Пример:
from pydantic import BaseModel, ConfigDict
class PetCls:
def __init__(self, *, name: str) -> None:
self.name = name
class PersonCls:
def __init__(self, *, name: str, pets: list[PetCls]) -> None:
self.name = name
self.pets = pets
class Pet(BaseModel):
model_config = ConfigDict(from_attributes=True)
name: str
class Person(BaseModel):
model_config = ConfigDict(from_attributes=True)
name: str
pets: list[Pet]
bones = PetCls(name='Bones')
orion = PetCls(name='Orion')
anna = PersonCls(name='Anna', pets=[bones, orion])
anna_model = Person.model_validate(anna)
print(anna_model)
#> name='Anna' pets=[Pet(name='Bones'), Pet(name='Orion')]
Копирование модели (Model copy)
model_copy() создаёт копию модели (с опциональными обновлениями), удобно для замороженных моделей.
from pydantic import BaseModel
class BarModel(BaseModel):
whatever: int
class FooBarModel(BaseModel):
banana: float
foo: str
bar: BarModel
m = FooBarModel(banana=3.14, foo='hello', bar={'whatever': 123})
print(m.model_copy(update={'banana': 0}))
#> banana=0 foo='hello' bar=BarModel(whatever=123)
# обычная копия — тот же объект для bar
print(id(m.bar) == id(m.model_copy().bar))
#> True
# глубокая копия — новый объект для bar
print(id(m.bar) == id(m.model_copy(deep=True).bar))
#> False
Дженерик-модели (Generic models)
Pydantic поддерживает дженерик-модели для переиспользования общей структуры. Поддерживаются и новый синтаксис параметров типов (PEP 695, Python 3.12), и старый.
Пример обёртки ответа API (Python 3.9+):
from typing import Generic, TypeVar
from pydantic import BaseModel, ValidationError
DataT = TypeVar('DataT')
class DataModel(BaseModel):
number: int
class Response(BaseModel, Generic[DataT]):
data: DataT
print(Response[int](data=1))
#> data=1
print(Response[str](data='value'))
#> data='value'
print(Response[str](data='value').model_dump())
#> {'data': 'value'}
data = DataModel(number=1)
print(Response[DataModel](data=data).model_dump())
#> {'data': {'number': 1}}
try:
Response[int](data='value')
except ValidationError as e:
print(e)
Python 3.12+ (новый синтаксис): class Response[DataT](BaseModel): с type parameters.
Наследование от дженерик-модели с сохранением дженерика — подкласс должен наследовать Generic:
from typing import Generic, TypeVar
from pydantic import BaseModel
TypeX = TypeVar('TypeX')
class BaseClass(BaseModel, Generic[TypeX]):
X: TypeX
class ChildClass(BaseClass[TypeX], Generic[TypeX]):
pass
print(ChildClass[int](X=1))
#> X=1
Подкласс может частично или полностью подставлять type variables:
class ChildClass(BaseClass[int, TypeY], Generic[TypeY, TypeZ]):
z: TypeZ
# ChildClass[str, int](x='1', y='y', z='3') -> x=1 y='y' z=3
Переопределение имени параметризованного класса — model_parametrized_name(): например, IntResponse(data=1), StrResponse(data='a'). Параметризованные дженерик-модели можно использовать как типы полей в других моделях (Order с product: ResponseModel[Product]). Один и тот же type variable во вложенных моделях связывает типы на разных уровнях (InnerT[T], OuterT[T]).
Для наследования от дженерик-модели с сохранением дженерика подкласс должен наследовать Generic. Имя параметризованных подклассов можно задать через model_parametrized_name().
При параметризации модели конкретным типом Pydantic не проверяет, что тип совместим с верхней границей type variable.
Предупреждение
Конфигурация, валидаторы и логика сериализации дженерик-модели применяются к параметризованным классам. Внутри Pydantic создаёт подклассы дженерик-модели при параметризации и кэширует их.
Валидация непараметризованных type variables: для неограниченных переменных используется Any; при default (PEP 696) или bound/constraints — соответствующий тип. Пример с T, U (bound=int), V (default=str): Model(t='t', u=1, v='v') валидно; при ItemHolder с ItemT bound на ItemBase без параметризации поле item валидируется как ItemBase() и теряет поля подтипа — нужно явно ItemHolder[IntItem](**loaded_data).
Сериализация непараметризованных type variables: при upper bound без параметризации валидация идёт по верхней границе, сериализация — как Any (все поля сохраняются). При явной параметризации Error[ErrorDetails] в вывод попадают только поля объявленного типа. При constraints или default тип по умолчанию используется и для валидации, и для сериализации; переопределить можно через SerializeAsAny.
Не используйте параметризованные дженерики в isinstance() (например,
isinstance(my_model, MyGenericModel[int])). Для проверки типа создайте подкласс:class MyIntModel(MyGenericModel[int]): ...и проверяйтеisinstance(my_model, MyIntModel).Предупреждение
Динамическое создание моделей (Dynamic model creation)
create_model() создаёт модель по полям, заданным в runtime:
from pydantic import BaseModel, create_model
DynamicFoobarModel = create_model('DynamicFoobarModel', foo=str, bar=(int, 123))
# Эквивалент классу с полями foo: str и bar: int = 123
Поля задаются именованными аргументами: либо пара (тип, значение по умолчанию или Field()), либо один тип. Расширенный пример с Field, PrivateAttr:
from typing import Annotated
from pydantic import BaseModel, Field, PrivateAttr, create_model
DynamicModel = create_model(
'DynamicModel',
foo=(str, Field(alias='FOO')),
bar=Annotated[str, Field(description='Bar field')],
_private=(int, PrivateAttr(default=1)),
)
Специальные аргументы __config__ и __base__ настраивают модель; __base__ задаёт базовую модель:
from pydantic import BaseModel, create_model
class FooModel(BaseModel):
foo: str
bar: int = 123
BarModel = create_model(
'BarModel',
apple=(str, 'russet'),
banana=(str, 'yellow'),
__base__=FooModel,
)
print(BarModel.model_fields.keys())
#> dict_keys(['foo', 'bar', 'apple', 'banana'])
Валидаторы передаются в __validators__:
from pydantic import ValidationError, create_model, field_validator
def alphanum(cls, v):
assert v.isalnum(), 'must be alphanumeric'
return v
validators = {'username_validator': field_validator('username')(alphanum)}
UserModel = create_model('UserModel', username=(str, ...), __validators__=validators)
user = UserModel(username='scolvin')
# UserModel(username='scolvi%n') вызовет ValidationError
Для pickle динамической модели нужны
__module__и глобальное определение модели.Примечание
Функция может выполнять произвольный код в аннотациях при разрешении строковых ссылок. См. Security implications of introspecting annotations.
Предупреждение
См. также пример динамических моделей.
RootModel и пользовательский корневой тип
Модели с «пользовательским корневым типом» задаются наследованием от pydantic.RootModel. Корневой тип задаётся дженерик-параметром; корневое значение передаётся первым (и единственным) аргументом в __init__ или model_validate.
Пример:
from pydantic import RootModel
Pets = RootModel[list[str]]
PetsByName = RootModel[dict[str, str]]
print(Pets(['dog', 'cat']))
#> root=['dog', 'cat']
print(Pets(['dog', 'cat']).model_dump_json())
#> ["dog","cat"]
print(Pets.model_validate(['dog', 'cat']))
print(Pets.model_json_schema())
# {'items': {'type': 'string'}, 'title': 'RootModel[list[str]]', 'type': 'array'}
print(PetsByName({'Otis': 'dog', 'Milo': 'cat'}))
#> root={'Otis': 'dog', 'Milo': 'cat'}
print(PetsByName.model_validate({'Otis': 'dog', 'Milo': 'cat'}))
Для доступа к элементам root по индексу или итерации реализуйте __iter__ и __getitem__:
from pydantic import RootModel
class Pets(RootModel):
root: list[str]
def __iter__(self):
return iter(self.root)
def __getitem__(self, item):
return self.root[item]
pets = Pets.model_validate(['dog', 'cat'])
print(pets[0])
#> dog
print([pet for pet in pets])
#> ['dog', 'cat']
Подкласс параметризованной RootModel:
from pydantic import RootModel
class Pets(RootModel[list[str]]):
def describe(self) -> str:
return f'Pets: {", ".join(self.root)}'
my_pets = Pets.model_validate(['dog', 'cat'])
print(my_pets.describe())
#> Pets: dog, cat
Псевдо-неизменяемость (Faux immutability)
Неизменяемость задаётся через model_config['frozen'] = True. Попытка изменить атрибут приведёт к ошибке. В V1 использовалось allow_mutation = False (устарело в V2).
В Python неизменяемость не гарантируется на уровне языка; изменяемые вложенные объекты (например, dict) по-прежнему можно менять.
Предупреждение
from pydantic import BaseModel, ConfigDict, ValidationError
class FooBarModel(BaseModel):
model_config = ConfigDict(frozen=True)
a: str
b: dict
foobar = FooBarModel(a='hello', b={'apple': 'pear'})
try:
foobar.a = 'different'
except ValidationError as e:
print(e)
# Instance is frozen
print(foobar.a)
#> hello
foobar.b['apple'] = 'grape' # dict изменяем, изменение допустимо
print(foobar.b)
#> {'apple': 'grape'}
Абстрактные базовые классы (Abstract base classes)
Модели Pydantic можно сочетать с ABC: наследуйте модель от abc.ABC и помечайте методы @abc.abstractmethod.
Порядок полей (Field ordering)
Порядок полей сохраняется при сериализации, в ошибках валидации и в JSON Schema модели.
from pydantic import BaseModel, ValidationError
class Model(BaseModel):
a: int
b: int = 2
c: int = 1
d: int = 0
e: float
print(Model.model_fields.keys())
#> dict_keys(['a', 'b', 'c', 'd', 'e'])
m = Model(e=2, a=1)
print(m.model_dump())
#> {'a': 1, 'b': 2, 'c': 1, 'd': 0, 'e': 2.0}
try:
Model(a='x', b='x', c='x', d='x', e='x')
except ValidationError as err:
error_locations = [e['loc'] for e in err.errors()]
print(error_locations)
#> [('a',), ('b',), ('c',), ('d',), ('e',)]
Автоматически исключаемые атрибуты
Переменные класса
Атрибуты с аннотацией ClassVar считаются переменными класса и не становятся полями экземпляра.
from typing import ClassVar
from pydantic import BaseModel
class Model(BaseModel):
x: ClassVar[int] = 1
y: int = 2
m = Model()
print(m)
#> y=2
print(Model.x)
#> 1
Приватные атрибуты модели
Атрибуты с ведущим подчёркиванием не считаются полями и не входят в схему. Они превращаются в «приватные атрибуты» и не валидируются и не устанавливаются в __init__/model_validate. Задаются через PrivateAttr. Имена с двойным подчёркиванием (__attr__) не поддерживаются и игнорируются.
from datetime import datetime
from random import randint
from typing import Any
from pydantic import BaseModel, PrivateAttr
class TimeAwareModel(BaseModel):
_processed_at: datetime = PrivateAttr(default_factory=datetime.now)
_secret_value: str
def model_post_init(self, context: Any) -> None:
self._secret_value = randint(1, 5)
m = TimeAwareModel()
print(m._processed_at)
print(m._secret_value)
Сигнатура модели (Model signature)
Сигнатура модели генерируется по полям и подходит для интроспекции и библиотек вроде FastAPI и hypothesis.
import inspect
from pydantic import BaseModel, Field
class FooModel(BaseModel):
id: int
name: str = None
description: str = 'Foo'
apple: int = Field(alias='pear')
print(inspect.signature(FooModel))
#> (*, id: int, name: str = None, description: str = 'Foo', pear: int) -> None
Учитывается и кастомный __init__. В сигнатуру попадают alias или имя поля, если это валидный идентификатор; при model_config['extra'] == 'allow' в сигнатуре всегда есть **data.
Сопоставление структур (Structural pattern matching)
Поддерживается структурное сопоставление образцов (PEP 636, Python 3.10):
from pydantic import BaseModel
class Pet(BaseModel):
name: str
species: str
a = Pet(name='Bones', species='dog')
match a:
case Pet(species='dog', name=dog_name):
print(f'{dog_name} is a dog')
#> Bones is a dog
case _:
print('No dog matched')
Это синтаксический сахар для доступа к атрибутам и сравнения/присваивания, не создаёт новую модель.
Копирование атрибутов (Attribute copies)
Во многих случаях аргументы конструктора копируются для валидации и приведения типов.
from pydantic import BaseModel
class C1:
arr = []
def __init__(self, in_arr):
self.arr = in_arr
class C2(BaseModel):
arr: list[int]
arr_orig = [1, 9, 10, 3]
c1 = C1(arr_orig)
c2 = C2(arr=arr_orig)
print(f'{id(c1.arr) == id(c2.arr)=}')
#> id(c1.arr) == id(c2.arr)=False
В части случаев (например, при передаче моделей) копирование не выполняется; переопределить можно через model_config['revalidate_instances'] = 'always'.