Marshmallow,一个有点甜的Python库

解决Python模块缺失错误:以marshmallow为例 在Python开发中,模块缺失错误是常见问题,尤其是像marshmallow这样的数据序列化库。数据序列化是将复杂数据结构转换为可存储或传输格式的过程,广泛应用于API开发和数据处理。marshmallow作为Python生态中的核心库,提供了数据验证和转换功能。当出现ModuleNotFoundError时,通常源于虚拟环境未激活、依赖未正确安装或Python环境混乱。理解这些底层原理后,开发者可以系统化排查问题,确保开发环境和生产环境的一致性。本文以marshmallow为例,详细解析错误原因并提供解决 阅读详情

前言

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

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

import json

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

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

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

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

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

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

楔子

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

class Novel:
    def __init__(self, title, author, genre, pages):
        self.title = title
        self.author = author
        self.genre = genre
        self.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 json

novel_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: str
    author: str
    genre: str
    pages: 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数据类型转换为JSON、XML等格式,也能反向将外部数据解析为Python对象。

from marshmallow import Schema, fields, post_load


class Novel:
    def __init__(self, title, author, genre, pages):
        self.title = title
        self.author = author
        self.genre = genre
        self.pages = pages

    def __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_load
    def 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)

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


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

from marshmallow import Schema, fields, post_load
from loguru import logger


class Novel:
    def __init__(self, title, author, genre, pages):
        self.title = title
        self.author = author
        self.genre = genre
        self.pages = pages

    def __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_load
    def 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类具有两个参数用于控制序列化的输出,即only和exclude。only 参数返回的输出结果仅包含列表中指定的类属性,而exclude则正好相反,它会排除列表中指定的类属性。

from marshmallow import Schema, fields, post_load, validates, ValidationError, validate
from loguru import logger


class Novel:
    def __init__(self, title, author, genre="Fantasy2", pages=300):
        self.title = title
        self.author = author
        self.genre = genre
        self.pages = pages

    def __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_load
    def 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 logger


class Novel:
    def __init__(self, title, author, genre, pages):
        self.title = title
        self.author = author
        self.genre = genre
        self.pages = pages

    def __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_load
    def 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 logger


class Novel:
    def __init__(self, title, author, genre, pages):
        self.title = title
        self.author = author
        self.genre = genre
        self.pages = pages

    def __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_load
    def 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 logger


class Novel:
    def __init__(self, title, author, genre, pages):
        self.title = title
        self.author = author
        self.genre = genre
        self.pages = pages

    def __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_load
    def 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 logger


class Novel:
    def __init__(self, title, author, genre, pages):
        self.title = title
        self.author = author
        self.genre = genre
        self.pages = pages

    def __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_load
    def 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 datetime

from marshmallow import Schema, fields, post_load, validates, ValidationError
from loguru import logger


class CustomField(fields.Int):
    def _deserialize(self, value, attr, obj, **kwargs):
        # 将数字加1
        return value + 1


class 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 = title
        self.author = author
        self.genre = genre
        self.pages = pages
        self.date = date

    def __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_load
    def 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, fields


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}

您可以通过设置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, INCLUDE


class UserSchema(Schema):
    name = fields.String(required=True)
    age = fields.Integer(required=True)

    # class Meta:
    #     unknown = INCLUDE


result = 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, INCLUDE


class 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 datetime

from marshmallow import Schema, fields, INCLUDE

from collections import OrderedDict


class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
        self.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 = True


u = 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, fields


class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
        self.created_at = dt.datetime.now()
        self.friends = []
        self.employer = None


class Blog:
    def __init__(self, title, author):
        self.title = title
        self.author = author  # A User object


class 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_load、post_load、pre_dump和post_dump装饰器注册数据预处理和后处理方法。

from marshmallow import Schema, fields, post_load, pre_load, pre_dump, post_dump
from loguru import logger


class User:
    def __init__(self, username, email):
        self.username = username
        self.email = email

    def __repr__(self):
        return f"User(username={self.username!r}, email={self.email!r})"


class UserSchema(Schema):
    username = fields.Str()
    email = fields.Email()

    @pre_load
    def preprocess_data(self, data, **kwargs):
        # 在反序列化之前对数据进行预处理
        if 'username' in data:
            data['username'] = data['username'].lower()  # 将用户名转换为小写
        logger.info("do pre_load...")
        return data

    @post_load
    def make_user(self, data, **kwargs):
        # 在反序列化之后创建用户对象
        logger.info("do post_load...")
        return User(**data)

    @pre_dump
    def 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_dump
    def 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, fields


class AppError(Exception):
    pass


class 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 datetime

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.exc import NoResultFound

from marshmallow import Schema, ValidationError, fields, pre_load

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///quotes.db"

db = SQLAlchemy(app)

##### MODELS #####


class Author(db.Model):  # type: ignore
    id = db.Column(db.Integer, primary_key=True)
    first = db.Column(db.String(80))
    last = db.Column(db.String(80))


class Quote(db.Model):  # type: ignore
    id = 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_load
    def 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_dict
        return data


author_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 queryset
    result = 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."}, 400
    author_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."}, 400
    result = 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 input
    try:
        data = quote_schema.load(json_data)
    except ValidationError as err:
        return err.messages, 422
    first, last = data["author"]["first"], data["author"]["last"]
    author = Author.query.filter_by(first=first, last=last).first()
    if author is None:
        # Create a new author
        author = Author(first=first, last=last)
        db.session.add(author)
    # Create new quote
    quote = 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-marshmallow、flask-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

Python 库 Marshmallow 的功能、用法和优势 其实,尽管这些方法能够完成基本的序列化和反序列化任务,但在处理更加复杂的数据结构、数据验证、预处理逻辑以及与其他库的集成等方面,它们可能显得不够灵活和方便。novel = {"title": "公众号:海哥python", "author_name": "暴走的海鸽2", "genre": "Fantasy"}novel1 = Novel(title="海哥python1", author="暴走的海鸽", genre="Fantasy", pages=300) 阅读详情

相关推荐

Python Marshmallow【对象序列化和反序列化库】 简介与使用指南

id = fields.Int(dump_only=True) # 只用于序列化输出created_at = fields.DateTime(dump_only=True) # 自动忽略输入。

记录点滴 787

Python之marshmallow

python之Marshmallow marshmallow是一个用来将复杂的orm对象与python原生数据类型之间相互转换的库,简而言之,就是实现object -> dict, objects -> list, string -> dict 和 string -> list。 序列化:序列化的意思是将数据对象转化为可存储或可传输的数据类型 反序列化:将可存储或可传输的数据类型转化为数据对象 要进行序列化或反序列化,首先我们需要一个用来操作的object,这里我们先定义一个类:

最爱吃大米的博客 3167

ESP32驱动GC9A01圆形屏实战:Arduino平台LVGL移植与图像显示优化

本文详细介绍了在Arduino平台上使用ESP32驱动GC9A01圆形屏的实战指南,包括硬件连接、LVGL移植与图像显示优化。通过SPI配置、LVGL库安装和双缓冲机制等技巧,帮助开发者快速实现高效显示效果,适用于智能家居控制面板等应用场景。

weixin_29182481的博客 732

总结:Python 轻量级序列化和反序列化包 marshmallow 详细使用指南

第一部分、marshmallow 基本的数据类型及基础属性介绍 1、基础数据类型 fields.String(*, default, missing, data_key, …) 字符串类型 fields.UUID(*, default, missing, data_key, …) UUID字符串类型 fields.Integer(*, strict, **kwargs) 整数类型 fields.Decimal(places, rounding, *, allow_nan, …) 支持Python中的deci

haeasringnar的博客 4485

Obsidian的Git插件设置配置全流程 -- 简单的电脑端多平台同步方案及常见问题

Git同步obsidian的保姆级教程和常见问题

weixin_44370439的博客 1万+

什么是PMOS?什么是NMOS?两者有什么区别?

这个电压差被称为阈值电压(Vth),通常为0.7V至2V(低压NMOS)或4V至10V(功率NMOS)。当Vgs(栅极-源极电压)大于Vth时,沟道中的电子被吸引到栅极下方,形成导电通路,使电流可以从漏极流向源极。NMOS与PMOS的结构类似,都由三大部分组成:栅极(Gate)、源极(Source)和漏极(Drain)。由于NMOS在开启时,电子是主要的载流子,电子的迁移率比空穴高,因此NMOS通常具有更低的导通电阻(Rds(on)),这使其成为高速开关电路中的首选。它的沟道由电子(负载子)传导。

硅农深芯的博客 6430

python之Marshmallow

在编写Schema类的时候,可以向内建的fields中设置validate参数的值来定制验证的逻辑, validate的值可以是函数, 匿名函数lambda, 或者是定义了__call__的对象。try:raise ValidationError("name长度必须大于2位")raise ValidationError("name长度不能大于6位")try:# {'name': ['name长度不能大于6位']}注意:只会在反序列化的时候发生验证!序列化的时候不会验证!

qq_29800373的博客 1038

第1期技术: DQN算法原理及实现过程

利用神经网络近似值函数的方法表示为: V^(s,w)≈Vπ(s)q^(s,a,w)≈qπ(s,a) \hat{V}(s, w) \approx V_{\pi}(s) \\ \hat{q}(s, a, w) \approx q_{\pi}(s, a) V^(s,w)≈Vπ​(s)q^​(s,a,w)≈qπ​(s,a) 那么具体的工作过程是怎样实现的? 以及如何从端到端的过程,本文将讲解Deep Q Network(DQN, 而这正是由DeepMind于2013年和2015年分别提出的两篇论文《Playi

深度强化学习实验室:一个“开源开放、共享共进”的强化学习学术组织。 4953

marshmallow,一个超酷的 Python 库!

Marshmallow是一个用于对象序列化(serialization)和反序列化(deserialization)的Python库。它的主要目的是将Python对象转换为诸如JSON、XML或其他格式的数据,以便在不同应用程序之间进行通信。Marshmallow不仅支持数据的序列化,还支持将序列化数据反序列化为Python对象。灵活性:Marshmallow允许您定义数据的序列化和反序列化规则,以满足特定需求。数据验证:它提供了强大的验证功能,以确保序列化和反序列化的数据是有效的。

涛哥聊Python 2199

AS5600磁编码器IIC通信与角度读取实战指南

本文提供了AS5600磁编码器通过IIC通信协议实现高精度角度读取的完整实战指南。详细解析了硬件连接要点、IIC通信时序、Arduino与STM32平台驱动代码,并分享了多圈测量、噪声滤波等进阶应用与避坑技巧,帮助开发者快速将这款非接触式角度传感器集成到电机控制、旋钮等项目中。

weixin_29172201的博客 242

图文:银行核心账务处理逻辑(白话篇)

银行账务系统分为对内账(记录银行自身资产负债)和对外账(处理客户交易),通过特定编码规则的账号体系进行管理。采用T型账户结构和凭证制度确保每笔交易可追溯,通过明细核算与综合核算双线并行、每日轧账核对(科目日结单、总账、日计表)实现账务精准平衡。从业务触发到最终账平的全流程体现了银行账务"分层记录、双向核对、闭环校验"的核心原则,保障资金流动的透明性与安全性。

银行信息系统应用架构详解 537

深入了解Python Marshmallow库:数据验证及序列化神器

Marshmallow是一款功能强大且灵活的库,用于将复杂的数据类型(如对象)转换为Python原生数据类型或从Python原生数据数据类型转换为对象。

zhangkexin_z的博客 1362

基于STM32、HAL库的WF162A 11BA T&R气压传感器 驱动程序设计

/ 转换为7位地址。* @param temperature: 存储读取到的温度值(°C)* @param temperature: 存储读取到的温度值(°C)* @param pressure: 存储读取到的压力值(bar)* @param pressure: 存储读取到的压力值(bar)* @param range: 压力范围(bar)

corlin6688的博客 361

Marshmallow 库

marshmallow是一个用来将复杂的orm对象与python原生数据类型之间相互转换的库,简而言之,就是实现object -> dict, objects -> list, string -> dict 和 string -> list。序列化:序列化的意思是将数据对象转化为可存储或可传输的数据类型 反序列化:将可存储或可传输的数据类型转化为数据对象demo"name" , "age" }) 参数可以 在实例化 sechema 对象时配置,也可以在使用序列化 / 反序列化时 传入参数名。

go|Python的个人博客 1225

R 语言 ggplot2 绘制精美高级全球地图

在本教程中,我们将学习如何使用R语言和ggplot2包绘制精美高级地图。ggplot2是一个功能强大的数据可视化包,它可以让我们创建漂亮、交互式的地图。我们将使用R中的一些其他包来处理地理空间数据,以便与ggplot2无缝协作。

Mrrunsen的博客 1866

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Marshmallow Enum:为 Marshmallow 提供枚举支持的 Python 库

Marshmallow Enum:为 Marshmallow 提供枚举支持的 Python 库 开源项目 marshmallow_enum 是一个专为 Marshmallow 提供枚举支持的 Python 库。它允许开发者在 Marshmallow 模式(Schema)中使用枚举类型,简化了序列化和反序列化的过程。该项目主要使用 Python 编程语言。 项目核心功能 枚举支持:marshmal...

gitblog_00702的博客 342

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

探索 marshmallow-jsonschema:将 marshmallow 模式转换为 JSON Schema

探索 marshmallow-jsonschema:将 marshmallow 模式转换为 JSON Schema 在现代软件开发中,数据验证和表单生成是不可或缺的环节。marshmallow-jsonschema 项目提供了一个强大的工具,能够将 Python 中的 marshmallow 模式转换为 JSON Schema Draft v7 兼容的 JSON Schema。这不仅简化了跨系统数...

gitblog_00959的博客 979

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Python(二)Marshmallow 库相关学习

0. 前言 Marshmallow 是一个用于将 ORM 对象与 Python 原生数据类型之间转换的库。实现 object→ dict、object→ list、string→ dict 和 string→ list 等功能 1. Schema 实现一个 object 和 json 之间的转化需要一个 Schema 对象作为中间载体,同时实现校验的功能: cl...

weixin_30273501的博客 501

Python marshmallow库:灵活的数据序列化与反序列化

更多Python学习内容:ipengtao.com在现代 Web 开发和数据处理项目中,数据序列化和反序列化是不可避免的任务。Python 中的 Marshmallow 库提供了一种简洁且强大的方式来对复杂的数据结构进行序列化(将数据对象转换为JSON或字典)和反序列化(将JSON或字典转换为对象)。无论是在API数据交互中,还是在验证用户输入时,Marshmallow都能提供极高的灵活性和可扩展...

GitHub_miao的博客 952

Python序列化:我为什么放弃使用json库,转而拥抱Marshmallow

Marshmallow的核心概念是Schema。你可以通过定义Schema来指定如何将Python对象序列化为JSON,以及如何将JSON反序列化为Python对象。举个例子,假设你有一个User# 创建一个User对象user = User(name="小李", age=30, email="xiaoli@example.com")# 创建UserSchema实例# 序列化对象(User -> JSON)这里,我们定义了一个User类和一个UserSchema类,UserSchema继承自。

奔跑的蜗牛 1091

Marshmallow-SQLAlchemy 使用教程

Marshmallow-SQLAlchemy 是一个开源的 Python 库,它将 Marshmallow 序列化/反序列化库与 SQLAlchemy ORM 集成在一起。这个库允许开发者轻松地将 SQLAlchemy 模型转换为 Python 数据类型,反之亦然。通过使用 Marshmallow-SQLAlchemy,开发者可以更高效地进行数据库操作和数据验证。 ## 项目快速启动 ###

gitblog_00634的博客 1054

marshmallow:Python 对象序列化,一个库就够了

marshmallow是一个Python对象序列化库(GitHub 7.2k+ Star),主要用于复杂数据类型与Python原生类型间的转换。它通过声明式Schema实现三大功能:序列化(对象转dict/JSON)、反序列化(数据转对象)和输入校验。典型应用场景包括Web API开发中处理ORM对象序列化、嵌套关系转换和字段验证。其优势在于用简洁的Schema定义替代手工字段处理,支持日期格式化、嵌套对象等常见需求,显著减少重复代码。该库与框架无关,适合Flask/Django/FastAPI等Web框架

omegamatrix65797的博客 240

Marshmallow——python中一个轻量级的数据格式转换的模块

官方文档:https://marshmallow.readthedocs.io/en/latest/Marshmallow,中文译作:棉花糖。是一个轻量级的数据格式转换的模块,也叫序列化和反序列化模块,常用于将复杂的orm模型对象与python原生数据类型之间相互转换。marshmallow提供了丰富的api功能。序列化[可以把数据对象转化为可存储或可传输的数据类型,例如:objects/object->list/dict,dict/list->string]

m0_66925868的博客 1310

Flask Marshmallow基本使用

Marshmallow 官方文档:https://marshmallow.readthedocs.io/en/latest/ Marshmallow,中文译作:棉花糖。是一个轻量级的数据格式转换的模块,也叫序列化和反序列化模块,常用于将复杂的orm模型对象与python原生数据类型之间相互转换。marshmallow提供了丰富的api功能。如下: Serializing 序列化[可以把数据对象转化为可存储或可传输的数据类型,例如:objects/object->list/dict,dict/li

yutu75的博客 8805
上一篇: 猴子吃桃:玩转二分思维
下一篇: Python方法链调用的优雅之处
海哥python
博客等级 码龄9年 1487粉丝 74原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海哥python

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值