Перейти к содержанию

??? API "Документация по API" pydantic.main.BaseModel

Одним из основных способов определения схемы в Pydantic является использование моделей. Модели — это просто классы, которые наследуются от pydantic.BaseModel и определяют поля как аннотированные атрибуты.

Вы можете думать о моделях как о структурах в таких языках, как C, или как о требованиях к одной конечной точке в API.

Модели имеют много общего с классами данных Python, но были разработаны с некоторыми тонкими, но важными различиями, которые упрощают определенные рабочие процессы, связанные с проверкой, сериализацией и генерацией схемы JSON. Более подробное обсуждение этого вопроса можно найти в разделе документации « Классы данных ».

Ненадежные данные могут быть переданы в модель, и после анализа и проверки Pydantic гарантирует, что поля результирующего экземпляра модели будут соответствовать типам полей, определенным в модели.

!!! примечание «Проверка — намеренное неправильное название» ### TL;DR

We use the term "validation" to refer to the process of instantiating a model (or other type) that adheres to specified types and
constraints. This task, which Pydantic is well known for, is most widely recognized as "validation" in colloquial terms,
even though in other contexts the term "validation" may be more restrictive.

---

### The long version

The potential confusion around the term "validation" arises from the fact that, strictly speaking, Pydantic's
primary focus doesn't align precisely with the dictionary definition of "validation":

> ### validation
> _noun_
> the action of checking or proving the validity or accuracy of something.

In Pydantic, the term "validation" refers to the process of instantiating a model (or other type) that adheres to specified
types and constraints. Pydantic guarantees the types and constraints of the output, not the input data.
This distinction becomes apparent when considering that Pydantic's `ValidationError` is raised
when data cannot be successfully parsed into a model instance.

While this distinction may initially seem subtle, it holds practical significance.
In some cases, "validation" goes beyond just model creation, and can include the copying and coercion of data.
This can involve copying arguments passed to the constructor in order to perform coercion to a new type
without mutating the original input data. For a more in-depth understanding of the implications for your usage,
refer to the [Data Conversion](#data-conversion) and [Attribute Copies](#attribute-copies) sections below.

In essence, Pydantic's primary goal is to assure that the resulting structure post-processing (termed "validation")
precisely conforms to the applied type hints. Given the widespread adoption of "validation" as the colloquial term
for this process, we will consistently use it in our documentation.

While the terms "parse" and "validation" were previously used interchangeably, moving forward, we aim to exclusively employ "validate",
with "parse" reserved specifically for discussions related to [JSON parsing](../concepts/json.md).

Базовое использование модели

from pydantic import BaseModel


class User(BaseModel):
    id: int
    name: str = 'Jane Doe'

В этом примере User — это модель с двумя полями:

  • id , который является целым числом и является обязательным
  • name , которое является строкой и не является обязательным (оно имеет значение по умолчанию).

пользователь = Пользователь (id = '123')

В этом примере user является экземпляром User . Инициализация объекта выполнит весь анализ и проверку. Если ValidationError не возникает, вы знаете, что полученный экземпляр модели действителен.

assert user.id == 123
assert isinstance(user.id, int)
# Note that '123' was coerced to an int and its value is 123

Более подробную информацию о логике приведения pydantic можно найти в Data Conversion . Доступ к полям модели возможен как к обычным атрибутам user объекта. Строка '123' была преобразована в целое число в соответствии с типом поля.

assert user.name == 'Jane Doe'

name не было установлено при инициализации user , поэтому оно имеет значение по умолчанию.

assert user.model_fields_set == {'id'}

Поля, которые были предоставлены при инициализации пользователя.

assert user.model_dump() == {'id': 123, 'name': 'Jane Doe'}

Либо .model_dump() либо dict(user) предоставит набор полей, но .model_dump() может принимать множество других аргументов. (Обратите внимание, что dict(user) не будет рекурсивно преобразовывать вложенные модели в dict, а .model_dump() это сделает.)

user.id = 321
assert user.id == 321

По умолчанию модели являются изменяемыми, и значения полей можно изменить путем назначения атрибутов.

Методы и свойства модели

Приведенный выше пример показывает лишь верхушку айсберга того, на что способны модели. Модели обладают следующими методами и атрибутами:

!!! Примечание. См. BaseModel для определения класса, включая полный список методов и атрибутов.

!!! подсказка Подробные сведения об изменениях по сравнению с Pydantic V1 см. в разделе «Изменения в pydantic.BaseModel в Руководстве по миграции .

Вложенные модели

Более сложные иерархические структуры данных можно определить, используя сами модели в качестве типов в аннотациях.

from typing import List, 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)
"""
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'}],
}
"""

Информацию о моделях, ссылающихся на себя, см. в отложенных аннотациях .

!!! note Примечание. При определении моделей следите за конфликтами имен между именем поля и его типом, ранее определенной моделью или импортированной библиотекой.

For example, the following would yield a validation error:
```py
from typing import Optional

from pydantic import BaseModel


class Boo(BaseModel):
    int: Optional[int] = None


m = Boo(int=123)  # errors
```
An error occurs since the field  `int` is set to a default value of `None` and has the exact same name as its type, so both are interpreted to be `None`.

Перестроить схему модели

Схему модели можно перестроить с помощью model_rebuild(). Это полезно для построения рекурсивных универсальных моделей.

from pydantic import BaseModel, PydanticUserError


class Foo(BaseModel):
    x: 'Bar'


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()`.

    For further information visit https://errors.pydantic.dev/2/u/class-not-fully-defined
    """


class Bar(BaseModel):
    pass


Foo.model_rebuild()
print(Foo.model_json_schema())
"""
{
    '$defs': {'Bar': {'properties': {}, 'title': 'Bar', 'type': 'object'}},
    'properties': {'x': {'$ref': '#/$defs/Bar'}},
    'required': ['x'],
    'title': 'Foo',
    'type': 'object',
}
"""

Pydantic пытается автоматически определить, когда это необходимо, и выдает ошибку, если это не было сделано, но вы можете захотеть заранее вызвать model_rebuild() при работе с рекурсивными моделями или обобщенными моделями.

В версии V2 model_rebuild() заменил update_forward_refs() из версии V1. Есть некоторые небольшие различия в новом поведении. Самое большое изменение заключается в том, что при вызове model_rebuild() для самой внешней модели он создает базовую схему, используемую для проверки всей модели (вложенных моделей и всего остального), поэтому все типы вообще уровни должны быть готовы до вызова model_rebuild().

Произвольные экземпляры классов

(Ранее известный как «Режим ORM»/ from_orm .)

Модели Pydantic также можно создавать из произвольных экземпляров классов путем чтения атрибутов экземпляра, соответствующих именам полей модели. Одним из распространенных применений этой функциональности является интеграция с объектно-реляционными сопоставлениями (ORM).

Для этого установите атрибут конфигурации model_config['from_attributes'] = True . Дополнительные сведения см. в разделах Конфигурация модели и ConfigDict.

В примере здесь используется SQLAlchemy , но тот же подход должен работать для любого ORM.

from typing import List

from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.orm import declarative_base
from typing_extensions import Annotated

from pydantic import BaseModel, ConfigDict, StringConstraints

Base = declarative_base()


class CompanyOrm(Base):
    __tablename__ = 'companies'

    id = Column(Integer, primary_key=True, nullable=False)
    public_key = Column(String(20), index=True, nullable=False, unique=True)
    name = Column(String(63), unique=True)
    domains = Column(ARRAY(String(255)))


class CompanyModel(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    public_key: Annotated[str, StringConstraints(max_length=20)]
    name: Annotated[str, StringConstraints(max_length=63)]
    domains: List[Annotated[str, StringConstraints(max_length=255)]]


co_orm = CompanyOrm(
    id=123,
    public_key='foobar',
    name='Testing',
    domains=['example.com', 'foobar.com'],
)
print(co_orm)
#> <__main__.CompanyOrm object at 0x0123456789ab>
co_model = CompanyModel.model_validate(co_orm)
print(co_model)
"""
id=123 public_key='foobar' name='Testing' domains=['example.com', 'foobar.com']
"""

Зарезервированные имена

Возможно, вы захотите назвать Column в честь зарезервированного поля SQLAlchemy. В этом случае будут удобны псевдонимы Field :

import typing

import sqlalchemy as sa
from sqlalchemy.orm import declarative_base

from pydantic import BaseModel, ConfigDict, Field


class MyModel(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    metadata: typing.Dict[str, str] = Field(alias='metadata_')


Base = declarative_base()


class SQLModel(Base):
    __tablename__ = 'my_table'
    id = sa.Column('id', sa.Integer, primary_key=True)
    # 'metadata' is reserved by SQLAlchemy, hence the '_'
    metadata_ = sa.Column('metadata', sa.JSON)


sql_model = SQLModel(metadata_={'key': 'val'}, id=1)

pydantic_model = MyModel.model_validate(sql_model)

print(pydantic_model.model_dump())
#> {'metadata': {'key': 'val'}}
print(pydantic_model.model_dump(by_alias=True))
#> {'metadata_': {'key': 'val'}}

!!! note Примечание. Приведенный выше пример работает, поскольку псевдонимы имеют приоритет над именами полей при заполнении полей. Доступ к атрибуту metadata SQLModel приведет к ValidationError .

Вложенные атрибуты

При использовании атрибутов для анализа моделей экземпляры модели будут создаваться как из атрибутов верхнего уровня, так и из атрибутов с более глубокой вложенностью, в зависимости от ситуации.

Вот пример, демонстрирующий принцип:

from typing import List

from pydantic import BaseModel, ConfigDict


class PetCls:
    def __init__(self, *, name: str, species: str):
        self.name = name
        self.species = species


class PersonCls:
    def __init__(self, *, name: str, age: float = None, pets: List[PetCls]):
        self.name = name
        self.age = age
        self.pets = pets


class Pet(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    name: str
    species: str


class Person(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    name: str
    age: float = None
    pets: List[Pet]


bones = PetCls(name='Bones', species='dog')
orion = PetCls(name='Orion', species='cat')
anna = PersonCls(name='Anna', age=20, pets=[bones, orion])
anna_model = Person.model_validate(anna)
print(anna_model)
"""
name='Anna' age=20.0 pets=[Pet(name='Bones', species='dog'), Pet(name='Orion', species='cat')]
"""

Обработка ошибок

Pydantic будет вызывать ValidationError всякий раз, когда обнаруживает ошибку в проверяемых данных.

Независимо от количества найденных ошибок будет вызвано единственное исключение типа ValidationError , и это ValidationError будет содержать информацию обо всех ошибках и о том, как они произошли.

Подробную информацию о стандартных и пользовательских ошибках см. в разделе «Обработка ошибок» .

В качестве демонстрации:

from typing import List

from pydantic import BaseModel, ValidationError


class Model(BaseModel):
    list_of_ints: List[int]
    a_float: float


data = dict(
    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]
    """

Вспомогательные функции

Pydantic предоставляет три вспомогательные функции classmethod для моделей для анализа данных:

  • model_validate(): это очень похоже на метод __init__ модели, за исключением того, что он принимает словарь или объект, а не аргументы ключевого слова. Если переданный объект не может быть проверен или если он не является словарем или экземпляром рассматриваемой модели, будет выдана ValidationError .
  • model_validate_json(): принимает строку или байты и анализирует их как json , затем передает результат в model_validate().
  • model_validate_strings(): принимает словарь (может быть вложенным) со строковыми ключами и значениями и проверяет данные в режиме json , чтобы указанные строки можно было привести к правильным типам.

    from datetime import datetime from typing import Optional

    from pydantic import BaseModel, ValidationError

    class User(BaseModel): id: int name: str = 'John Doe' signup_ts: Optional[datetime] = None

    m = User.model_validate({'id': 123, 'name': 'James'}) print(m)

    > id=123 name='James' signup_ts=None

    try: User.model_validate(['not', 'a', 'dict']) except ValidationError as e: print(e) """ 1 validation error for User Input should be a valid dictionary or instance of User [type=model_type, input_value=['not', 'a', 'dict'], input_type=list] """

    m = User.model_validate_json('{"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] """

    try: m = User.model_validate_json('invalid JSON') except ValidationError as e: print(e) """ 1 validation error for User Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='invalid JSON', input_type=str] """

    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] """

Если вы хотите проверить сериализованные данные в формате, отличном от JSON, вам следует самостоятельно загрузить данные в dict, а затем передать их в model_validate.

!!! note Примечание В зависимости от используемых типов и конфигураций модели model_validate и model_validate_json могут иметь разное поведение при проверке. Если у вас есть данные, поступающие из источника, отличного от JSON, но вы хотите использовать такое же поведение проверки и ошибки, которые вы получили от model_validate_json, наша рекомендация на данный момент — использовать либо использование model_validate_json(json.dumps(data)) или используйте model_validate_strings, если данные принимают форму (потенциально вложенного) словаря со строковыми ключами и значениями.

!!! note Дополнительную информацию о синтаксическом анализе JSON можно найти в разделе документации JSON .

!!! Примечание. Если вы передаете экземпляр модели в model_validate, вам следует рассмотреть возможность установки revalidate_instances в конфигурации модели. Если вы не установите это значение, проверка экземпляров модели будет пропущена. См. пример ниже:

\=== "❌ revalidate_instances='never' " ```py из pydantic import BaseModel

class Model(BaseModel):
    a: int


m = Model(a=0)
# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehavior
m.a = 'not an int'

# doesn't raise a validation error even though m is invalid
m2 = Model.model_validate(m)
```

\=== "✅ revalidate_instances='always' " ```py из pydantic import BaseModel, ConfigDict, ValidationError

class Model(BaseModel):
    a: int

    model_config = ConfigDict(revalidate_instances='always')


m = Model(a=0)
# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehavior
m.a = 'not an int'

try:
    m2 = Model.model_validate(m)
except ValidationError as e:
    print(e)
    """
    1 validation error for Model
    a
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='not an int', input_type=str]
    """
```

Создание моделей без проверки

Pydantic также предоставляет метод model_construct(), который позволяет создавать модели без проверки . Это может быть полезно, по крайней мере, в нескольких случаях:

  • при работе со сложными данными, о которых уже известно, что они действительны (из соображений производительности)
  • когда одна или несколько функций валидатора неидемпотентны, или
  • когда одна или несколько функций валидатора имеют побочные эффекты, которые вы не хотите запускать.

!!! note Примечание. В Pydantic V2 разрыв в производительности между BaseModel.__init__ и BaseModel.model_construct значительно сократился. Для простых моделей вызов BaseModel.__init__ может быть даже быстрее. Если вы используете model_construct() из соображений производительности, возможно, вам захочется профилировать свой вариант использования, прежде чем предполагать, что model_construct() работает быстрее.

!!! предупреждение model_construct() не выполняет никакой проверки, то есть может создавать недопустимые модели. Вам следует использовать метод model_construct() только с данными, которые уже проверены или которым вы определенно доверяете.

from pydantic import BaseModel


class User(BaseModel):
    id: int
    age: int
    name: str = 'John Doe'


original_user = User(id=123, age=32)

user_data = original_user.model_dump()
print(user_data)
#> {'id': 123, 'age': 32, 'name': 'John Doe'}
fields_set = original_user.model_fields_set
print(fields_set)
#> {'age', 'id'}

# ...
# pass user_data and fields_set to RPC or save to the database etc.
# ...

# you can then create a new instance of User without
# re-running validation which would be unnecessary at this point:
new_user = User.model_construct(_fields_set=fields_set, **user_data)
print(repr(new_user))
#> User(id=123, age=32, name='John Doe')
print(new_user.model_fields_set)
#> {'age', 'id'}

# construct can be dangerous, only use it with validated data!:
bad_user = User.model_construct(id='dog')
print(repr(bad_user))
#> User(id='dog', name='John Doe')

Аргумент ключевого слова _fields_set для model_construct() не является обязательным, но позволяет более точно определить, какие поля были изначально установлены, а какие нет. Если он опущен, model_fields_set будут просто ключами предоставленных данных.

Например, в приведенном выше примере, если _fields_set не был указан, new_user.model_fields_set будет {'id', 'age', 'name'} .

Обратите внимание, что для подклассов RootModel корневое значение может быть передано в model_construct() позиционно, вместо использования аргумента ключевого слова.

Вот несколько дополнительных примечаний о поведении model_construct():

  • Когда мы говорим «проверка не выполняется», это включает в себя преобразование диктовок в экземпляры модели. Итак, если у вас есть поле с типом Model вам нужно будет самостоятельно преобразовать внутренний словарь в модель, прежде чем передавать его в model_construct().
    • В частности, метод model_construct() не поддерживает рекурсивное построение моделей из диктовок.
  • Если вы не передаете аргументы ключевого слова для полей со значениями по умолчанию, значения по умолчанию все равно будут использоваться.
  • Для моделей с частными атрибутами dict __pydantic_private__ будет инициализирован так же, как и при вызове __init__ .
  • При создании экземпляра с помощью model_construct() метод __init__ из модели или любого из ее родительских классов вызываться не будет, даже если определен собственный метод __init__ .

!!! примечание «О extra поведении с model_construct » * Для моделей с model_config['extra'] == 'allow' , данные, не соответствующие полям, будут правильно сохранены в __pydantic_extra__ dict и сохранены в __dict__ модели. * Для моделей с model_config['extra'] == 'ignore' , данные, не соответствующие полям, будут игнорироваться, то есть не будут храниться в __pydantic_extra__ или __dict__ экземпляра. * В отличие от вызова __init__ , вызов model_construct с model_config['extra'] == 'forbid' не выдает ошибку при наличии данных, не соответствующих полям. Скорее, указанные входные данные просто игнорируются.

Общие модели

Pydantic поддерживает создание универсальных моделей, упрощающих повторное использование общей структуры модели.

Чтобы объявить базовую модель, необходимо выполнить следующие шаги:

  1. Объявите один или несколько экземпляров typing.TypeVar , которые будут использоваться для параметризации вашей модели.
  2. Объявите модель pydantic, которая наследуется от pydantic.BaseModel и typing.Generic , где вы передаете экземпляры TypeVar в качестве параметров в typing.Generic .
  3. Используйте экземпляры TypeVar в качестве аннотаций, где вы захотите заменить их другими типами или моделями pydantic.

Вот пример использования универсального подкласса BaseModel для создания легко повторно используемой оболочки полезных данных HTTP-ответа:

from typing import Generic, List, Optional, TypeVar

from pydantic import BaseModel, ValidationError

DataT = TypeVar('DataT')


class DataModel(BaseModel):
    numbers: List[int]
    people: List[str]


class Response(BaseModel, Generic[DataT]):
    data: Optional[DataT] = None


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(numbers=[1, 2, 3], people=[])
print(Response[DataModel](data=data).model_dump())
#> {'data': {'numbers': [1, 2, 3], 'people': []}}
try:
    Response[int](data='value')
except ValidationError as e:
    print(e)
    """
    1 validation error for Response[int]
    data
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='value', input_type=str]
    """

Если вы установите model_config или используете @field_validator или другие декораторы Pydantic в определении общей модели, они будут применяться к параметризованным подклассам так же, как при наследовании от подкласса BaseModel . Любые методы, определенные в вашем универсальном классе, также будут унаследованы.

Обобщенные шаблоны Pydantic также правильно интегрируются с средствами проверки типов, поэтому вы получаете всю проверку типов, которую можно было бы ожидать, если бы вы объявляли отдельный тип для каждой параметризации.

!!! note Примечание Внутренне Pydantic создает подклассы BaseModel во время выполнения, когда параметризуются универсальные модели. Эти классы кэшируются, поэтому накладные расходы, связанные с использованием обобщенных моделей, должны быть минимальными.

Чтобы наследовать универсальную модель и сохранить тот факт, что она является универсальной, подкласс также должен наследовать от typing.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]):
    # Inherit from Generic[TypeX]
    pass


# Replace TypeX by int
print(ChildClass[int](X=1))
#> X=1

Вы также можете создать общий подкласс BaseModel , который частично или полностью заменяет параметры типа в суперклассе:

from typing import Generic, TypeVar

from pydantic import BaseModel

TypeX = TypeVar('TypeX')
TypeY = TypeVar('TypeY')
TypeZ = TypeVar('TypeZ')


class BaseClass(BaseModel, Generic[TypeX, TypeY]):
    x: TypeX
    y: TypeY


class ChildClass(BaseClass[int, TypeY], Generic[TypeY, TypeZ]):
    z: TypeZ


# Replace TypeY by str
print(ChildClass[str, int](x='1', y='y', z='3'))
#> x=1 y='y' z=3

Если имя конкретного подкласса важно, вы также можете переопределить генерацию имени по умолчанию:

from typing import Any, Generic, Tuple, Type, TypeVar

from pydantic import BaseModel

DataT = TypeVar('DataT')


class Response(BaseModel, Generic[DataT]):
    data: DataT

    @classmethod
    def model_parametrized_name(cls, params: Tuple[Type[Any], ...]) -> str:
        return f'{params[0].__name__.title()}Response'


print(repr(Response[int](data=1)))
#> IntResponse(data=1)
print(repr(Response[str](data='a')))
#> StrResponse(data='a')

Вы можете использовать параметризованные универсальные модели в качестве типов в других моделях:

from typing import Generic, TypeVar

from pydantic import BaseModel

T = TypeVar('T')


class ResponseModel(BaseModel, Generic[T]):
    content: T


class Product(BaseModel):
    name: str
    price: float


class Order(BaseModel):
    id: int
    product: ResponseModel[Product]


product = Product(name='Apple', price=0.5)
response = ResponseModel[Product](content=product)
order = Order(id=1, product=response)
print(repr(order))
"""
Order(id=1, product=ResponseModel[Product](content=Product(name='Apple', price=0.5)))
"""

!!! Совет. При использовании параметризованной универсальной модели в качестве типа в другой модели (например, product: ResponseModel[Product] ), обязательно параметризуйте указанную общую модель при инициализации экземпляра модели (например, response = ResponseModel[Product](content=product) ). Если вы этого не сделаете, возникнет ошибка ValidationError , поскольку Pydantic не определяет тип универсальной модели на основе переданных в нее данных.

Использование одной и той же TypeVar во вложенных моделях позволяет вам обеспечить соблюдение отношений типизации в разных точках вашей модели:

from typing import Generic, TypeVar

from pydantic import BaseModel, ValidationError

T = TypeVar('T')


class InnerT(BaseModel, Generic[T]):
    inner: T


class OuterT(BaseModel, Generic[T]):
    outer: T
    nested: InnerT[T]


nested = InnerT[int](inner=1)
print(OuterT[int](outer=1, nested=nested))
#> outer=1 nested=InnerT[int](inner=1)
try:
    nested = InnerT[str](inner='a')
    print(OuterT[int](outer='a', nested=nested))
except ValidationError as e:
    print(e)
    """
    2 validation errors for OuterT[int]
    outer
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    nested
      Input should be a valid dictionary or instance of InnerT[int] [type=model_type, input_value=InnerT[str](inner='a'), input_type=InnerT[str]]
    """

При использовании параметров связанного типа и оставлении параметров типа неуказанными Pydantic обрабатывает универсальные модели аналогично тому, как он обрабатывает встроенные универсальные типы, такие как List и Dict :

  • Если вы не укажете параметры перед созданием экземпляра универсальной модели, они проверяются как граница TypeVar .
  • Если задействованные TypeVar не имеют границ, они обрабатываются как Any .

Кроме того, как и в случае с List и Dict , любые параметры, указанные с помощью TypeVar позже можно заменить конкретными типами:

from typing import Generic, TypeVar

from pydantic import BaseModel, ValidationError

AT = TypeVar('AT')
BT = TypeVar('BT')


class Model(BaseModel, Generic[AT, BT]):
    a: AT
    b: BT


print(Model(a='a', b='a'))
#> a='a' b='a'

IntT = TypeVar('IntT', bound=int)
typevar_model = Model[int, IntT]
print(typevar_model(a=1, b=1))
#> a=1 b=1
try:
    typevar_model(a='a', b='a')
except ValidationError as exc:
    print(exc)
    """
    2 validation errors for Model[int, TypeVar]
    a
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    b
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    """

concrete_model = typevar_model[int]
print(concrete_model(a=1, b=1))
#> a=1 b=1

!!! предупреждение Хотя это может и не вызвать ошибку, мы настоятельно не рекомендуем использовать параметризованные универсальные шаблоны в проверках isinstance.

For example, you should not do `isinstance(my_model, MyGenericModel[int])`. However, it is fine to do `isinstance(my_model, MyGenericModel)`. (Note that, for standard generics, it would raise an error to do a subclass check with a parameterized generic.)

If you need to perform isinstance checks against parametrized generics, you can do this by subclassing the parametrized generic class. This looks like `class MyIntModel(MyGenericModel[int]): ...` and `isinstance(my_model, MyIntModel)`.

Если модель Pydantic используется в привязке TypeVar и универсальный тип никогда не параметризуется, тогда Pydantic будет использовать привязку для проверки, но рассматривать значение как Any с точки зрения сериализации:

from typing import Generic, Optional, TypeVar

from pydantic import BaseModel


class ErrorDetails(BaseModel):
    foo: str


ErrorDataT = TypeVar('ErrorDataT', bound=ErrorDetails)


class Error(BaseModel, Generic[ErrorDataT]):
    message: str
    details: Optional[ErrorDataT]


class MyErrorDetails(ErrorDetails):
    bar: str


# serialized as Any
error = Error(
    message='We just had an error',
    details=MyErrorDetails(foo='var', bar='var2'),
)
assert error.model_dump() == {
    'message': 'We just had an error',
    'details': {
        'foo': 'var',
        'bar': 'var2',
    },
}

# serialized using the concrete parametrization
# note that `'bar': 'var2'` is missing
error = Error[ErrorDetails](
    message='We just had an error',
    details=ErrorDetails(foo='var'),
)
assert error.model_dump() == {
    'message': 'We just had an error',
    'details': {
        'foo': 'var',
    },
}

Вот еще один пример описанного выше поведения, перечисляющий все перестановки, касающиеся связанной спецификации и параметризации универсального типа:

from typing import Generic

from typing_extensions import TypeVar

from pydantic import BaseModel

TBound = TypeVar('TBound', bound=BaseModel)
TNoBound = TypeVar('TNoBound')


class IntValue(BaseModel):
    value: int


class ItemBound(BaseModel, Generic[TBound]):
    item: TBound


class ItemNoBound(BaseModel, Generic[TNoBound]):
    item: TNoBound


item_bound_inferred = ItemBound(item=IntValue(value=3))
item_bound_explicit = ItemBound[IntValue](item=IntValue(value=3))
item_no_bound_inferred = ItemNoBound(item=IntValue(value=3))
item_no_bound_explicit = ItemNoBound[IntValue](item=IntValue(value=3))

# calling `print(x.model_dump())` on any of the above instances results in the following:
#> {'item': {'value': 3}}

Если вы используете default=... (доступно в Python >= 3.13 или через typing-extensions ) или ограничения ( TypeVar('T', str, int) ; обратите внимание, что вы редко хотите использовать эту форму TypeVar ), тогда значение или ограничения по умолчанию будут использоваться как для проверки, так и для сериализации, если переменная типа не параметризована. Вы можете переопределить это поведение, используя pydantic.SerializeAsAny :

from typing import Generic, Optional

from typing_extensions import TypeVar

from pydantic import BaseModel, SerializeAsAny


class ErrorDetails(BaseModel):
    foo: str


ErrorDataT = TypeVar('ErrorDataT', default=ErrorDetails)


class Error(BaseModel, Generic[ErrorDataT]):
    message: str
    details: Optional[ErrorDataT]


class MyErrorDetails(ErrorDetails):
    bar: str


# serialized using the default's serializer
error = Error(
    message='We just had an error',
    details=MyErrorDetails(foo='var', bar='var2'),
)
assert error.model_dump() == {
    'message': 'We just had an error',
    'details': {
        'foo': 'var',
    },
}


class SerializeAsAnyError(BaseModel, Generic[ErrorDataT]):
    message: str
    details: Optional[SerializeAsAny[ErrorDataT]]


# serialized as Any
error = SerializeAsAnyError(
    message='We just had an error',
    details=MyErrorDetails(foo='var', bar='baz'),
)
assert error.model_dump() == {
    'message': 'We just had an error',
    'details': {
        'foo': 'var',
        'bar': 'baz',
    },
}

!!! note Обратите внимание, что у вас могут возникнуть некоторые проблемы, если вы не параметризуете универсальный шаблон, поскольку проверка на соответствие привязке универсального шаблона может привести к потере данных. См. пример ниже:

from typing import Generic

from typing_extensions import TypeVar

from pydantic import BaseModel

TItem = TypeVar('TItem', bound='ItemBase')


class ItemBase(BaseModel): ...


class IntItem(ItemBase):
    value: int


class ItemHolder(BaseModel, Generic[TItem]):
    item: TItem


loaded_data = {'item': {'value': 1}}


print(ItemHolder(**loaded_data).model_dump())  # (1)!
#> {'item': {}}

print(ItemHolder[IntItem](**loaded_data).model_dump())  # (2)!
#> {'item': {'value': 1}}
  1. Если универсальный параметр не параметризован, входные данные проверяются на соответствие универсальной границе. Поскольку в ItemBase нет полей, информация о полях item теряется.
  2. В этом случае информация о типе среды выполнения предоставляется явно через общую параметризацию, поэтому входные данные проверяются на соответствие классу IntItem , а выходные данные сериализации соответствуют ожидаемым.

Создание динамической модели

??? API "Документация по API" pydantic.main.create_model

В некоторых случаях желательно создать модель, используя информацию времени выполнения для указания полей. Для этого Pydantic предоставляет функцию create_model , позволяющую создавать модели «на лету»:

from pydantic import BaseModel, create_model

DynamicFoobarModel = create_model(
    'DynamicFoobarModel', foo=(str, ...), bar=(int, 123)
)


class StaticFoobarModel(BaseModel):
    foo: str
    bar: int = 123

Здесь StaticFoobarModel и DynamicFoobarModel идентичны.

Поля определяются одной из следующих форм кортежа:

  • (<type>, <default value>)
  • (<type>, Field(...))
  • typing.Annotated[<type>, Field(...)]

Использование вызова Field(...) в качестве второго аргумента в кортеже (значение по умолчанию) позволяет выполнить более сложную настройку поля. Таким образом, аналогичны:

from pydantic import BaseModel, Field, create_model

DynamicModel = create_model(
    'DynamicModel',
    foo=(str, Field(..., description='foo description', alias='FOO')),
)


class StaticModel(BaseModel):
    foo: str = Field(..., description='foo description', alias='FOO')

Специальные аргументы ключевых слов __config__ и __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)
#> <class '__main__.BarModel'>
print(BarModel.model_fields.keys())
#> dict_keys(['foo', 'bar', 'apple', 'banana'])

Вы также можете добавить валидаторы, передав словарь в аргумент __validators__ .

from pydantic import ValidationError, create_model, field_validator


def username_alphanumeric(cls, v):
    assert v.isalnum(), 'must be alphanumeric'
    return v


validators = {
    'username_validator': field_validator('username')(username_alphanumeric)
}

UserModel = create_model(
    'UserModel', username=(str, ...), __validators__=validators
)

user = UserModel(username='scolvin')
print(user)
#> username='scolvin'

try:
    UserModel(username='scolvi%n')
except ValidationError as e:
    print(e)
    """
    1 validation error for UserModel
    username
      Assertion failed, must be alphanumeric [type=assertion_error, input_value='scolvi%n', input_type=str]
    """

!!! note Чтобы сохранить динамически созданную модель:

- the model must be defined globally
- it must provide `__module__`

RootModel и пользовательские корневые типы

??? API "Документация по API" pydantic.root_model.RootModel

Модели Pydantic можно определить с помощью «пользовательского корневого типа» путем создания подкласса pydantic.RootModel.

Корневым типом может быть любой тип, поддерживаемый Pydantic, и он указывается универсальным параметром RootModel . Корневое значение можно передать модели __init__ или model_validate через первый и единственный аргумент.

Вот пример того, как это работает:

from typing import Dict, List

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']))
#> root=['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({'Otis': 'dog', 'Milo': 'cat'}).model_dump_json())
#> {"Otis":"dog","Milo":"cat"}
print(PetsByName.model_validate({'Otis': 'dog', 'Milo': 'cat'}))
#> root={'Otis': 'dog', 'Milo': 'cat'}

Если вы хотите напрямую обращаться к элементам в root поле или перебирать их, вы можете реализовать собственные функции __iter__ и __getitem__ , как показано в следующем примере.

from typing import List

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']

Вы также можете напрямую создавать подклассы параметризованной корневой модели:

from typing import List

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

Искусственная неизменность

Модели можно настроить как неизменяемые с помощью model_config['frozen'] = True . Если этот параметр установлен, попытка изменить значения атрибутов экземпляра приведет к ошибкам. Дополнительные сведения см. в справке по API.

!!! Примечание. Такое поведение было достигнуто в Pydantic V1 с помощью параметра allow_mutation = False . Этот флаг конфигурации устарел в Pydantic V2 и заменен на frozen .

!!! предупреждение. В Python неизменяемость не обеспечивается. Разработчики имеют возможность изменять объекты, которые традиционно считаются «неизменяемыми», если они захотят это сделать.

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)
    """
    1 validation error for FooBarModel
    a
      Instance is frozen [type=frozen_instance, input_value='different', input_type=str]
    """

print(foobar.a)
#> hello
print(foobar.b)
#> {'apple': 'pear'}
foobar.b['apple'] = 'grape'
print(foobar.b)
#> {'apple': 'grape'}

Попытка изменить a привела к ошибке, и a остается неизменной. Однако dict b является изменчивым, и неизменность foobar не препятствует изменению b .

Абстрактные базовые классы

Модели Pydantic можно использовать вместе с абстрактными базовыми классами Python (ABC).

import abc

from pydantic import BaseModel


class FooBarModel(BaseModel, abc.ABC):
    a: str
    b: int

    @abc.abstractmethod
    def my_abstract_method(self):
        pass

Порядок полей

Порядок полей влияет на модели следующим образом:

  • порядок полей сохраняется в схеме модели
  • порядок полей сохраняется при ошибках проверки
  • порядок полей сохраняется с помощью .model_dump() и .model_dump_json() и т.д.

    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())

    >

    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',)]

Обязательные поля

Чтобы объявить поле необходимым, вы можете объявить его с помощью аннотации или аннотации в сочетании со спецификацией Field . Вы также можете использовать Ellipsis / ... , чтобы подчеркнуть, что поле является обязательным, особенно при использовании конструктора Field .

Функция Field в основном используется для настройки таких параметров, как alias или description атрибута. Конструктор поддерживает Ellipsis / ... в качестве единственного позиционного аргумента. Это используется как способ указать, что указанное поле является обязательным, хотя это требование обеспечивается подсказкой типа.

from pydantic import BaseModel, Field


class Model(BaseModel):
    a: int
    b: int = ...
    c: int = Field(..., alias='C')

Здесь необходимы a , b и c . Однако такое использование b: int = ... не работает должным образом с mypy , и начиная с версии 1.0 его следует избегать в большинстве случаев.

!!! Примечание. В Pydantic V1 полям, помеченным Optional или Any будет неявно присвоено значение по умолчанию None , даже если явно не указано значение по умолчанию. Это поведение изменилось в Pydantic V2, и больше нет аннотаций типов, которые приводили бы к тому, что поле имело бы неявное значение по умолчанию.

See [the migration guide](../migration.md#required-optional-and-nullable-fields) for more details on changes
to required and nullable fields.

Поля с нехешируемыми значениями по умолчанию

Распространенным источником ошибок в Python является использование изменяемого объекта в качестве значения по умолчанию для аргумента функции или метода, поскольку один и тот же экземпляр в конечном итоге используется повторно при каждом вызове.

В этом случае модуль dataclasses фактически выдает ошибку, указывая на то, что вам следует использовать аргумент default_factory для dataclasses.field .

Pydantic также поддерживает использование default_factory для нехэшируемых значений по умолчанию, но это не обязательно. Если значение по умолчанию не является хешируемым, Pydantic будет глубоко копировать значение по умолчанию при создании каждого экземпляра модели:

from typing import Dict, List

from pydantic import BaseModel


class Model(BaseModel):
    item_counts: List[Dict[str, int]] = [{}]


m1 = Model()
m1.item_counts[0]['a'] = 1
print(m1.item_counts)
#> [{'a': 1}]

m2 = Model()
print(m2.item_counts)
#> [{}]

Поля с динамическими значениями по умолчанию

При объявлении поля со значением по умолчанию вы можете захотеть, чтобы оно было динамическим (т. е. различным для каждой модели). Для этого вы можете использовать default_factory .

Вот пример:

from datetime import datetime, timezone
from uuid import UUID, uuid4

from pydantic import BaseModel, Field


def datetime_now() -> datetime:
    return datetime.now(timezone.utc)


class Model(BaseModel):
    uid: UUID = Field(default_factory=uuid4)
    updated: datetime = Field(default_factory=datetime_now)


m1 = Model()
m2 = Model()
assert m1.uid != m2.uid

Более подробную информацию вы можете найти в документации функции Field .

Автоматически исключенные атрибуты

Классовые переменные

Атрибуты, аннотированные с помощью typing.ClassVar , правильно обрабатываются Pydantic как переменные класса и не становятся полями в экземплярах модели:

from typing import ClassVar

from pydantic import BaseModel


class Model(BaseModel):
    x: int = 2
    y: ClassVar[int] = 1


m = Model()
print(m)
#> x=2
print(Model.y)
#> 1

Атрибуты частной модели

??? API "Документация по API" pydantic.fields.PrivateAttr

Атрибуты, имя которых начинается с подчеркивания, не рассматриваются Pydantic как поля и не включаются в схему модели. Вместо этого они преобразуются в «частный атрибут», который не проверяется и даже не устанавливается во время вызовов __init__ , model_validate и т. д.

!!! note Примечание. Начиная с Pydantic версии 2.1.0, вы получите ошибку NameError при попытке использовать функцию Field с частным атрибутом. Поскольку частные атрибуты не рассматриваются как поля, функцию Field() применить невозможно.

Вот пример использования:

from datetime import datetime
from random import randint

from pydantic import BaseModel, PrivateAttr


class TimeAwareModel(BaseModel):
    _processed_at: datetime = PrivateAttr(default_factory=datetime.now)
    _secret_value: str

    def __init__(self, **data):
        super().__init__(**data)
        # this could also be done with default_factory
        self._secret_value = randint(1, 5)


m = TimeAwareModel()
print(m._processed_at)
#> 2032-01-02 03:04:05.000006
print(m._secret_value)
#> 3

Имена частных атрибутов должны начинаться с подчеркивания, чтобы избежать конфликтов с полями модели. Однако имена dunder (например, __attr__ ) не поддерживаются.

Преобразование данных

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, и зачастую это наиболее полезный подход. См. здесь более подробное обсуждение этой темы.

Тем не менее, строгая проверка типов также поддерживается.

Подпись модели

Все модели Pydantic будут иметь подпись, созданную на основе их полей:

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

Точная подпись полезна для целей самоанализа и таких библиотек, как FastAPI или hypothesis .

Сгенерированная подпись также будет учитывать пользовательские функции __init__ :

import inspect

from pydantic import BaseModel


class MyModel(BaseModel):
    id: int
    info: str = 'Foo'

    def __init__(self, id: int = 1, *, bar: str, **data) -> None:
        """My custom init!"""
        super().__init__(id=id, bar=bar, **data)


print(inspect.signature(MyModel))
#> (id: int = 1, *, bar: str, info: str = 'Foo') -> None

Чтобы быть включенным в подпись, псевдоним или имя поля должно быть допустимым идентификатором Python. Pydantic будет отдавать приоритет псевдониму поля над его именем при создании подписи, но может использовать имя поля, если псевдоним не является допустимым идентификатором Python.

Если псевдоним и имя поля не являются допустимыми идентификаторами (что может быть возможно благодаря экзотическому использованию create_model ), будет добавлен аргумент **data . Кроме того, аргумент **data всегда будет присутствовать в подписи, если model_config['extra'] == 'allow' .

Структурное сопоставление с образцом

Pydantic поддерживает сопоставление структурных шаблонов для моделей, как это представлено в PEP 636 в Python 3.10.

from pydantic import BaseModel


class Pet(BaseModel):
    name: str
    species: str


a = Pet(name='Bones', species='dog')

match a:
    # match `species` to 'dog', declare and initialize `dog_name`
    case Pet(species='dog', name=dog_name):
        print(f'{dog_name} is a dog')
#> Bones is a dog
    # default case
    case _:
        print('No dog matched')

!!! note Примечание. Может показаться, что оператор match-case создает новую модель, но не дайте себя обмануть; это просто синтаксический сахар для получения атрибута и его сравнения или объявления и инициализации.

Копии атрибутов

Во многих случаях аргументы, передаваемые конструктору, копируются для выполнения проверки и, при необходимости, приведения.

Обратите внимание, что в этом примере идентификатор списка изменяется после создания класса, поскольку он был скопирован во время проверки:

from typing import List

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('id(c1.arr) == id(c2.arr):', id(c1.arr) == id(c2.arr))
#> id(c1.arr) == id(c2.arr): False

!!! note Примечание. В некоторых ситуациях Pydantic не копирует атрибуты, например при передаче моделей — мы используем модель как есть. Вы можете переопределить это поведение, установив model_config['revalidate_instances'] = 'always' .

Дополнительные поля

По умолчанию модели Pydantic не выдают ошибок при предоставлении данных для нераспознанных полей, они просто игнорируются:

from pydantic import BaseModel


class Model(BaseModel):
    x: int


m = Model(x=1, y='a')
assert m.model_dump() == {'x': 1}

Если вы хотите, чтобы это вызывало ошибку, вы можете добиться этого с помощью model_config :

from pydantic import BaseModel, ConfigDict, ValidationError


class Model(BaseModel):
    x: int

    model_config = ConfigDict(extra='forbid')


try:
    Model(x=1, y='a')
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Model
    y
      Extra inputs are not permitted [type=extra_forbidden, input_value='a', input_type=str]
    """

Чтобы вместо этого сохранить любые предоставленные дополнительные данные, вы можете установить extra='allow' . Дополнительные поля будут сохранены в BaseModel.__pydantic_extra__ :

from pydantic import BaseModel, ConfigDict


class Model(BaseModel):
    x: int

    model_config = ConfigDict(extra='allow')


m = Model(x=1, y='a')
assert m.__pydantic_extra__ == {'y': 'a'}

По умолчанию к этим дополнительным элементам проверка не применяется, но вы можете установить тип значений, переопределив аннотацию типа для __pydantic_extra__ :

from typing import Dict

from pydantic import BaseModel, ConfigDict, Field, ValidationError


class Model(BaseModel):
    __pydantic_extra__: Dict[str, int] = Field(init=False)  # (1)!

    x: int

    model_config = ConfigDict(extra='allow')


try:
    Model(x=1, y='a')
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Model
    y
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    """

m = Model(x=1, y='2')
assert m.x == 1
assert m.y == 2
assert m.model_dump() == {'x': 1, 'y': 2}
assert m.__pydantic_extra__ == {'y': 2}
  1. = Field(init=False) не оказывает никакого эффекта во время выполнения, но предотвращает обработку поля __pydantic_extra__ в качестве аргумента метода __init__ модели средствами проверки типов.

Те же конфигурации применяются к TypedDict и dataclass ', за исключением того, что конфигурация контролируется путем установки атрибута __pydantic_config__ класса в допустимый ConfigDict .


本文总阅读量