콘텐츠로 이동

검증 데코레이터

??? api "API 문서" [pydantic.validate_call_decorator.validate_call][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 객체 및 regex로 변환됩니다.
  • 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 과 함께 사용할 수도 있습니다. 일반적으로 default_factory 지정되지 않는 한 Annotated 가 있는 유형 힌트에 사용해야 하며, 이 경우 필드의 기본값으로 사용해야 합니다.

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 뒤에 있는 모델은 일반 모델에서 ConfigDict 하위 클래스를 설정하는 것과 동일한 config 설정을 사용하여 사용자 정의할 수 있습니다.

구성은 데코레이터에 대한 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을 가능한 한 성능 좋게 만들기 위해 많은 노력을 기울였으며 인수 검사와 모델 생성은 함수가 정의될 때 한 번만 수행됩니다. 그러나 원시 함수를 호출하는 것과 비교하여 @validate_call 데코레이터를 사용하면 성능에 여전히 영향이 있습니다. .

많은 상황에서 이는 눈에 띄는 효과가 거의 또는 전혀 없습니다. 그러나 @validate_call 강력한 유형의 언어에서 함수 정의와 동등하거나 대안이 아니라는 점에 유의하십시오. 결코 그렇지 않을 것입니다.

반환 값

함수의 반환 값은 반환 유형 주석에 대해 선택적으로 유효성을 검사할 수 있습니다.


本文总阅读量