コンテンツにスキップ

バリデーションデコレーター

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

いくつかのメモ:

  • これらは文字列として渡されますが、 pathregexデコレータによってそれぞれ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}

フィールドを使用して関数の引数を記述する

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)

mypyでの使い方

validate_callデコレーターは、デコレーターが装飾する関数と同じシグネチャーを持つ関数を返すように定義されているため、 mypyで「すぐに」動作するはずです。唯一の制限は、mypy を騙して、デコレーターによって返される関数がデコレーターされる関数と同じであると思わせることです。 raw 関数またはその他の属性にアクセスするには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 TypeErrorではなくValueErrorを継承します。これは、引数が無効または欠落している場合に Python がTypeErrorを発生させるため、予期しない可能性があります。この問題は、カスタム エラーを許可するか、デフォルトで別の例外を発生させるか、あるいはその両方によって将来的に解決される可能性があります。

強制と厳格さ

Pydantic は現在、型が間違っている場合にエラーを発生させるのではなく、型を強制しようとする側に傾いています。モデル データ変換を参照してください。 @validate_callも同様です。

パフォーマンス

Pydantic のパフォーマンスを可能な限り高めるために多大な努力を払っており、引数の検査とモデルの作成は関数の定義時に 1 回だけ実行されますが、生の関数を呼び出す場合と比較して@validate_callデコレーターを使用するとパフォーマンスに影響が残ります。 。

多くの状況では、これは目立った影響をほとんどまたはまったく与えませんが、 @validate_call厳密に型指定された言語の関数定義と同等または代替ではないことに注意してください。それは決してありません。

戻り値

オプションで、関数の戻り値を戻り値の型の注釈に対して検証することができます。


本文总阅读量