Декоратор валидации
??? API "Документация по API" pydantic.validate_call_decorator.validate_call
Декоратор @validate_call
позволяет анализировать и проверять аргументы, передаваемые функции, с использованием аннотаций функции перед ее вызовом.
Несмотря на то, что внутри используется тот же подход к созданию и инициализации модели (более подробную информацию см. в разделе «Валидаторы »), он обеспечивает чрезвычайно простой способ применить проверку к вашему коду с минимальным использованием шаблона.
Пример использования:
from pydantic import ValidationError, validate_call
@validate_call
def repeat(s: str, count: int, *, separator: bytes = b'') -> bytes:
b = s.encode()
return separator.join(b for _ in range(count))
a = repeat('hello', 3)
print(a)
#> b'hellohellohello'
b = repeat('x', '4', separator=b' ')
print(b)
#> b'x x x x'
try:
c = repeat('hello', 'wrong')
except ValidationError as exc:
print(exc)
"""
1 validation error for repeat
1
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='wrong', input_type=str]
"""
Типы аргументов¶
Типы аргументов выводятся из аннотаций типов функции, аргументы без декоратора типа считаются Any
. Все типы, перечисленные в типах, могут быть проверены, включая модели Pydantic и пользовательские типы . Как и в остальной части Pydantic, декоратор может привести к типам до того, как они будут переданы фактической функции:
# TODO replace find_file with something that isn't affected the filesystem
import os
from pathlib import Path
from typing import Optional, Pattern
from pydantic import DirectoryPath, validate_call
@validate_call
def find_file(path: DirectoryPath, regex: Pattern, max=None) -> Optional[Path]:
for i, f in enumerate(path.glob('**/*')):
if max and i > max:
return
if f.is_file() and regex.fullmatch(str(f.relative_to(path))):
return f
# note: this_dir is a string here
this_dir = os.path.dirname(__file__)
print(find_file(this_dir, '^validation.*'))
print(find_file(this_dir, '^foobar.*', max=3))
Несколько примечаний:
- Хотя они передаются как строки,
path
иregex
преобразуются декоратором в объектPath
и регулярное выражение соответственно. max
не имеет аннотации типа, поэтому декоратор будет рассматривать его какAny
.
Подобное приведение типов может быть чрезвычайно полезным, но в то же время сбивающим с толку или нежелательным. См. Принуждение и строгость для обсуждения ограничений @validate_call
в этом отношении.
Сигнатуры функций¶
Декоратор @validate_call
предназначен для работы с функциями, использующими все возможные конфигурации параметров и все возможные их комбинации:
- Позиционные или ключевые аргументы со значениями по умолчанию или без них.
- Переменные позиционные аргументы, определенные через
*
(часто*args
). - Переменные аргументы ключевого слова, определенные через
**
(часто**kwargs
). - Аргументы только по ключевым словам: аргументы после
*,
. - Аргументы только позиционные: аргументы перед
, /
(новое в Python 3.8).
Чтобы продемонстрировать все вышеуказанные типы параметров:
from pydantic import validate_call
@validate_call
def pos_or_kw(a: int, b: int = 2) -> str:
return f'a={a} b={b}'
print(pos_or_kw(1))
#> a=1 b=2
print(pos_or_kw(a=1))
#> a=1 b=2
print(pos_or_kw(1, 3))
#> a=1 b=3
print(pos_or_kw(a=1, b=3))
#> a=1 b=3
@validate_call
def kw_only(*, a: int, b: int = 2) -> str:
return f'a={a} b={b}'
print(kw_only(a=1))
#> a=1 b=2
print(kw_only(a=1, b=3))
#> a=1 b=3
@validate_call
def pos_only(a: int, b: int = 2, /) -> str: # python 3.8 only
return f'a={a} b={b}'
print(pos_only(1))
#> a=1 b=2
print(pos_only(1, 2))
#> a=1 b=2
@validate_call
def var_args(*args: int) -> str:
return str(args)
print(var_args(1))
#> (1,)
print(var_args(1, 2))
#> (1, 2)
print(var_args(1, 2, 3))
#> (1, 2, 3)
@validate_call
def var_kwargs(**kwargs: int) -> str:
return str(kwargs)
print(var_kwargs(a=1))
#> {'a': 1}
print(var_kwargs(a=1, b=2))
#> {'a': 1, 'b': 2}
@validate_call
def armageddon(
a: int,
/, # python 3.8 only
b: int,
*c: int,
d: int,
e: int = None,
**f: int,
) -> str:
return f'a={a} b={b} c={c} d={d} e={e} f={f}'
print(armageddon(1, 2, d=3))
#> a=1 b=2 c=() d=3 e=None f={}
print(armageddon(1, 2, 3, 4, 5, 6, d=8, e=9, f=10, spam=11))
#> a=1 b=2 c=(3, 4, 5, 6) d=8 e=9 f={'f': 10, 'spam': 11}
Использование поля для описания аргументов функции¶
Поле также можно использовать с @validate_call
для предоставления дополнительной информации о поле и проверках. Обычно его следует использовать в подсказке типа с Annotated , если не указан default_factory
, и в этом случае его следует использовать как значение поля по умолчанию:
from datetime import datetime
from typing_extensions import Annotated
from pydantic import Field, ValidationError, validate_call
@validate_call
def how_many(num: Annotated[int, Field(gt=10)]):
return num
try:
how_many(1)
except ValidationError as e:
print(e)
"""
1 validation error for how_many
0
Input should be greater than 10 [type=greater_than, input_value=1, input_type=int]
"""
@validate_call
def when(dt: datetime = Field(default_factory=datetime.now)):
return dt
print(type(when()))
#> <class 'datetime.datetime'>
alias
можно использовать с декоратором как обычно.
from typing_extensions import Annotated
from pydantic import Field, validate_call
@validate_call
def how_many(num: Annotated[int, Field(gt=10, alias='number')]):
return num
how_many(number=42)
Использование с mypy¶
Декоратор validate_call
должен работать с mypy «из коробки», поскольку он возвращает функцию с той же сигнатурой, что и функция, которую он декорирует. Единственное ограничение заключается в том, что, поскольку мы обманываем mypy, заставляя его думать, что функция, возвращаемая декоратором, такая же, как и декорируемая функция; доступ к необработанной функции или другим атрибутам потребует ввода type: ignore
.
Необработанная функция¶
Необработанная функция, которая была декорирована, доступна. Это полезно, если в некоторых сценариях вы доверяете своим входным аргументам и хотите вызвать функцию наиболее производительным способом (см. примечания по производительности ниже):
from pydantic import validate_call
@validate_call
def repeat(s: str, count: int, *, separator: bytes = b'') -> bytes:
b = s.encode()
return separator.join(b for _ in range(count))
a = repeat('hello', 3)
print(a)
#> b'hellohellohello'
b = repeat.raw_function('good bye', 2, separator=b', ')
print(b)
#> b'good bye, good bye'
Асинхронные функции¶
@validate_call
также можно использовать в асинхронных функциях:
class Connection:
async def execute(self, sql, *args):
return 'testing@example.com'
conn = Connection()
# ignore-above
import asyncio
from pydantic import PositiveInt, ValidationError, validate_call
@validate_call
async def get_user_email(user_id: PositiveInt):
# `conn` is some fictional connection to a database
email = await conn.execute('select email from users where id=$1', user_id)
if email is None:
raise RuntimeError('user not found')
else:
return email
async def main():
email = await get_user_email(123)
print(email)
#> testing@example.com
try:
await get_user_email(-4)
except ValidationError as exc:
print(exc.errors())
"""
[
{
'type': 'greater_than',
'loc': (0,),
'msg': 'Input should be greater than 0',
'input': -4,
'ctx': {'gt': 0},
'url': 'https://errors.pydantic.dev/2/v/greater_than',
}
]
"""
asyncio.run(main())
# requires: `conn.execute()` that will return `'testing@example.com'`
Пользовательская конфигурация¶
Модель @validate_call
можно настроить с помощью параметра config
, что эквивалентно настройке подкласса ConfigDict
в обычных моделях.
Конфигурация задается с использованием аргумента ключевого слова config
для декоратора. Это может быть либо класс конфигурации, либо набор свойств, которые позже преобразуются в класс.
from pydantic import ValidationError, validate_call
class Foobar:
def __init__(self, v: str):
self.v = v
def __add__(self, other: 'Foobar') -> str:
return f'{self} + {other}'
def __str__(self) -> str:
return f'Foobar({self.v})'
@validate_call(config=dict(arbitrary_types_allowed=True))
def add_foobars(a: Foobar, b: Foobar):
return a + b
c = add_foobars(Foobar('a'), Foobar('b'))
print(c)
#> Foobar(a) + Foobar(b)
try:
add_foobars(1, 2)
except ValidationError as e:
print(e)
"""
2 validation errors for add_foobars
0
Input should be an instance of Foobar [type=is_instance_of, input_value=1, input_type=int]
1
Input should be an instance of Foobar [type=is_instance_of, input_value=2, input_type=int]
"""
Расширение — проверка аргументов перед вызовом функции.¶
В некоторых случаях может быть полезно отделить проверку аргументов функции от самого вызова функции. Это может быть полезно, когда конкретная функция требует больших затрат или времени.
Вот пример обходного пути, который вы можете использовать для этого шаблона:
from pydantic import validate_call
@validate_call
def validate_foo(a: int, b: int):
def foo():
return a + b
return foo
foo = validate_foo(a=1, b=2)
print(foo())
#> 3
Ограничения¶
Исключение проверки¶
В настоящее время при сбое проверки возникает стандартная ошибка Pydantic ValidationError
. Подробности см. в разделе обработка ошибок модели .
Это полезно, поскольку его метод str()
предоставляет полезную информацию о произошедшей ошибке, а такие методы, как .errors()
и .json()
могут быть полезны при раскрытии ошибок конечным пользователям. Однако ValidationError
наследуется от ValueError
, а не от TypeError
, что может быть неожиданным, поскольку Python будет вызывать TypeError
при недопустимых или отсутствующих аргументах. В будущем эту проблему можно решить, либо разрешив пользовательскую ошибку, либо вызвав другое исключение по умолчанию, либо и то, и другое.
Принуждение и строгость¶
Pydantic в настоящее время склоняется к попыткам привести типы, а не выдавать ошибку, если тип неправильный, см. Преобразование данных модели , и @validate_call
ничем не отличается.
Производительность¶
Мы приложили большие усилия, чтобы сделать Pydantic максимально производительным, а проверка аргументов и создание модели выполняются только один раз, когда функция определена, однако использование декоратора @validate_call
по-прежнему будет влиять на производительность по сравнению с вызовом необработанной функции. .
Во многих ситуациях это будет иметь незначительный или вообще не иметь заметного эффекта, однако имейте в виду, что @validate_call
не является эквивалентом или альтернативой определениям функций в строго типизированных языках; этого никогда не будет.
Возвращаемое значение¶
Возвращаемое значение функции может быть дополнительно проверено на соответствие ее аннотации типа возвращаемого значения.
本文总阅读量次