如何为 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))
食谱中还有一个例子:
我正在使用 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))
食谱中还有一个例子: