python 之 参数校验《三》 marshmallow参数校验

邵耀
2023-12-01

python 之 参数校验《一》 jsonschema模块
python 之 参数校验《二》 pydantic模块
python 之 参数校验《三》 marshmallow参数校验与序列化

一、背景

  • sanictortoise-orm 构建一个项目,由于sanic不具备自动序列化的功能,所以采用第三方的序列化模块
  • 采用fastapi 自带序列化输出,切能用 from tortoise.contrib.pydantic import pydantic_model_creator, pydantic_queryset_creator 进行序列化
  • 上面pydantic在 sanic 序列化觉得有点反人类,于是采用marshmallow 进行序列化

二、安装与介绍

  • 2.1 marshmallow的安装
    pip install marshmallow            
    pip install marshmallow-enum       
    
  • 2.2 字段及属性介绍

三、简单示例

  • 常规参数校验(字符串、int、列表、字典、邮箱、时间、日期、常量、默认值、自定义校验规则等)

    class ScoreSchema(Schema):
        English = fields.Integer(required=True, validate=validate.Range(min=0, max=100))
        Chinese = fields.Integer(required=True, validate=validate.Range(min=0, max=100))
        creature = fields.Integer(required=True, validate=validate.Range(min=0, max=100))
    
    class PersonSchema(Schema):
    	username = fields.String(required=True, validate=lambda s:len(s) < 6)   # required
    	gender = fields.Str(required=True, validate=validate.OneOf(['male', 'female', 'other']))  # 枚举类型
    	age = fields.Integer(required=True)  # 自定义验证器方式
    	height = fields.Integer(validate=validate.Range(min=100, max=120)) 
    	email = fields.Email()               # 非必传
    	location = fields.String(required=True, validate=validate.Length(min=4, max=16))
    	activate = fields.Boolean(default=True)
    	country = fields.Constant('China')
    	score = fields.Nested(ScoreSchema, required=True)
    	lucky_num = fields.List(fields.Integer(), required=True)
    	like = fields.List(fields.String(), required=True)
    	birthdate = fields.Date(required=True, format='%Y-%m-%d', error_messages={'invalid': 'Invalid date format. Expected YYYY-MM-DD.'})
    	created_at = fields.DateTime(format='%Y-%m-%d %H:%M:%S', required=True, error_messages={'required': 'Created at is required.', 'invalid': 'Invalid datetime format. Expected YYYY-MM-DD HH:MM:SS.'})
    	declaration = fields.Str(dump_default="Hello World")  # 仅在dump生成
    	declaration2 = fields.Str(load_default="Hello World") # 仅在dump生成
    
        @validates('age')             # 自定义验证器方式
        def validate_age(self, value):
            if value < 18:
                raise ValidationError("the age is young!")
    
        @validates_schema
        def validate_gender_age(self, data, **kwargs):
            if data['gender'] == 'male' and data['age'] > 35:
                # raise ValidationError({'age': "people age 35 no support"})   # 自定义
                raise ValidationError("people male age 35 no support")         
                
    try:
    	input_data = {
    	    "username": "zhangsan",
    	    "age": '18',
    	    "location": "location",
    	    'gender': 'male',
    	    'activate': True,
    	    'birthdate': '1993-02-03',
    	   	'created_at': '1993-02-03 00:00:00',
    	    'like': ["成龙", "科比", "罗大佑"],
    	    'lucky_num': ["22",33, 55],
    	    "score": {
    	        "English": 66, "Chinese": 88, "creature": 34
    	    }
    	}
    	schema = PersonSchema()
    	person = schema.load(input_data)
    	print("通过1", person)
    	result = schema.dump(person)
    	print("通过2", result)
    	
    except ValidationError as err:
    	print("错误", err)
    	print("错误", err.valid_data)
    
  • 校验列表中带有字典功能

    input_data = [
    	{"English": 66,"Chinese": 88,"creature": 34},
    	{"English": 66,"Chinese": 88,"creature": 34}
    ]
    
    errors = ScoreSchema(many=True).validate(input_data)
    if errors:
        print(errors)
    else:
        print("Validation succeeded!")
    
  • 枚举的校验、以及教研时生成常量

    class MyEnum(Enum):
        QUICK = "quick"
        STANDARD = "standard"
        DEEP = "deep"
    
    class MySchema(Schema):
        mode = fields.String(validate=validate.OneOf([mode.value for mode in MyEnum]))
        my_enum = fields.Str(required=True, validate=lambda x: x in [e.value for e in MyEnum])
        param = fields.Dict()
        
    data = {
        'mode': 'quick','my_enum': 'standard', 'param': {}
    }
    result = MySchema().dump(data)
    print(result)
    

四、fields字段的default、missing、dump_default、load_default 区别(此处有坑,留心留意)

条件存在不存在
defaultload 如果存在仅做验证,不存不验证
dump 如果存在不验证, 只输出默认值不存在输出默认值
missingload 如果存在则验证不存在则输出默认值
dump 如果存在则验证不存在则无输出
dump_defaultload 如果存在则验证不存在则无输出
dump 如果存在不验证, 输出为字符串不存在则输出默认
load_defaultload 如果存在则验证不存在则输出默认值
dump 如果存在不验证, 输出为字符串不存在则无输出
  • default 字段的默认值。如果输入数据中没有该字段,将使用默认值。默认值可以是常量、函数、类等。如果是函数,则该函数将在初始化时被调用并返回默认值。
  • missing:当输入数据缺少该字段时使用的默认值。与 default 参数不同,该值仅在输入数据中缺少该字段时使用,并不影响输出数据。
  • dump_default:当字段的值为默认值时,使用的输出值。如果没有指定该参数,则默认输出该字段的实际值。可以用于压缩输出数据,避免输出大量相同的默认值。
  • load_default 当字段的值为默认值时,使用的输入值。用于在输入数据中填充缺失的默认值。如果没有指定该参数,则默认使用 missing 参数的值。
from marshmallow import Schema, fields

class PersonSchema(Schema):
    name = fields.String()
    age = fields.Integer(missing=18)
    activate = fields.Boolean(default=True)  # json load 仅做验证 不会输出
    motto = fields.Str(dump_default="Hello World")  # 仅在dump生成
    declaration = fields.Str(load_default="爱我中华")  # 仅在dump生成


data = {"name": "Alice", "age": 20, "activate":True, "motto": "PHP is best", "declaration": "Life is short. I use python" }# "Life is short. I use python"
person = PersonSchema().load(data)
print(person)
result = PersonSchema().dump(data)
print(result)

综上所述,

1. default 和 missing 都是用于指定默认值的,
	但 default 用于指定输入数据中缺失时使用的默认值,
	而 missing 用于指定输出数据中缺失时使用的默认值。
2. dump_default 和 load_default 则分别用于指定输出数据中字段值为默认值时使用的输出值,
	以及在输入数据中缺少该字段时使用的默认值。

五、字段类型及属性

字段类型注释属性备注
String字符串allow_none是否允许为 None
as_string是否转化为字符串
Integer整数allow_none是否允许为 None
error校验错误信息
Float浮点数allow_nan是否允许 NaN 值
allow_infinity是否允许无穷大值
Decimal十进制数places小数保留位数
rounding小数舍入方式
allow_nan是否允许 NaN 值
allow_infinity是否允许无穷大值
as_string是否转化为字符串
coerce是否允许强制转化
quantize是否允许量化
context上下文信息
Boolean布尔值truthy布尔值为 True 的值
falsy布尔值为 False 的值 falsy=[‘no’, ‘false’, ‘0’]
DateTime时间/日期format时间/日期格式
Time时间format时间格式
Date日期format日期格式
TimeDelta时间差error校验错误信息
List列表cls_or_instance列表元素类型
Tuple元组cls_or_instance元组元素类型
Dict字典cls_or_instance字典元素类型
keys字典键的类型
values字典值的类型
Union多个字段类型field_names字段类型名称列表
fields字段类型列表
Function函数deserialize反序列化方法
serialize序列化方法
validate校验方法
Nested嵌套nested嵌套 schema
exclude排除字段列表
only仅包含字段列表
many是否为多个对象
context
 类似资料: