Validation Decorator
API Documentation
@validate_call
デコレータを使用すると、関数が呼び出される前に、関数に渡された引数を解析し、関数の注釈を使用して検証することができます。
内部的には、これはモデルの作成と初期化と同じアプローチを使用しますが(詳細についてはValidatorsを参照)、最小限の定型文でコードに検証を適用する非常に簡単な方法を提供します。
使用例:
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]
"""
Argument types¶
引数の型は関数の型アノテーションから推測され、型デコレータのない引数はAny
と見なされます。typesにリストされているすべての型は、Pydanticモデルやcustom typesを含めて検証できます。
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
オブジェクトとregexに変換されます。max
は型アノテーションを持たないので、デコレータによってAny
と見なされます。
このような型強制は非常に役立ちますが、混乱を招くこともあれば、望まれないこともあります。
この点に関する@validate_call
の制限については、Coercion and strictnessを参照してください。
Function signatures¶
@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}
Using Field to describe function arguments¶
Fieldを@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)
Usage with mypy¶
validate_call
デコレータは、修飾する関数と同じシグネチャを持つ関数を返すように定義されているので、mypyで"そのまま"動作するはずです。
唯一の制限は、デコレータによって返される関数がデコレートされる関数と同じであるとmypyに思わせるため、raw functionやその他の属性にアクセスするにはtype:ignore
が必要になるということです。
Raw function¶
デコレートされた生の関数はアクセス可能です。これは、入力引数を信頼し、最もパフォーマンスの高い方法で関数を呼び出したい場合に便利です(下記のnotes on performanceを参照)。
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'
Async functions¶
@validate_call
can also be used on async functions:
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'`
Custom config¶
@validate_call
の背後にあるモデルは、config
設定を使用してカスタマイズできます。これは、通常のモデルでConfigDict
サブクラスを設定するのと同じです。
設定はデコレータへのconfig
キーワード引数を使用して設定されます。これは設定クラスか、後でクラスに変換されるプロパティのdictのいずれかです。
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]
"""
Extension — validating arguments before calling a function¶
場合によっては、関数の引数の検証を関数呼び出し自体から分離すると便利なことがあります。 これは、特定の機能にコストや時間がかかる場合に便利です。
このパターンに使用できる回避策の例を次に示します。
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
Limitations¶
Validation exception¶
現在、検証が失敗すると、標準のPydanticValidationError
が発生します。
詳細については、model error handlingを参照してください。
これは、そのstr()
メソッドが発生したエラーの有用な詳細を提供し、.errors()
や.json()
のようなメソッドがエンドユーザにエラーを公開する際に有用であるため、有用です。
ただし、ValidationError
はTypeError
ではなくValueError
**から継承します。これは、Pythonが引数が無効または欠落している場合にTypeError
を呼び出すため、予期しないことがあります。
これは、カスタムエラーを許可するか、デフォルトで別の例外を発生させるか、またはその両方によって、将来対処される可能性があります。
Coercion and strictness¶
Pydanticは現在、型が間違っている場合にエラーを発生させるのではなく、型を強制しようとする側に傾いています。model data conversionを参照してください。@validate_call
も同じです。
Performance¶
私たちはPydanticを可能な限りパフォーマンスの高いものにするために多大な努力をし、引数の検査とモデルの作成は関数が定義されたときに一度だけ実行されますが、生の関数を呼び出す場合と比較して、@validate_call
デコレータを使用するとパフォーマンスに影響があります。
多くの場合、これはほとんど、あるいはまったく効果がありませんが、@validate_call
は、強く型付けされた言語での関数定義と同等、あるいはそれに代わるものではないことに注意してください。
Return value¶
関数の戻り値は、その戻り値の型アノテーションに対して検証することもできます。