Возможности ORM API для создания запросов¶
Опции загрузчика ORM¶
Опции загрузчика - это объекты, которые при передаче в метод Select.options()
объекта Select
или аналогичной конструкции SQL влияют на загрузку как столбцов, так и атрибутов, ориентированных на отношения. Большинство опций загрузчика спускается из иерархии Load
. Полный обзор использования опций загрузчика приведен в следующих разделах.
См.также
Варианты загрузки колонн - подробное описание параметров отображения и загрузки, влияющих на загрузку атрибутов отображения колонок и SQL-выражений
Техники загрузки отношений - детализация отношений и опций загрузки, влияющих на то, как загружаются атрибуты отображения
relationship()
Варианты выполнения 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
является поддержка различных функций загрузки атрибутов, которые могут изменять способ загрузки атрибутов на основе каждого запроса. К таким возможностям относятся:
Опция
with_expression()
Метод
PropComparator.and_()
, который может изменять то, что загружает стратегия загрузчикаОпция
contains_eager()
Опция
with_loader_criteria()
Опция выполнения 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, ...]) |
Создать псевдоним заданного элемента, обычно экземпляр |
Представляет собой «псевдослучайную» форму сопоставленного класса для использования с Query. |
|
Предоставить интерфейс проверки для объекта |
|
Группировка SQL-выражений, которые возвращает |
|
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]) |
Создайте критерий фильтрации, который связывает первичную сущность данного запроса с заданным связанным экземпляром, используя установленную конфигурацию |
- 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-сопоставленным.См.также
Псевдонимы сущностей в ORM - в Унифицированный учебник по SQLAlchemy
Выбор псевдонимов ORM - в Руководство по составлению запросов в 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 с привязкой к идентификаторам.Members
__init__(), c, columns, create_row_processor(), is_aliased_class, is_bundle, is_clause_element, is_mapper, label(), single_entity
Классная подпись
класс
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)
См.также
-
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 будут возвращаться как единое целое, а не как элемент в кортеже с ключом.
-
method
- 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