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

Mypy

Pydantic хорошо работает с mypy прямо из коробки.

Однако Pydantic также поставляется с плагином mypy, который добавляет в mypy ряд важных функций, специфичных для pydantic, которые улучшают его способность проверять тип вашего кода.

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

from datetime import datetime
from typing import List, Optional

from pydantic import BaseModel


class Model(BaseModel):
    age: int
    first_name = 'John'
    last_name: Optional[str] = None
    signup_ts: Optional[datetime] = None
    list_of_ints: List[int]


m = Model(age=42, list_of_ints=[1, '2', b'3'])
print(m.middle_name)  # not a model field!
Model()  # will raise a validation error for age and list_of_ints

Без какой-либо специальной настройки mypy не улавливает отсутствующую аннотацию поля модели и предупреждает об аргументе list_of_ints , который Pydantic правильно анализирует:

test.py:15: error: List item 1 has incompatible type "str"; expected "int"  [list-item]
test.py:15: error: List item 2 has incompatible type "bytes"; expected "int"  [list-item]
test.py:16: error: "Model" has no attribute "middle_name"  [attr-defined]
test.py:17: error: Missing named argument "age" for "Model"  [call-arg]
test.py:17: error: Missing named argument "list_of_ints" for "Model"  [call-arg]

Но при включенном плагине выдает правильную ошибку:

9: error: Untyped fields disallowed  [pydantic-field]
16: error: "Model" has no attribute "middle_name"  [attr-defined]
17: error: Missing named argument "age" for "Model"  [call-arg]
17: error: Missing named argument "list_of_ints" for "Model"  [call-arg]

С помощью плагина pydantic mypy вы можете безбоязненно реорганизовать свои модели, зная, что mypy обнаружит любые ошибки, если имена или типы ваших полей изменятся.

Есть и другие преимущества! Более подробную информацию смотрите ниже.

Использование mypy без плагина

Вы можете запустить свой код через mypy с помощью:

mypy \
  --ignore-missing-imports \
  --follow-imports=skip \
  --strict-optional \
  pydantic_mypy_test.py

Строгий Факультативный

Чтобы ваш код прошел с --strict-optional , вам необходимо использовать Optional[] или псевдоним Optional[] для всех полей с None по умолчанию. (Это стандартно для mypy.)

Другие интерфейсы Pydantic

Классы данных Pydantic и декоратор validate_call также должны хорошо работать с mypy.

Возможности плагина Mypy

Создайте подпись для Model.__init__

  • Любые обязательные поля, не имеющие динамически определенных псевдонимов, будут включены в качестве обязательных аргументов ключевого слова.
  • Если Config.populate_by_name=True , сгенерированная подпись будет использовать имена полей, а не псевдонимы.
  • Если Config.extra='forbid' и вы не используете динамически определяемые псевдонимы, сгенерированная подпись не допустит неожиданных входных данных.
  • Необязательно: если для параметра плагина init_forbid_extra установлено значение True , неожиданные входные данные в __init__ вызовут ошибки, даже если Config.extra не имеет значения 'forbid' .
  • Необязательно: если для параметра плагина init_typed установлено значение True , сгенерированная подпись будет использовать типы полей модели (в противном случае они будут помечены как Any чтобы разрешить анализ).

Создайте типизированную подпись для Model.model_construct

  • Метод model_construct является альтернативой __init__ , когда известно, что входные данные действительны и не должны анализироваться. Поскольку этот метод не выполняет проверку во время выполнения, для обнаружения ошибок важна статическая проверка.

Соблюдайте Config.frozen

  • Если Config.frozen имеет True , вы получите ошибку mypy, если попытаетесь изменить значение поля модели; ср. ложная неизменяемость .

Создать подпись для dataclasses

  • классы, украшенные @pydantic.dataclasses.dataclass проверяются ли типы так же, как и стандартные классы данных Python
  • @pydantic.dataclasses.dataclass Декоратор принимает аргумент ключевого слова config , который имеет то же значение, что и подкласс Config .

Уважайте тип Field default и default_factory

  • Поле со default и default_factory приведет к ошибке во время статической проверки.
  • Тип значения default и default_factory должен быть совместим с типом поля.

Предупреждать об использовании нетипизированных полей

  • Вы получите сообщение об ошибке mypy каждый раз, когда назначаете общедоступный атрибут модели без аннотации его типа.
  • Если ваша цель — установить ClassVar, вам следует явно аннотировать поле, используя typing.ClassVar.

Дополнительные возможности:

Запретить использование необходимых динамических псевдонимов

  • Если для параметра плагина warn_required_dynamic_aliases установлено значение True , вы будете получать ошибку mypy каждый раз, когда используете динамически определяемый псевдоним или генератор псевдонимов в модели с Config.populate_by_name=False .
  • Это важно, поскольку если такие псевдонимы присутствуют, mypy не сможет правильно набирать проверочные вызовы __init__ . В этом случае по умолчанию все аргументы будут рассматриваться как необязательные.

Включение плагина

Чтобы включить плагин, просто добавьте pydantic.mypy в список плагинов в файле конфигурации mypy (это может быть mypy.ini , pyproject.toml или setup.cfg ).

Для начала все, что вам нужно сделать, это создать файл mypy.ini со следующим содержимым:

[mypy]
plugins = pydantic.mypy

!!! Примечание. Если вы используете модели pydantic.v1 , вам необходимо добавить pydantic.v1.mypy в список плагинов.

Плагин совместим с версиями mypy >=0.930 .

Дополнительную информацию см. в документации по настройке плагина .

Настройка плагина

Чтобы изменить значения настроек плагина, создайте в файле конфигурации mypy раздел под названием [pydantic-mypy] и добавьте любые пары ключ-значение для настроек, которые вы хотите переопределить.

Файл mypy.ini со всеми включенными флагами строгости плагина (а также некоторыми другими флагами строгости mypy) может выглядеть так:

[mypy]
plugins = pydantic.mypy

follow_imports = silent
warn_redundant_casts = True
warn_unused_ignores = True
disallow_any_generics = True
check_untyped_defs = True
no_implicit_reexport = True

# for strict mypy: (this is the tricky one :-))
disallow_untyped_defs = True

[pydantic-mypy]
init_forbid_extra = True
init_typed = True
warn_required_dynamic_aliases = True

Начиная с mypy>=0.900 , конфигурация mypy также может быть включена в файл pyproject.toml а не в mypy.ini . Та же конфигурация, что и выше, будет:

[tool.mypy]
plugins = [
  "pydantic.mypy"
]

follow_imports = "silent"
warn_redundant_casts = true
warn_unused_ignores = true
disallow_any_generics = true
check_untyped_defs = true
no_implicit_reexport = true

# for strict mypy: (this is the tricky one :-))
disallow_untyped_defs = true

[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true

Примечание по --disallow-any-explicit

Если вы используете параметр конфигурации mypy --disallow-any-explicit (или другие параметры, запрещающие Any ), вы можете столкнуться с ошибками no-any-explicit при расширении BaseModel . Это связано с тем, что по умолчанию плагин mypy от Pydantic добавляет метод __init__ с сигнатурой типа def __init__(self, field_1: Any, field_2: Any, **kwargs: Any):

!!! Примечание «Зачем лишняя подпись?» Плагин Pydantic mypy добавляет метод __init__ с сигнатурой типа def __init__(self, field_1: Any, field_2: Any, **kwargs: Any): во избежание ошибок типов при инициализации моделей с типами, не соответствующими аннотациям полей. Например, Model(date='2024-01-01') вызовет ошибку типа без этой подписи Any , но Pydantic имеет возможность анализировать строку '2024-01-01' в тип datetime.date .

Чтобы решить эту проблему, вам необходимо включить настройки строгого режима для плагина Pydantic mypy. В частности, добавьте эти параметры в раздел [pydantic-mypy] :

[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true

Если init_forbid_extra = True **kwargs удаляются из сгенерированной подписи __init__ . При init_typed = True Any типы полей заменяются фактическими подсказками типов.

Эта конфигурация позволяет вам использовать --disallow-any-explicit без возникновения ошибок в ваших моделях Pydantic. Однако имейте в виду, что эта более строгая проверка может пометить некоторые допустимые варианты использования Pydantic (например, передачу строки для поля даты и времени) как ошибки типа.


本文总阅读量