一、前言
在搭建多进程的框架的时候,初始化进程需要传递一些参数。本项目在参阅blakeblackshear/frigate: NVR with realtime local object detection for IP cameras后决定将所有的数据交给Pydantic管理。而我们的后端框架fastapi,也是用pydantic管理的,模块复用了。
Pydantic的优点有很多,他像一个功能更丰富的字典。比如:
1 2 3 4 5
| class CameraConfig(BaseModel): name: str = Field(title="相机名称") width: int = Field(title="相机采集宽度") height: int = Field(title="相机采集高度") fps: Optional[int] = Field(title="相机采集帧率")
|
在上述配置类中,规定好了各个参数的类型,并用title字段添加了提示信息,对编程很友好。
重要的是,该model支持对字典的无缝转换。
比如我们可以用dict字典的数据格式先创建一个配置实例,然后转换为pydantic格式:
1 2 3 4 5 6 7 8 9 10 11 12 13
| camera_config = { "name": "MIPI", "width": 800, "height": 600, "fps": 30 } data = CameraConfig(**camera_config) print(data) print(data.name)
|
支持从pydantic格式转换为dict格式:
1 2 3 4 5 6 7 8 9 10
| camera_config = CameraConfig(name="MIPI",width=800,height=600,fps=30) data = camera_config.model_dump() print(data) print(type(data)) print(data['name'])
|
支持pydantic格式和json格式互相转换等。
支持嵌套的pydantic类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from typing import Optional from pydantic import BaseModel class Foo(BaseModel): count: int size: Optional[float] = None class Bar(BaseModel): apple: str = 'x' banana: str = 'y' class Spam(BaseModel): foo: Foo bars: list[Bar] m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}]) print(m) """ foo=Foo(count=4, size=None) bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y')] """ print(m.model_dump()) """ { 'foo': {'count': 4, 'size': None}, 'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}], } """
|
pydantic还支持参数范围校验,这个我认为是比较好用的点。
在实际使用场景中,使用pydantic一般是:从config.yaml中读取配置好的参数,程序中能正常访问各个字段的参数,发给各个模块初始化;在参数服务中能定期收集参数,并存储在文件中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import yaml from pydantic import BaseModel
class ServerConfig(BaseModel): host: str port: int
with open("config.yaml", "r", encoding="utf-8") as f: data = yaml.safe_load(f)
config = ServerConfig(**data)
print(config.host)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| from app.core import AppConfig, CameraConfig, AlgoConfig, FrontendConfig, WebConfig import yaml
config = AppConfig( cameras=[ CameraConfig(name="MIPI", width=800, height=600, fps=30) ], algo=AlgoConfig(status="idle"), frontend=FrontendConfig(web=WebConfig(ip="127.0.0.1", port=8080)) )
with open("test.yaml", "w", encoding="utf-8") as f: yaml.dump(config.model_dump(), f) """ test.yaml的内容: algo: status: idle cameras: - fps: 30 height: 600 name: MIPI width: 800 frontend: web: ip: 127.0.0.1 port: 8080 """
|
三、对Pydantic的补充
参考frigate中,用斜杠访问数据的函数get_nested_object(),我觉得可能会用到,就直接抄过来了。另外,针对我们现在的业务场景,可以适当封装一下几种转换,毕竟我英语不好,上来一个model_dump()我确实反应不过来是干啥的。
针对上面验证过的几种数据格式,我们封装几种转换,分别是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def to_dict(self) -> dict[str, Any]: """将当前实例转换为字典""" @classmethod def from_dict(cls: Type[T], data: dict[str, Any]) -> T: """从字典创建实例""" def to_yaml(self) -> str: """将当前实例转换为 YAML 字符串""" @classmethod def from_yaml(cls: Type[T], yaml_str: str) -> T: """从 YAML 字符串创建实例""" def to_file(self, file_path: str): """直接保存到文件""" @classmethod def from_file(cls: Type[T], file_path: str) -> T: """从文件加载"""
|
测试调用:
1 2 3 4 5 6 7 8 9 10
| config = AppConfig( cameras=[ CameraConfig(name="MIPI", width=1080, height=600, fps=30) ], algo=AlgoConfig(status="idle"), frontend=FrontendConfig(web=WebConfig(ip="127.0.0.1", port=8080)) ) config.to_file("test.yaml") config2 = AppConfig.from_file("test.yaml") print(config2)
|
没什么问题了,可以继续对多进程的框架进行搭建了。