引言
Pydantic 是一个用于数据验证和解析的流行库,经常被用于 FastAPI 和其他现代 Python 项目中。在处理 API 请求时,我们经常需要对请求参数进行有效性检查,例如日期范围、分页和排序等。在本文中,我们将介绍如何在 Pydantic 中使用 Mixin 和组合模式来实现这些功能,并讨论它们的优缺点。
通用model
首先,我们定义了以下几个基础的 Pydantic 模型:
-
DateModel
:用于表示日期范围,包含开始日期和结束日期。 -
OrderModel
:用于表示排序参数,包含排序字段和排序方式(升序或降序)。 -
PageModel
:用于表示分页参数,包含页码和每页数量。
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
#!/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 之间存在依赖关系时。
1
2
3
|
class DateOrderModelMixin(DateModel, OrderModel): """日期与排序模型Mixin""" pass |
组合模式
PageOrderModel
类通过组合的方式将 OrderModel
和 PageModel
作为它的属性。在初始化方法中,我们将请求参数映射到这两个模型,并调用基类的初始化方法。
组合模式的优点是代码结构更清晰,易于维护和扩展。但是,它可能需要编写更多的代码来将功能委托给组合的组件。
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
29
30
31
32
33
34
35
36
37
38
|
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
。
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
29
30
31
32
33
34
35
36
37
38
39
40
41
|
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的结构体嵌套就有点像组合
1
2
3
4
5
6
7
8
9
10
11
12
|
type Address struct { Street string City string State string Zip string } type Person struct { Name string Age int Address Address } |
通过结构体的组合,可以方便地组合多个不同的数据结构,构建出更加复杂的结构体。这种组合方式可以让代码更加灵活和可维护,同时也可以提高代码的可读性和可重用性。
以上就是基于Pydantic封装的通用模型在API请求验证中的应用详解的详细内容,更多关于Pydantic封装API请求验证的资料请关注服务器之家其它相关文章!
原文链接:https://juejin.cn/post/7222666499662577725