有条件地更改 Python Marshmallow 中的字段属性

Conditionally changing field properties in Python Marshmallow

我正在尝试在扩展 marshmallow.Schema 的 class 中实现一些逻辑,以便我可以覆盖 load_only 属性 之一的默认值用 pre_dump 修饰的方法中的字段。

默认情况下,load_only=True,但我希望在某些情况下能够将其设置为 False,并且认为实现此目的的最佳方法是设置一个可以签入的上下文值预转储方法,然后设置 load_only=False 以在序列化期间包含该字段。

为了演示这一点,我有一个 ParentSchema class 和一个 ChildSchema class。 ParentSchema 有一个名为 children 的字段,其中包含 ChildSchema 对象的列表。

from marshmallow import Schema, fields, post_load, pre_dump
import json


class Child(object):
    def __init__(self, id=None, data=None, parent_id=None):
        self.id = id
        self.data = data
        self.parent_id = parent_id

    def __repr__(self):
        return 'id=' + str(self.id) + ', data=' + str(self.data) + ', parent_id=' + str(self.parent_id)


class ChildSchema(Schema):
    id = fields.Integer(dump_only=True)
    data = fields.String(required=True)
    parent_id = fields.Integer(required=False)

    @post_load
    def make_child(self, data, **kwargs):
        return Child(**data)


class Parent(object):
    def __init__(self, id=None, data=None, children=None):
        self.id = id
        self.data = data
        self.children = children

    def __repr__(self):
        return 'id=' + str(self.id) + ', data=' + str(self.data) + ', children=' + str(self.children)


class ParentSchema(Schema):
    id = fields.Integer(dump_only=True)
    data = fields.String(required=True)
    children = fields.Nested(ChildSchema, many=True, load_only=True)

    @post_load
    def make_parent(self, data, **kwargs):
        return Parent(**data)

    @pre_dump
    def check_context(self, data, **kwargs):
        if 'dump_children' in self.context:
            self.fields['children'].load_only = False
        return data

默认行为应该是不序列化子字段,但是如果在创建 ParentSchema 对象时在上下文字典中提供键 'dump_children',则 load_only 设置为 False,我会这样做expect 会导致子字段被序列化。

parent_data = [
    {
        "data": "parent 1",
        "children": [
            {
                "data": "child 1"
            },
            {
                "data": "child 2"
            }
        ]
    },
    {
        "data": "parent 2",
        "children": [
            {
                "data": "child 3"
            }
        ]
    }
]

parents = ParentSchema(context={'dump_children': True}, many=True).dump(parent_data)
print(json.dumps(parents, indent=2))

我不明白的是,尽管 load_only 按预期设置为 False,但为什么会产生:

[
  {
    "data": "parent 1"
  },
  {
    "data": "parent 2"
  }
]

而不是:

[
  {
    "data": "parent 1",
    "children": [
      {
        "data": "child 1"
      },
      {
        "data": "child 2"
      }
    ]
  },
  {
    "data": "parent 2",
    "children": [
      {
        "data": "child 3"
      }
    ]
  }
]

是否有其他逻辑阻止此字段被序列化?或者有更好的方法来实现我所追求的目标吗?

所以我想使用 context 做一些类似的事情来控制被转储的内容,我遇到了你的问题。在 source code 中稍加挖掘表明,转储过程不会使用带有 load_only=False 的字段动态确定要输出的字段。事实上,在 Schema 实例初始化之后更改字段上的该属性似乎根本没有效果。相反,它使用 _init_fields() 方法在初始化时设置的 dump_fields 属性来确定要转储的字段。理论上,您可以在更改 childrenload_only 属性后再次调用该方法,但它是一个私有的 api 方法,看起来很老套。

如果您想有条件地控制输出内容,我认为您最好使用 only/exclude:

parents_only = ParentSchema(exclude=('children',), many=True)
parents_only.dump(parent_data)

[{'data': 'parent 1'}, {'data': 'parent 2'}]

parents_and_children = ParentSchema(many=True)
parents_and_children.dump(parent_data)

[{'data': 'parent 1', 'children': [{'data': 'child 1'}, {'data': 'child 2'}]}, {'data': 'parent 2', 'children': [{'data': 'child 3'}]}]

我个人觉得 only/exclude 是 class 级别的参数而不是 dump 级别的参数有点奇怪,但是有一些讨论在 this GitHub issue and also this one.

除了像这样创建单独的 Schema 实例(您已经在做并在上下文中传递)之外,您可能会把一些东西组合在一起以动态修改 dump_fields 但这似乎不太可取并且可能会产生更多问题为你自己。