pydantic constr 和 mypy 检查之间的冲突

问题描述

我正在使用 pydantic 来验证 Json/Dict 输入。但我也在使用 mypy 来验证代码的类型完整性。

当使用 pydantic.constr 类型时,它会验证给定的字符串是否符合正则表达式,我收到一个 mypy 错误

代码如下:

from typing import List

import pydantic

Regex = pydantic.constr(regex="[0-9a-z_]*")


class Data(pydantic.BaseModel):
    regex: List[Regex]


data = Data(**{"regex":["abc","123","etc"]})
print(data,data.json())

这是 mypy 的输出

$ mypy main.py 
main.py:9: error: Variable "main.Regex" is not valid as a type
main.py:9: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases

我查看了文档,但找不到处理此问题的方法。我知道我可以为该正则表达式创建一个静态类型,但是这种方式违背了 pydantic 的目的。我可以通过的唯一方法是使用远非理想的 # type: ignore

那么有没有一种方法可以同时拥有 pydantic 和 mypy 的好处?

解决方法

有几种方法可以实现这一点:

继承自 pydantic.ConstrainedStr

您可以直接从 constr 继承:

pydantic.ConstrainedStr

Mypy 愉快地接受了这一点,pydantic 进行了正确的验证。 pydantic.ConstrainedStr 的类型是 import re import pydantic from pydantic import Field from typing import List class Regex(pydantic.ConstrainedStr): regex = re.compile("^[0-9a-z_]*$") class Data(pydantic.BaseModel): regex: List[Regex] data = Data(**{"regex": ["abc","123","asdf"]}) print(data) # regex=['abc','123','asdf'] print(data.json()) # {"regex": ["abc","asdf"]} ,但由于 data.regex[i] 本身继承自 Regex,所以它在大多数地方都可以用作字符串。

使用 pydantic.ConstrainedStr

正则表达式约束也可以指定为 Field 的参数:

str

因为 pydantic.Field 不是直接用作 pydantic 模型中的字段(而是在您的示例中作为列表中的条目),所以我们需要强制引入模型。 import pydantic from pydantic import Field from typing import List class Regex(pydantic.BaseModel): __root__: str = Field(regex="^[0-9a-z_]*$") class Data(pydantic.BaseModel): regex: List[Regex] data = Data(**{"regex": ["abc","asdf"]}) print(data) # regex=[Regex(__root__='abc'),Regex(__root__='123'),Regex(__root__='asdf')] print(data.json()) # {"regex": ["abc","asdf"]} 使 Regex 模型在验证和序列化时充当其单个字段(更多详细信息 here)。

但它有一个缺点:__root__ 的类型又是 Regex,但这次不是继承自 data.regex[i]。这导致例如Regex 不进行类型检查。必须改用 str

我仍然在这里提到这一点,因为当约束直接应用于字段而不是列表条目时,它可能是最简单的解决方案(并且 foo: str = data.regex[0] 不可用,见下文)。例如像这样:

foo: str = data.regex[0].__root__

typing.Annotatedclass DataNotList(pydantic.BaseModel): regex: str = Field(regex="^[0-9a-z_]*$") 一起使用

您可以将其指定为 Field 的参数,然后与 typing.Annotated 结合使用,而不是使用 pydantic.Field 来指定正则表达式约束:

constr

Mypy 将 typing.Annotated 视为 import pydantic from pydantic import Field from typing import Annotated Regex = Annotated[str,Field(regex="^[0-9a-z_]*$")] class DataNotList(pydantic.BaseModel): regex: Regex data = DataNotList(**{"regex": "abc"}) print(data) # regex='abc' print(data.json()) # {"regex": "abc"} 的类型别名。但它也告诉 pydantic 进行验证。 这在 pydantic 文档 here 中有描述。

不幸的是,它目前不适用于以下内容:

Annotated[str,Field(regex="^[0-9a-z_]*$")]

验证根本没有运行。这是一个开放的错误 (github issue)。修复错误后,这可能是总体上最好的解决方案。

请注意,str 仅从 Python 3.9 开始可用。对于较旧的 Python 版本,可以使用 typing_extensions.Annotated


附带说明:我在正则表达式中使用了 class Data(pydantic.BaseModel): regex: List[Regex] 而不是 typing.Annotated,因为后者将接受 any 字符串作为有效字符串,如 {{3} }.