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

Строгий режим

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

По умолчанию Pydantic попытается привести значения к желаемому типу, если это возможно. Например, вы можете передать строку "123" в качестве входных данных в поле int , и она будет преобразована в 123 . Такое поведение принуждения полезно во многих сценариях — например: UUID, параметры URL, заголовки HTTP, переменные среды, пользовательский ввод и т. д.

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

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

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

from pydantic import BaseModel, ValidationError


class MyModel(BaseModel):
    x: int


print(MyModel.model_validate({'x': '123'}))  # lax mode
#> x=123

try:
    MyModel.model_validate({'x': '123'}, strict=True)  # strict mode
except ValidationError as exc:
    print(exc)
    """
    1 validation error for MyModel
    x
      Input should be a valid integer [type=int_type, input_value='123', input_type=str]
    """

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

Приведение типов в строгом режиме

Для большинства типов при проверке данных из Python в строгом режиме принимаются только экземпляры конкретных типов. Например, при проверке поля int принимаются только экземпляры int ; передача экземпляров float или str приведет к возникновению ValidationError .

Обратите внимание, что мы более свободны при проверке данных из JSON в строгом режиме. Например, при проверке поля UUID экземпляры str будут приниматься при проверке из JSON, но не из Python:

import json
from uuid import UUID

from pydantic import BaseModel, ValidationError


class MyModel(BaseModel):
    guid: UUID


data = {'guid': '12345678-1234-1234-1234-123456789012'}

print(MyModel.model_validate(data))  # OK: lax
#> guid=UUID('12345678-1234-1234-1234-123456789012')

print(
    MyModel.model_validate_json(json.dumps(data), strict=True)
)  # OK: strict, but from json
#> guid=UUID('12345678-1234-1234-1234-123456789012')

try:
    MyModel.model_validate(data, strict=True)  # Not OK: strict, from python
except ValidationError as exc:
    print(exc.errors(include_url=False))
    """
    [
        {
            'type': 'is_instance_of',
            'loc': ('guid',),
            'msg': 'Input should be an instance of UUID',
            'input': '12345678-1234-1234-1234-123456789012',
            'ctx': {'class': 'UUID'},
        }
    ]
    """

Для получения более подробной информации о том, какие типы разрешены в качестве входных данных в строгом режиме, вы можете просмотреть Таблицу преобразования .

Строгий режим при вызове методов

Все включенные до сих пор примеры проходят проверку в строгом режиме за счет использования strict=True в качестве аргумента ключевого слова для методов проверки. Хотя мы показали это для BaseModel.model_validate , это также работает с произвольными типами благодаря использованию TypeAdapter :

from pydantic import TypeAdapter, ValidationError

print(TypeAdapter(bool).validate_python('yes'))  # OK: lax
#> True

try:
    TypeAdapter(bool).validate_python('yes', strict=True)  # Not OK: strict
except ValidationError as exc:
    print(exc)
    """
    1 validation error for bool
      Input should be a valid boolean [type=bool_type, input_value='yes', input_type=str]
    """

Обратите внимание, что это также работает даже при использовании более «сложных» типов в TypeAdapter :

from dataclasses import dataclass

from pydantic import TypeAdapter, ValidationError


@dataclass
class MyDataclass:
    x: int


try:
    TypeAdapter(MyDataclass).validate_python({'x': '123'}, strict=True)
except ValidationError as exc:
    print(exc)
    """
    1 validation error for MyDataclass
      Input should be an instance of MyDataclass [type=dataclass_exact_type, input_value={'x': '123'}, input_type=dict]
    """

Это также работает с методами TypeAdapter.validate_json и BaseModel.model_validate_json :

import json
from typing import List
from uuid import UUID

from pydantic import BaseModel, TypeAdapter, ValidationError

try:
    TypeAdapter(List[int]).validate_json('["1", 2, "3"]', strict=True)
except ValidationError as exc:
    print(exc)
    """
    2 validation errors for list[int]
    0
      Input should be a valid integer [type=int_type, input_value='1', input_type=str]
    2
      Input should be a valid integer [type=int_type, input_value='3', input_type=str]
    """


class Model(BaseModel):
    x: int
    y: UUID


data = {'x': '1', 'y': '12345678-1234-1234-1234-123456789012'}
try:
    Model.model_validate(data, strict=True)
except ValidationError as exc:
    # Neither x nor y are valid in strict mode from python:
    print(exc)
    """
    2 validation errors for Model
    x
      Input should be a valid integer [type=int_type, input_value='1', input_type=str]
    y
      Input should be an instance of UUID [type=is_instance_of, input_value='12345678-1234-1234-1234-123456789012', input_type=str]
    """

json_data = json.dumps(data)
try:
    Model.model_validate_json(json_data, strict=True)
except ValidationError as exc:
    # From JSON, x is still not valid in strict mode, but y is:
    print(exc)
    """
    1 validation error for Model
    x
      Input should be a valid integer [type=int_type, input_value='1', input_type=str]
    """

Строгий режим с Field

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

Будут затронуты только поля, для которых установлено strict=True :

from pydantic import BaseModel, Field, ValidationError


class User(BaseModel):
    name: str
    age: int
    n_pets: int


user = User(name='John', age='42', n_pets='1')
print(user)
#> name='John' age=42 n_pets=1


class AnotherUser(BaseModel):
    name: str
    age: int = Field(strict=True)
    n_pets: int


try:
    anotheruser = AnotherUser(name='John', age='42', n_pets='1')
except ValidationError as e:
    print(e)
    """
    1 validation error for AnotherUser
    age
      Input should be a valid integer [type=int_type, input_value='42', input_type=str]
    """

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

from pydantic import BaseModel, Field, ValidationError


class Model(BaseModel):
    x: int = Field(strict=True)
    y: int = Field(strict=False)


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

Использование Field в качестве аннотации

Обратите внимание, что Field(strict=True) (или с любыми другими ключевыми аргументами) при необходимости можно использовать в качестве аннотации, например, при работе с TypedDict :

from typing_extensions import Annotated, TypedDict

from pydantic import Field, TypeAdapter, ValidationError


class MyDict(TypedDict):
    x: Annotated[int, Field(strict=True)]


try:
    TypeAdapter(MyDict).validate_python({'x': '1'})
except ValidationError as exc:
    print(exc)
    """
    1 validation error for typed-dict
    x
      Input should be a valid integer [type=int_type, input_value='1', input_type=str]
    """

Строгий режим с Annotated[..., Strict()]

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

Pydantic также предоставляет класс Strict , который предназначен для использования в качестве метаданных с классом typing.Annotated; эта аннотация указывает, что аннотированное поле должно быть проверено в строгом режиме:

from typing_extensions import Annotated

from pydantic import BaseModel, Strict, ValidationError


class User(BaseModel):
    name: str
    age: int
    is_active: Annotated[bool, Strict()]


User(name='David', age=33, is_active=True)
try:
    User(name='David', age=33, is_active='True')
except ValidationError as exc:
    print(exc)
    """
    1 validation error for User
    is_active
      Input should be a valid boolean [type=bool_type, input_value='True', input_type=str]
    """

Фактически, это метод, используемый для реализации некоторых стандартных типов, предоставляемых Pydantic, таких как StrictInt .

Строгий режим с ConfigDict

BaseModel

Если вы хотите включить строгий режим для всех полей сложного типа ввода, вы можете использовать ConfigDict(strict=True) в model_config :

from pydantic import BaseModel, ConfigDict, ValidationError


class User(BaseModel):
    model_config = ConfigDict(strict=True)

    name: str
    age: int
    is_active: bool


try:
    User(name='David', age='33', is_active='yes')
except ValidationError as exc:
    print(exc)
    """
    2 validation errors for User
    age
      Input should be a valid integer [type=int_type, input_value='33', input_type=str]
    is_active
      Input should be a valid boolean [type=bool_type, input_value='yes', input_type=str]
    """

!!! note Примечание. При использовании strict=True через model_config модели вы все равно можете переопределить строгость отдельных полей, установив strict=False для отдельных полей:

```py
from pydantic import BaseModel, ConfigDict, Field


class User(BaseModel):
    model_config = ConfigDict(strict=True)

    name: str
    age: int = Field(strict=False)
```

Обратите внимание, что строгий режим не применяется рекурсивно к вложенным полям модели:

from pydantic import BaseModel, ConfigDict, ValidationError


class Inner(BaseModel):
    y: int


class Outer(BaseModel):
    model_config = ConfigDict(strict=True)

    x: int
    inner: Inner


print(Outer(x=1, inner=Inner(y='2')))
#> x=1 inner=Inner(y=2)

try:
    Outer(x='1', inner=Inner(y='2'))
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Outer
    x
      Input should be a valid integer [type=int_type, input_value='1', input_type=str]
    """

(Это также относится к классам данных и TypedDict .)

Если это нежелательно, следует убедиться, что строгий режим включен для всех задействованных типов. Например, это можно сделать для классов моделей, используя общий базовый класс с model_config = ConfigDict(strict=True) :

from pydantic import BaseModel, ConfigDict, ValidationError


class MyBaseModel(BaseModel):
    model_config = ConfigDict(strict=True)


class Inner(MyBaseModel):
    y: int


class Outer(MyBaseModel):
    x: int
    inner: Inner


try:
    Outer.model_validate({'x': 1, 'inner': {'y': '2'}})
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Outer
    inner.y
      Input should be a valid integer [type=int_type, input_value='2', input_type=str]
    """

Классы данных и TypedDict

Классы данных Pydantic ведут себя аналогично примерам, показанным выше с BaseModel , с той лишь разницей, что вместо model_config вы должны использовать аргумент ключевого слова config для @pydantic.dataclasses.dataclass декоратор.

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

Однако если это невозможно (например, при работе со сторонними типами), вы можете установить конфигурацию, которую Pydantic должен использовать для типа, установив атрибут __pydantic_config__ для типа:

from typing_extensions import TypedDict

from pydantic import ConfigDict, TypeAdapter, ValidationError


class Inner(TypedDict):
    y: int


Inner.__pydantic_config__ = ConfigDict(strict=True)


class Outer(TypedDict):
    x: int
    inner: Inner


adapter = TypeAdapter(Outer)
print(adapter.validate_python({'x': '1', 'inner': {'y': 2}}))
#> {'x': 1, 'inner': {'y': 2}}


try:
    adapter.validate_python({'x': '1', 'inner': {'y': '2'}})
except ValidationError as exc:
    print(exc)
    """
    1 validation error for typed-dict
    inner.y
      Input should be a valid integer [type=int_type, input_value='2', input_type=str]
    """

TypeAdapter

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

from pydantic import ConfigDict, TypeAdapter, ValidationError

adapter = TypeAdapter(bool, config=ConfigDict(strict=True))

try:
    adapter.validate_python('yes')
except ValidationError as exc:
    print(exc)
    """
    1 validation error for bool
      Input should be a valid boolean [type=bool_type, input_value='yes', input_type=str]
    """

@validate_call

Строгий режим также можно использовать с декоратором @validate_call передав аргумент ключевого слова config :

from pydantic import ConfigDict, ValidationError, validate_call


@validate_call(config=ConfigDict(strict=True))
def foo(x: int) -> int:
    return x


try:
    foo('1')
except ValidationError as exc:
    print(exc)
    """
    1 validation error for foo
    0
      Input should be a valid integer [type=int_type, input_value='1', input_type=str]
    """

本文总阅读量