Как обработать обратную совместимость тега / класса в PyYaml? - PullRequest
1 голос
/ 08 июля 2019

Предположим, у меня есть класс MyClass, находящийся в пакете my_package.Вывод таких данных в YAML может привести к:

!!python/object:my_package.MyClass
my_field_1: "foo"
my_field_2: "bar"

Эти данные могут быть десериализованы с помощью загрузчика по умолчанию.Но как мы можем справиться с рефакторингом имени пакета / класса?Например, если мы переименуем пакет в my_new_package, код не сможет десериализовать существующие файлы YAML, как можно было бы ожидать:

yaml.constructor.ConstructorError: while constructing a Python object
cannot find 'MyClass' in the module 'my_package'

Как можно добавить обратную совместимость в загрузчик YAML, чтобызагрузка старых данных все еще возможна?


Моя первая попытка состояла в том, чтобы настроить загрузчик и зарегистрировать старое имя тега для обратной совместимости:

class CustomLoader(yaml.SafeLoader):
    pass

def my_class_loader(loader, node):
    # to be implemented

CustomLoader.add_constructor("!!python/object:my_package.MyClass", my_class_loader)

data = yaml.load(f, Loader=CustomLoader)

К сожалению, PyYaml никогда не вызывает этогопользовательская загрузка.Есть ли другой способ вставить старый тег в процесс загрузки?

1 Ответ

0 голосов
/ 08 июля 2019

Я нашел рабочее решение, но я не уверен, что это самый элегантный способ его решения:

Похоже, что PyYaml внутренне преобразует сохраненный тег !!python/object:my_package.MyClass в tag:yaml.org,2002:python/object:my_package.MyClass перед выполнением поиска в конструкторе.

Таким образом, чтобы зарегистрировать пользовательский загрузчик для старого имени тега, можно сделать что-то вроде:

# Example class
class MyClass(object):
    def __init__(my_field_1, my_field_2):
        self.my_field_1 = my_field_1
        self.my_field_2 = my_field_2

    @staticmethod
    def from_dict(d):
        return MyClass(
            my_field_1=d["my_field_1"],
            my_field_2=d["my_field_2"],
        )

# Build a custom loader
class CustomLoader(yaml.SafeLoader):
    pass

def my_class_loader(loader, node):
    mapping = loader.construct_mapping(node)
    return MyClass.from_dict(mapping)

# Register the old tag name
CustomLoader.add_constructor(
    "tag:yaml.org,2002:python/object:my_package.MyClass", 
    my_class_loader)

# Load using custom loader
data = yaml.load(f, Loader=CustomLoader)
...