使用棉花糖而不重复自己

Tey*_*ras 11 python dry marshmallow

根据官方的Marshmallow文档,建议声明一个Schema,然后有一个单独的类接收加载的数据,如下所示:

class UserSchema(Schema):
    name = fields.Str()
    email = fields.Email()
    created_at = fields.DateTime()

    @post_load
    def make_user(self, data):
        return User(**data)
Run Code Online (Sandbox Code Playgroud)

但是,我的User课程看起来像这样:

class User:
    def __init__(name, email, created_at):
        self.name = name
        self.email = email
        self.created_at = created_at
Run Code Online (Sandbox Code Playgroud)

这似乎不必要地重复我自己,我真的不喜欢不得不再写三次属性名称.但是,我确实喜欢IDE自动完成和静态类型检查明确定义的结构.

那么,有没有最佳实践根据Marshmallow模式加载序列化数据而不定义另一个类?

Mos*_*oye 5

对于vanilla Python类,没有一种现成的方法来定义模式的类而不重复字段名称.

例如,如果您使用SQLAlchemy,则可以直接从模型定义架构marshmallow_sqlalchemy.ModelSchema:

from marshmallow_sqlalchemy import ModelSchema
from my_alchemy_models import User

class UserSchema(ModelSchema):
    class Meta:
        model = User
Run Code Online (Sandbox Code Playgroud)

同样适用于使用的flask-sqlalchemy flask_marshmallow.sqla.ModelSchema.

对于vanilla Python类,您可以定义一次字段并将其用于模式和模型/类:

USER_FIELDS = ('name', 'email', 'created_at')

class User:
    def __init__(self, name, email, created_at):
        for field in USER_FIELDS:
            setattr(self, field, locals()[field])

class UserSchema(Schema):
    class Meta:
        fields = USER_FIELDS

    @post_load
    def make_user(self, data):
        return User(**data)
Run Code Online (Sandbox Code Playgroud)

  • 使用 `setattr` 定义类会阻止我指定验证规则,毕竟这就是我为 Marshmallow 烦恼的原因。我可以使用`collections.namedtuple`,但是当涉及嵌套模式时,这看起来相当笨拙。 (2认同)

Vot*_*fee 2

除非您需要反序列化为特定类或者需要自定义序列化逻辑,否则您可以简单地执行此操作(改编自https://kimsereylam.com/python/2019/10/25/serialization-with-marshmallow.html):

from marshmallow import Schema, fields
from datetime import datetime

class UserSchema(Schema):
    name = fields.Str(required=True)
    email = fields.Email()
    created_at = fields.DateTime()

schema = UserSchema()
data = { "name": "Some Guy", "email": "sguy@google.com": datetime.now() }
user = schema.load(data)
Run Code Online (Sandbox Code Playgroud)

您还可以在类中创建一个函数,该函数创建带有验证规则的字典,尽管它仍然是多余的,但它允许您将所有内容保留在模型类中:

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

        @classmethod
        def Schema(cls):
            return {"name": fields.Str(), "email": fields.Email(), "created_at": fields.DateTime()}

UserSchema = Schema.from_dict(User.Schema)
Run Code Online (Sandbox Code Playgroud)

如果您需要强类型和完整的验证功能,请考虑flask-pydantic或marshmallow-dataclass。

marshmallow-dataclass 提供了许多与 marshmallow 类似的验证功能。但它有点束缚你的双手。它没有对自定义字段/多态性的内置支持(必须使用 marshmallow-union 来代替),并且似乎与 Flask-marshmallow 和 marshmallow-sqlalchemy 等堆栈包配合得不好。 https://pypi.org/project/marshmallow-dataclass/

from typing import ClassVar, Type
from marshmallow_dataclass import dataclasses
from marshmallow import Schema, field, validate


@dataclass
class Person:
    name: str = field(metadata=dict(load_only=True))
    height: float = field(metadata=dict(validate=validate.Range(min=0)))
    Schema: ClassVar[Type[Schema]] = Schema


Person.Schema().dump(Person('Bob', 2.0))
# => {'height': 2.0}
Run Code Online (Sandbox Code Playgroud)

从验证的角度来看,flask-pydantic 不太优雅,但提供了许多相同的功能,并且验证内置于类中。请注意,像最小/最大这样的简单验证比棉花糖更尴尬。就我个人而言,我更喜欢将视图/API 逻辑保留在类之外。https://pypi.org/project/Flask-Pydantic/

from typing import Optional
from flask import Flask, request
from pydantic import BaseModel

from flask_pydantic import validate

app = Flask("flask_pydantic_app")

class QueryModel(BaseModel):
  age: int

class ResponseModel(BaseModel):
  id: int
  age: int
  name: str
  nickname: Optional[str]

# Example 1: query parameters only
@app.route("/", methods=["GET"])
@validate()
def get(query:QueryModel):
  age = query.age
  return ResponseModel(
    age=age,
    id=0, name="abc", nickname="123"
    )
Run Code Online (Sandbox Code Playgroud)