Атрибуты гибрида¶
Определите атрибуты для ORM-сопоставленных классов, которые имеют «гибридное» поведение.
«Гибридный» означает, что атрибут имеет различное поведение, определенное на уровне класса и на уровне экземпляра.
Расширение hybrid
предоставляет специальную форму декоратора методов и имеет минимальные зависимости от остальной части SQLAlchemy. Его базовая теория работы может работать с любой системой выражений, основанной на дескрипторах.
Рассмотрим отображение Interval
, представляющее целочисленные значения start
и end
. Мы можем определить функции более высокого уровня на отображенных классах, которые производят SQL-выражения на уровне класса и оценку выражений Python на уровне экземпляра. Ниже каждая функция, украшенная символами hybrid_method
или hybrid_property
, может получать self
как экземпляр класса, а может получать класс напрямую, в зависимости от контекста:
from __future__ import annotations
from sqlalchemy.ext.hybrid import hybrid_method
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class Interval(Base):
__tablename__ = 'interval'
id: Mapped[int] = mapped_column(primary_key=True)
start: Mapped[int]
end: Mapped[int]
def __init__(self, start: int, end: int):
self.start = start
self.end = end
@hybrid_property
def length(self) -> int:
return self.end - self.start
@hybrid_method
def contains(self, point: int) -> bool:
return (self.start <= point) & (point <= self.end)
@hybrid_method
def intersects(self, other: Interval) -> bool:
return self.contains(other.start) | self.contains(other.end)
Выше свойство length
возвращает разность между атрибутами end
и start
. С экземпляром Interval
это вычитание происходит на языке Python, используя обычную механику дескрипторов Python:
>>> i1 = Interval(5, 10)
>>> i1.length
5
При работе с самим классом Interval
дескриптор hybrid_property
оценивает тело функции, задавая в качестве аргумента класс Interval
, который при оценке с помощью механики выражений SQLAlchemy возвращает новое SQL-выражение:
>>> from sqlalchemy import select
>>> print(select(Interval.length))
{printsql}SELECT interval."end" - interval.start AS length
FROM interval{stop}
>>> print(select(Interval).filter(Interval.length > 10))
{printsql}SELECT interval.id, interval.start, interval."end"
FROM interval
WHERE interval."end" - interval.start > :param_1
Такие методы фильтрации, как Select.filter_by()
, поддерживаются и с гибридными атрибутами:
>>> print(select(Interval).filter_by(length=5))
{printsql}SELECT interval.id, interval.start, interval."end"
FROM interval
WHERE interval."end" - interval.start = :param_1
В примере класса Interval
также показаны два метода, contains()
и intersects()
, декорированные с помощью hybrid_method
. Этот декоратор применяет к методам ту же идею, что и hybrid_property
к атрибутам. Методы возвращают булевы значения и используют преимущества побитовых операторов Python |
и &
для получения эквивалентного поведения булевых значений на уровне экземпляра и на уровне выражения SQL:
>>> i1.contains(6)
True
>>> i1.contains(15)
False
>>> i1.intersects(Interval(7, 18))
True
>>> i1.intersects(Interval(25, 29))
False
>>> print(select(Interval).filter(Interval.contains(15)))
{printsql}SELECT interval.id, interval.start, interval."end"
FROM interval
WHERE interval.start <= :start_1 AND interval."end" > :end_1{stop}
>>> ia = aliased(Interval)
>>> print(select(Interval, ia).filter(Interval.intersects(ia)))
{printsql}SELECT interval.id, interval.start,
interval."end", interval_1.id AS interval_1_id,
interval_1.start AS interval_1_start, interval_1."end" AS interval_1_end
FROM interval, interval AS interval_1
WHERE interval.start <= interval_1.start
AND interval."end" > interval_1.start
OR interval.start <= interval_1."end"
AND interval."end" > interval_1."end"{stop}
Определение поведения выражения, отличного от поведения атрибута¶
В предыдущем разделе мы удачно использовали побитовые операторы &
и |
в методах Interval.contains
и Interval.intersects
, поскольку наши функции оперировали двумя булевыми значениями, возвращая новое. Во многих случаях построение функции на языке Python и SQL-выражения SQLAlchemy имеет достаточно различий, чтобы определить два отдельных Python-выражения. Для этого в декораторе hybrid
определяется модификатор hybrid_property.expression()
. В качестве примера определим радиус интервала, для чего необходимо использовать функцию абсолютного значения:
from sqlalchemy import ColumnElement
from sqlalchemy import Float
from sqlalchemy import func
from sqlalchemy import type_coerce
class Interval(Base):
# ...
@hybrid_property
def radius(self) -> float:
return abs(self.length) / 2
@radius.inplace.expression
@classmethod
def _radius_expression(cls) -> ColumnElement[float]:
return type_coerce(func.abs(cls.length) / 2, Float)
В приведенном примере метод hybrid_property
, впервые присвоенный имени Interval.radius
, изменяется последующим методом Interval._radius_expression
с использованием декоратора @radius.inplace.expression
, который объединяет два модификатора hybrid_property.inplace
и hybrid_property.expression
. Использование hybrid_property.inplace
указывает на то, что модификатор hybrid_property.expression()
должен мутировать существующий гибридный объект по адресу Interval.radius
на месте, не создавая нового объекта. Замечания по этому модификатору и его обоснование рассматриваются в следующем разделе Использование inplace для создания гибридных свойств, совместимых с pep-484. Использование @classmethod
является необязательным и служит исключительно для того, чтобы дать средствам типизации подсказку, что cls
в данном случае должен быть классом Interval
, а не экземпляром Interval
.
Примечание
hybrid_property.inplace
, а также использование @classmethod
для правильной поддержки типизации доступны начиная с версии SQLAlchemy 2.0.4 и не будут работать в более ранних версиях.
Поскольку Interval.radius
теперь включает элемент выражения, при обращении к Interval.radius
на уровне класса возвращается SQL-функция ABS()
:
>>> from sqlalchemy import select
>>> print(select(Interval).filter(Interval.radius > 5))
{printsql}SELECT interval.id, interval.start, interval."end"
FROM interval
WHERE abs(interval."end" - interval.start) / :abs_1 > :param_1
Использование inplace
для создания гибридных свойств, совместимых с pep-484¶
В предыдущем разделе показан декоратор hybrid_property
, который включает в себя две отдельные декорируемые функции уровня метода, обе из которых создают один атрибут объекта, обозначаемый как Interval.radius
. На самом деле существует несколько различных модификаторов, которые мы можем использовать для hybrid_property
, включая hybrid_property.expression()
, hybrid_property.setter()
и hybrid_property.update_expression()
.
Декоратор SQLAlchemy hybrid_property
предполагает, что добавление этих методов может осуществляться так же, как и встроенный в Python декоратор @property
, где идиоматическое использование заключается в том, чтобы продолжать переопределять атрибут многократно, используя каждый раз одно и то же имя атрибута, как в примере ниже, иллюстрирующем использование hybrid_property.setter()
и hybrid_property.expression()
для дескриптора Interval.radius
:
# correct use, however is not accepted by pep-484 tooling
class Interval(Base):
# ...
@hybrid_property
def radius(self):
return abs(self.length) / 2
@radius.setter
def radius(self, value):
self.length = value * 2
@radius.expression
def radius(cls):
return type_coerce(func.abs(cls.length) / 2, Float)
Выше приведены три метода Interval.radius
, но поскольку каждый из них украшен сначала декоратором hybrid_property
, а затем самим именем @radius
, то в итоге получается, что Interval.radius
- это один атрибут с тремя различными функциями, содержащимися в нем. Этот стиль использования заимствован из Python’s documented use of @property. Важно отметить, что при работе как @property
, так и hybrid_property
каждый раз создается копия дескриптора. То есть при каждом вызове @radius.expression
, @radius.setter
и т.д. создается полностью новый объект. Это позволяет без проблем переопределять атрибут в подклассах (о том, как это используется, см. раздел Повторное использование гибридных свойств в подклассах далее в этой главе).
Однако указанный подход несовместим с такими средствами типизации, как mypy и pyright. Собственный декоратор Python @property
не имеет этого ограничения только потому, что these tools hardcode the behavior of @property, то есть этот синтаксис недоступен для SQLAlchemy при соответствии PEP 484.
Для того чтобы обеспечить приемлемый синтаксис и при этом не нарушить требования к набору текста, декоратор hybrid_property.inplace
позволяет повторно использовать один и тот же декоратор с разными именами методов, но при этом создавать один декоратор под одним именем:
# correct use which is also accepted by pep-484 tooling
class Interval(Base):
# ...
@hybrid_property
def radius(self) -> float:
return abs(self.length) / 2
@radius.inplace.setter
def _radius_setter(self, value: float) -> None:
# for example only
self.length = value * 2
@radius.inplace.expression
@classmethod
def _radius_expression(cls) -> ColumnElement[float]:
return type_coerce(func.abs(cls.length) / 2, Float)
Использование hybrid_property.inplace
дополнительно квалифицирует использование декоратора, что новая копия не должна создаваться, тем самым сохраняя имя Interval.radius
и позволяя дополнительным методам Interval._radius_setter
и Interval._radius_expression
иметь другое имя.
Добавлено в версии 2.0.4: Добавлена функция hybrid_property.inplace
, позволяющая менее многословно строить составные объекты hybrid_property
и не использовать повторяющиеся имена методов. Дополнительно разрешено использовать @classmethod
внутри hybrid_property.expression
, hybrid_property.update_expression
и hybrid_property.comparator
для того, чтобы средства набора текста могли идентифицировать cls
как класс, а не экземпляр в сигнатуре метода.
Определение задатчиков¶
Модификатор hybrid_property.setter()
позволяет построить пользовательский метод-сеттер, который может изменять значения на объекте:
class Interval(Base):
# ...
@hybrid_property
def length(self) -> int:
return self.end - self.start
@length.inplace.setter
def _length_setter(self, value: int) -> None:
self.end = self.start + value
Теперь метод length(self, value)
вызывается по команде set:
>>> i1 = Interval(5, 10)
>>> i1.length
5
>>> i1.length = 12
>>> i1.end
17
Разрешение массового обновления ORM¶
Гибрид может определить пользовательский обработчик «UPDATE» для использования обновлений с поддержкой ORM, что позволяет использовать гибрид в предложении SET обновления.
Обычно при использовании гибрида с update()
в качестве столбца, являющегося целью SET, используется SQL-выражение. Если бы в нашем классе Interval
был гибрид start_point
, который ссылался бы на Interval.start
, то его можно было бы подставить непосредственно:
from sqlalchemy import update
stmt = update(Interval).values({Interval.start_point: 10})
Однако при использовании составного гибрида, например Interval.length
, этот гибрид представляет собой более одного столбца. С помощью декоратора hybrid_property.update_expression()
мы можем создать обработчик, который будет принимать значение, переданное в выражении VALUES, которое может повлиять на это. Обработчик, работающий аналогично нашему сеттеру, будет выглядеть так:
from typing import List, Tuple, Any
class Interval(Base):
# ...
@hybrid_property
def length(self) -> int:
return self.end - self.start
@length.inplace.setter
def _length_setter(self, value: int) -> None:
self.end = self.start + value
@length.inplace.update_expression
def _length_update_expression(cls, value: Any) -> List[Tuple[Any, Any]]:
return [
(cls.end, cls.start + value)
]
Выше, если мы используем Interval.length
в выражении UPDATE, то получаем гибридное выражение SET:
>>> from sqlalchemy import update
>>> print(update(Interval).values({Interval.length: 25}))
{printsql}UPDATE interval SET "end"=(interval.start + :start_1)
Это выражение SET подстраивается под ORM автоматически.
См.также
Операции INSERT, UPDATE и DELETE с поддержкой ORM - включает справочную информацию по операторам UPDATE с поддержкой ORM
Работа с отношениями¶
При создании гибридов, работающих со связанными объектами, существенной разницы нет, в отличие от данных, основанных на столбцах. Потребность в отдельных выражениях, как правило, выше. Мы рассмотрим два варианта: гибрид «зависимый от соединения» и гибрид «коррелированный подзапрос».
Гибрид отношений «присоединение-зависимость¶
Рассмотрим следующее декларативное отображение, которое связывает User
с SavingsAccount
:
from __future__ import annotations
from decimal import Decimal
from typing import cast
from typing import List
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import Numeric
from sqlalchemy import String
from sqlalchemy import SQLColumnExpression
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class SavingsAccount(Base):
__tablename__ = 'account'
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey('user.id'))
balance: Mapped[Decimal] = mapped_column(Numeric(15, 5))
owner: Mapped[User] = relationship(back_populates="accounts")
class User(Base):
__tablename__ = 'user'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
accounts: Mapped[List[SavingsAccount]] = relationship(
back_populates="owner", lazy="selectin"
)
@hybrid_property
def balance(self) -> Optional[Decimal]:
if self.accounts:
return self.accounts[0].balance
else:
return None
@balance.inplace.setter
def _balance_setter(self, value: Optional[Decimal]) -> None:
assert value is not None
if not self.accounts:
account = SavingsAccount(owner=self)
else:
account = self.accounts[0]
account.balance = value
@balance.inplace.expression
@classmethod
def _balance_expression(cls) -> SQLColumnExpression[Optional[Decimal]]:
return cast("SQLColumnExpression[Optional[Decimal]]", SavingsAccount.balance)
Приведенное выше гибридное свойство balance
работает с первой записью SavingsAccount
в списке учетных записей для данного пользователя. Методы in-Python getter/setter могут рассматривать accounts
как список Python, доступный на self
.
Совет
Геттер User.balance
в приведенном примере обращается к коллекции self.acccounts
, которая обычно загружается с помощью стратегии загрузчика selectinload()
, настроенной на User.balance
relationship()
. Если на relationship()
не указано иное, то по умолчанию используется стратегия загрузчика lazyload()
, которая выдает SQL по требованию. При использовании asyncio загрузчики по требованию, такие как lazyload()
, не поддерживаются, поэтому при использовании asyncio следует позаботиться о том, чтобы коллекция self.accounts
была доступна для этого гибридного аксессора.
На уровне выражений ожидается, что класс User
будет использоваться в соответствующем контексте так, что будет присутствовать соответствующее присоединение к SavingsAccount
:
>>> from sqlalchemy import select
>>> print(select(User, User.balance).
... join(User.accounts).filter(User.balance > 5000))
{printsql}SELECT "user".id AS user_id, "user".name AS user_name,
account.balance AS account_balance
FROM "user" JOIN account ON "user".id = account.user_id
WHERE account.balance > :balance_1
Заметим, однако, что в то время как аксессоры на уровне экземпляра должны быть озабочены тем, присутствует ли вообще self.accounts
, на уровне SQL-выражений эта проблема выражается иначе, где мы в основном используем внешнее соединение:
>>> from sqlalchemy import select
>>> from sqlalchemy import or_
>>> print (select(User, User.balance).outerjoin(User.accounts).
... filter(or_(User.balance < 5000, User.balance == None)))
{printsql}SELECT "user".id AS user_id, "user".name AS user_name,
account.balance AS account_balance
FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
WHERE account.balance < :balance_1 OR account.balance IS NULL
Построение пользовательских компараторов¶
Свойство hybrid также включает в себя помощник, позволяющий создавать пользовательские компараторы. Объект компаратора позволяет настраивать поведение каждого оператора выражения SQLAlchemy в отдельности. Они полезны при создании пользовательских типов, которые имеют весьма идиосинкразическое поведение на стороне SQL.
Примечание
Декоратор hybrid_property.comparator()
, представленный в этом разделе, заменяет использование декоратора hybrid_property.expression()
. Их совместное использование невозможно.
Приведенный ниже пример класса позволяет проводить сравнение без учета регистра на атрибуте с именем word_insensitive
:
from __future__ import annotations
from typing import Any
from sqlalchemy import ColumnElement
from sqlalchemy import func
from sqlalchemy.ext.hybrid import Comparator
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class CaseInsensitiveComparator(Comparator[str]):
def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
return func.lower(self.__clause_element__()) == func.lower(other)
class SearchWord(Base):
__tablename__ = 'searchword'
id: Mapped[int] = mapped_column(primary_key=True)
word: Mapped[str]
@hybrid_property
def word_insensitive(self) -> str:
return self.word.lower()
@word_insensitive.inplace.comparator
@classmethod
def _word_insensitive_comparator(cls) -> CaseInsensitiveComparator:
return CaseInsensitiveComparator(cls.word)
Выше SQL-выражения относительно word_insensitive
будут применять SQL-функцию LOWER()
к обеим сторонам:
>>> from sqlalchemy import select
>>> print(select(SearchWord).filter_by(word_insensitive="Trucks"))
{printsql}SELECT searchword.id, searchword.word
FROM searchword
WHERE lower(searchword.word) = lower(:lower_1)
Приведенный выше CaseInsensitiveComparator
реализует часть интерфейса ColumnOperators
. Операция «принуждения», такая как выделение нижнего регистра, может быть применена ко всем операциям сравнения (т.е. eq
, lt
, gt
и т.д.) с помощью Operators.operate()
:
class CaseInsensitiveComparator(Comparator):
def operate(self, op, other, **kwargs):
return op(
func.lower(self.__clause_element__()),
func.lower(other),
**kwargs,
)
Повторное использование гибридных свойств в подклассах¶
На гибрид можно ссылаться из суперкласса, что позволяет использовать модифицирующие методы типа hybrid_property.getter()
, hybrid_property.setter()
для переопределения этих методов в подклассе. Это похоже на то, как работает стандартный объект Python @property
:
class FirstNameOnly(Base):
# ...
first_name: Mapped[str]
@hybrid_property
def name(self) -> str:
return self.first_name
@name.inplace.setter
def _name_setter(self, value: str) -> None:
self.first_name = value
class FirstNameLastName(FirstNameOnly):
# ...
last_name: Mapped[str]
# 'inplace' is not used here; calling getter creates a copy
# of FirstNameOnly.name that is local to FirstNameLastName
@FirstNameOnly.name.getter
def name(self) -> str:
return self.first_name + ' ' + self.last_name
@name.inplace.setter
def _name_setter(self, value: str) -> None:
self.first_name, self.last_name = value.split(' ', 1)
Выше класс FirstNameLastName
обращается к гибриду из FirstNameOnly.name
, чтобы перепрофилировать его геттер и сеттер для подкласса.
При переопределении hybrid_property.expression()
и hybrid_property.comparator()
только в качестве первой ссылки на суперкласс эти имена конфликтуют с одноименными аксессорами на объекте QueryableAttribute
, возвращаемом на уровне класса. Чтобы переопределить эти методы при обращении непосредственно к дескриптору родительского класса, добавьте специальный квалификатор hybrid_property.overrides
, который де-ссылается на инструментальный атрибут обратно на гибридный объект:
class FirstNameLastName(FirstNameOnly):
# ...
last_name: Mapped[str]
@FirstNameOnly.name.overrides.expression
@classmethod
def name(cls):
return func.concat(cls.first_name, ' ', cls.last_name)
Гибридные объекты стоимости¶
Обратите внимание, что в нашем предыдущем примере, если бы мы сравнивали атрибут word_insensitive
экземпляра SearchWord
с обычной строкой Python, то обычная строка Python не была бы приведена к нижнему регистру - построенная нами CaseInsensitiveComparator
, возвращаемая @word_insensitive.comparator
, относится только к стороне SQL.
Более полной формой пользовательского компаратора является построение гибридного объекта значения. В этом случае целевое значение или выражение применяется к объекту значения, который затем возвращается аксессором во всех случаях. Объект значения позволяет контролировать все операции над значением, а также то, как обрабатываются сравниваемые значения, как со стороны SQL-выражения, так и со стороны Python-значения. Замена предыдущего класса CaseInsensitiveComparator
новым классом CaseInsensitiveWord
:
class CaseInsensitiveWord(Comparator):
"Hybrid value representing a lower case representation of a word."
def __init__(self, word):
if isinstance(word, basestring):
self.word = word.lower()
elif isinstance(word, CaseInsensitiveWord):
self.word = word.word
else:
self.word = func.lower(word)
def operate(self, op, other, **kwargs):
if not isinstance(other, CaseInsensitiveWord):
other = CaseInsensitiveWord(other)
return op(self.word, other.word, **kwargs)
def __clause_element__(self):
return self.word
def __str__(self):
return self.word
key = 'word'
"Label to apply to Query tuple results"
Выше объект CaseInsensitiveWord
представляет self.word
, который может быть SQL-функцией, а может быть и Python-функцией. Переопределив operate()
и __clause_element__()
для работы в терминах self.word
, все операции сравнения будут работать с «преобразованной» формой word
, будь то на стороне SQL или на стороне Python. Теперь наш класс SearchWord
может безоговорочно предоставлять объект CaseInsensitiveWord
из одного гибридного вызова:
class SearchWord(Base):
__tablename__ = 'searchword'
id: Mapped[int] = mapped_column(primary_key=True)
word: Mapped[str]
@hybrid_property
def word_insensitive(self) -> CaseInsensitiveWord:
return CaseInsensitiveWord(self.word)
Атрибут word_insensitive
теперь повсеместно имеет поведение сравнения без учета регистра, включая сравнение выражений SQL с выражениями Python (обратите внимание, что значение Python преобразуется в нижний регистр на стороне Python):
>>> print(select(SearchWord).filter_by(word_insensitive="Trucks"))
{printsql}SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
FROM searchword
WHERE lower(searchword.word) = :lower_1
SQL-выражение против SQL-выражения:
>>> from sqlalchemy.orm import aliased
>>> sw1 = aliased(SearchWord)
>>> sw2 = aliased(SearchWord)
>>> print(
... select(sw1.word_insensitive, sw2.word_insensitive).filter(
... sw1.word_insensitive > sw2.word_insensitive
... )
... )
{printsql}SELECT lower(searchword_1.word) AS lower_1,
lower(searchword_2.word) AS lower_2
FROM searchword AS searchword_1, searchword AS searchword_2
WHERE lower(searchword_1.word) > lower(searchword_2.word)
Только в языке Python выражение:
>>> ws1 = SearchWord(word="SomeWord")
>>> ws1.word_insensitive == "sOmEwOrD"
True
>>> ws1.word_insensitive == "XOmEwOrX"
False
>>> print(ws1.word_insensitive)
someword
Шаблон Hybrid Value очень полезен для любых значений, которые могут иметь несколько представлений, таких как временные метки, временные дельты, единицы измерения, валюты и зашифрованные пароли.
См.также
Hybrids and Value Agnostic Types - в блоге techspot.zzzeek.org
Value Agnostic Types, Part II - в блоге techspot.zzzeek.org
Справочник по API¶
Object Name | Description |
---|---|
Вспомогательный класс, позволяющий легко создавать собственные классы |
|
Декоратор, позволяющий определить метод объекта Python с поведением как на уровне экземпляра, так и на уровне класса. |
|
Декоратор, позволяющий определить дескриптор Python с поведением как на уровне экземпляров, так и на уровне классов. |
|
Перечисление. |
- class sqlalchemy.ext.hybrid.hybrid_method¶
Декоратор, позволяющий определить метод объекта Python с поведением как на уровне экземпляра, так и на уровне класса.
Members
__init__(), expression(), extension_type, inplace, is_attribute
Классическая подпись.
класс
sqlalchemy.ext.hybrid.hybrid_method
(sqlalchemy.orm.base.InspectionAttrInfo
,typing.Generic
)-
method
sqlalchemy.ext.hybrid.hybrid_method.
__init__(func: Callable[[Concatenate[Any, _P]], _R], expr: Optional[Callable[[Concatenate[Any, _P]], SQLCoreOperations[_R]]] = None)¶ Создайте новый
hybrid_method
.Использование обычно осуществляется через декоратор:
from sqlalchemy.ext.hybrid import hybrid_method class SomeClass: @hybrid_method def value(self, x, y): return self._value + x + y @value.expression @classmethod def value(cls, x, y): return func.some_function(cls._value, x, y)
-
method
sqlalchemy.ext.hybrid.hybrid_method.
expression(expr: Callable[[Concatenate[Any, _P]], SQLCoreOperations[_R]]) hybrid_method[_P, _R] ¶ Предоставьте модифицирующий декоратор, определяющий метод генерации SQL-выражений.
-
attribute
sqlalchemy.ext.hybrid.hybrid_method.
extension_type: InspectionAttrExtensionType = 'HYBRID_METHOD'¶ Тип расширения, если таковое имеется. По умолчанию
NotExtension.NOT_EXTENSION
-
attribute
sqlalchemy.ext.hybrid.hybrid_method.
inplace¶ Возвращает inplace-мутатор для данного
hybrid_method
.Класс
hybrid_method
уже выполняет мутацию «на месте» при вызове декоратораhybrid_method.expression()
, поэтому этот атрибут возвращает значение Self.Добавлено в версии 2.0.4.
-
attribute
sqlalchemy.ext.hybrid.hybrid_method.
is_attribute = True¶ True, если данный объект является Python descriptor.
Он может относиться к одному из многих типов. Обычно это
QueryableAttribute
, который обрабатывает события атрибутов от имениMapperProperty
. Но может быть и типом расширения, таким какAssociationProxy
илиhybrid_property
. При этомInspectionAttr.extension_type
будет ссылаться на константу, идентифицирующую конкретный подтип.См.также
-
method
- class sqlalchemy.ext.hybrid.hybrid_property¶
Декоратор, позволяющий определить дескриптор Python с поведением как на уровне экземпляров, так и на уровне классов.
Members
__init__(), comparator(), deleter(), expression(), extension_type, getter(), inplace, is_attribute, overrides, setter(), update_expression()
Классическая подпись.
класс
sqlalchemy.ext.hybrid.hybrid_property
(sqlalchemy.orm.base.InspectionAttrInfo
,sqlalchemy.orm.base.ORMDescriptor
)-
method
sqlalchemy.ext.hybrid.hybrid_property.
__init__(fget: _HybridGetterType[_T], fset: Optional[_HybridSetterType[_T]] = None, fdel: Optional[_HybridDeleterType[_T]] = None, expr: Optional[_HybridExprCallableType[_T]] = None, custom_comparator: Optional[Comparator[_T]] = None, update_expr: Optional[_HybridUpdaterType[_T]] = None)¶ Создайте новый
hybrid_property
.Использование обычно осуществляется через декоратор:
from sqlalchemy.ext.hybrid import hybrid_property class SomeClass: @hybrid_property def value(self): return self._value @value.setter def value(self, value): self._value = value
-
method
sqlalchemy.ext.hybrid.hybrid_property.
comparator(comparator: _HybridComparatorCallableType[_T]) hybrid_property[_T] ¶ Предоставьте модифицирующий декоратор, определяющий пользовательский метод создания компаратора.
Возвращаемое значение декорированного метода должно представлять собой экземпляр
Comparator
.Примечание
Декоратор
hybrid_property.comparator()
заменяет использование декоратораhybrid_property.expression()
. Их совместное использование невозможно.При вызове гибрида на уровне класса объект
Comparator
, переданный здесь, заворачивается внутрь специализированногоQueryableAttribute
, который является объектом того же типа, который используется ORM для представления других отображаемых атрибутов. Это делается для того, чтобы в возвращаемой структуре можно было хранить другие атрибуты уровня класса, такие как документальные строки и ссылку на сам гибрид, без каких-либо изменений в исходном объекте компаратора.Примечание
При обращении к гибридному свойству из класса-владельца (например,
SomeClass.some_hybrid
) возвращается экземплярQueryableAttribute
, представляющий объект выражения или компаратора в виде этого гибридного объекта. Однако сам этот объект имеет аксессорыexpression
иcomparator
, поэтому при попытке переопределить эти декораторы в подклассе может потребоваться сначала квалифицировать его с помощью модификатораhybrid_property.overrides
. Подробнее об этом модификаторе см.
-
method
sqlalchemy.ext.hybrid.hybrid_property.
deleter(fdel: _HybridDeleterType[_T]) hybrid_property[_T] ¶ Предоставьте модифицирующий декоратор, определяющий метод удаления.
-
method
sqlalchemy.ext.hybrid.hybrid_property.
expression(expr: _HybridExprCallableType[_T]) hybrid_property[_T] ¶ Предоставьте модифицирующий декоратор, определяющий метод генерации SQL-выражений.
Когда гибрид вызывается на уровне класса, приведенное здесь SQL-выражение оборачивается внутрь специализированного
QueryableAttribute
, который представляет собой объект того же типа, который используется ORM для представления других отображаемых атрибутов. Это делается для того, чтобы в возвращаемой структуре можно было сохранить другие атрибуты уровня класса, такие как документальные строки и ссылку на сам гибрид, без каких-либо изменений в исходном SQL-выражении.Примечание
При обращении к гибридному свойству из класса-владельца (например,
SomeClass.some_hybrid
) возвращается экземплярQueryableAttribute
, представляющий объект выражения или компаратора, а также данный гибридный объект. Однако сам этот объект имеет аксессорыexpression
иcomparator
, поэтому при попытке переопределить эти декораторы в подклассе может потребоваться сначала квалифицировать его с помощью модификатораhybrid_property.overrides
. Подробнее об этом модификаторе см.
-
attribute
sqlalchemy.ext.hybrid.hybrid_property.
extension_type: InspectionAttrExtensionType = 'HYBRID_PROPERTY'¶ Тип расширения, если таковое имеется. По умолчанию
NotExtension.NOT_EXTENSION
-
method
sqlalchemy.ext.hybrid.hybrid_property.
getter(fget: _HybridGetterType[_T]) hybrid_property[_T] ¶ Предоставьте модифицирующий декоратор, определяющий метод getter.
Добавлено в версии 1.2.
-
attribute
sqlalchemy.ext.hybrid.hybrid_property.
inplace¶ Возвращает inplace-мутатор для данного
hybrid_property
.Это сделано для того, чтобы обеспечить возможность мутации гибрида на месте, позволяя повторно использовать первый гибридный метод с определенным именем для добавления других методов без необходимости называть эти методы одинаково, например:
class Interval(Base): # ... @hybrid_property def radius(self) -> float: return abs(self.length) / 2 @radius.inplace.setter def _radius_setter(self, value: float) -> None: self.length = value * 2 @radius.inplace.expression def _radius_expression(cls) -> ColumnElement[float]: return type_coerce(func.abs(cls.length) / 2, Float)
Добавлено в версии 2.0.4.
-
attribute
sqlalchemy.ext.hybrid.hybrid_property.
is_attribute = True¶ True, если данный объект является Python descriptor.
Он может относиться к одному из многих типов. Обычно это
QueryableAttribute
, который обрабатывает события атрибутов от имениMapperProperty
. Но может быть и типом расширения, таким какAssociationProxy
илиhybrid_property
. При этомInspectionAttr.extension_type
будет ссылаться на константу, идентифицирующую конкретный подтип.См.также
-
attribute
sqlalchemy.ext.hybrid.hybrid_property.
overrides¶ Префикс для метода, переопределяющего существующий атрибут.
Аксессор
hybrid_property.overrides
просто возвращает этот гибридный объект, который при вызове на уровне класса из родительского класса отменяет ссылку на «инструментальный атрибут», обычно возвращаемый на этом уровне, и позволяет использовать модифицирующие декораторы типаhybrid_property.expression()
иhybrid_property.comparator()
без конфликта с одноименными атрибутами, обычно присутствующими наQueryableAttribute
:class SuperClass: # ... @hybrid_property def foobar(self): return self._foobar class SubClass(SuperClass): # ... @SuperClass.foobar.overrides.expression def foobar(cls): return func.subfoobar(self._foobar)
Добавлено в версии 1.2.
-
method
sqlalchemy.ext.hybrid.hybrid_property.
setter(fset: _HybridSetterType[_T]) hybrid_property[_T] ¶ Предоставьте модифицирующий декоратор, определяющий метод setter.
-
method
sqlalchemy.ext.hybrid.hybrid_property.
update_expression(meth: _HybridUpdaterType[_T]) hybrid_property[_T] ¶ Предоставьте модифицирующий декоратор, определяющий метод создания кортежа UPDATE.
Метод принимает одно значение, которое является значением, выводимым в предложение SET оператора UPDATE. Затем метод должен обработать это значение в отдельные столбцовые выражения, которые вписываются в конечный пункт SET, и вернуть их в виде последовательности из двух кортежей. Каждый кортеж содержит выражение столбца в качестве ключа и значение, которое должно быть выведено на экран.
Например:
class Person(Base): # ... first_name = Column(String) last_name = Column(String) @hybrid_property def fullname(self): return first_name + " " + last_name @fullname.update_expression def fullname(cls, value): fname, lname = value.split(" ", 1) return [ (cls.first_name, fname), (cls.last_name, lname) ]
Добавлено в версии 1.2.
-
method
- class sqlalchemy.ext.hybrid.Comparator¶
Вспомогательный класс, позволяющий легко создавать собственные классы
PropComparator
для использования в гибридах.Классическая подпись.
класс
sqlalchemy.ext.hybrid.Comparator
(sqlalchemy.orm.PropComparator
)
- class sqlalchemy.ext.hybrid.HybridExtensionType¶
Перечисление.
Members
Классическая подпись.
класс
sqlalchemy.ext.hybrid.HybridExtensionType
(sqlalchemy.orm.base.InspectionAttrExtensionType
)-
attribute
sqlalchemy.ext.hybrid.HybridExtensionType.
HYBRID_METHOD = 'HYBRID_METHOD'¶ Символ, указывающий на
InspectionAttr
, имеющий типhybrid_method
.Присваивается атрибуту
InspectionAttr.extension_type
.См.также
Mapper.all_orm_attributes
-
attribute
sqlalchemy.ext.hybrid.HybridExtensionType.
HYBRID_PROPERTY = 'HYBRID_PROPERTY'¶ - Символ, указывающий на
InspectionAttr
, который типа
hybrid_method
.
Присваивается атрибуту
InspectionAttr.extension_type
.См.также
Mapper.all_orm_attributes
- Символ, указывающий на
-
attribute