Возможности ORM API для создания запросов

Опции загрузчика ORM

Опции загрузчика - это объекты, которые при передаче в метод Select.options() объекта Select или аналогичной конструкции SQL влияют на загрузку как столбцов, так и атрибутов, ориентированных на отношения. Большинство опций загрузчика спускается из иерархии Load. Полный обзор использования опций загрузчика приведен в следующих разделах.

См.также

Варианты выполнения ORM

Параметры выполнения на уровне ORM - это параметры ключевых слов, которые могут быть связаны с выполнением оператора либо с помощью параметра Session.execute.execution_options, который является словарным аргументом, принимаемым методами Session, такими как Session.execute() и Session.scalars(), либо путем связывания их непосредственно с самим вызываемым оператором с помощью метода Executable.execution_options(), который принимает их как произвольные аргументы ключевых слов.

Опции уровня ORM отличаются от опций выполнения на уровне Core, документированных в Connection.execution_options(). Важно отметить, что рассматриваемые ниже опции ORM не совместимы с методами уровня Core Connection.execution_options() или Engine.execution_options(); опции игнорируются на этом уровне, даже если Engine или Connection связаны с используемым Session.

В данном разделе для примера будет рассмотрен стиль метода Executable.execution_options().

Заполнить существующие

Опция выполнения populate_existing гарантирует, что для всех загруженных строк соответствующие экземпляры в Session будут полностью обновлены - стерты все существующие данные в объектах (включая ожидающие изменения) и заменены данными, загруженными из результата.

Пример использования выглядит так:

>>> stmt = select(User).execution_options(populate_existing=True)
>>> result = session.execute(stmt)
{execsql}SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
...

Обычно объекты ORM загружаются только один раз, и если они совпадают с первичным ключом в последующей строке результата, то эта строка не применяется к объекту. Это делается как для того, чтобы сохранить незавершенные изменения в объекте, так и для того, чтобы избежать накладных расходов и сложностей, связанных с обновлением уже имеющихся данных. В Session по умолчанию предполагается рабочая модель строго изолированной транзакции, и в той степени, в которой ожидается, что данные будут изменяться в транзакции вне локальных изменений, такие случаи будут обрабатываться с помощью явных шагов, подобных этому методу.

Используя populate_existing, можно обновить любой набор объектов, соответствующий запросу, а также управлять параметрами загрузчика отношений. Например, обновить экземпляр и одновременно обновить связанный с ним набор объектов:

stmt = (
    select(User)
    .where(User.name.in_(names))
    .execution_options(populate_existing=True)
    .options(selectinload(User.addresses))
)
# will refresh all matching User objects as well as the related
# Address objects
users = session.execute(stmt).scalars().all()

Другим вариантом использования populate_existing является поддержка различных функций загрузки атрибутов, которые могут изменять способ загрузки атрибутов на основе каждого запроса. К таким возможностям относятся:

Опция выполнения populate_existing эквивалентна методу Query.populate_existing() в 1.x style ORM-запросах.

Автопромывка

Эта опция, переданная в виде False, заставит Session не вызывать шаг «autoflush». Это эквивалентно использованию менеджера контекста Session.no_autoflush для отключения автопромывки:

>>> stmt = select(User).execution_options(autoflush=False)
>>> session.execute(stmt)
{execsql}SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
...

Эта опция также будет работать с ORM-совместимыми запросами Update и Delete.

Опция выполнения autoflush эквивалентна методу Query.autoflush() в 1.x style ORM-запросах.

См.также

Промывка

Получение больших наборов результатов с доходностью за

Опция выполнения yield_per представляет собой целочисленное значение, которое заставляет Result буферизировать только ограниченное количество строк и/или ORM-объектов за один раз, прежде чем сделать данные доступными для клиента.

Обычно ORM немедленно получает все строки, создавая для каждой из них ORM-объекты и собирая их в единый буфер, а затем передает этот буфер объекту Result в качестве источника возвращаемых строк. Такое поведение объясняется необходимостью обеспечить корректное поведение таких функций, как объединенная ускоренная загрузка, уникализация результатов и общий случай логики обработки результатов, которая полагается на то, что карта идентификации сохраняет последовательное состояние для каждого объекта в наборе результатов по мере их получения.

Цель опции yield_per - изменить это поведение таким образом, чтобы набор результатов ORM был оптимизирован для итерации по очень большим наборам результатов (например, > 10 тыс. строк), где пользователь определил, что вышеуказанные шаблоны не применимы. При использовании опции yield_per вместо этого ORM будет разбивать результаты ORM на подколлекции и выдавать строки из каждой подколлекции по отдельности по мере итерации объекта Result, чтобы интерпретатору Python не приходилось объявлять очень большие области памяти, что отнимает много времени и приводит к чрезмерному использованию памяти. Опция влияет как на способ использования курсора базы данных, так и на то, как ORM конструирует строки и объекты для передачи в Result.

Совет

Из вышесказанного следует, что Result должен потребляться итеративно, то есть с использованием итерации, например for row in result, или с использованием методов частичных строк, например Result.fetchmany() или Result.partitions(). Вызов Result.all() лишает смысла использование yield_per.

Использование yield_per эквивалентно использованию как опции выполнения Connection.execution_options.stream_results, которая выбирает курсоры на стороне сервера для использования бэкендом, если они поддерживаются, так и метода Result.yield_per() для возвращаемого объекта Result, который устанавливает фиксированный размер извлекаемых строк, а также соответствующее ограничение на количество объектов ORM, которые будут построены одновременно.

Совет

yield_per теперь доступен и как вариант выполнения Core, подробно описанный в разделе Использование курсоров на стороне сервера (они же потоковые результаты). В этом разделе подробно описывается использование yield_per в качестве опции выполнения с ORM Session. В обоих контекстах опция ведет себя максимально одинаково.

При использовании с ORM, yield_per должен быть установлен либо через метод Executable.execution_options() на данном операторе, либо путем передачи его в параметр Session.execute.execution_options метода Session.execute() или другого подобного Session метода, например Session.scalars(). Типичное использование для получения объектов ORM показано ниже:

>>> stmt = select(User).execution_options(yield_per=10)
>>> for user_obj in session.scalars(stmt):
...     print(user_obj)
{execsql}SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
[...] ()
{stop}User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
...
>>> # ... rows continue ...

Приведенный выше код эквивалентен приведенному ниже примеру, в котором используются опции выполнения на уровне ядра Connection.execution_options.stream_results и Connection.execution_options.max_row_buffer в сочетании с методом Result.yield_per() из Result:

# equivalent code
>>> stmt = select(User).execution_options(stream_results=True, max_row_buffer=10)
>>> for user_obj in session.scalars(stmt).yield_per(10):
...     print(user_obj)
{execsql}SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
[...] ()
{stop}User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
...
>>> # ... rows continue ...

Метод yield_per также часто используется в сочетании с методом Result.partitions(), который обеспечивает итерацию строк в сгруппированных разделах. Размер каждого раздела по умолчанию равен целому значению, переданному в yield_per, как показано в следующем примере:

>>> stmt = select(User).execution_options(yield_per=10)
>>> for partition in session.scalars(stmt).partitions():
...     for user_obj in partition:
...         print(user_obj)
{execsql}SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
[...] ()
{stop}User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
...
>>> # ... rows continue ...

Опция выполнения yield_per не совместима с загрузкой «subquery» eager loading или «joined» eager loading при использовании коллекций. Он потенциально совместим с «select in» eager loading, если драйвер базы данных поддерживает несколько независимых курсоров.

Кроме того, вариант выполнения yield_per несовместим с методом Result.unique(); поскольку этот метод предполагает хранение полного набора идентификаторов для всех строк, это неизбежно приведет к потере цели использования yield_per, которая заключается в обработке произвольно большого количества строк.

Изменено в версии 1.4.6: Возникает исключение, когда строки ORM извлекаются из объекта Result, использующего фильтр Result.unique(), при одновременном использовании опции выполнения yield_per.

При использовании унаследованного объекта Query с применением 1.x style ORM метод Query.yield_per() будет иметь тот же результат, что и при варианте выполнения yield_per.

Токен идентичности

Deep Alchemy

Данная опция является расширенной и предназначена в основном для использования с расширением Горизонтальное разделение. Для типичных случаев загрузки объектов с одинаковыми первичными ключами из разных «осколков» или разделов следует сначала рассмотреть возможность использования отдельных объектов Session для каждого осколка.

Токен идентификации» - это произвольное значение, которое может быть связано с identity key вновь загружаемых объектов. Этот элемент существует, прежде всего, для поддержки расширений, выполняющих однорядное «разделение», когда объекты могут загружаться из любого количества копий определенной таблицы базы данных, которые, тем не менее, имеют совпадающие значения первичных ключей. Основным потребителем «идентификационного маркера» является расширение Горизонтальное разделение, которое предоставляет общую схему сохранения объектов между несколькими «осколками» конкретной таблицы базы данных.

Опция выполнения identity_token может быть использована на основе каждого запроса для прямого воздействия на этот маркер. Используя ее напрямую, можно заполнить Session несколькими экземплярами объекта, имеющими один и тот же первичный ключ и исходную таблицу, но разные «личности».

Одним из таких примеров является заполнение Session объектами из одноименных таблиц в разных схемах с использованием возможности Перевод имен схем, которая может влиять на выбор схемы в рамках запросов. Дано отображение в виде:

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column


class Base(DeclarativeBase):
    pass


class MyTable(Base):
    __tablename__ = "my_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]

По умолчанию «схемным» именем для приведенного выше класса является None, то есть в SQL-операторах не будет записано никаких схемных квалификаций. Однако если мы воспользуемся Connection.execution_options.schema_translate_map, отобразив None на альтернативную схему, то сможем поместить экземпляры MyTable в две разные схемы:

engine = create_engine(
    "postgresql+psycopg://scott:tiger@localhost/test",
)

with Session(
    engine.execution_options(schema_translate_map={None: "test_schema"})
) as sess:
    sess.add(MyTable(name="this is schema one"))
    sess.commit()

with Session(
    engine.execution_options(schema_translate_map={None: "test_schema_2"})
) as sess:
    sess.add(MyTable(name="this is schema two"))
    sess.commit()

Приведенные выше два блока создают объект Session, связанный каждый раз с разной картой трансляции схемы, а экземпляр MyTable сохраняется как в test_schema.my_table, так и в test_schema_2.my_table.

Приведенные выше объекты Session являются независимыми. Если бы мы хотели сохранить оба объекта в одной транзакции, то для этого нам потребовалось бы использовать расширение Горизонтальное разделение.

Однако мы можем проиллюстрировать запрос к этим объектам в одном сеансе следующим образом:

with Session(engine) as sess:
    obj1 = sess.scalar(
        select(MyTable)
        .where(MyTable.id == 1)
        .execution_options(
            schema_translate_map={None: "test_schema"},
            identity_token="test_schema",
        )
    )
    obj2 = sess.scalar(
        select(MyTable)
        .where(MyTable.id == 1)
        .execution_options(
            schema_translate_map={None: "test_schema_2"},
            identity_token="test_schema_2",
        )
    )

И obj1, и obj2 отличаются друг от друга. Однако они оба ссылаются на первичный ключ id 1 для класса MyTable, но при этом отличаются друг от друга. Таким образом, в дело вступает identity_token, что мы можем увидеть при осмотре каждого объекта, где мы смотрим на InstanceState.key, чтобы увидеть две различные идентификационные лексемы:

>>> from sqlalchemy import inspect
>>> inspect(obj1).key
(<class '__main__.MyTable'>, (1,), 'test_schema')
>>> inspect(obj2).key
(<class '__main__.MyTable'>, (1,), 'test_schema_2')

При использовании расширения Горизонтальное разделение приведенная выше логика происходит автоматически.

Добавлено в версии 2.0.0rc1: - added the identity_token ORM level execution option.

См.также

Горизонтальное разделение - в разделе Примеры ORM. Демонстрацию описанного выше случая с использованием полного API шардинга см. в сценарии separate_schema_translates.py.

Проверка сущностей и столбцов из операторов SELECT и DML с поддержкой ORM

Конструкция select(), а также конструкции insert(), update() и delete() (для последних DML-конструкций, начиная с SQLAlchemy 1.4.33) поддерживают возможность проверки сущностей, для которых создаются эти операторы, а также столбцов и типов данных, которые будут возвращены в наборе результатов.

Для объекта Select эта информация доступна из атрибута Select.column_descriptions. Этот атрибут работает так же, как и унаследованный атрибут Query.column_descriptions. Возвращаемый формат представляет собой список словарей:

>>> from pprint import pprint
>>> user_alias = aliased(User, name="user2")
>>> stmt = select(User, User.id, user_alias)
>>> pprint(stmt.column_descriptions)
[{'aliased': False,
  'entity': <class 'User'>,
  'expr': <class 'User'>,
  'name': 'User',
  'type': <class 'User'>},
 {'aliased': False,
  'entity': <class 'User'>,
  'expr': <....InstrumentedAttribute object at ...>,
  'name': 'id',
  'type': Integer()},
 {'aliased': True,
  'entity': <AliasedClass ...; User>,
  'expr': <AliasedClass ...; User>,
  'name': 'user2',
  'type': <class 'User'>}]

Когда Select.column_descriptions используется с объектами, не относящимися к категорииORM, такими как обычные объекты Table или Column, записи будут содержать основную информацию об отдельных колонках, возвращаемых во всех случаях:

>>> stmt = select(user_table, address_table.c.id)
>>> pprint(stmt.column_descriptions)
[{'expr': Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False),
  'name': 'id',
  'type': Integer()},
 {'expr': Column('name', String(), table=<user_account>, nullable=False),
  'name': 'name',
  'type': String()},
 {'expr': Column('fullname', String(), table=<user_account>),
  'name': 'fullname',
  'type': String()},
 {'expr': Column('id', Integer(), table=<address>, primary_key=True, nullable=False),
  'name': 'id_1',
  'type': Integer()}]

Изменено в версии 1.4.33: Атрибут Select.column_descriptions теперь возвращает значение, если используется против Select, не поддерживающего ORM. Ранее при этом возникала ошибка NotImplementedError.

Для конструкций insert(), update() и delete() существует два отдельных атрибута. Один из них - UpdateBase.entity_description, который возвращает информацию о первичной сущности ORM и таблице базы данных, на которую будет воздействовать конструкция DML:

>>> from sqlalchemy import update
>>> stmt = update(User).values(name="somename").returning(User.id)
>>> pprint(stmt.entity_description)
{'entity': <class 'User'>,
 'expr': <class 'User'>,
 'name': 'User',
 'table': Table('user_account', ...),
 'type': <class 'User'>}

Совет

В состав UpdateBase.entity_description входит запись "table", которая фактически является таблицей, вставляемой, обновляемой или удаляемой оператором, что **не всегда совпадает с SQL «selectable», с которой может быть сопоставлен класс. Например, в сценарии наследования объединенных таблиц "table" будет ссылаться на локальную таблицу для данной сущности.

Другой вариант - UpdateBase.returning_column_descriptions, который выдает информацию о столбцах, присутствующих в коллекции RETURNING, примерно так же, как и Select.column_descriptions:

>>> pprint(stmt.returning_column_descriptions)
[{'aliased': False,
  'entity': <class 'User'>,
  'expr': <sqlalchemy.orm.attributes.InstrumentedAttribute ...>,
  'name': 'id',
  'type': Integer()}]

Добавлено в версии 1.4.33: Добавлены атрибуты UpdateBase.entity_description и UpdateBase.returning_column_descriptions.

Дополнительные конструкции ORM API

Object Name Description

aliased(element[, alias, name, flat, ...])

Создать псевдоним заданного элемента, обычно экземпляр AliasedClass.

AliasedClass

Представляет собой «псевдослучайную» форму сопоставленного класса для использования с Query.

AliasedInsp

Предоставить интерфейс проверки для объекта AliasedClass.

Bundle

Группировка SQL-выражений, которые возвращает Query, в одном пространстве имен.

join(left, right[, onclause, isouter, ...])

Создайте внутреннее соединение между левой и правой клаузами.

outerjoin(left, right[, onclause, full])

Создайте левое внешнее соединение между левой и правой клаузами.

with_loader_criteria(entity_or_base, where_criteria[, loader_only, include_aliases, ...])

Добавьте в загрузку дополнительные критерии WHERE для поиска всех вхождений определенной сущности.

with_parent(instance, prop[, from_entity])

Создайте критерий фильтрации, который связывает первичную сущность данного запроса с заданным связанным экземпляром, используя установленную конфигурацию relationship().

function sqlalchemy.orm.aliased(element: Union[_EntityType[_O], FromClause], alias: Optional[Union[Alias, Subquery]] = None, name: Optional[str] = None, flat: bool = False, adapt_on_names: bool = False) Union[AliasedClass[_O], FromClause, AliasedType[_O]]

Создать псевдоним заданного элемента, обычно экземпляр AliasedClass.

Например:

my_alias = aliased(MyClass)

stmt = select(MyClass, my_alias).filter(MyClass.id > my_alias.id)
result = session.execute(stmt)

Функция aliased() используется для создания специального отображения отображаемого класса на новый selectable. По умолчанию selectable генерируется из обычно отображаемого selectable (обычно это Table) с помощью метода FromClause.alias(). Однако для привязки класса к новому оператору select() можно также использовать aliased(). Кроме того, функция with_polymorphic() является вариантом aliased(), который предназначен для задания так называемого «полиморфного селекта», соответствующего объединению сразу нескольких подклассов с объединенным наследованием.

Для удобства функция aliased() также принимает обычные конструкции FromClause, например, конструкцию Table или select(). В этих случаях на объекте вызывается метод FromClause.alias() и возвращается новый объект Alias. Возвращаемый объект Alias в этом случае не является ORM-сопоставленным.

Параметры:
  • element – Элемент, который будет псевдонимом. Обычно представляет собой сопоставленный класс, но для удобства может быть и элементом FromClause.

  • alias – Необязательная выбираемая единица для сопоставления элемента. Обычно он используется для связи объекта с подзапросом и должен представлять собой алиасированную конструкцию select, как это делается в методе Query.subquery() или методах Select.subquery() или Select.alias() конструкции select().

  • name – необязательное строковое имя, используемое для псевдонима, если оно не задано параметром alias. Имя, помимо прочего, формирует имя атрибута, который будет доступен через кортежи, возвращаемые объектом Query. Не поддерживается при создании псевдонимов объектов Join.

  • flat – Boolean, будет передаваться в вызов FromClause.alias(), так что псевдонимы объектов Join будут псевдонимами отдельных таблиц внутри объединения, а не созданием подзапроса. Это поддерживается всеми современными базами данных в отношении право-вложенных объединений и, как правило, позволяет получать более эффективные запросы.

  • adapt_on_names – если True, то при сопоставлении отображаемых колонок ORM-сущности с колонками данного селекта будет использоваться более либеральное «сопоставление» - будет выполняться сопоставление по имени, если в данном селекте нет колонки, соответствующей колонке сущности. Это можно сделать, если связать сущность с каким-либо производным selectable, например, с тем, который использует агрегатные функции:: class UnitPrice(Base): __tablename__ = „unit_price“ … unit_id = Column(Integer) price = Column(Numeric) aggregated_unit_price = Session.query( func.sum(UnitPrice.price).label(„price“) ).group_by(UnitPrice.unit_id). subquery() aggregated_unit_price = aliased(UnitPrice, alias=aggregated_unit_price, adapt_on_names=True) Выше, функции на aggregated_unit_price, которые ссылаются на .price, вернут колонку func.sum(UnitPrice.price).label('price'), поскольку она сопоставлена по имени «цена». В обычном случае функция «price» не будет иметь никакого «столбцового соответствия» с реальным столбцом UnitPrice.price, поскольку она не является прокси оригинала.

class sqlalchemy.orm.util.AliasedClass

Представляет собой «псевдослучайную» форму сопоставленного класса для использования с Query.

Являясь ORM-эквивалентом конструкции alias(), этот объект имитирует отображаемый класс по схеме __getattr__ и поддерживает ссылку на реальный объект Alias.

Основное назначение AliasedClass - служить альтернативой в SQL-операторе, генерируемом ORM, чтобы существующая сопоставленная сущность могла быть использована в нескольких контекстах. Простой пример:

# find all pairs of users with the same name
user_alias = aliased(User)
session.query(User, user_alias).\
                join((user_alias, User.id > user_alias.id)).\
                filter(User.name == user_alias.name)

AliasedClass также может отображать существующий отображаемый класс на совершенно новый selectable, если этот selectable совместим по столбцам с существующим отображаемым selectable, а также может быть сконфигурирован в отображении как цель relationship(). Примеры приведены по ссылкам ниже.

Объект AliasedClass строится, как правило, с помощью функции aliased(). Также он создается с дополнительной конфигурацией при использовании функции with_polymorphic().

Результирующим объектом является экземпляр AliasedClass. Этот объект реализует схему атрибутов, которая создает тот же интерфейс атрибутов и методов, что и исходный сопоставленный класс, что позволяет AliasedClass быть совместимым с любой техникой атрибутов, работающей на исходном классе, включая гибридные атрибуты (см. Атрибуты гибрида).

С помощью AliasedClass можно проверить на наличие лежащего в его основе Mapper, псевдовыбора и другой информации с помощью inspect():

from sqlalchemy import inspect
my_alias = aliased(MyClass)
insp = inspect(my_alias)

Результирующим объектом проверки является экземпляр AliasedInsp.

Классная подпись

класс sqlalchemy.orm.AliasedClass (sqlalchemy.inspection.Inspectable, sqlalchemy.orm.ORMColumnsClauseRole)

class sqlalchemy.orm.util.AliasedInsp

Предоставить интерфейс проверки для объекта AliasedClass.

Возврат объекта AliasedInsp осуществляется при задании AliasedClass с помощью функции inspect():

from sqlalchemy import inspect
from sqlalchemy.orm import aliased

my_alias = aliased(MyMappedClass)
insp = inspect(my_alias)

Атрибуты на AliasedInsp включают:

  • entity - представленный AliasedClass.

  • mapper - отображение Mapper на базовый класс.

  • selectable - конструкция Alias, которая в конечном итоге представляет собой псевдослучайную конструкцию Table или Select.

  • name - имя псевдонима. Также используется в качестве имени атрибута при возврате в кортеже результатов из Query.

  • with_polymorphic_mappers - коллекция объектов Mapper, указывающих на все те отображения, которые выражены в конструкции select для AliasedClass.

  • polymorphic_on - альтернативный столбец или SQL-выражение, которое будет использоваться в качестве «дискриминатора» для полиморфной загрузки.

Классная подпись

класс sqlalchemy.orm.AliasedInsp (sqlalchemy.orm.ORMEntityColumnsClauseRole, sqlalchemy.orm.ORMFromClauseRole, sqlalchemy.sql.cache_key.HasCacheKey, sqlalchemy.orm.base.InspectionAttr, sqlalchemy.util.langhelpers.MemoizedSlots, sqlalchemy.inspection.Inspectable, typing.Generic)

class sqlalchemy.orm.Bundle

Группировка SQL-выражений, которые возвращает Query, в одном пространстве имен.

По сути, Bundle позволяет вложить результаты, основанные на кортежах и возвращаемые объектом Query, ориентированным на столбцы. Он также расширяется с помощью простого подклассифицирования, где основной возможностью переопределения является способ возврата набора выражений, что позволяет выполнять постобработку, а также использовать пользовательские типы возврата без привлечения классов ORM с привязкой к идентификаторам.

Классная подпись

класс sqlalchemy.orm.Bundle (sqlalchemy.orm.ORMColumnsClauseRole, sqlalchemy.sql.annotation.SupportsCloneAnnotations, sqlalchemy.sql.cache_key.MemoizedHasCacheKey, sqlalchemy.inspection.Inspectable, sqlalchemy.orm.base.InspectionAttr)

method sqlalchemy.orm.Bundle.__init__(name: str, *exprs: _ColumnExpressionArgument[Any], **kw: Any)

Создайте новый Bundle.

например:

bn = Bundle("mybundle", MyClass.x, MyClass.y)

for row in session.query(bn).filter(
        bn.c.x == 5).filter(bn.c.y == 4):
    print(row.mybundle.x, row.mybundle.y)
Параметры:
  • name – имя пакета.

  • *exprs – столбцов или SQL-выражений, составляющих связку.

  • single_entity=False – Если значение True, то строки для данного Bundle могут быть возвращены как «одиночная сущность» вне любого вложенного кортежа таким же образом, как и сопоставленная сущность.

attribute sqlalchemy.orm.Bundle.c: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]

Псевдоним для Bundle.columns.

attribute sqlalchemy.orm.Bundle.columns: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]

Пространство имен SQL-выражений, на которые ссылается данный Bundle.

например:

bn = Bundle("mybundle", MyClass.x, MyClass.y)

q = sess.query(bn).filter(bn.c.x == 5)

Также поддерживается вложенность пакетов:

b1 = Bundle("b1",
        Bundle('b2', MyClass.a, MyClass.b),
        Bundle('b3', MyClass.x, MyClass.y)
    )

q = sess.query(b1).filter(
    b1.c.b2.c.a == 5).filter(b1.c.b3.c.y == 9)

См.также

Bundle.c

method sqlalchemy.orm.Bundle.create_row_processor(query: Select[Any], procs: Sequence[Callable[[Row[Any]], Any]], labels: Sequence[str]) Callable[[Row[Any]], Any]

Произвести функцию «обработки строки» для данного Bundle.

Может переопределяться подклассами для обеспечения пользовательского поведения при получении результатов. Во время выполнения запроса методу передается объект statement и набор функций «обработчиков строк», которые, получив строку результата, возвращают значение отдельного атрибута, которое затем может быть адаптировано в любую структуру возвращаемых данных.

Приведенный ниже пример иллюстрирует замену обычной структуры возврата Row на прямой словарь Python:

from sqlalchemy.orm import Bundle

class DictBundle(Bundle):
    def create_row_processor(self, query, procs, labels):
        'Override create_row_processor to return values as
        dictionaries'

        def proc(row):
            return dict(
                zip(labels, (proc(row) for proc in procs))
            )
        return proc

В результате выполнения приведенной выше команды Bundle будут возвращены значения словаря:

bn = DictBundle('mybundle', MyClass.data1, MyClass.data2)
for row in session.execute(select(bn)).where(bn.c.data1 == 'd1'):
    print(row.mybundle['data1'], row.mybundle['data2'])
attribute sqlalchemy.orm.Bundle.is_aliased_class = False

True, если данный объект является экземпляром AliasedClass.

attribute sqlalchemy.orm.Bundle.is_bundle = True

True, если данный объект является экземпляром Bundle.

attribute sqlalchemy.orm.Bundle.is_clause_element = False

True, если данный объект является экземпляром ClauseElement.

attribute sqlalchemy.orm.Bundle.is_mapper = False

True, если данный объект является экземпляром Mapper.

method sqlalchemy.orm.Bundle.label(name)

Предоставить копию этой Bundle, передав новую метку.

attribute sqlalchemy.orm.Bundle.single_entity = False

Если значение True, то запросы к одному Bundle будут возвращаться как единое целое, а не как элемент в кортеже с ключом.

function sqlalchemy.orm.with_loader_criteria(entity_or_base: _EntityType[Any], where_criteria: _ColumnExpressionArgument[bool], loader_only: bool = False, include_aliases: bool = False, propagate_to_loaders: bool = True, track_closure_variables: bool = True) LoaderCriteriaOption

Добавьте в загрузку дополнительные критерии WHERE для поиска всех вхождений определенной сущности.

Добавлено в версии 1.4.

Опция with_loader_criteria() предназначена для добавления ограничивающих критериев к определенному типу сущностей в запросе, глобально, т.е. они будут применяться к сущности в том виде, в котором она появляется в запросе SELECT, а также в любых подзапросах, условиях присоединения и загрузки отношений, включая нетерпеливые и ленивые загрузчики, без необходимости указывать их в какой-либо конкретной части запроса. Логика рендеринга использует ту же систему, что и при наследовании одной таблицы, для обеспечения применения к таблице определенного дискриминатора.

Например, используя запросы 2.0-style, мы можем ограничить способ загрузки коллекции User.addresses, независимо от используемого вида загрузки:

from sqlalchemy.orm import with_loader_criteria

stmt = select(User).options(
    selectinload(User.addresses),
    with_loader_criteria(Address, Address.email_address != 'foo'))
)

Выше, «selectinload» для User.addresses применит заданные критерии фильтрации к предложению WHERE.

Другой пример, в котором фильтрация будет применена к предложению ON объединения, в данном примере с использованием запросов 1.x style:

q = session.query(User).outerjoin(User.addresses).options(
    with_loader_criteria(Address, Address.email_address != 'foo'))
)

Основное назначение with_loader_criteria() - использовать его в обработчике события SessionEvents.do_orm_execute() для обеспечения фильтрации всех вхождений конкретной сущности определенным образом, например, для фильтрации по ролям управления доступом. Его также можно использовать для применения критериев к загрузке отношений. В приведенном ниже примере мы можем применить определенный набор правил ко всем запросам, выдаваемым конкретным Session:

session = Session(bind=engine)

@event.listens_for("do_orm_execute", session)
def _add_filtering_criteria(execute_state):

    if (
        execute_state.is_select
        and not execute_state.is_column_load
        and not execute_state.is_relationship_load
    ):
        execute_state.statement = execute_state.statement.options(
            with_loader_criteria(
                SecurityRole,
                lambda cls: cls.role.in_(['some_role']),
                include_aliases=True
            )
        )

В приведенном примере событие SessionEvents.do_orm_execute() будет перехватывать все запросы, созданные с помощью Session. Для тех запросов, которые являются операторами SELECT и не загружают атрибуты или отношения, в запрос добавляется пользовательская опция with_loader_criteria(). Опция with_loader_criteria() будет использоваться в данном запросе, а также автоматически распространяться на все нагрузки отношений, исходящие из этого запроса.

В качестве аргумента критерия задается lambda, который принимает аргумент cls. Данный класс расширяется до всех отображаемых подклассов и сам не обязательно должен быть отображаемым классом.

Совет

При использовании опции with_loader_criteria() в сочетании с опцией загрузчика contains_eager() важно отметить, что with_loader_criteria() влияет только на ту часть запроса, которая определяет, какой SQL будет выведен в предложениях WHERE и FROM. Опция contains_eager() не влияет на отображение оператора SELECT за пределами предложения columns, поэтому не имеет никакого взаимодействия с опцией with_loader_criteria(). Однако «работа» заключается в том, что опция contains_eager() предназначена для использования с запросом, который уже каким-либо образом выбирает из дополнительных сущностей, где опция with_loader_criteria() может применить свои дополнительные критерии.

В приведенном ниже примере, если принять отношение отображения за A -> A.bs -> B, то заданная опция with_loader_criteria() будет влиять на способ отображения JOIN:

stmt = select(A).join(A.bs).options(
    contains_eager(A.bs),
    with_loader_criteria(B, B.flag == 1)
)

Выше указанная опция with_loader_criteria() будет влиять на предложение ON в JOIN, которое задается опцией .join(A.bs), поэтому применяется, как и ожидалось. Опция contains_eager() приводит к тому, что столбцы из B добавляются к предложению columns:

SELECT
    b.id, b.a_id, b.data, b.flag,
    a.id AS id_1,
    a.data AS data_1
FROM a JOIN b ON a.id = b.a_id AND b.flag = :flag_1

Использование опции contains_eager() в приведенном выше выражении не влияет на поведение опции with_loader_criteria(). Если бы опция contains_eager() была опущена, то SQL был бы тем же самым в отношении предложений FROM и WHERE, где with_loader_criteria() продолжает добавлять свои критерии к предложению ON в JOIN. Добавление contains_eager() влияет только на предложение columns, поскольку добавляются дополнительные столбцы к b, которые затем потребляются ORM для создания экземпляров B.

Предупреждение

Использование лямбды внутри вызова with_loader_criteria() вызывается только один раз для каждого уникального класса. Пользовательские функции не должны вызываться внутри этой лямбды. Обзор функции «лямбда SQL» см. в разделе Использование ламбдас для значительного увеличения скорости создания операторов, который предназначен только для расширенного использования.

Параметры:
  • entity_or_base – сопоставленный класс или класс, являющийся суперклассом определенного набора сопоставленных классов, к которому будет применяться правило.

  • where_criteria – выражение Core SQL, применяющее ограничивающие критерии. Это также может быть «лямбда:» или функция Python, принимающая целевой класс в качестве аргумента, когда данный класс является базовым с множеством различных отображаемых подклассов. … note:: Для поддержки pickling используйте Python-функцию на уровне модуля для создания SQL-выражения вместо лямбды или фиксированного SQL-выражения, которые, как правило, не являются picklable.

  • include_aliases – если True, то правило применяется и к конструкциям aliased().

  • propagate_to_loaders – По умолчанию имеет значение True, применяется для загрузчиков отношений, таких как ленивые загрузчики. Это означает, что сам объект опции, включая SQL-выражение, переносится вместе с каждым загружаемым экземпляром. Если установить значение False, то объект не будет присваиваться отдельным экземплярам. … seealso:: События запросов ORM - содержит примеры использования with_loader_criteria(). Добавление глобальных критериев WHERE / ON - базовый пример объединения with_loader_criteria() с событием SessionEvents.do_orm_execute().

  • track_closure_variables – Если значение False, то закрывающие переменные внутри лямбда-выражения не будут использоваться в качестве ключа кэша. Это позволяет использовать более сложные выражения внутри лямбда-выражения, но требует, чтобы лямбда гарантированно возвращала идентичный SQL каждый раз, когда задан конкретный класс. … versionadded:: 1.4.0b2

function sqlalchemy.orm.join(left: _FromClauseArgument, right: _FromClauseArgument, onclause: Optional[_OnClauseArgument] = None, isouter: bool = False, full: bool = False) _ORMJoin

Создайте внутреннее соединение между левой и правой клаузами.

join() является расширением основного интерфейса join, предоставляемого join(), где левый и правый selectable могут быть не только основными selectable-объектами, такими как Table, но и отображенными классами или экземплярами AliasedClass. Предложение «on» может быть выражением SQL или отображаемым атрибутом ORM, ссылающимся на сконфигурированный relationship().

В современном использовании метод join() обычно не нужен, поскольку его функциональность заключена в методах Select.join() и Query.join(), которые обладают значительной степенью автоматизации, превышающей возможности join() как такового. Явное использование join() с операторами SELECT, поддерживающими ORM, предполагает использование метода Select.select_from(), как в:

from sqlalchemy.orm import join
stmt = select(User).\
    select_from(join(User, Address, User.addresses)).\
    filter(Address.email_address=='foo@bar.com')

В современной SQLAlchemy приведенное выше соединение можно записать более лаконично:

stmt = select(User).\
        join(User.addresses).\
        filter(Address.email_address=='foo@bar.com')

Предупреждение

Использование join() напрямую может некорректно работать с современными опциями ORM, такими как with_loader_criteria(). Настоятельно рекомендуется при создании ORM-соединений использовать идиоматические схемы соединения, предоставляемые такими методами, как Select.join() и Select.join_from().

См.также

Присоединяйтесь к - в Руководство по составлению запросов в ORM для ознакомления с идиоматическими схемами присоединения в ORM

function sqlalchemy.orm.outerjoin(left: _FromClauseArgument, right: _FromClauseArgument, onclause: Optional[_OnClauseArgument] = None, full: bool = False) _ORMJoin

Создайте левое внешнее соединение между левой и правой клаузами.

Это «внешняя» версия функции join(), имеющая такое же поведение, за исключением того, что создается OUTER JOIN. Другие подробности использования этой функции см. в документации к ней.

function sqlalchemy.orm.with_parent(instance: object, prop: attributes.QueryableAttribute[Any], from_entity: Optional[_EntityType[Any]] = None) ColumnElement[bool]

Создайте критерий фильтрации, который связывает первичную сущность данного запроса с заданным связанным экземпляром, используя установленную конфигурацию relationship().

Например:

stmt = select(Address).where(with_parent(some_user, User.addresses))

SQL, отображаемый при этом, аналогичен тому, который отображается при работе ленивого загрузчика с данным родителем по данному атрибуту. Это означает, что соответствующее состояние берется из родительского объекта в Python без необходимости отображения соединений с родительской таблицей в отображаемом операторе.

В данном свойстве также может использоваться PropComparator.of_type() для обозначения левой части критерия:

a1 = aliased(Address)
a2 = aliased(Address)
stmt = select(a1, a2).where(
    with_parent(u1, User.addresses.of_type(a2))
)

Приведенное выше использование эквивалентно использованию аргумента from_entity():

a1 = aliased(Address)
a2 = aliased(Address)
stmt = select(a1, a2).where(
    with_parent(u1, User.addresses, from_entity=a2)
)
Параметры:
  • instance – Экземпляр, который имеет некоторое relationship().

  • property – Атрибут Class-bound, указывающий, какое отношение из экземпляра должно быть использовано для согласования отношений родитель/ребенок.

  • from_entity – Сущность, которую следует рассматривать в качестве левой стороны. По умолчанию это «нулевая» сущность самого Query. … versionadded:: 1.2

Вернуться на верх