问题描述
我在 Python 3.8+ Django/Rest-Framework 环境中工作,在新代码中强制执行类型,但构建在许多未类型化的遗留代码和数据上。我们广泛使用 TypedDicts 以确保我们生成的数据以正确的数据类型传递到我们的 TypeScript 前端。
MyPy/PyCharm/等。在检查我们的新代码是否吐出符合要求的数据方面做得很好,但我们想测试我们许多 RestSerializers/ModelSerializers 的输出是否符合 TypeDict。如果我有一个序列化程序并输入 dict 如下:
class PersonSerializer(ModelSerializer):
class Meta:
model = Person
fields = ['first','last']
class PersonData(TypedDict):
first: str
last: str
email: str
然后运行如下代码:
person_dict: PersonData = PersonSerializer(Person.objects.first()).data
静态类型检查器无法确定 person_dict
缺少所需的 email
键,因为(根据 PEP-589 的设计)它只是一个普通的 dict
.但我可以写一些类似的东西:
annotations = PersonData.__annotations__
for k in person_dict:
assert k in annotations # or something more complex.
assert isinstance(person_dict[k],annotations[k])
它会发现序列化器的数据中缺少 email
。在这种情况下这很好,我没有由 from __future__ import annotations
引入任何更改(不确定这是否会破坏它),并且我所有的类型注释都是裸类型。但是如果 PersonData
被定义为:
class PersonData(TypedDict):
email: Optional[str]
affiliations: Union[List[str],Dict[int,str]]
那么 isinstance
不足以检查数据是否通过(因为“下标泛型不能与类和实例检查一起使用”)。
我想知道的是,是否已经存在一个可调用的函数/方法(在 mypy 或其他检查器中)可以让我验证 TypedDict(甚至是单个变量,因为我可以自己迭代一个 dict)注释并查看它是否有效?
我不关心速度等问题,因为这样做的目的是检查我们所有的数据/方法/函数一次,然后在我们对当前数据验证感到高兴后删除检查。
解决方法
有点小技巧,但您可以使用 mypy 命令行 -c
选项检查两种类型。只需将它包装在一个 python 函数中:
import subprocess
def is_assignable(type_to,type_from) -> bool:
"""
Returns true if `type_from` can be assigned to `type_to`,e. g. type_to := type_from
Example:
>>> is_assignable(bool,str)
False
>>> from typing import *
>>> is_assignable(Union[List[str],Dict[int,str]],List[str])
True
"""
code = "\n".join((
f"import typing",f"type_to: {type_to}",f"type_from: {type_from}",f"type_to = type_from",))
return subprocess.call(("mypy","-c",code)) == 0
,
我发现的最简单的解决方案是使用 pydantic。
from typing import cast,TypedDict
import pydantic
class SomeDict(TypedDict):
val: int
name: str
# this could be a valid/invalid declaration
obj: SomeDict = {
'val': 12,'name': 'John',}
# validate with pydantic
try:
obj = cast(SomeDict,pydantic.create_model_from_typeddict(SomeDict)(**obj).dict())
except pydantic.ValidationError as exc:
print(f"ERROR: Invalid schema: {exc}")
编辑:当类型检查时,它当前返回一个错误,但按预期工作。请参阅此处:https://github.com/samuelcolvin/pydantic/issues/3008
,您可能想看看https://pypi.org/project/strongtyping/。这可能会有所帮助。
在文档中你可以找到这个例子:
from typing import List,TypedDict
from strongtyping.strong_typing import match_class_typing
@match_class_typing
class SalesSummary(TypedDict):
sales: int
country: str
product_codes: List[str]
# works like expected
SalesSummary({"sales": 10,"country": "Foo","product_codes": ["1","2","3"]})
# will raise a TypeMisMatch
SalesSummary({"sales": "Foo","country": 10,"product_codes": [1,2,3]})