Marshmallow,一个有点甜的Python库

前言

在许多场景中,我们常常需要执行Python对象的序列化、反序列化操作。例如,在开发REST API时,或者在进行一些面向对象化的数据加载和保存时,这一功能经常派上用场。

经常cv Python代码的臭宝,接触最多的应该是通过jsonpickle模块进行序列化或反序列化,这是一种常见的做法。

import jsondata = {'name': 'John', 'age': 30, 'city': 'New York'}
serialized_data = json.dumps(data)

往往Python对象的序列化、反序列化同时也要伴随着数据的处理和校验。

而今天要自我介绍的主角:Marshmallow,则为我们带来更强大的数据序列化和反序列化,更优雅的参数校验、数据处理能力。

Github地址:https://github.com/marshmallow-code/marshmallow

它可以帮助您将复杂的数据类型(例如对象或数据库记录)转换为Python数据类型(例如字典),反之亦然。

它被大量用于FlaskDjangoWeb开发框架中,以处理数据输入和输出。

楔子

为了执行序列化或反序列化操作,首先需要一个操作对象。在这里,我们先定义一个类:

class Novel:def __init__(self, title, author, genre, pages):self.title = titleself.author = authorself.genre = genreself.pages = pages# 实例化一个小说对象
interesting_novel = Novel(title="The Enchanting Adventure", author="Jane Doe", genre="Fantasy", pages=300)

现在的需求是将这个小说对象转成字典。你会怎么来实现呢?

笨方法

  • 手动创建一个字典,将小说对象的属性映射到字典的键和值。
novel_dict = {"title": interesting_novel.title,"author": interesting_novel.author,"genre": interesting_novel.genre,"pages": interesting_novel.pages
}

  • 使用vars函数:

Python 中的 vars 函数可以返回对象的 dict 属性,该属性包含了对象的所有属性和对应的值。这样,你可以直接使用 vars 将对象转换为字典:

novel_dict = vars(interesting_novel)

  • 使用__dict__属性:

对于具有__dict__属性的对象,可以直接访问该属性获取对象的属性和值。

novel_dict = interesting_novel.__dict__

  • 使用json.loads(json.dumps(obj))

利用JSON库,通过先将对象转换为JSON字符串,然后再将其解析为字典。

import jsonnovel_json = json.dumps(interesting_novel, default=lambda o: o.__dict__)
novel_dict = json.loads(novel_json)

数据类使用dataclass/attrs的内置方法

  • dataclass版本
from dataclasses import dataclass, asdict
from loguru import logger@dataclass
class Novel:title: strauthor: strgenre: strpages: int# 实例化一个小说对象
interesting_novel = Novel(title="The Enchanting Adventure", author="Jane Doe", genre="Fantasy", pages=300)# 将对象序列化为字典
novel_dict = asdict(interesting_novel)
logger.info(novel_dict)# 将字典反序列化为对象
new_novel = Novel(**novel_dict)
logger.info(new_novel)

  • attrs版本
import attr
import cattr
from loguru import logger@attr.define
class Novel:title = attr.ib()author = attr.ib()genre = attr.ib()pages = attr.ib()# 实例化一个小说对象
interesting_novel = Novel(title="The Enchanting Adventure", author="Jane Doe", genre="Fantasy", pages=300)# 将对象序列化为字典
novel_dict = cattr.unstructure(interesting_novel)
logger.info(novel_dict)# 将字典反序列化为对象
new_novel_dict = {'title': 'AI之旅', 'author': 'HaiGe', 'genre': 'Fantasy', 'pages': 668}
new_novel = cattr.structure(new_novel_dict, Novel)
logger.info(new_novel)

更优雅的方案:marshmallow

上面介绍的几种序列化与反序列化方法看起来已经相当炸裂,但是为什么还要选择 marshmallow 呢?

其实,尽管这些方法能够完成基本的序列化和反序列化任务,但在处理更加复杂的数据结构、数据验证、预处理逻辑以及与其他库的集成等方面,它们可能显得不够灵活和方便。而 marshmallow 库正是为了解决这些问题而诞生的。

marshmallow 提供了强大而灵活的schema(模式)定义,可以精确地控制数据的序列化和反序列化过程。它支持复杂的数据结构、自定义验证器、预处理逻辑等高级功能,同时与许多其他常见的Python库和框架无缝集成。

无论是构建RESTful API、数据持久化、数据迁移或者简单的数据处理、数据验证等领域,marshmallow都能发挥出色的作用,特别适合于需要处理复杂数据结构、进行数据交换的场景

此外,marshmallow还提供了丰富的文档和社区支持,使得学习和使用起来更加容易。因此,尽管已经有了许多其他方法,但选择marshmallow依然是一个明智的选择,特别是在处理复杂的数据结构和场景下。

marshmallow库的基本用法

安装

要使用marshmallow这个库,需要先安装下:

# 3.20.2
pip3 install marshmallow  

A. 序列化与反序列化

marshmallow提供了灵活且强大的数据序列化与反序列化功能,可以将复杂的Python数据类型转换为JSONXML等格式,也能反向将外部数据解析为Python对象。

from marshmallow import Schema, fields, post_loadclass Novel:def __init__(self, title, author, genre, pages):self.title = titleself.author = authorself.genre = genreself.pages = pagesdef __repr__(self):return f"Novel(title={self.title!r}, author={self.author!r}, genre={self.genre!r}, pages={self.pages!r})"class NovelSchema(Schema):title = fields.Str()author = fields.Str()genre = fields.Str()pages = fields.Int()@post_loaddef make(self, data, **kwargs):return Novel(**data)# 创建一个 Novel 对象
novel = Novel(title="The Enchanting Adventure", author="Jane Doe", genre="Fantasy", pages=300)# 序列化 Novel 对象
novel_schema = NovelSchema()
serialized_data = novel_schema.dump(novel)
print(serialized_data)# 反序列化
deserialized_data = novel_schema.load(serialized_data)
print(deserialized_data)

这里我们需要稍微区分一下schemadump方法和dumps方法:dump()方法返回的是dict格式,而dumps()方法返回的是JSON字符串。
同理,load方法用来加载字典,而loads方法用来加载JSON字符串。


让我们再来看下多个对象序列化与反序列化,同样非常简单!!

from marshmallow import Schema, fields, post_load
from loguru import loggerclass Novel:def __init__(self, title, author, genre, pages):self.title = titleself.author = authorself.genre = genreself.pages = pagesdef __repr__(self):return f"Novel(title={self.title!r}, author={self.author!r}, genre={self.genre!r}, pages={self.pages!r})"class NovelSchema(Schema):title = fields.Str()author = fields.Str()genre = fields.Str()pages = fields.Int()@post_loaddef make(self, data, **kwargs):return Novel(**data)# 创建一个 Novel 对象
novel1 = Novel(title="海哥python1", author="暴走的海鸽", genre="Fantasy", pages=300)
novel2 = Novel(title="海哥python2", author="暴走的海鸽", genre="Fantasy", pages=300)
novel3 = Novel(title="海哥python3", author="暴走的海鸽", genre="Fantasy", pages=300)novels = [novel1, novel2, novel3]# 序列化 Novel 对象
novel_schema = NovelSchema(many=True)
serialized_data = novel_schema.dump(novels)
logger.info(serialized_data)# 反序列化
deserialized_data = novel_schema.load(serialized_data)
logger.info(deserialized_data)

此外,Schema类具有两个参数用于控制序列化的输出,即onlyexcludeonly 参数返回的输出结果仅包含列表中指定的类属性,而exclude则正好相反,它会排除列表中指定的类属性。

from marshmallow import Schema, fields, post_load, validates, ValidationError, validate
from loguru import loggerclass Novel:def __init__(self, title, author, genre="Fantasy2", pages=300):self.title = titleself.author = authorself.genre = genreself.pages = pagesdef __repr__(self):return f"Novel(title={self.title!r}, author={self.author!r}, genre={self.genre!r}, pages={self.pages!r})"class NovelSchema(Schema):title = fields.Str(validate=validate.Length(min=1, max=10))author = fields.Str()genre = fields.Str()pages = fields.Int()@post_loaddef make(self, data, **kwargs):logger.info(data)return Novel(**data)@validates('pages')def validate_pages(self, value):if value <= 0:raise ValidationError('Pages must be a positive integer.')# Create a Novel object
novel = Novel(title="The Enchanting Adventure", author="Jane Doe", genre="Fantasy", pages=-300)# Serialize the Novel object
novel_schema = NovelSchema(only=("title", "author",))
serialized_data = novel_schema.dump(novel)
logger.info(serialized_data)

B. 数据验证

数据验证是marshmallow的另一个重要特性,它能够定制对数据进行验证,包括类型验证长度验证自定义验证等,保证数据的完整性和正确性。

内置的常见验证器有:

from marshmallow import Schema, fields, post_load, validates, ValidationError, validate
from loguru import loggerclass Novel:def __init__(self, title, author, genre, pages):self.title = titleself.author = authorself.genre = genreself.pages = pagesdef __repr__(self):return f"Novel(title={self.title!r}, author={self.author!r}, genre={self.genre!r}, pages={self.pages!r})"class NovelSchema(Schema):title = fields.Str(validate=validate.Length(min=1, max=10))author = fields.Str()genre = fields.Str()pages = fields.Int()@post_loaddef make(self, data, **kwargs):return Novel(**data)@validates('pages')def validate_pages(self, value):if value <= 0:raise ValidationError('Pages must be a positive integer.')# Create a Novel object
novel = Novel(title="The Enchanting Adventure", author="Jane Doe", genre="Fantasy", pages=-300)# Serialize the Novel object
novel_schema = NovelSchema()
serialized_data = novel_schema.dump(novel)
logger.info(serialized_data)# Deserialization
try:deserialized_data = novel_schema.load(serialized_data)logger.info(deserialized_data)
except ValidationError as e:logger.error(e.messages)logger.error(e.valid_data)

在这个例子中,我们对title使用了validate字段验证,并且定义了一个validate_pages方法,用于验证pages字段。如果pages字段的值小于等于0,将会引发一个ValidationError异常。在反序列化时,如果遇到校验失败,Marshmallow将会捕获异常,并将校验错误信息存储在messages属性中。

如果需要对属性进行缺失验证,则在schema中规定required参数,即表明该参数是必要的,不可缺失。

from marshmallow import Schema, fields, post_load, validates, ValidationError, validate
from loguru import loggerclass Novel:def __init__(self, title, author, genre, pages):self.title = titleself.author = authorself.genre = genreself.pages = pagesdef __repr__(self):return f"Novel(title={self.title!r}, author={self.author!r}, genre={self.genre!r}, pages={self.pages!r})"class NovelSchema(Schema):title = fields.Str(validate=validate.Length(min=1, max=10))author = fields.Str(required=True)genre = fields.Str()pages = fields.Int()@post_loaddef make(self, data, **kwargs):return Novel(**data)@validates('pages')def validate_pages(self, value):if value <= 0:raise ValidationError('Pages must be a positive integer.')# Create a Novel object
# novel = Novel(title="The Enchanting Adventure", author="Jane Doe", genre="Fantasy", pages=-300)
novel = Novel(title="The Enchanting Adventure", author="Jane Doe", genre="Fantasy", pages=-300)# Serialize the Novel object
novel_schema = NovelSchema()
serialized_data = novel_schema.dump(novel)
logger.info(serialized_data)# Deserialization
serialized_data.pop("author")  # 移除author
try:deserialized_data = novel_schema.load(serialized_data)logger.info(deserialized_data)
except ValidationError as e:logger.error(e.messages)logger.error(e.valid_data)

我们给author字段定义了required属性,但是反序列化的时候并没有传入,具体报错如下:

Marshmallow在序列化和反序列化字段方面也提供了默认值,并且非常清晰地区分它们!例如,load_default参数用于在反序列化时自动填充数据,而dump_default参数则用于在序列化时自动填充数据。

from marshmallow import Schema, fields, post_load, validates, ValidationError, validate
from loguru import loggerclass Novel:def __init__(self, title, author, genre, pages):self.title = titleself.author = authorself.genre = genreself.pages = pagesdef __repr__(self):return f"Novel(title={self.title!r}, author={self.author!r}, genre={self.genre!r}, pages={self.pages!r})"class NovelSchema(Schema):title = fields.Str(validate=validate.Length(min=1, max=1000))author = fields.Str(required=True)genre = fields.Str()pages = fields.Int(load_default=300, dump_default=500)  # 设置反序列化默认值为300,序列化默认值为500@post_loaddef make(self, data, **kwargs):return Novel(**data)@validates('pages')def validate_pages(self, value):if value <= 0:raise ValidationError('Pages must be a positive integer.')# Create a Novel object
novel = {"title": "公众号:海哥python", "author": "暴走的海鸽", "genre": "Fantasy"}# Serialize the Novel object
novel_schema = NovelSchema()
serialized_data = novel_schema.dump(novel)
logger.info(f"序列化:{serialized_data}")# Deserialization
novel2 = {"title": "公众号:海哥python", "author": "暴走的海鸽", "genre": "Fantasy"}
try:deserialized_data = novel_schema.load(novel2)logger.info(f"反序列化:{deserialized_data}")
except ValidationError as e:logger.error(e.messages)logger.error(f"合法的数据:{e.valid_data}")

在序列化过程中,Schema对象默认会使用与其自身定义相同的fields属性名,但也可以根据需要进行自定义。
如果使用和生成与架构不匹配的数据,则可以通过data_key参数指定输出键,类似于起了别名。

from marshmallow import Schema, fields, post_load, validates, ValidationError, validate
from loguru import loggerclass Novel:def __init__(self, title, author, genre, pages):self.title = titleself.author = authorself.genre = genreself.pages = pagesdef __repr__(self):return f"Novel(title={self.title!r}, author={self.author!r}, genre={self.genre!r}, pages={self.pages!r})"class NovelSchema(Schema):title = fields.Str(validate=validate.Length(min=1, max=1000))author = fields.Str(data_key="author_name")genre = fields.Str()pages = fields.Int(missing=300, default=500)  # 设置反序列化默认值为300,序列化默认值为500@post_loaddef make(self, data, **kwargs):return Novel(**data)@validates('pages')def validate_pages(self, value):if value <= 0:raise ValidationError('Pages must be a positive integer.')# Create a Novel object
novel = {"title": "公众号:海哥python", "author_name": "暴走的海鸽2", "genre": "Fantasy"}# Serialize the Novel object
novel_schema = NovelSchema()
serialized_data = novel_schema.dump(novel)
logger.info(f"序列化:{serialized_data}")# Deserialization
novel2 = {"title": "公众号:海哥python", "author": "暴走的海鸽", "genre": "Fantasy"}
try:deserialized_data = novel_schema.load(novel2)logger.info(f"反序列化:{deserialized_data}")
except ValidationError as e:logger.error(e.messages)logger.error(f"合法的数据:{e.valid_data}")

C. 自定义字段类型

通过marshmallow,我们可以轻松定义自定义字段,满足各种特殊数据类型的序列化、反序列化需求,使得数据处理更加灵活。

from datetime import datetimefrom marshmallow import Schema, fields, post_load, validates, ValidationError
from loguru import loggerclass CustomField(fields.Int):def _deserialize(self, value, attr, obj, **kwargs):# 将数字加1return value + 1class CustomDateField(fields.Field):def _deserialize(self, value, attr, obj, **kwargs):return value.strftime('%Y-%m-%d')class Novel:def __init__(self, title, author, genre, pages, date):self.title = titleself.author = authorself.genre = genreself.pages = pagesself.date = datedef __repr__(self):return f"Novel(title={self.title!r}, author={self.author!r}, genre={self.genre!r}, pages={self.pages!r}, date={self.date!r})"class NovelSchema(Schema):title = fields.Str()author = fields.Str()genre = fields.Str()pages = CustomField()date = CustomDateField()@post_loaddef make(self, data, **kwargs):return Novel(**data)@validates('pages')def validate_pages(self, value):if value <= 0:raise ValidationError('Pages must be a positive integer.')# Create a Novel object
novel = Novel(title="The Enchanting Adventure", author="Jane Doe", genre="Fantasy", pages=300,date=datetime(2024, 3, 13))# Serialize the Novel object
novel_schema = NovelSchema()
serialized_data = novel_schema.dump(novel)
logger.info(serialized_data)# Deserialization
try:deserialized_data = novel_schema.load(serialized_data)logger.info(deserialized_data)
except ValidationError as e:logger.error(e.messages)logger.error(e.valid_data)

高级应用技巧和场景

部分加载

在多个位置使用同一Schema时,您可能只想通过传递partial来跳过required验证。

from marshmallow import Schema, fieldsclass UserSchema(Schema):name = fields.String(required=True)age = fields.Integer(required=True)result = UserSchema().load({"age": 42}, partial=True)
# OR UserSchema(partial=True).load({'age': 42})
print(result)  # => {'age': 42}

您可以通过设置partial=True来完全忽略缺少的字段。

class UserSchema(Schema):name = fields.String(required=True)age = fields.Integer(required=True)result = UserSchema().load({"age": 42}, partial=True)
# OR UserSchema(partial=True).load({'age': 42})
print(result)  # => {'age': 42}

处理未知字段

默认情况下,如果遇到Schema中没有匹配Field项的键,load将引发marshmallow.exceptions.ValidationError

from marshmallow import Schema, fields, INCLUDEclass UserSchema(Schema):name = fields.String(required=True)age = fields.Integer(required=True)# class Meta:#     unknown = INCLUDEresult = UserSchema().load({"age": 42, "name": "公众号: 海哥python", "email": "16666@qq.com"})
# OR UserSchema(partial=True).load({'age': 42})
print(result)  # => {'age': 42}

我们可以对未知字段进行处理:

  • 可以在Meta类中指定unknown Schema
  • 在实例化时: schema = UserSchema(unknown=INCLUDE)
  • 调用load时: UserSchema().load(data, unknown=INCLUDE)

该选项接受以下选项之一:

  • RAISE (默认值): ValidationError 如果存在任何未知字段,则引发
  • EXCLUDE :排除未知字段
  • INCLUDE :接受并包含未知字段

dump_only“只读”和load_only“只写”字段

from datetime import datetime
from marshmallow import Schema, fields, INCLUDEclass UserSchema(Schema):name = fields.Str()# password is "write-only"password = fields.Str(load_only=True)# created_at is "read-only"created_at = fields.DateTime(dump_only=True)# 序列化
user_data = {"name": "Alice", "password": "s3cr3t", "created_at": datetime.now()}
user_schema = UserSchema()
serialized_data = user_schema.dump(user_data)
print("序列化:", serialized_data)# 反序列化
user_input = {"name": "Bob", "password": "pass123"}
user_schema = UserSchema()
try:deserialized_data = user_schema.load(user_input)print("反序列化:", deserialized_data)
except Exception as e:print("反序列化报错:", e)

排序

对于某些用例,维护序列化输出的字段顺序可能很有用。要启用排序,请将ordered选项设置为true。这将指示marshmallow将数据序列化到collections.OrderedDict

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/3/14 21:40
import datetimefrom marshmallow import Schema, fields, INCLUDEfrom collections import OrderedDictclass User:def __init__(self, name, email):self.name = nameself.email = emailself.created_time = datetime.datetime.now()class UserSchema(Schema):uppername = fields.Function(lambda obj: obj.name.upper())class Meta:fields = ("name", "email", "created_time", "uppername")ordered = Trueu = User("Charlie", "charlie@stones.com")
schema = UserSchema()
res = schema.dump(u)
print(isinstance(res, OrderedDict))
# True
print(res)
# OrderedDict([('name', 'Charlie'), ('email', 'charlie@stones.com'), ('created_time', '2019-08-05T20:22:05.788540+00:00'), ('uppername', 'CHARLIE')])

嵌套模式

对于嵌套属性,marshmallow毫无疑问也能胜任,这正是我认为marshmallow非常强大的地方。

一个Blog可能有一个作者,由User对象表示。

import datetime as dt
from pprint import pprint
from marshmallow import Schema, fieldsclass User:def __init__(self, name, email):self.name = nameself.email = emailself.created_at = dt.datetime.now()self.friends = []self.employer = Noneclass Blog:def __init__(self, title, author):self.title = titleself.author = author  # A User objectclass UserSchema(Schema):name = fields.String()email = fields.Email()created_at = fields.DateTime()class BlogSchema(Schema):title = fields.String()author = fields.Nested(UserSchema)user = User(name="Monty", email="monty@python.org")
blog = Blog(title="Something Completely Different", author=user)
result = BlogSchema().dump(blog)
pprint(result)# {'title': u'Something Completely Different',
#  'author': {'name': u'Monty',
#             'email': u'monty@python.org',
#             'created_at': '2014-08-17T14:58:57.600623+00:00'}}

更多嵌套玩法详见: https://marshmallow.readthedocs.io/en/stable/nesting.html

扩展 Schema

预处理和后处理方法

可以使用pre_loadpost_loadpre_dumppost_dump装饰器注册数据预处理和后处理方法。

from marshmallow import Schema, fields, post_load, pre_load, pre_dump, post_dump
from loguru import loggerclass User:def __init__(self, username, email):self.username = usernameself.email = emaildef __repr__(self):return f"User(username={self.username!r}, email={self.email!r})"class UserSchema(Schema):username = fields.Str()email = fields.Email()@pre_loaddef preprocess_data(self, data, **kwargs):# 在反序列化之前对数据进行预处理if 'username' in data:data['username'] = data['username'].lower()  # 将用户名转换为小写logger.info("do pre_load...")return data@post_loaddef make_user(self, data, **kwargs):# 在反序列化之后创建用户对象logger.info("do post_load...")return User(**data)@pre_dumpdef prepare_data(self, data, **kwargs):# 在序列化之前对数据进行预处理logger.info(type(data))if isinstance(data, User):data.username = data.username.upper()elif 'username' in data:data['username'] = data['username'].upper()  # 将用户名转换为大写logger.info("do pre_dump...")return data@post_dumpdef clean_data(self, data, **kwargs):# 在序列化之后对序列化结果进行清理logger.info(type(data))if 'email' in data:del data['email']  # 删除 email 字段logger.info("do post_dump...")return data# 准备要反序列化的数据
input_data = [{"username": "公众号:海哥Python","email": "haige@qq.com"
}]# 创建 Schema 对象并进行反序列化
user_schema = UserSchema()
result = user_schema.load(input_data, many=True)logger.info(f"Post Load Result: {result}")  # 输出反序列化后的结果# 创建一个 User 对象
user = User(username="公众号:海哥Python", email="haige@qq.com")# 序列化 User 对象
serialized_data = user_schema.dump(user)logger.info(f"Post Dump Result: {serialized_data}")  # 输出序列化后的结果

自定义错误处理

import logging
from marshmallow import Schema, fieldsclass AppError(Exception):passclass UserSchema(Schema):email = fields.Email()def handle_error(self, exc, data, **kwargs):"""Log and raise our custom exception when (de)serialization fails."""logging.error(exc.messages)raise AppError("An error occurred with input: {0}".format(data))schema = UserSchema()
schema.load({"email": "invalid-email"})  # raises AppError

场景示例

REST API中基于marshmallow做参数校验是一种相对优雅的操作。

安装:

# Flask                    3.0.2
# Flask-SQLAlchemy         3.1.1
pip install flask flask-sqlalchemy

应用代码

# demo.py
import datetimefrom flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.exc import NoResultFoundfrom marshmallow import Schema, ValidationError, fields, pre_loadapp = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///quotes.db"db = SQLAlchemy(app)##### MODELS #####class Author(db.Model):  # type: ignoreid = db.Column(db.Integer, primary_key=True)first = db.Column(db.String(80))last = db.Column(db.String(80))class Quote(db.Model):  # type: ignoreid = db.Column(db.Integer, primary_key=True)content = db.Column(db.String, nullable=False)author_id = db.Column(db.Integer, db.ForeignKey("author.id"))author = db.relationship("Author", backref=db.backref("quotes", lazy="dynamic"))posted_at = db.Column(db.DateTime)##### SCHEMAS #####class AuthorSchema(Schema):id = fields.Int(dump_only=True)first = fields.Str()last = fields.Str()formatted_name = fields.Method("format_name", dump_only=True)def format_name(self, author):return f"{author.last}, {author.first}"# Custom validator
def must_not_be_blank(data):if not data:raise ValidationError("Data not provided.")class QuoteSchema(Schema):id = fields.Int(dump_only=True)author = fields.Nested(AuthorSchema, validate=must_not_be_blank)content = fields.Str(required=True, validate=must_not_be_blank)posted_at = fields.DateTime(dump_only=True)# Allow client to pass author's full name in request body# e.g. {"author': 'Tim Peters"} rather than {"first": "Tim", "last": "Peters"}@pre_loaddef process_author(self, data, **kwargs):author_name = data.get("author")if author_name:first, last = author_name.split(" ")author_dict = dict(first=first, last=last)else:author_dict = {}data["author"] = author_dictreturn dataauthor_schema = AuthorSchema()
authors_schema = AuthorSchema(many=True)
quote_schema = QuoteSchema()
quotes_schema = QuoteSchema(many=True, only=("id", "content"))##### API #####@app.route("/authors")
def get_authors():authors = Author.query.all()# Serialize the querysetresult = authors_schema.dump(authors)return {"authors": result}@app.route("/authors/<int:pk>")
def get_author(pk):try:author = Author.query.filter(Author.id == pk).one()except NoResultFound:return {"message": "Author could not be found."}, 400author_result = author_schema.dump(author)quotes_result = quotes_schema.dump(author.quotes.all())return {"author": author_result, "quotes": quotes_result}@app.route("/quotes/", methods=["GET"])
def get_quotes():quotes = Quote.query.all()result = quotes_schema.dump(quotes, many=True)return {"quotes": result}@app.route("/quotes/<int:pk>")
def get_quote(pk):try:quote = Quote.query.filter(Quote.id == pk).one()except NoResultFound:return {"message": "Quote could not be found."}, 400result = quote_schema.dump(quote)return {"quote": result}@app.route("/quotes/", methods=["POST"])
def new_quote():json_data = request.get_json()if not json_data:return {"message": "No input data provided"}, 400# Validate and deserialize inputtry:data = quote_schema.load(json_data)except ValidationError as err:return err.messages, 422first, last = data["author"]["first"], data["author"]["last"]author = Author.query.filter_by(first=first, last=last).first()if author is None:# Create a new authorauthor = Author(first=first, last=last)db.session.add(author)# Create new quotequote = Quote(content=data["content"], author=author, posted_at=datetime.datetime.utcnow())db.session.add(quote)db.session.commit()result = quote_schema.dump(Quote.query.get(quote.id))return {"message": "Created new quote.", "quote": result}if __name__ == "__main__":with app.app_context():db.create_all()app.run(debug=True, port=5000)

启动服务:

python .\demo.py

安装httpie进行测试:

pip install httpie

添加有效报价:

添加无效报价:

查询报价:

常与marshmallow搭档的库还有flask-marshmallowflask-smorest

小结

总的来说,marshmallow库具有强大的序列化、反序列化和数据验证功能,能够适用于各种复杂的数据处理场景,使得数据处理变得更加便捷和高效。

  • 相比于Python标准库中的json库,marshmallow提供了更为灵活且功能强大的数据序列化与验证功能,适用于更多复杂数据结构的处理。
  • Django框架中的序列化器相比,marshmallow更加轻量且不依赖于特定的框架,能够在各种Python项目中灵活应用。

适用于Web开发、RESTful API构建、数据持久化、数据验证等多种场景,尤其适合处理复杂数据结构的情况。

随着数据处理需求的日益复杂,marshmallow作为一款强大的数据序列化与验证库,必将在Python数据处理领域有着更为广阔的发展前景。

无论您是在开发Web API、进行表单验证、与数据库交互,还是进行数据导入和导出,Marshmallow都能够高效地处理数据。希望本文能够帮助您更好地理解和使用Marshmallow

Marshmallow的强大远不止这些,更多使用技巧请查阅官方文档!

最后

今天的分享就到这里。如果觉得不错,点赞关注安排起来吧。

参考

https://marshmallow.readthedocs.io/en/stable/
https://rest-apis-flask.teclado.com/docs/flask_smorest/validation_with_marshmallow/
https://marshmallow.readthedocs.io/en/stable/nesting.html
https://www.cnblogs.com/ChangAn223/p/11305376.html
https://blog.51cto.com/u_15703497/6858837
https://www.cnblogs.com/erhuoyuan/p/17450757.html
https://github.com/marshmallow-code/marshmallow-sqlalchemy

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/746425.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

被军训到的两天

1.gradle7.6.1 1.安装gradle7.6.1,一定要注意的是&#xff0c;使用的JDK是否能用&#xff0c;比如gradle7.6.1用的是JDK11。 2. F:/sofer....是Gradle自己的仓库地址&#xff0c;注意不能和maven使用一样的仓库。 使用specified location,可以避免下本项目的gradle版本&…

ego - 人工智能原生 3D 模拟引擎——基于AI的3D引擎,可以做游戏、空间计算、元宇宙等项目

1. 产品概述:Ego是一款AI本地化的3D模拟引擎,旨在让非技术创作者通过自然语言生成逼真的角色、3D世界和交互式脚本。该平台提供了创建和分享游戏、虚拟世界和交互体验的功能。 2. 定位:Ego定位于解决开放世界游戏和模拟的三大难题:难以编写游戏脚本、非玩家角色无法展现人…

linux系统关闭防火墙和SELINUX及配置网络

一&#xff0c;关闭防火墙和SELINUX 当我们进入界面后&#xff0c;输入用户名root&#xff0c;以及密码&#xff0c;密码我们是看不见的 然后输入指令cat -n /etc/sysconfig/selinux &#xff08;注意空格&#xff09; 输入指令 vi /etc/sysconfig/selinux &#xf…

【Python】进阶学习:一文了解NotImplementedError的作用

【Python】进阶学习&#xff1a;一文了解NotImplementedError的作用 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望…

MongoDB实战面试指南:常见问题一网打尽

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! MongoDB是一款流行的非关系型数据库&#xff0c;以其高效、可扩展的特性受到开发者的青睐。了解MongoDB的架构、存储引擎和数据结…

ETH共识升级之路

简介 根据我们之前的介绍&#xff0c;了解到ETH网络的共识方式&#xff0c;已经从 PoW 切换到了 PoS&#xff0c;今天我们就回顾下升级之路&#xff0c;以及升级带来的影响 最早的共识机制 PoW 以太坊创建之初采用了类似比特币的工作量证明机制&#xff0c;即矿工通过计算哈希函…

CircuitBreaker熔断器

CircuitBreaker熔断器 1、Hystrix目前也进入维护模式 ​ Hystrix是一个用于处理分布式系统的延迟和容错的开源库&#xff0c;在分布式系统里&#xff0c;许多依赖不可避免的会调用失败&#xff0c;比如超时、异常等&#xff0c;Hystrix能够保证在一个依赖出问题的情况下&…

C++演变历史

C 从 C 98 版本到今年确定的 C23&#xff0c;共经历了 6 个版本的迭代。上面这张图里&#xff0c;列出了每次版本更新变化的内容。顶部黑体字的大小说明了版本变化的大小&#xff0c;其中 C14 是字体最小&#xff0c;这个版本是 C11 的增量版本&#xff0c;之所以没有大的变动&…

Server-Sent Events (SSE) 实现从服务器到客户端的实时数据流

前期回顾 避免阻塞主线程 —— Web Worker 示例项目-CSDN博客https://blog.csdn.net/m0_57904695/article/details/136721297?spm1001.2014.3001.5501 目录 CSDN 彩色之外 &#x1f4dd; 前言 &#x1f6e0;️ 安装 ✂️ 运行服务器 ✂️ 运行index.html ♻️ 贡献…

引领短剧风尚,打造全新观影体验——短剧APP开发之旅

随着移动互联网的迅猛发展&#xff0c;短视频和短剧成为了大众休闲娱乐的新宠。为了满足用户对于高质量、快节奏内容的需求&#xff0c;我们决定开发一款全新的短剧APP&#xff0c;为用户带来前所未有的观影体验。 这款短剧APP将集合丰富多样的短剧资源&#xff0c;涵盖各种题…

了解什么是 Redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?

目录 一、面试官心理分析 二、面试题剖析 1. 缓存雪崩 2. 缓存穿透 3. 缓存击穿 一、面试官心理分析 其实这是问到缓存必问的&#xff0c;因为缓存雪崩和穿透&#xff0c;是缓存最大的两个问题&#xff0c;要么不出现&#xff0c;一旦出现就是致命性的问题&#x…

B/S基于云计算的云HIS智慧医院管理系统源码带电子病历编辑器

目录 一、系统概述 二、开发环境 三、系统功能 1、门诊部分 2、住院部分 3、电子病历 4、药物管理 5、统计报表 6、综合维护 7、运营运维 云HIS系统&#xff1a;病案首页 云his系统源码 SaaS应用 功能易扩 统一对外接口管理 现如今&#xff0c;大数据、云计算、移动…

【WPF】Canvas的Path画线 颜色值偏差和面积不准确

图像中左上角2个红色线段 颜色值有偏差&#xff0c;且线段高度不准确&#xff08;我设置的是Red,1&#xff09;。 解决方案&#xff1a; 1、PathGeometry PathFigure LineSegment 2、Stretch设置Fill <Path Stretch"Fill" Stroke"Red"…

Axure软件安装汉化教程

Axure软件安装汉化教程 一、准备教程 下载Axure的软件&#xff0c;并解压打开 二、安装过程 双击Axure软件的运行程序&#xff0c;修改安装程序的路径&#xff0c;默认下一步即可。 三、软件汉化 打开Axure的软件安装路径&#xff0c;将汉化包复制粘贴进入到Axure RP 9安装…

C语言从入门到实战————数组和指针的深入理解

前言 在C语言中&#xff0c;数组和指针有的密切得联系&#xff0c;因为数组名本身就相当于一个指针常量。指针是一个变量&#xff0c;专门用来存储另一个变量的内存地址&#xff0c;通过这个地址可以访问和操作该变量的值&#xff0c;同时也包括数组。数组是一组连续存储的同类…

【CKA模拟题】查询消耗CPU最多的Pod

题干 For this question, please set this context (In exam, diff cluster name) 对于此问题&#xff0c;请设置此上下文&#xff08;在考试中&#xff0c;diff 集群名称&#xff09; kubectl config use-context kubernetes-adminkubernetesFind the pod that consumes the …

QT信号与槽实现方式

1、第一种实现方式 在QT开发工具UI界面先拖入按钮&#xff0c;然后鼠标右键拖入按钮&#xff0c;点击选中槽&#xff0c;在页面选着需要的信号&#xff0c;然后OK&#xff0c;随即将会跳转到类的.cpp文件&#xff0c;&#xff08;这种UI代码结合的方式&#xff0c;会自动去绑定…

在SwiftUI中使用Buider模式创建复杂组件

在SwiftUI中使用Buider模式创建复杂组件 我们在前面的博客闲聊SwiftUI中的自定义组件中聊到了如何在SwiftU中创建自定义组件。 在那里&#xff0c;我们创建了一个非常简单的组件RedBox&#xff0c;它将展示内容增加一个红色的边框。 RedBox非常简单&#xff0c;我们用普通的方…

腾讯春招后端一面(算法篇)

前言&#xff1a; 哈喽大家好&#xff0c;前段时间在小红书和牛客上发了面试的经验贴&#xff0c;很多同学留言问算法的具体解法&#xff0c;今天就详细写个帖子回复大家。 因为csdn是写的比较详细&#xff0c;所以更新比较慢&#xff0c;大家见谅~~ 就题目而言&#xff0c;…

深度学习_GoogLeNet_4

目标 知道GoogLeNet网络结构的特点能够利用GoogLeNet完成图像分类 一、开发背景 GoogLeNet在2014年由Google团队提出&#xff0c; 斩获当年ImageNet(ILSVRC14)竞赛中Classification Task (分类任务) 第一名&#xff0c;VGG获得了第二名&#xff0c;为了向“LeNet”致敬&#x…