跳转至

Pydantic Company

我们根据我认为导致 Pydantic 成功的原则创办了一家公司。

公司公告中了解更多信息.

校验装饰器

validate_arguments 装饰器允许传递给函数的参数在调用函数之前使用函数的注释进行解析和验证。 在引擎盖下,它使用相同的模型创建和初始化方法; 它提供了一种极其简单的方法,可以使用最少的样板文件将验证应用于您的代码。

测试版

validate_arguments 装饰器在 beta 中,它已在临时基础上添加到 v1.5 中的 pydantic。 它可能会在未来的版本中发生重大变化,其界面将在 v2 之前具体化。 来自社区的反馈在它仍然是临时的时候将非常有用; 评论 #1205 或创建一个新问题。

使用示例:

from pydantic import validate_arguments, ValidationError


@validate_arguments
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=' ')
print(b)
#> b'x x x x'

try:
    c = repeat('hello', 'wrong')
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Repeat
    count
      value is not a valid integer (type=type_error.integer)
    """

(这个脚本是完整的,它应该“按原样”运行)

参数类型(Argument Types)

参数类型是从函数的类型注释中推断出来的,没有类型装饰器的参数被认为是Any。 由于 validate_arguments 在内部使用标准的 BaseModel,因此可以验证 types 中列出的所有类型,包括 pydantic 模型和 自定义类型。 与 pydantic 的其余部分一样,类型可以在传递给实际函数之前由装饰器强制转换:

import os
from pathlib import Path
from typing import Pattern, Optional

from pydantic import validate_arguments, DirectoryPath


@validate_arguments
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.*'))
#> /home/user/myprojects/pydantic-zh-
#> cn/docs/examples/validation_decorator_parameter_types.py
print(find_file(this_dir, '^foobar.*', max=3))
#> None
import os
from pathlib import Path
from typing import Optional
from re import Pattern

from pydantic import validate_arguments, DirectoryPath


@validate_arguments
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.*'))
#> /home/user/myprojects/pydantic-zh-
#> cn/docs/.tmp_examples/upgraded/validation_decorator_types_3_10.py
print(find_file(this_dir, '^foobar.*', max=3))
#> None
import os
from pathlib import Path
from re import Pattern

from pydantic import validate_arguments, DirectoryPath


@validate_arguments
def find_file(path: DirectoryPath, regex: Pattern, max=None) -> Path | None:
    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.*'))
#> /home/user/myprojects/pydantic-zh-
#> cn/docs/.tmp_examples/upgraded/validation_decorator_types_3_10.py
print(find_file(this_dir, '^foobar.*', max=3))
#> None

(这个脚本是完整的,它应该“按原样”运行)

一些注意事项:

  • 尽管它们作为字符串传递,但装饰器将 pathregex 分别转换为 Path 对象和 regex
  • max 没有类型注释,因此将被装饰器视为 Any

像这样的类型强制可能非常有用,但也会造成混淆或不受欢迎,请参阅下文,了解 validate_arguments 在这方面的局限性。

函数签名(Function Signatures)

装饰器旨在使用所有可能的参数配置及其所有可能的组合来处理函数:

  • 带或不带默认值的位置或关键字参数
  • 通过 * 定义的变量位置参数(通常是 *args
  • 通过 ** 定义的变量关键字参数(通常是 **kwargs
  • 仅关键字参数 - *, 之后的参数
  • 仅位置参数 - , / 之前的参数(Python 3.8 中的新功能)

演示以上所有参数类型:

from pydantic import validate_arguments


@validate_arguments
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_arguments
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_arguments
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_arguments
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_arguments
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_arguments
def armageddon(
    a: int,
    /,  # python 3.8 only
    b: int,
    c: int = None,
    *d: int,
    e: int,
    f: int = None,
    **g: int,
) -> str:
    return f'a={a} b={b} c={c} d={d} e={e} f={f} g={g}'


print(armageddon(1, 2, e=3))
#> a=1 b=2 c=None d=() e=3 f=None g={}
print(armageddon(1, 2, 3, 4, 5, 6, e=8, f=9, g=10, spam=11))
#> a=1 b=2 c=3 d=(4, 5, 6) e=8 f=9 g={'g': 10, 'spam': 11}

(这个脚本是完整的,它应该“按原样”运行)

使用Field描述函数参数(Using Field to describe function arguments)

Field 也可以与validate_arguments一起使用,以提供有关字段和验证的额外信息。 一般来说,它应该在带有 Annotated 的类型提示中使用,除非指定了 default_factory,在这种情况下,它应该用作字段的默认值:

from datetime import datetime
from pydantic import validate_arguments, Field, ValidationError
from pydantic.typing import Annotated


@validate_arguments
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 HowMany
    num
      ensure this value is greater than 10 (type=value_error.number.not_gt;
    limit_value=10)
    """


@validate_arguments
def when(dt: datetime = Field(default_factory=datetime.now)):
    return dt


print(type(when()))
#> <class 'datetime.datetime'>

(这个脚本是完整的,它应该“按原样”运行)

别名 可以像往常一样与装饰器一起使用。

from pydantic import Field, validate_arguments
from pydantic.typing import Annotated


@validate_arguments
def how_many(num: Annotated[int, Field(gt=10, alias='number')]):
    return num


how_many(number=42)

(这个脚本是完整的,它应该“按原样”运行)

和mypy一起使用(Usage with mypy)

validate_arguments 装饰器应该与 mypy 一起“开箱即用”,因为它被定义为返回一个与它装饰的函数具有相同签名的函数。 唯一的限制是因为我们欺骗 mypy 认为装饰器返回的函数与被装饰的函数相同; 访问 raw function 或其他属性将需要设置 type: ignore

无需调用函数的校验(Validate without calling the function)

默认情况下,参数验证是通过直接调用带有参数的装饰函数来完成的。 但是,如果您想在不 实际 调用函数的情况下验证它们怎么办? 为此,您可以调用绑定到装饰函数的 validate 方法。

from pydantic import validate_arguments, ValidationError


@validate_arguments
def slow_sum(a: int, b: int) -> int:
    print(f'Called with a={a}, b={b}')
    #> Called with a=1, b=1
    return a + b


slow_sum(1, 1)

slow_sum.validate(2, 2)

try:
    slow_sum.validate(1, 'b')
except ValidationError as exc:
    print(exc)
    """
    1 validation error for SlowSum
    b
      value is not a valid integer (type=type_error.integer)
    """

(这个脚本是完整的,它应该“按原样”运行)

原始函数(Raw function)

被装饰的原始函数是可访问的,如果在某些情况下您信任您的输入参数并希望以最高性能的方式调用该函数,这将很有用(请参阅下面的性能说明):

from pydantic import validate_arguments


@validate_arguments
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_arguments 也可以用于异步函数:

import asyncio
from pydantic import PositiveInt, ValidationError, validate_arguments


@validate_arguments
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())
        """
        [
            {
                'loc': ('user_id',),
                'msg': 'ensure this value is greater than 0',
                'type': 'value_error.number.not_gt',
                'ctx': {'limit_value': 0},
            },
        ]
        """


asyncio.run(main())

(这个脚本依赖 conn.execute() that will return 'testing@example.com')

自定义配置(Custom Config)

validate_arguments 背后的模型可以使用配置设置进行自定义,这相当于在普通模型中设置 Config 子类。

Warning

@validate_arguments 尚不支持允许配置别名的 Configfieldsalias_generator 属性,使用它们会引发错误。

配置是使用装饰器的 config 关键字参数设置的,它可以是配置类或稍后转换为类的属性字典。

from pydantic import ValidationError, validate_arguments


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_arguments(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 AddFoobars
    a
      instance of Foobar expected (type=type_error.arbitrary_type;
    expected_arbitrary_type=Foobar)
    b
      instance of Foobar expected (type=type_error.arbitrary_type;
    expected_arbitrary_type=Foobar)
    """

(这个脚本是完整的,它应该“按原样”运行)

限制(Limitations)

validate_arguments 已在临时基础上发布,没有所有花里胡哨的东西,可能会在以后添加,请参阅 #1205 以获得更多关于这方面的信息。

尤其:

校验异常(Validation Exception)

目前在验证失败时,会引发标准的 pydantic ValidationError,请参阅模型错误处理

这很有用,因为它的 str() 方法提供了所发生错误的有用详细信息,而 .errors().json() 等方法在向最终用户公开错误时很有用,但是 ValidationError 继承自 ValueError 不是 TypeError 这可能是意外的,因为 Python 会在无效或缺少参数时引发 TypeError。 将来可以通过允许自定义错误或默认引发不同的异常或两者同时解决。

强制和严格(Coercion and Strictness)

pydantic 目前倾向于尝试强制类型而不是在类型错误时引发错误,请参阅 模型数据转换validate_arguments 也不例外。

请参阅 #1098 和其他带有“严格”标签的问题,以了解对此的讨论。 如果 pydantic 将来获得“严格”模式,validate_arguments 将可以选择使用它,它甚至可能成为装饰器的默认模式。

性能(Performance)

我们付出了巨大的努力使 pydantic 尽可能高效,并且参数检查和模型创建仅在定义函数时执行一次,但是与调用原始函数相比,使用 validate_arguments 装饰器仍然会对性能产生影响。

在许多情况下,这几乎没有或根本没有明显的影响,但是请注意,validate_arguments 不是强类型语言中函数定义的等效项或替代项; 永远不会。

返回值(Return Value)

函数的返回值未根据其返回类型注释进行验证,这可能会在将来添加为一个选项。

配置和校验(Config and Validators)

不支持自定义 Config 上的 fieldsalias_generator,请参见 above

验证器 也不是。

模型字段和保留参数(Model fields and reserved arguments)

以下名称可能不会被参数使用,因为它们可以在内部用于存储有关函数签名的信息:

  • v__args
  • v__kwargs
  • v__positional_only

这些名称(与“args”“kwargs”一起)可能会或可能不会(取决于函数的签名)显示为内部pydantic模型上的字段,可通过.model访问,因此该模型不是 目前特别有用(例如用于生成模式)。

随着错误产生方式的改变,这在未来应该是可以修复的。