【问题标题】:Conflict between pydantic constr and mypy checkingpydantic constr 和 mypy 检查之间的冲突
【发布时间】:2021-06-29 14:26:53
【问题描述】:

我正在使用 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 的好处?

【问题讨论】:

    标签: python mypy pydantic


    【解决方案1】:

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

    继承自pydantic.ConstrainedStr

    您可以直接从pydantic.ConstrainedStr 继承,而不是使用constr 指定正则表达式约束(内部使用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", "123", "asdf"]}
    

    Mypy 很高兴地接受了这一点,并且 pydantic 进行了正确的验证。 data.regex[i] 的类型是Regex,但由于pydantic.ConstrainedStr 本身继承自str,所以大部分地方都可以作为字符串使用。

    使用pydantic.Field

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

    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", "123", "asdf"]})
    print(data)
    # regex=[Regex(__root__='abc'), Regex(__root__='123'), Regex(__root__='asdf')]
    print(data.json())
    # {"regex": ["abc", "123", "asdf"]}
    

    因为Regex不直接用作pydantic模型中的字段(但在您的示例中作为列表中的条目),我们需要强制引入模型。 __root__ 使 Regex 模型在验证和序列化时充当其单个字段(更多详细信息 here)。

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

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

    class DataNotList(pydantic.BaseModel):
        regex: str = Field(regex="^[0-9a-z_]*$")
    

    使用typing.Annotatedpydantic.Field

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

    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"}
    

    Mypy 将Annotated[str, Field(regex="^[0-9a-z_]*$")] 视为str 的类型别名。但它也告诉 pydantic 进行验证。 这在 pydantic 文档here 中有描述。

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

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

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

    请注意,typing.Annotated 仅在 Python 3.9 之后可用。对于较旧的 Python 版本,可以使用 typing_extensions.Annotated


    附带说明:我使用^[0-9a-z_]*$ 而不是[0-9a-z_]* 作为正则表达式,因为后者将接受 any 字符串作为有效字符串,如pydantic uses re.match for validation

    【讨论】:

      猜你喜欢
      • 2022-01-19
      • 2014-10-28
      • 1970-01-01
      • 2021-03-02
      • 1970-01-01
      • 1970-01-01
      • 2014-05-31
      • 1970-01-01
      • 2012-06-27
      相关资源
      最近更新 更多