【问题标题】:How to dynamically generate marshmallow schemas for SQLAlchemy models如何为 SQLAlchemy 模型动态生成棉花糖模式
【发布时间】:2017-08-10 23:54:43
【问题描述】:

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

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

我希望每个模型都有一个架构,以便它可以轻松地转储自己。 创建默认 Schema 并设置 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

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

【问题讨论】:

    标签: python flask sqlalchemy marshmallow


    【解决方案1】:

    您可以创建一个类装饰器,将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 类是使用custom metaclass 构造的,它检查通过执行类主体和does its thing 创建的命名空间。当你修改已经构建好的类时,已经来不及了。

    如果您不熟悉 Python 中的元类,请在 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 并在构建架构之前检查类,以便它处理特殊情况,例如枚举。

    【讨论】:

    • 没有sqlalchemy你能做到吗?我有一些包含列表的非规范化类,但 sqlalchemy 不允许列表。
    • UPD:发现 this project 使用 Python 的数据类而不是 sqlalchemy。还有更多语法糖!
    • 如果模型有Enum,会报错:Object of type MyEnum is not JSON serializabledumping
    • @jgozal 这很不幸,但与装饰本身并没有严格的关系,而是与棉花糖的工作原理有关——我最终对它并不熟悉。如果它需要额外的信息来使其正确序列化枚举,那么您可以将add_schema 转换为接受必要参数的装饰器工厂,并返回“配置的”装饰器闭包。
    • 感谢@IljaEverilä。这是一个可靠的解决方案。我正在研究一种在模式中查找枚举的方法,方法是在创建模式后查看 _declared_fields,然后将它们中的每一个动态地转换为棉花糖枚举字段。如果我能让它工作,我会告诉你的!我在这里也发布了一个问题:stackoverflow.com/questions/63697476/…
    【解决方案2】:

    来自 marshmallow-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

    【讨论】:

    • 一个非常好的方法,也是一般 SQLA 事件的一个很好的例子。
    【解决方案3】:

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

    # 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
    

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-02-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-21
      • 2021-12-14
      • 2022-11-04
      • 1970-01-01
      相关资源
      最近更新 更多