Зарегистрировать хук post_load после определения схемы? - PullRequest
1 голос
/ 01 июня 2019

Я пытаюсь реализовать схему, которая десериализуется в объект , где класс объекта неизвестен во время определения схемы. Я бы предположил, что я могу зарегистрировать функцию post_load во время выполнения, но кажется , что post_load работает только для методов класса.

Кажется, я мог бы заставить его работать либо:

  • Обновление Schema._hooks вручную или
  • Каким-то образом создать связанный метод во время выполнения и зарегистрировать его.

Поскольку оба эти варианта несколько взломаны, существует ли официальный способ достижения того же результата?

Ответы [ 2 ]

1 голос
/ 03 июня 2019

Не думаю, что вам нужен метакласс.

Определите базовую схему с помощью метода после загрузки, которому просто нужен класс.

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

0 голосов
/ 02 июня 2019

Просто если кому-то еще это нужно, я решил это с помощью пользовательского метакласса, который предварительно регистрирует функцию 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
)
...