pydantic constr 和 mypy 检查之间的冲突
Conflict between pydantic constr and mypy checking
我正在使用 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
),您可以直接从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.Annotated
与 pydantic.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.
我正在使用 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
),您可以直接从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.Annotated
与 pydantic.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.