在Marshmallow Schema中以编程方式定义字段

时间:2017-02-14 16:29:45

标签: python marshmallow

说我有这样的架构:

class MySchema(Schema):

    field_1 = Float()
    field_2 = Float()
    ...
    field_42 = Float()

有没有办法以编程方式将这些字段添加到类中?

这样的事情:

class MyClass(BaseClass):

    FIELDS = ('field_1', 'field_2',..., 'field_42')

    for field in FIELDS:
        setattr(?, field, Float())  # What do I replace this "?" with?

我见过关于动态向类实例添加属性的帖子,但这是不同的,因为

  • 我不想修补一个实例而是一个类
  • Marshmallow Schema使用自定义元类

同样的问题可能适用于其他模型定义库,如ODM / ORM(uMongo / MongoEngine,SQL Alchemy,...)

5 个答案:

答案 0 :(得分:10)

您需要做的就是使用 type()函数来构建具有您想要的任何属性的类:

MySchema = type('MySchema', (marshmallow.Schema,), {
    attr: marshmallow.fields.Float()
    for attr in FIELDS
})

您甚至可以在那里使用不同类型的字段:

fields = {}
fields['foo'] = marshmallow.fields.Float()
fields['bar'] = marshmallow.fields.String()
MySchema = type('MySchema', (marshmallow.Schema,), fields)

或作为自定义的基础:

class MySchema(type('_MySchema', (marshmallow.Schema,), fields)):
    @marshmallow.post_dump
    def update_something(self, data):
        pass

答案 1 :(得分:1)

我设法通过继承默认的元类来完成它:

class MySchemaMeta(SchemaMeta):

    @classmethod
    def get_declared_fields(mcs, klass, cls_fields, inherited_fields, dict_cls):
        fields = super().get_declared_fields(klass, cls_fields, inherited_fields, dict_cls)
        FIELDS = ('field_1', 'field_2',..., 'field_42')
        for field in FIELDS:
            fields.update({fluid: Float()})
        return fields

class MySchema(Schema, metaclass=MySchemaMeta):

    class Meta:
        strict = True

我使这更通用:

class DynamicSchemaOpts(SchemaOpts):

    def __init__(self, meta):
        super().__init__(meta)
        self.auto_fields = getattr(meta, 'auto_fields', [])


class DynamicSchemaMeta(SchemaMeta):

    @classmethod
    def get_declared_fields(mcs, klass, cls_fields, inherited_fields, dict_cls):

        fields = super().get_declared_fields(klass, cls_fields, inherited_fields, dict_cls)

        for auto_field_list in klass.opts.auto_fields:
            field_names, field = auto_field_list
            field_cls = field['cls']
            field_args = field.get('args', [])
            field_kwargs = field.get('kwargs', {})
            for field_name in field_names:
                fields.update({field_name: field_cls(*field_args, **field_kwargs)})

        return fields


class MySchema(Schema, metaclass=DynamicSchemaMeta):

    OPTIONS_CLASS = DynamicSchemaOpts

    class Meta:
        strict = True
        auto_fields = [
            (FIELDS,
             {'cls': Float}),
        ]

我没写

class Meta:
    strict = True
    auto_fields = [
        (FIELDS, Float()),
    ]

因为那时所有这些字段都会共享相同的Field实例。

必须单独指定Field及其args / kwargs:

    class Meta:
        strict = True
        auto_fields = [
            (FIELDS,
             {'cls': Nested,
              'args': (MyEmbeddedSchema),
              'kwargs': {'required': True}
             }),
        ]

由于多个字段共享同一个实例,我没有任何示例用例失败,但听起来不安全。如果这种预防措施毫无用处,那么代码可以简化并提高可读性:

    class Meta:
        strict = True
        auto_fields = [
            (FIELDS, Nested(MyEmbeddedSchema, required=True)),
        ]

显然,这个答案特定于Marshmallow,不适用于其他ODM / ORM库。

答案 2 :(得分:1)

  

类别Meta范例类使您可以指定要使用的属性   要序列化。棉花糖将选择适当的字段类型   根据属性的类型。

class MySchema(Schema):
    class Meta:
        fields = ('field_1', 'field_2', ..., 'field_42')
    ...

Refactoring: Implicit Field Creation

答案 3 :(得分:1)

以下方法适用于我。

我已经使用Marshmallow-SQLAlchemy进行了演示,因为我不确定普通的棉花糖是否需要这样的东西-在3.0.0版中,使用from_dict以编程方式创建模式非常简单。但是您当然可以将这些概念与普通棉花糖一起使用。

在这里,我使用Marshmallow-SQLAlchemy来推断大多数模式,然后以编程方式对几个字段进行特殊处理。

import enum

from marshmallow_enum import EnumField
from marshmallow_sqlalchemy import ModelSchema
from sqlalchemy import Column
from sqlalchemy import Enum
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy.ext.declarative import declarative_base


BaseResource = declarative_base()


class CustomEnum(enum.Enum):
    VALUE_1 = "the first value"
    VALUE_2 = "the second value"


class ExampleResource(BaseResource):
    __tablename__ = "example_resource"

    id = Column(Integer, primary_key=True)
    enum_field = Column(Enum(CustomEnum), nullable=False)
    title = Column(String)
    string_two = Column(String)

    def __init__(self, **kwargs):
        super(ExampleResource, self).__init__(**kwargs)


def generate_schema(class_, serialization_fields, serialization_fields_excluded):
    """A method for programmatically generating schema.

    Args:
        class_ (class): the class to generate the schema for
        serialization_fields (dict): key-value pairs with the field name and its Marshmallow `Field`
        serialization_fields_excluded (tuple): fields to exclude

    Returns:
        schema (marshmallow.schema.Schema): the generated schema

    """

    class MarshmallowBaseSchema(object):
        pass

    if serialization_fields is not None:
        for field, marshmallow_field in serialization_fields.items():
            setattr(MarshmallowBaseSchema, field, marshmallow_field)

    class MarshmallowSchema(MarshmallowBaseSchema, ModelSchema):
        class Meta:
            model = class_
            exclude = serialization_fields_excluded

    return MarshmallowSchema


generated_schema = generate_schema(
    class_=ExampleResource,
    # I'm using a special package to handle the field `enum_field`
    serialization_fields=dict(enum_field=EnumField(CustomEnum, by_value=True, required=True)),
    # I'm excluding the field `string_two`
    serialization_fields_excluded=("string_two",),
)

example_resource = ExampleResource(
    id=1,
    enum_field=CustomEnum.VALUE_2,
    title="A Title",
    string_two="This will be ignored."
)
print(generated_schema().dump(example_resource))
# {'title': 'A Title', 'id': 1, 'enum_field': 'the second value'}

有必要将MarshmallowBaseSchema定义为一个普通对象,添加所有字段,然后从该类继承,因为棉花糖模式会在init上初始化所有字段(特别是_init_fields()),因此这种继承模式可确保当时所有字段都在那里。

答案 4 :(得分:0)

您可以使用 marshmallow.Schema.from_dict 生成混合模式。

class MySchema(
    ma.Schema.from_dict({f"field_{i}": ma.fields.Int() for i in range(1, 4)})
):
    field_4 = ma.fields.Str()