基于Pydantic自动生成PyQt Dialog

4 篇文章 0 订阅
1 篇文章 0 订阅

最近对Pydantic的应用很感兴趣。目前的PyQt5项目中正好要用Pydantic对象去构造一些配置信息,于是思考能否用Pydantic去简化配置对话框的构造过程。预期的目标是写一个泛型的对话框基类,接收具体的"Pydantaic类"类型,跟据类类型的元信息自动地去构造相应的对话框。描述下来感觉还是比较晦涩的,阅读和运行代码反而更容易理解。下面直接给出代码:

#!/usr/bin/python3
# coding: utf-8

import sys
from PyQt5.QtWidgets import QApplication, QDialog, QGridLayout, QLabel, QComboBox, QLineEdit
from typing import TypeVar, Generic
from pydantic import BaseModel, Field
from enum import IntEnum

ModelType = TypeVar('ModelType', bound=BaseModel)

class PydanticDialog(QDialog):
    def __init__(self, model: type[ModelType], parent=None):
        super(QDialog, self).__init__(parent=parent)
        self.model = model
        self.pydantic_layout = QGridLayout()
        self.pydantic_widgets = dict()
        self.constructFromMetaInfo()

    def constructFromMetaInfo(self):
        fileds = self.model.model_fields
        for row, (field_name, field_info) in enumerate(fileds.items()):
            self.pydantic_layout.addWidget(QLabel(field_info.description), row, 0)
            if issubclass(field_info.annotation, IntEnum):
                self.pydantic_widgets[field_name] = QComboBox()
                self.pydantic_widgets[field_name].addItems([item.alias for item in list(field_info.annotation)])
                self.pydantic_widgets[field_name].setCurrentText(field_info.default.alias)
            else:
                self.pydantic_widgets[field_name] = QLineEdit()
                self.pydantic_widgets[field_name].setText(str(field_info.default))
            self.pydantic_layout.addWidget(self.pydantic_widgets[field_name], row, 1)
            
    def asLayout(self):
        self.setLayout(self.pydantic_layout)

class IntEnumWithAlias(IntEnum):
    def __new__(cls, value, alias):
         obj = int.__new__(cls, value)
         obj._value_ = value
         obj.alias = alias
         return obj
    
class FruitEnum(IntEnumWithAlias):
    APPLE    = 0, "苹果"
    ORANGE   = 1, "橙子"
    BANANA   = 2, "香蕉"

class ItemEnum(IntEnumWithAlias):
    FOO   = 0, "FOO"
    BAR   = 1, "BAR"

class Config(BaseModel):
    Ip:    str          = Field(alias='ip', default = "192.168.1.100", description="IP地址")
    Port:  int          = Field(alias="port", default = 554, description="服务端口") 
    Mac:   str          = Field(alias="mac", default = "CC-F9-E4-81-77-51", description="MAC地址")
    PosX:  float        = Field(alias="pos_x", default = 1.5, description="坐标X")
    PosY:  float        = Field(alias="pos_y", default = 2.2, description="坐标Y")
    Fruit: FruitEnum    = Field(alias="fruit", default = FruitEnum.BANANA, description="水果")
    Item:  ItemEnum     = Field(alias="item", default = ItemEnum.FOO, description="组件")

class ConfigDialog(PydanticDialog):
    def __init__(self,  parent=None):
        super().__init__(Config, parent)
        self.setWindowTitle("配置")
        self.setLayout(self.pydantic_layout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    cd = ConfigDialog()
    cd.show()
    sys.exit(app.exec_()) 

直接运行代码,可以看到如下对话框:

上述程序中,值得解释的几点如下:

  1. 构造了一个PydanticDialog类,接收一个type[ModelType]类型的参数,实质上就是接收类类型,从而实现泛型(最开始尝试了用Generic[ModelType],想在代码里直接用ModelType去获取类的元信息,但发现构造PydanticDialog[Config]泛型类后,ModelType并未指向Config,因此换用了本文的赋值方案)。
  2. PydanticDialog类中,通过获取Pydantic类的model_fields字段,获取到全部Field的元信息,包括Field的名字、类型、默认值和描述。跟据这些信息就可以构造对话框了。
  3. 跟据Field为文本还是枚举,构造QLineEditQComboBox两种输入框,其中QComboBox的取值范围由枚举类获得。(注意到界面上ComboBox展示的是中文,是因为代码中给枚举加了alias这个附属字段,可参看这篇文章)。
  4. 最终的ConfigDialog在继承PydanticDialog的基础上,可更改Layout并增加其他控件,比如增加提交按钮等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值