如何为 SQLAlchemy 模型动态生成棉花糖模式

How to dynamically generate marshmallow schemas for SQLAlchemy models

我正在使用 SQLAlchemy 模型创建 Flask API。 我不想为我拥有的每个模型定义模式,我不想每次都这样做:

class EntrySchema(ma.ModelSchema):
    class Meta:
        model = Entry

我希望每个模型都有一个模式,这样它就可以很容易地自行转储。 创建默认架构并设置 Schema.Meta.model 无效:

class Entry(db.Model):
    __tablename__ = 'entries'

    id = db.Column(db.Integer, primary_key=True)
    started_at = db.Column(db.DateTime)
    ended_at = db.Column(db.DateTime)
    description = db.Column(db.Text())

    def __init__(self, data):
        for key in data:
            setattr(self, key, data[key])

        self.Schema = Schema
        self.Schema.Meta.model = self.__class__

    def dump(self):
        schema = self.Schema()
        result = schema.dump(self)
        return result


class Schema(ma.ModelSchema):
    class Meta:
        pass

为什么覆盖了模型的通用架构与声明了模型的架构不同?

您可以创建一个 class 装饰器,将 Schema 添加到您的模型中:

def add_schema(cls):
    class Schema(ma.ModelSchema):
        class Meta:
            model = cls
    cls.Schema = Schema
    return cls

然后

@add_schema
class Entry(db.Model):
    ...

架构将作为 class 属性 Entry.Schema

您最初尝试失败的原因是棉花糖 Schema classes 是使用 custom metaclass, which inspects the namespace created from executing the class body and does its thing 构建的。当你修改已经构造好的class时,已经来不及了。

如果您不熟悉 Python 中的元classes,请阅读 language reference 中的内容。它们是一种允许伟大的事情和伟大的误用的工具。


一些更复杂的类型,例如枚举,需要额外的信息和专用的字段类型才能正常工作。例如 using marshmallow-enum 和装饰器工厂模式,可以配置模型模式以适应枚举:

from marshmallow_enum import EnumField

def add_schema(**kwgs):
    def decorator(cls): 
        class Meta:
            model = cls

        schema = type("Schema", (ma.ModelSchema,), {"Meta": Meta, **kwgs})
        cls.Schema = schema
        return cls

    return decorator

...


@add_schema(
    my_enum=EnumField(MyEnumType, by_value=True)
)
class Entry(db.Model):
    ...

当然,另一种方法是 make the decorator itself smarter 并在构建模式之前检查 class,以便它处理特殊情况,例如枚举。

marshmallow recipes 规定了几个替代选项,用于将通用模式选项放入基础 class。这是直接来自文档的简单示例:

# myproject/schemas.py

from marshmallow_sqlalchemy import ModelSchema

from .db import Session

class BaseSchema(ModelSchema):
    class Meta:
        sqla_session = Session

然后扩展基础架构:

# myproject/users/schemas.py

from ..schemas import BaseSchema
from .models import User

class UserSchema(BaseSchema):

    # Inherit BaseSchema's options
    class Meta(BaseSchema.Meta):
        model = User

这种方法的优点是您可以向特定模型添加更多 de/serialization

链接文档中的更多示例和食谱

来自marshmallow-sqlalchemy 食谱:

"Automatically Generating Schemas For SQLAlchemy Models It can be tedious to implement a large number of schemas if not overriding any of the generated fields as detailed above. SQLAlchemy has a hook that can be used to trigger the creation of the schemas, assigning them to the SQLAlchemy model property ".

我的示例使用 flask_sqlalchemy & marshmallow_sqlalchemy:

from flask_sqlalchemy import SQLAlchemy
from marshmallow_sqlalchemy import ModelConversionError, ModelSchema
from sqlalchemy import event
from sqlalchemy.orm import mapper


db = SQLAlchemy()


def setup_schema(Base, session):
    # Create a function which incorporates the Base and session information
    def setup_schema_fn():
        for class_ in Base._decl_class_registry.values():
            if hasattr(class_, "__tablename__"):
                if class_.__name__.endswith("Schema"):
                    raise ModelConversionError(
                        "For safety, setup_schema can not be used when a"
                        "Model class ends with 'Schema'"
                    )

                class Meta(object):
                    model = class_
                    sqla_session = session

                schema_class_name = "%sSchema" % class_.__name__

                schema_class = type(schema_class_name, (ModelSchema,), {"Meta": Meta})

                setattr(class_, "Schema", schema_class)

    return setup_schema_fn


event.listen(mapper, "after_configured", setup_schema(db.Model, db.session))

食谱中还有一个例子:

https://marshmallow-sqlalchemy.readthedocs.io/en/latest/recipes.html#automatically-generating-schemas-for-sqlalchemy-models