11

我正在使用 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

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

4

3 回答 3

11

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

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

接着

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

该模式将作为类属性提供Entry.Schema

您最初的尝试失败的原因是棉花糖Schema类是使用自定义元类构造的,该元类检查通过执行类主体创建的命名空间并执行其操作。当你修改已经构建的类时,为时已晚。

如果您不熟悉 Python 中的元类,请在语言参考中阅读它们。它们是一种允许伟大事物和严重滥用的工具。


一些更复杂的类型,例如枚举,需要额外的信息和专用的字段类型才能正常工作。例如,使用 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):
    ...

当然,另一种方法是让装饰器本身更智能,并在构建模式之前检查类,以便它处理特殊情况,例如枚举。

于 2017-03-19T21:22:40.207 回答
2

从棉花糖-sqlalchemy 食谱:

“自动为 SQLAlchemy 模型生成模式如果不覆盖上面详述的任何生成字段,实现大量模式可能会很乏味。SQLAlchemy 有一个钩子,可用于触发模式的创建,将它们分配给SQLAlchemy 模型属性”。

我使用 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

于 2019-05-20T16:36:46.200 回答
1

棉花糖食谱规定了几个替代选项,用于将通用模式选项放入基类中。这是直接来自文档的快速示例:

# 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

这种方法的优点是您可以为特定模型添加更多的反序列化

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

于 2018-01-09T19:42:34.513 回答