在棉花糖架构中以编程方式定义字段



假设我有一个这样的模式:

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?

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

  • 我不想修补一个实例,而是一个类
  • 棉花糖架构使用自定义元类

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

您需要做的就是使用 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

类元范式允许您指定哪些属性 想要序列化。棉花糖将选择合适的字段类型 基于属性的类型。

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

重构:隐式字段创建

以下方法对我有用。

我已经使用 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() (,因此此继承模式确保当时所有字段都在那里。

您可以使用

marshmallow.Schema.from_dict生成mixin架构。

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

我设法通过子类化默认元类来做到这一点:

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({field: 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及其参数/夸格必须单独指定:

    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)),
        ]

显然,这个答案是特定于棉花糖的,不适用于其他ODM/ORM库。

如果您使用的是棉花糖 3 或更高版本,则可以利用 Schema.from_dict 方法。

from marshmallow import Schema, fields
MySchema = Schema.from_dict(
    {
        "id": fields.Str(dump_only=True),
        "content": fields.Str(required=True),
    }
)

如果架构的形状需要在运行时更改,则可以执行以下操作:

my_schema = {
  "id": fields.Str(dump_only=True),
}
if (some_condition):
    my_schema["additional_field"] = fields.Str(dump_only=True)
MySchema = Schema.from_dict(my_schema)

此示例在此博客文章中进行了更详细的说明。

@Panic也分享了这个例子,但答案是不完整的。

相关内容

  • 没有找到相关文章

最新更新