Добавление метода реконструкции sqlalchemy с декоратором класса - PullRequest
0 голосов
/ 14 января 2020

У меня есть сопоставленный класс с методом @reconstructor, который создает словарь поиска из отношений с этим классом. Это работает. Он вызывается в init (явно), затем также вызывается sqlalchemy при восстановлении из базы данных и отображает связанные «соединения» каждого экземпляра на self.compound, так что self.compound ['составное_имя '] извлекает соединение с таким именем из соответствующего поля self.compounds. Он работает в постоянном времени, в отличие от предыдущего линейного поиска собственных значений. Это здорово ...

@reconstructor
def _create_lookup(self):
    print('Create lookup called.')
    attr = getattr(self, 'compounds')

    lookup = dict(zip(
        [getattr(c, 'name') for c in attr],
        [getattr(c, '', c) for c in attr]
    ))

    setattr(self, 'compound', lookup)

Однако , более общая форма ниже не работает при использовании в качестве декоратора классов (я хочу это, потому что это поведение желательно для нескольких моих классов , но не обязательно наследуется хорошо):

def give_class_lookup_on_attr(attr_to_lookup, lookup_key_attr, lookup_value_attr, lookup_name):

    # None is a key for the lookup_value_attr to return the object itself (set in the default)
    if lookup_value_attr is None:
        lookup_value_attr = ''
        # passing an empty string will never find an attribute, which makes it the key for returning the default

    print('give_class_... decorator called.')

    def class_wrap(cls):

        @reconstructor
        def func(self):
            print('Create lookup called.')
            attr = getattr(self, attr_to_lookup)

            lookup = dict(zip(
                [getattr(c, lookup_key_attr) for c in attr],
                [getattr(c, lookup_value_attr, c) for c in attr]
                # return the object as the value in dict if attr is not present
            ))

            setattr(self, lookup_name, lookup)

        cls._create_lookup = func
        print('reconstructor method added to cls and cls returned')
        return cls

    return class_wrap

По инструкциям печати, я смог подтвердить, что когда я вызываю декоратор в моем классе с соответствующими параметрами, реконструктор создается на класс, но никогда не называется. Я также проверил флаг класса .__ sa_reconstructor__, который имеет значение True (единственное, что декоратор-декоратор делает с методом). Но он по-прежнему не вызывается.

Поскольку декоратор класса вызывается после создания класса, а затем класс передается, мое рабочее предположение состоит в том, что преобразователь sqlalchemy имеет уже выполнено, это работает (вот почему это работает, когда выполняется внутри класса, но не с декоратором класса), поэтому он никогда не находит метод реконструкции с добавлением декоратора.

Итак, мое предположение о правильный путь, и есть ли способы обновить маппер sqlalchemy, чтобы он выглядел вторично? Или я должен начать смотреть на написание и / или добавление своих собственных слушателей событий sqlalchemy (при условии, что у них не будет той же самой проблемы).

РЕДАКТИРОВАТЬ: мое предположение кажется дополнительно подтверждается:

mapper = class_mapper(MyMappedClass)  
print(mapper._reconstructor) 

Если я вызову выше в моих тестах с моим встроенным объявлением метода, он возвращает реконструктор (функция MyMappedClass._create_lookup в 0x7f452a49e8c8), но возвращает None при использовании моего декоратора класса.

1 Ответ

0 голосов
/ 17 января 2020

Надеюсь, это избавит кого-то еще от неприятностей. Мой ответ частично- Python и частично-Sqlalchemy благодаря @ IljaEverilä.

Он очень похож на мою первую версию, но использует событие load из sqlalchemy для обработки повторной загрузки из базы данных. и в противном случае создаст свой поисковый словарь, только если он вызван и недоступен (через свойство).

Свойство может быть заменено событием 'init' из sqlalchemy, но только если все, на что оно ссылается, доступно (или не доступно) до для запуска инициализации. В моем случае доступ к отношениям до вызова __init__ всегда приводил к пустым таблицам поиска.

def give_class_lookup_on_attr(attr_to_lookup, lookup_key_attr, lookup_value_attr, lookup_name):
    # None is a key for the lookup_value_attr to return the object itself (set in the default)
    if lookup_value_attr is None:
        lookup_value_attr = ''
        # passing an empty string will never find an attribute, which makes it the key for returning the default

    def class_wrap(cls):

        def func(self):
            attr = getattr(self, attr_to_lookup)

            lookup = dict(zip(
                [getattr(c, lookup_key_attr) for c in attr],
                [getattr(c, lookup_value_attr, c) for c in attr]
            ))

            # set a private attr as "self._<lookup_name>"
            setattr(self, '_' + lookup_name, lookup)

        cls._create_lookup = func

        # create a property getter to reveal private attr, bind lookup_name to it
        def prop_getter(self, name=lookup_name):
            attr = getattr(self, '_' + name, None)

            if not attr:
                print('Lookup was not found. Created and returned.')
                self._create_lookup()
                attr = getattr(self, '_' + name)

            return attr

        setattr(cls, lookup_name, property(prop_getter))

        @event.listens_for(cls, 'load')
        def recieve_load(target, context):
            target._create_lookup()

        return cls

    return class_wrap
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...