SQLAlchemy: фильтрация существующего запроса по подклассу polymorphi c (эквивалентно `.of_type ()`, но для `session.query ()`) - PullRequest
1 голос
/ 09 января 2020

Вопрос

Если у меня есть существующий запрос для сущности, как я могу ограничиться, чтобы он возвращал только результаты подкласса polymorphi c этой сущности?

Подробности

Использование Сотрудник / Инженер / Менеджер из раздела Иерархии наследования классов отображения раздела документации sqlalchemy:

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

def get_complex_employee_query():
    """ Super complex query """
    query = session.query(Employee).filter(Employee.name.like('John %'))

    [... imagine a bunch of other `.filter()`, `.join()` and/or `.options()` here ...]

    return query

query = get_complex_query()

Я знаю, что могу отфильтровать запрос, чтобы получить список только инженеров, выполнив

query = query.filter(Employee.type='engineer')

Но предположим, что:

  • существуют полиморфные c подклассы Engineer,
  • или что я не знаю / не волнуюсь о том, что Employee - это polymorphic_on столбец type,
  • или что я не знаю / мне небезразлично правильное значение для столбца type для фильтрации только для инженеров

Есть ли способ применить .filter(), .options() или какой-либо другой метод query, который ограничит очередь попробуйте инженеры (и подклассы) без знания определенных c полей, которые конфигурируют наследование polymorphi c?

Я тоже в порядке с .join(), пока я не знаю должен знать / заботиться о связях первичного / внешнего ключа между классами / таблицами, которые являются частью иерархии polymorphi c.

Короче говоря

Я бы хотел что-то вроде Model.relationship.of_type() метод, но для запросов вместо отношений.

Есть ли такая вещь?

1 Ответ

1 голос
/ 09 января 2020

Краткий ответ

from sqlalchemy import inspect

    [...]
    eng_mapper = inspect(Engineer)
    query.filter(
        eng_mapper.polymorphic_on.in_(
            m.polymorphic_identity
            for m in eng_mapper.polymorphic_iterator()
        ),
    )

Я бы предпочел немного менее многословное заклинание, но это работает и не требует знания конкретной c конфигурации иерархии полиморфизма c.

Подробности

Когда inspect() вызывается для сопоставленного класса ORM, он возвращает Mapper для этого класса. Это идентично атрибуту класса Model.__mapper__.

Mapper содержит всю информацию, необходимую для анализа иерархии polymorphi c. В частности:

  • .polymorphic_on - это поле (столбец) в модели в верхней части иерархии, которое содержит значение идентификации polymorphi c для записи (например, для Engineer это будет поле Employee.type.
  • .polymorphic_identity - это значение, которое будет иметь каждый экземпляр сопоставленной модели в поле .polymorphic_on (например, для Engineer это было бы "engineer").
  • .polymorphic_iterator() перебирает коллекцию моделей Mapper s, которая включает в себя Model.__mapper__ и .__mapper__ всех подклассов Model рекурсивно (например, для Engineer это будет итератор, содержащий только Engineer.__mapper__).

Чтобы сделать его более читабельным, можно легко превратить приведенное выше выражение фильтра в функцию:

from sqlalchemy import inspect

def filter_instances_of(cls):
    mapper = inspect(cls)
    return mapper.polymorphic_on.in_(
        m.polymorphic_identity for m in mapper.polymorphic_iterator()
    )

И используйте его как:

query = query.filter(
    filter_instances_of(Engineer),
    [... other filter criteria ...]
)
...