在模式定义后注册post_load挂钩?

时间:2019-06-01 11:58:53

标签: serialization deserialization marshalling marshmallow

我正在尝试实现deserializes into an object的Schema,其中在Schema定义时不知道对象类。我本来可以在运行时注册post_load函数,但是appears post_load仅适用于类方法。

似乎我可以通过以下任何一种方法使其工作:

  • 手动或通过{li>更新Schema._hooks
  • 以某种方式在运行时创建绑定方法并将其注册。

由于这两个选项都有些破绽,是否有正式的方法可以达到相同的结果?

2 个答案:

答案 0 :(得分:1)

我认为您不需要元类。

使用仅需要类的后加载方法定义基本架构。

class CustomSchema(Schema):

    @post_load
    def make_obj(self, data):
        return self.OBJ_CLS(**data)

如果在导入时知道该类(而不是您的用例),则可以通过仅提供该类来将实例化。很好。

class PetSchema(CustomSchema):

    OBJ_CLS = Pet

如果在导入时不知道该类,则可以在以后提供它。

class PetSchema(CustomSchema):
    pass


PetSchema.OBJ_CLS = Pet

如果您需要在实例化之前进行更多处理,则可以按照答案中的显示在任何类中覆盖make_obj

class PetSchema(CustomSchema):
    def make_obj(self, data):
        data = my_func(data)
        return Pet(**data)

更一般而言,此机制允许您在基本架构中定义挂钩。这是克服棉花糖当前局限性的好方法:多个post_load方法可以以任何顺序执行。在基类中定义单个post_load方法,并为每个处理步骤添加一个钩子。 (这个人为的例子并没有真正说明问题所在。)

class CustomSchema(Schema):

    @post_load
    def post_load_steps(self, data):
        data = self.post_load_step_1(data)
        data = self.post_load_step_2(data)
        data = self.post_load_step_3(data)
        return data

    def post_load_step_1(self, data):
        return data

    def post_load_step_2(self, data):
        return data

    def post_load_step_3(self, data):
        return data

答案 1 :(得分:0)

只要其他人需要它,我就用一个自定义的元类解决了它,该类预注册了一个post_load函数,该函数的实际实现可以在运行时提供:

from types import MethodType

from marshmallow import Schema, post_load
from marshmallow.schema import SchemaMeta

class MyCustomSchemaMeta(SchemaMeta)

    def __init__(cls, *args, **kwargs):
        super().__init__(*args, **kwargs)

        def make_obj(*args, **kwargs):
            raise NotImplementedError

        # This post_load call registers the method with the Schema._hooks dict
        cls.make_obj = post_load(make_obj)

class MyCustomSchema(Schema, metaclass=MyCustomSchemaMeta):
    """This is the base class that my schemas inherit."""

# The actual implementation of make_obj (and hence the class to deserialize to)
# can now be provided at runtime. The post_load call does not affect the schema
# anymore, but sets some parameters on the method.
MyCustomSchema.make_obj = MethodType(
    post_load(lambda self, data: MyClass(**data)), MyCustomSchema
)