为了处理这个任务,我实现了一个子库 - convtools models (docs / github)。它负责验证、类型转换(当明确要求时),并且它的错误非常清晰,可以自动处理。
它与许多其他库一样基于typing:
import typing as t
from convtools.contrib.models import (
DictModel,
build,
validate,
validators,
)
class TestModel(DictModel):
name: str
age: int
friends: t.List[int]
settings: t.Dict[str, float]
input_data = {
"name": "John",
"age": 42,
"friends": list(range(200)),
"settings": {f"v_{i}": float(i) for i in range(50)},
}
obj, errors = build(TestModel, input_data)
"""
>>> In [3]: obj.name
>>> Out[3]: 'John'
>>> In [4]: obj["name"]
>>> Out[4]: 'John'
>>> In [31]: obj.to_dict() == input_data
>>> Out[31]: True
"""
让我们看看如果验证失败会发生什么:
# replacing the expected int with a float
input_data["friends"][15] = float(input_data["friends"][15])
# replacing the expected float with an int
input_data["settings"]["v_25"] = -1
obj, errors = build(TestModel, input_data)
"""
>>> In [7]: errors
>>> Out[7]:
>>> {'friends': {15: {'__ERRORS': {'type': 'float instead of int'}}},
>>> 'settings': {'__VALUES': {'v_25': {'__ERRORS': {'type': 'int instead of float'}}}}}
"""
它还支持嵌套模型、泛型类型、联合、显式类型转换——请参阅文档。
恰如其分 - 它可以很好地处理循环依赖:
class UserModel(DictModel):
name: str
friends: t.List["UserModel"]
user_1 = {"name": "Nick", "friends": []}
user_2 = {"name": "John", "friends": []}
user_1["friends"].append(user_2)
user_2["friends"].append(user_1)
obj, errors = build(UserModel, user_1)
"""
>>> In [21]: obj
>>> Out[21]: UserModel(name='Nick', friends=[UserModel(name='John', friends=[<convtools.contrib.models.type_handlers.ProxyUserModel object at 0x109f84f10>])])
>>> In [23]: obj.friends[0]
>>> Out[23]: UserModel(name='John', friends=[<convtools.contrib.models.type_handlers.ProxyUserModel object at 0x109f84f10>])
>>> In [25]: obj.friends[0].friends[0].name
>>> Out[25]: 'Nick'
"""