Типы составных колонн¶
Наборы столбцов могут быть связаны с одним определяемым пользователем типом данных, который в современном использовании обычно представляет собой класс_данных Python. ORM предоставляет единственный атрибут, который представляет группу столбцов с помощью предоставленного вами класса.
Простой пример представляет пары столбцов Integer
в виде объекта Point
с атрибутами .x
и .y
. Используя класс данных, эти атрибуты определяются с помощью соответствующего типа int
Python:
import dataclasses
@dataclasses.dataclass
class Point:
x: int
y: int
Формы, не являющиеся классами данных, также принимаются, но требуют реализации дополнительных методов. Пример использования класса, не являющегося классом данных, приведен в разделе Использование унаследованных недатаклассов.
Добавлено в версии 2.0: Конструкция composite()
полностью поддерживает классы данных Python, включая возможность выводить сопоставленные типы данных столбцов из составного класса.
Мы создадим отображение на таблицу vertices
, которая представляет две точки как x1/y1
и x2/y2
. Класс Point
связан с отображаемыми столбцами с помощью конструкции composite()
.
Приведенный ниже пример иллюстрирует самую современную форму composite()
, используемую с полностью Annotated Declarative Table конфигурацией. Конструкции mapped_column()
, представляющие каждый столбец, передаются непосредственно в composite()
, указывая ноль или более аспектов генерируемых столбцов, в данном случае имена; конструкция composite()
извлекает типы столбцов (в данном случае int
, соответствующие Integer
) из класса данных непосредственно:
from sqlalchemy.orm import DeclarativeBase, Mapped
from sqlalchemy.orm import composite, mapped_column
class Base(DeclarativeBase):
pass
class Vertex(Base):
__tablename__ = "vertices"
id: Mapped[int] = mapped_column(primary_key=True)
start: Mapped[Point] = composite(mapped_column("x1"), mapped_column("y1"))
end: Mapped[Point] = composite(mapped_column("x2"), mapped_column("y2"))
def __repr__(self):
return f"Vertex(start={self.start}, end={self.end})"
Приведенное выше отображение будет соответствовать оператору CREATE TABLE в виде:
>>> from sqlalchemy.schema import CreateTable
>>> print(CreateTable(Vertex.__table__))
{printsql}CREATE TABLE vertices (
id INTEGER NOT NULL,
x1 INTEGER NOT NULL,
y1 INTEGER NOT NULL,
x2 INTEGER NOT NULL,
y2 INTEGER NOT NULL,
PRIMARY KEY (id)
)
Работа с сопоставленными составными типами колонок¶
С отображением, как показано в верхней секции, мы можем работать с классом Vertex
, где атрибуты .start
и .end
будут прозрачно ссылаться на колонки, на которые ссылается класс Point
, а также с экземплярами класса Vertex
, где атрибуты .start
и .end
будут ссылаться на экземпляры класса Point
. Колонки x1
, y1
, x2
и y2
обрабатываются прозрачно:
Существующие точечные объекты
Мы можем создать объект
Vertex
, назначить объектыPoint
в качестве членов, и они будут сохранены, как и ожидалось:>>> v = Vertex(start=Point(3, 4), end=Point(5, 6)) >>> session.add(v) >>> session.commit() {execsql}BEGIN (implicit) INSERT INTO vertices (x1, y1, x2, y2) VALUES (?, ?, ?, ?) [generated in ...] (3, 4, 5, 6) COMMIT
Выбор точечных объектов в качестве столбцов.
composite()
позволит атрибутамVertex.start
иVertex.end
вести себя как одно SQL-выражение в максимально возможной степени при использовании ORMSession
(включая унаследованный объектQuery
) для выбора объектовPoint
:>>> stmt = select(Vertex.start, Vertex.end) >>> session.execute(stmt).all() {execsql}SELECT vertices.x1, vertices.y1, vertices.x2, vertices.y2 FROM vertices [...] () {stop}[(Point(x=3, y=4), Point(x=5, y=6))]
Сравнение точечных объектов в выражениях SQL.
Атрибуты
Vertex.start
иVertex.end
могут использоваться в критериях WHERE и подобных, используя специальные объектыPoint
для сравнений:>>> stmt = select(Vertex).where(Vertex.start == Point(3, 4)).where(Vertex.end < Point(7, 8)) >>> session.scalars(stmt).all() {execsql}SELECT vertices.id, vertices.x1, vertices.y1, vertices.x2, vertices.y2 FROM vertices WHERE vertices.x1 = ? AND vertices.y1 = ? AND vertices.x2 < ? AND vertices.y2 < ? [...] (3, 4, 7, 8) {stop}[Vertex(Point(x=3, y=4), Point(x=5, y=6))]
Добавлено в версии 2.0: Конструкции
composite()
теперь поддерживают «упорядочивающие» сравнения, такие как<
,>=
и подобные, в дополнение к уже имеющейся поддержке==
,!=
.Совет
Сравнение «порядка» с использованием оператора «меньше чем» (
<
), а также сравнение «равенства» с использованием==
при использовании для генерации SQL-выражений реализуются классомComparator
и не используют методы сравнения самого составного класса, например, методы__lt__()
или__eq__()
. Из этого следует, что вышеуказанный класс данныхPoint
также не должен реализовывать параметр dataclassesorder=True
для работы вышеуказанных операций SQL. В разделе Пересмотр операций сравнения для композитов содержится информация о том, как настраивать операции сравнения.Обновление объектов точек на экземплярах вершин.
По умолчанию объект
Point
должен быть заменен новым объектом, чтобы изменения были обнаружены:>>> v1 = session.scalars(select(Vertex)).one() {execsql}SELECT vertices.id, vertices.x1, vertices.y1, vertices.x2, vertices.y2 FROM vertices [...] () {stop} >>> v1.end = Point(x=10, y=14) >>> session.commit() {execsql}UPDATE vertices SET x2=?, y2=? WHERE vertices.id = ? [...] (10, 14, 1) COMMIT
Для того чтобы разрешить изменения на месте составного объекта, необходимо использовать расширение Отслеживание мутаций. Примеры см. в разделе Установление мутабельности композитов.
Другие картографические формы для композитов¶
Конструкции composite()
могут быть переданы соответствующие столбцы с помощью конструкции mapped_column()
, Column
или строкового имени существующего сопоставленного столбца. Следующие примеры иллюстрируют эквивалентное отображение, как в основном разделе выше.
Сопоставьте столбцы напрямую, затем передайте в составной
Здесь мы передаем существующие экземпляры
mapped_column()
в конструкциюcomposite()
, как в неаннотированном примере ниже, где мы также передаем классPoint
в качестве первого аргумента вcomposite()
:from sqlalchemy import Integer from sqlalchemy.orm import mapped_column, composite class Vertex(Base): __tablename__ = "vertices" id = mapped_column(Integer, primary_key=True) x1 = mapped_column(Integer) y1 = mapped_column(Integer) x2 = mapped_column(Integer) y2 = mapped_column(Integer) start = composite(Point, x1, y1) end = composite(Point, x2, y2)
Сопоставляйте столбцы напрямую, передавайте имена атрибутов композиту
Мы можем написать тот же пример, используя более аннотированные формы, где у нас есть возможность передавать имена атрибутов в
composite()
вместо полных конструкций столбцов:from sqlalchemy.orm import mapped_column, composite, Mapped class Vertex(Base): __tablename__ = "vertices" id: Mapped[int] = mapped_column(primary_key=True) x1: Mapped[int] y1: Mapped[int] x2: Mapped[int] y2: Mapped[int] start: Mapped[Point] = composite("x1", "y1") end: Mapped[Point] = composite("x2", "y2")
Императивное отображение и императивная таблица
При использовании отображений imperative table или полностью imperative мы имеем доступ непосредственно к объектам
Column
. Их также можно передавать вcomposite()
, как в императивном примере ниже:mapper_registry.map_imperatively( Vertex, vertices_table, properties={ "start": composite(Point, vertices_table.c.x1, vertices_table.c.y1), "end": composite(Point, vertices_table.c.x2, vertices_table.c.y2), }, )
Использование унаследованных недатаклассов¶
Если не используется класс данных, то требования к пользовательскому классу типа данных заключаются в том, чтобы он имел конструктор, который принимает позиционные аргументы, соответствующие формату столбцов, а также предоставлял метод __composite_values__()
, который возвращает состояние объекта в виде списка или кортежа в порядке следования его атрибутов, основанных на столбцах. Он также должен предоставлять адекватные методы __eq__()
и __ne__()
, которые проверяют равенство двух экземпляров.
Для иллюстрации эквивалентного класса Point
из основного раздела, не использующего класс данных:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __composite_values__(self):
return self.x, self.y
def __repr__(self):
return f"Point(x={self.x!r}, y={self.y!r})"
def __eq__(self, other):
return isinstance(other, Point) and other.x == self.x and other.y == self.y
def __ne__(self, other):
return not self.__eq__(other)
Использование с composite()
затем продолжается, где столбцы, которые будут связаны с классом Point
, также должны быть объявлены с явными типами, используя одну из форм в Другие картографические формы для композитов.
Отслеживание мутаций на месте на композитах¶
Изменения на месте существующего составного значения не отслеживаются автоматически. Вместо этого композитный класс должен явно предоставлять события своему родительскому объекту. Эта задача в значительной степени автоматизирована с помощью миксина MutableComposite
, который использует события для связывания каждого определенного пользователем составного объекта со всеми родительскими ассоциациями. Пожалуйста, посмотрите пример в Установление мутабельности композитов.
Пересмотр операций сравнения для композитов¶
Операция сравнения «равно» по умолчанию производит И всех соответствующих столбцов, приравненных друг к другу. Это можно изменить с помощью аргумента comparator_factory
в composite()
, где мы указываем пользовательский класс Comparator
для определения существующих или новых операций. Ниже мы проиллюстрируем оператор «больше чем», реализующий то же выражение, что и базовый «больше чем»:
import dataclasses
from sqlalchemy.orm import composite
from sqlalchemy.orm import CompositeProperty
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.sql import and_
@dataclasses.dataclass
class Point:
x: int
y: int
class PointComparator(CompositeProperty.Comparator):
def __gt__(self, other):
"""redefine the 'greater than' operation"""
return and_(
*[
a > b
for a, b in zip(
self.__clause_element__().clauses,
dataclasses.astuple(other),
)
]
)
class Base(DeclarativeBase):
pass
class Vertex(Base):
__tablename__ = "vertices"
id: Mapped[int] = mapped_column(primary_key=True)
start: Mapped[Point] = composite(
mapped_column("x1"), mapped_column("y1"), comparator_factory=PointComparator
)
end: Mapped[Point] = composite(
mapped_column("x2"), mapped_column("y2"), comparator_factory=PointComparator
)
Поскольку Point
является классом данных, мы можем использовать dataclasses.astuple()
для получения кортежной формы экземпляров Point
.
Затем пользовательский компаратор возвращает соответствующее выражение SQL:
>>> print(Vertex.start > Point(5, 6))
{printsql}vertices.x1 > :x1_1 AND vertices.y1 > :y1_1
Композиты для раскроя¶
Составные объекты могут быть определены для работы в простых вложенных схемах путем переопределения поведения внутри составного класса для работы по желанию, а затем отображения составного класса на всю длину отдельных столбцов обычным образом. Это требует определения дополнительных методов для перехода между «вложенной» и «плоской» формами.
Ниже мы реорганизуем класс Vertex
, чтобы он сам был составным объектом, который ссылается на объекты Point
. Vertex
и Point
могут быть классами данных, однако мы добавим к Vertex
пользовательский метод построения, который может быть использован для создания новых объектов Vertex
, заданных значениями четырех столбцов, которые мы произвольно назовем _generate()
и определим как метод класса, чтобы мы могли создавать новые объекты Vertex
, передавая значения в метод Vertex._generate()
.
Мы также реализуем метод __composite_values__()
, который представляет собой фиксированное имя, распознаваемое конструкцией composite()
(представленной ранее в Использование унаследованных недатаклассов), указывающее на стандартный способ получения объекта в виде плоского кортежа значений столбцов, что в данном случае заменит обычную методологию, ориентированную на работу с классами данных.
С помощью нашего пользовательского конструктора _generate()
и метода сериализатора __composite_values__()
мы теперь можем перемещаться между плоским кортежем столбцов и объектами Vertex
, содержащими экземпляры Point
. Метод Vertex._generate
передается в качестве первого аргумента конструкции composite()
как источник новых экземпляров Vertex
, а метод __composite_values__()
будет неявно использоваться composite()
.
Для целей примера композит Vertex
затем отображается на класс HasVertex
, в котором в конечном итоге размещается Table
, содержащий четыре исходных столбца:
from __future__ import annotations
import dataclasses
from typing import Any
from typing import Tuple
from sqlalchemy.orm import composite
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
@dataclasses.dataclass
class Point:
x: int
y: int
@dataclasses.dataclass
class Vertex:
start: Point
end: Point
@classmethod
def _generate(cls, x1: int, y1: int, x2: int, y2: int) -> Vertex:
"""generate a Vertex from a row"""
return Vertex(Point(x1, y1), Point(x2, y2))
def __composite_values__(self) -> Tuple[Any, ...]:
"""generate a row from a Vertex"""
return dataclasses.astuple(self.start) + dataclasses.astuple(self.end)
class Base(DeclarativeBase):
pass
class HasVertex(Base):
__tablename__ = "has_vertex"
id: Mapped[int] = mapped_column(primary_key=True)
x1: Mapped[int]
y1: Mapped[int]
x2: Mapped[int]
y2: Mapped[int]
vertex: Mapped[Vertex] = composite(Vertex._generate, "x1", "y1", "x2", "y2")
Приведенное выше отображение можно использовать в терминах HasVertex
, Vertex
и Point
:
hv = HasVertex(vertex=Vertex(Point(1, 2), Point(3, 4)))
session.add(hv)
session.commit()
stmt = select(HasVertex).where(HasVertex.vertex == Vertex(Point(1, 2), Point(3, 4)))
hv = session.scalars(stmt).first()
print(hv.vertex.start)
print(hv.vertex.end)
Композитный API¶
Object Name | Description |
---|---|
composite([_class_or_attr], *attrs, [group, deferred, raiseload, comparator_factory, active_history, init, repr, default, default_factory, compare, kw_only, info, doc], **__kw) |
Возвращает составное свойство на основе столбцов для использования с Mapper. |
- function sqlalchemy.orm.composite(_class_or_attr: Union[None, Type[_CC], Callable[..., _CC], _CompositeAttrType[Any]] = None, *attrs: _CompositeAttrType[Any], group: Optional[str] = None, deferred: bool = False, raiseload: bool = False, comparator_factory: Optional[Type[Composite.Comparator[_T]]] = None, active_history: bool = False, init: Union[_NoArg, bool] = _NoArg.NO_ARG, repr: Union[_NoArg, bool] = _NoArg.NO_ARG, default: Optional[Any] = _NoArg.NO_ARG, default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG, compare: Union[_NoArg, bool] = _NoArg.NO_ARG, kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG, info: Optional[_InfoType] = None, doc: Optional[str] = None, **__kw: Any) Composite[Any] ¶
Возвращает составное свойство на основе столбцов для использования с Mapper.
Полный пример использования см. в разделе документации по отображению Типы составных колонн.
MapperProperty
, возвращаемыйcomposite()
, являетсяComposite
.- Параметры:
class_ – Класс «составной тип» или любой класс-метод или вызываемый объект, который создаст новый экземпляр составного объекта, учитывая значения столбцов по порядку.
*attrs – Список отображаемых элементов, которые могут включать: *
Column
объекты *mapped_column()
конструкции * строковые имена других атрибутов сопоставленного класса, которые могут быть любыми другими SQL или объектно-сопоставленными атрибутами. Это может, например, позволить композиту, который ссылается на отношения «многие-к-одному».active_history=False – При
True
указывает, что при замене скалярного атрибута должно быть загружено «предыдущее» значение, если оно еще не загружено. См. тот же флаг дляcolumn_property()
.group – Имя группы для этого свойства, когда оно помечено как отложенное.
deferred – При значении True свойство колонки является «отложенным», то есть оно не загружается немедленно, а загружается при первом обращении к атрибуту экземпляра. См. также
deferred()
.comparator_factory – класс, расширяющий
Comparator
, который обеспечивает генерацию пользовательских предложений SQL для операций сравнения.doc – необязательная строка, которая будет применяться в качестве doc для дескриптора, связанного с классом.
info – Необязательный словарь данных, который будет заполнен в атрибут
MapperProperty.info
этого объекта.init – Специфично для Декларативное отображение классов данных, указывает, должен ли сопоставленный атрибут быть частью метода
__init__()
, создаваемого процессом класса данных.repr – Специфично для Декларативное отображение классов данных, указывает, должен ли сопоставленный атрибут быть частью метода
__repr__()
, создаваемого процессом класса данных.default_factory – Специфично для Декларативное отображение классов данных, определяет функцию генерации значения по умолчанию, которая будет иметь место как часть метода
__init__()
, генерируемого процессом dataclass.compare – Специфично для Декларативное отображение классов данных, указывает, следует ли включать это поле в операции сравнения при генерации методов
__eq__()
и__ne__()
для сопоставленного класса. … versionadded:: 2.0.0b4kw_only – Специфично для Декларативное отображение классов данных, указывает, должно ли это поле быть помечено как предназначенное только для ключевого слова при генерации
__init__()
.