基于Pydantic封装的通用模型在API请求验证中的应用详解

作者:忆想不到的晖 时间:2022-02-20 20:12:24 

引言

Pydantic 是一个用于数据验证和解析的流行库,经常被用于 FastAPI 和其他现代 Python 项目中。在处理 API 请求时,我们经常需要对请求参数进行有效性检查,例如日期范围、分页和排序等。在本文中,我们将介绍如何在 Pydantic 中使用 Mixin 和组合模式来实现这些功能,并讨论它们的优缺点。

通用model

首先,我们定义了以下几个基础的 Pydantic 模型:

  • DateModel:用于表示日期范围,包含开始日期和结束日期。

  • OrderModel:用于表示排序参数,包含排序字段和排序方式(升序或降序)。

  • PageModel:用于表示分页参数,包含页码和每页数量。

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @author: hui
# @Desc: { 通用的一些Pydantic模型 }
# @Date: 2023/03/30 11:57
from pydantic import BaseModel, Field, validator
from typing import Optional
from datetime import date

class DateModel(BaseModel):
   """日期模型"""

start_date: Optional[date] = Field(None, description="开始日期")
   end_date: Optional[date] = Field(None, description="结束日期")

@validator("end_date", always=True)
   def validate_end_date(cls, end_date, values):
       start_date = values.get("start_date")
       if all([start_date, end_date]) and end_date < start_date:
           raise ValueError("结束日期必须大于等于开始日期")
       return end_date

class OrderModel(BaseModel):
   """排序模型"""

order_by: Optional[str] = Field(None, description="排序字段,逗号分隔")
   order_mode: Optional[str] = Field(None, description="排序方式,逗号分隔,asc升序desc降序")

@validator("order_by", "order_mode", always=True)
   def split_comma_separated_string(cls, value):
       if value:
           return value.split(",")
       return value

@validator("order_mode", always=True)
   def check_length(cls, order_mode, values):
       order_by = values.get("order_by")
       if order_by and order_mode and len(order_by) != len(order_mode):
           raise ValueError("order_by and order_mode must have the same length")
       return order_mode

class PageModel(BaseModel):
   """分页模型"""

page: Optional[int] = Field(default=1, ge=1, description="页码")
   page_size: Optional[int] = Field(default=10, le=1000, description="每页数量, 默认10,最大1000")

接下来,我们通过混入(Mixin)和组合两种不同的方式将这些基础模型应用到一个实际的 API 请求中。

Mixin 模式

DateOrderModelMixin 类通过多重继承的方式继承了 DateModel 和 OrderModel。这种方式的优点是简单易懂,可以实现代码重用。然而,它也可能导致类层次结构变得复杂,尤其是当有多个 Mixin 之间存在依赖关系时。

class DateOrderModelMixin(DateModel, OrderModel):
   """日期与排序模型Mixin"""
   pass

组合模式

PageOrderModel 类通过组合的方式将 OrderModel 和 PageModel 作为它的属性。在初始化方法中,我们将请求参数映射到这两个模型,并调用基类的初始化方法。

组合模式的优点是代码结构更清晰,易于维护和扩展。但是,它可能需要编写更多的代码来将功能委托给组合的组件。

class PageOrderModel(BaseModel):
   """分页排序模型"""
   order_model: OrderModel = Field(OrderModel(), description="排序模型")
   page_model: PageModel = Field(PageModel(), description="分页模型")
   def __init__(self, **data):
       if "order_model" in data and "page_model" in data:
           order_model = data.pop("order_model", None)
           page_model = data.pop("page_model", None)
       else:
           # 用于直接平铺的字典入参
           order_params = {
               "order_by": data.pop("order_by", None),
               "order_mode": data.pop("order_mode", None),
           }
           page_params = {
               "page": data.pop("page", None),
               "page_size": data.pop("page_size", None),
           }
           order_model = OrderModel(**order_params)
           page_model = PageModel(**page_params)
       super().__init__(order_model=order_model, page_model=page_model, **data)
page_order = PageOrderModel(
   order_model=OrderModel(order_by="field1,field2", order_mode="asc,desc"),
   page_model=PageModel(page=1, page_size=10)
)
>>>out
order_model=OrderModel(order_by=['field1', 'field2'], order_mode=['asc', 'desc'])
page_model=PageModel(page=1, page_size=10)
req_params = {
   "order_by": "field1,field2",
   "order_mode": "asc,desc",
   "page": 1,
   "page_size": 10
}
req_model = PageOrderModel(**req_params)
>>>out
order_model=OrderModel(order_by=['field1', 'field2'], order_mode=['asc', 'desc'])
page_model=PageModel(page=1, page_size=10)

再来几个业务逻辑模型继承 DateOrderModelMixin 和 PageOrderModel 然后模拟一些请求参数去验证

让我们创建两个业务逻辑模型,一个用于查询商品信息,另一个用于查询订单信息。这两个模型分别继承 DateOrderModelMixin 和 PageOrderModel

from pydantic import BaseModel, Field
from typing import Optional

class ProductQueryModel(DateOrderModelMixin):
   product_category: Optional[str] = Field(None, description="商品类别")

class OrderQueryModel(PageOrderModel):
   customer_id: Optional[int] = Field(None, description="客户ID")

# 使用 ProductQueryModel 进行参数验证
product_query_params = {
   "start_date": "2023-04-01",
   "end_date": "2023-04-30",
   "order_by": "price",
   "order_mode": "desc",
   "product_category": "Electronics"
}

product_query = ProductQueryModel(**product_query_params)

>>>out
order_by=['price'] order_mode=['desc'] start_date=datetime.date(2023, 4, 1) end_date=datetime.date(2023, 4, 30) product_category='Electronics'

# 使用 OrderQueryModel 进行参数验证
order_query_params = {
   "start_date": "2023-04-01",
   "end_date": "2023-04-30",
   "order_by": "order_date",
   "order_mode": "asc",
   "page": 1,
   "page_size": 20,
   "customer_id": 12345
}

order_query = OrderQueryModel(**order_query_params)

>>>out
order_model=OrderModel(order_by=['order_date'], order_mode=['asc']) page_model=PageModel(page=1, page_size=20) customer_id=12345

这里的 ProductQueryModel 和 OrderQueryModel 分别用于处理商品查询和订单查询的请求参数。ProductQueryModel 继承自 DateOrderModelMixin,因此它具有日期范围和排序功能。OrderQueryModel 则继承自 PageOrderModel,具有分页和排序功能。

通过这两个模型,我们可以轻松地验证和解析传入的请求参数。在上面的示例代码中,我们分别创建了 product_query_params 和 order_query_params 字典来模拟请求参数,并使用 ProductQueryModel 和 OrderQueryModel 进行验证。可以看到,这两个模型成功解析了请求参数,并对日期范围、排序和分页进行了验证。

结论

在处理Pydantic模型时,根据具体的业务场景和需求来选择组合或Mixin模式。

Mixin模式适用于简单的继承关系,代码简洁易懂;组合模式适用于复杂的类关系,提供更好的灵活性和扩展性。在实际项目中,可以根据需求灵活选择这两种模式,或者根据情况将它们结合使用。

在实践中,如果需要将多个通用功能混合到一个业务逻辑模型中,Mixin模式可能是一个更好的选择,因为它可以让我们轻松地将这些功能组合在一起。然而,当我们需要对这些功能进行更精细的控制,或者在多个业务逻辑模型之间共享某些功能时,组合模式可能会更合适。

总之,在处理Pydantic模型时,我们应根据项目的实际需求和场景来权衡这两种模式的优缺点,从而做出合适的选择。这里的入参校验感觉使用多继承会更简单点,但到一些复杂的业务逻辑处理时可以使用组合模式,来做到更好的维护与扩展。

由于 GET 请求的入参不太好定义数据结构,减少的代码冗余就想到了多继承来组合属性和方法,如果使用 POST 请求传递 json 数据入参就可以更好设计参数结构,这时使用组合的方式hui更好。

杂谈

go的结构体嵌套就有点像组合

type Address struct {
   Street string
   City   string
   State  string
   Zip    string
}

type Person struct {
   Name    string
   Age     int
   Address Address
}

通过结构体的组合,可以方便地组合多个不同的数据结构,构建出更加复杂的结构体。这种组合方式可以让代码更加灵活和可维护,同时也可以提高代码的可读性和可重用性。

来源:https://juejin.cn/post/7222666499662577725

标签:Pydantic,封装,API,通用模型
0
投稿

猜你喜欢

  • python单线程实现多个定时器示例

    2023-05-11 08:59:18
  • 利用pandas将非数值数据转换成数值的方式

    2023-05-18 15:17:17
  • Pandas高级教程之Pandas中的GroupBy操作

    2022-04-14 19:22:25
  • python3使用requests模块爬取页面内容的实战演练

    2022-01-08 18:26:57
  • 原生javascript AJAX 三级联动的实现代码

    2024-04-18 10:00:46
  • python删除某个字符

    2022-03-31 09:43:23
  • golang bufio包中Write方法的深入讲解

    2024-05-08 10:45:31
  • 基于pandas将类别属性转化为数值属性的方法

    2021-11-24 07:15:16
  • python微信跳一跳系列之棋子定位颜色识别

    2023-01-16 04:52:49
  • 伪静态技术介绍与优缺点分析(较完整篇)

    2023-03-17 05:16:10
  • 由黄钻等级图标处理引发的思考

    2009-11-16 12:37:00
  • 随机抽取的sql语句 每班任意抽取3名学生

    2024-01-27 10:12:26
  • Go1.18新特性对泛型支持详解

    2024-05-22 17:46:25
  • Mysql数据库高级用法之视图、事务、索引、自连接、用户管理实例分析

    2024-01-15 20:17:00
  • 精妙的SQL和SQL SERVER 与ACCESS、EXCEL的数据导入导出转换

    2024-01-15 18:21:30
  • Anaconda的安装与虚拟环境建立

    2022-05-26 01:06:22
  • 最新PyCharm从安装到PyCharm永久激活再到PyCharm官方中文汉化详细教程

    2022-09-24 01:38:47
  • Perl初学笔记之Hello World

    2022-05-21 10:28:55
  • MySQL自动为查询数据结果加序号

    2024-01-20 03:17:51
  • Sql学习第一天——SQL 练习题(建表/sql语句)

    2024-01-22 05:44:39
  • asp之家 网络编程 m.aspxhome.com