SQLAlchemy 2.0 Dokumentation
SQLAlchemy ORM
- ORM Schnellstart
- ORM Abgebildete Klassenkonfiguration
- Beziehungskonfiguration
- ORM Abfragehandbuch
- Verwendung der Sitzung
- Ereignisse und Interna
- ORM Erweiterungen
- Asynchrone E/A (asyncio)
- Assoziationsproxy
- Automap
- Baked Queries
- Deklarative Erweiterungen
- Mypy / Pep-484 Unterstützung für ORM-Mappings
- Mutationsverfolgung
- Sortierungsliste
- Horizontale Sharding
- Hybrid-Attribute¶
- Definieren von Ausdrucksverhalten, das sich vom Attributverhalten unterscheidet
- Verwenden von
inplacezur Erstellung von PEP-484-konformen Hybrid-Eigenschaften - Definieren von Settern
- Erlauben von Massen-ORM-Updates
- Arbeiten mit Beziehungen
- Erstellen von benutzerdefinierten Komparatoren
- Wiederverwenden von Hybrid-Eigenschaften über Unterklassen hinweg
- Hybrid-Wertobjekte
- API-Referenz
hybrid_methodhybrid_propertyhybrid_property.__init__()hybrid_property.comparator()hybrid_property.deleter()hybrid_property.expression()hybrid_property.extension_typehybrid_property.getter()hybrid_property.inplacehybrid_property.is_attributehybrid_property.overrideshybrid_property.setter()hybrid_property.update_expression()
ComparatorHybridExtensionType
- Indexierbar
- Alternative Klassen-Instrumentierung
- ORM Beispiele
Projektversionen
- Vorheriges: Horizontale Sharding
- Nächstes: Indexierbar
- Oben: Startseite
- Auf dieser Seite
- Hybrid-Attribute
- Definieren von Ausdrucksverhalten, das sich vom Attributverhalten unterscheidet
- Verwenden von
inplacezur Erstellung von PEP-484-konformen Hybrid-Eigenschaften - Definieren von Settern
- Erlauben von Massen-ORM-Updates
- Arbeiten mit Beziehungen
- Erstellen von benutzerdefinierten Komparatoren
- Wiederverwenden von Hybrid-Eigenschaften über Unterklassen hinweg
- Hybrid-Wertobjekte
- API-Referenz
hybrid_methodhybrid_propertyhybrid_property.__init__()hybrid_property.comparator()hybrid_property.deleter()hybrid_property.expression()hybrid_property.extension_typehybrid_property.getter()hybrid_property.inplacehybrid_property.is_attributehybrid_property.overrideshybrid_property.setter()hybrid_property.update_expression()
ComparatorHybridExtensionType
Hybrid-Attribute¶
Definieren Sie Attribute auf ORM-gemappten Klassen, die ein "hybrides" Verhalten aufweisen.
"Hybrid" bedeutet, dass das Attribut unterschiedliche Verhaltensweisen auf Klassenebene und Instanzenebene aufweist.
Die hybrid-Erweiterung bietet eine spezielle Form des Methoden-Dekorators und hat minimale Abhängigkeiten vom Rest von SQLAlchemy. Ihre grundlegende Funktionsweise kann mit jedem deskriptorbasierten Ausdruckssystem arbeiten.
Betrachten Sie ein Mapping Interval, das ganzzahlige start- und end-Werte darstellt. Wir können höherwertige Funktionen auf gemappten Klassen definieren, die auf Klassenebene SQL-Ausdrücke und auf Instanzenebene Python-Ausdrucksauswertung erzeugen. Unten kann jede Funktion, die mit hybrid_method oder hybrid_property dekoriert ist, self als Instanz der Klasse erhalten oder je nach Kontext die Klasse direkt erhalten.
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)Oben gibt die length-Eigenschaft die Differenz zwischen den end- und start-Attributen zurück. Mit einer Instanz von Interval erfolgt diese Subtraktion in Python unter Verwendung normaler Python-Deskriptormechanismen.
>>> i1 = Interval(5, 10)
>>> i1.length
5Wenn Sie sich mit der Interval-Klasse selbst befassen, wertet der hybrid_property-Deskriptor den Funktionskörper aus, wobei die Interval-Klasse als Argument übergeben wird. Wenn dies mit SQLAlchemy-Ausdrucksmechanismen ausgewertet wird, gibt dies einen neuen SQL-Ausdruck zurück.
>>> from sqlalchemy import select
>>> print(select(Interval.length))
SELECT interval."end" - interval.start AS length
FROM interval
>>> print(select(Interval).filter(Interval.length > 10))
SELECT interval.id, interval.start, interval."end"
FROM interval
WHERE interval."end" - interval.start > :param_1
Filtermethoden wie Select.filter_by() werden auch mit Hybrid-Attributen unterstützt.
>>> print(select(Interval).filter_by(length=5))
SELECT interval.id, interval.start, interval."end"
FROM interval
WHERE interval."end" - interval.start = :param_1
Die Interval-Klassenbeispiel illustriert auch zwei Methoden, contains() und intersects(), die mit hybrid_method dekoriert sind. Dieser Dekorator wendet dieselbe Idee auf Methoden an, die hybrid_property auf Attribute anwendet. Die Methoden geben boolesche Werte zurück und nutzen die bitweisen Operatoren | und & von Python, um äquivalente boolesche Verhaltensweisen auf Instanz- und SQL-Ausdrucksebene zu erzeugen.
>>> 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)))
SELECT interval.id, interval.start, interval."end"
FROM interval
WHERE interval.start <= :start_1 AND interval."end" > :end_1
>>> ia = aliased(Interval)
>>> print(select(Interval, ia).filter(Interval.intersects(ia)))
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"
Definieren von Ausdrucksverhalten, das sich vom Attributverhalten unterscheidet¶
Im vorherigen Abschnitt war unsere Verwendung der bitweisen Operatoren & und | innerhalb der Methoden Interval.contains und Interval.intersects glücklich, da unsere Funktionen auf zwei booleschen Werten operierten, um einen neuen zu erhalten. In vielen Fällen gibt es genug Unterschiede zwischen der Konstruktion einer Funktion in Python und einem SQLAlchemy SQL-Ausdruck, dass zwei separate Python-Ausdrücke definiert werden sollten. Der hybrid-Dekorator definiert zu diesem Zweck einen Modifikator hybrid_property.expression(). Als Beispiel definieren wir den Radius des Intervalls, der die Verwendung der Absolutwertfunktion erfordert.
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)Im obigen Beispiel wird die hybrid_property, die zuerst dem Namen Interval.radius zugewiesen wird, durch eine nachfolgende Methode namens Interval._radius_expression ergänzt, unter Verwendung des Dekorators @radius.inplace.expression, der zwei Modifikatoren hybrid_property.inplace und hybrid_property.expression verkettet. Die Verwendung von hybrid_property.inplace zeigt an, dass der Modifikator hybrid_property.expression() das vorhandene Hybridobjekt unter Interval.radius vor Ort mutieren soll, ohne ein neues Objekt zu erstellen. Anmerkungen zu diesem Modifikator und seiner Begründung werden im nächsten Abschnitt Verwenden von inplace zur Erstellung von PEP-484-konformen Hybrid-Eigenschaften erläutert. Die Verwendung von @classmethod ist optional und dient lediglich dazu, Typisierungswerkzeugen einen Hinweis zu geben, dass cls in diesem Fall die Interval-Klasse und nicht eine Instanz von Interval sein soll.
Hinweis
hybrid_property.inplace sowie die Verwendung von @classmethod zur korrekten Typunterstützung sind ab SQLAlchemy 2.0.4 verfügbar und funktionieren in früheren Versionen nicht.
Mit Interval.radius, das nun ein Ausdruckselement enthält, wird beim Zugriff auf Interval.radius auf Klassenebene die SQL-Funktion ABS() zurückgegeben.
>>> from sqlalchemy import select
>>> print(select(Interval).filter(Interval.radius > 5))
SELECT interval.id, interval.start, interval."end"
FROM interval
WHERE abs(interval."end" - interval.start) / :abs_1 > :param_1
Verwenden von inplace zur Erstellung von PEP-484-konformen Hybrid-Eigenschaften¶
Im vorherigen Abschnitt wird ein hybrid_property-Dekorator veranschaulicht, der zwei separate funktionsbasierte Methoden umfasst, die beide ein einzelnes Objektattribut namens Interval.radius erzeugen. Es gibt tatsächlich mehrere verschiedene Modifikatoren, die wir für hybrid_property verwenden können, darunter hybrid_property.expression(), hybrid_property.setter() und hybrid_property.update_expression().
SQLAlchemy's hybrid_property-Dekorator sieht vor, dass das Hinzufügen dieser Methoden auf dieselbe Weise erfolgen kann wie bei Pythons integriertem @property-Dekorator, wobei die idiomatische Verwendung darin besteht, das Attribut wiederholt neu zu definieren, unter Verwendung desselben Attributnamens jedes Mal, wie im folgenden Beispiel, das die Verwendung von hybrid_property.setter() und hybrid_property.expression() für den Interval.radius-Deskriptor veranschaulicht.
# 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)Oben gibt es drei Interval.radius-Methoden, aber da jede zuerst mit dem hybrid_property-Dekorator und dann mit dem Namen @radius selbst dekoriert wird, ist das Endergebnis, dass Interval.radius ein einzelnes Attribut mit drei verschiedenen Funktionen darin ist. Dieser Stil der Verwendung stammt aus Pythons dokumentierter Verwendung von @property. Es ist wichtig zu beachten, dass, wie sowohl @property als auch hybrid_property funktionieren, eine Kopie des Deskriptors jedes Mal erstellt wird. Das heißt, jeder Aufruf von @radius.expression, @radius.setter usw. erstellt ein komplett neues Objekt. Dies ermöglicht die Neudefinition des Attributs in Unterklassen ohne Probleme (siehe Wiederverwenden von Hybrid-Eigenschaften über Unterklassen hinweg später in diesem Abschnitt, wie dies verwendet wird).
Der obige Ansatz ist jedoch nicht mit Typisierungswerkzeugen wie mypy und pyright kompatibel. Pythons eigener @property-Dekorator hat diese Einschränkung nur, weil diese Werkzeuge das Verhalten von @property hartkodieren, was bedeutet, dass diese Syntax für SQLAlchemy unter der PEP 484-Konformität nicht verfügbar ist.
Um eine sinnvolle Syntax zu erzeugen und gleichzeitig typisierungskonform zu bleiben, ermöglicht der Dekorator hybrid_property.inplace die Wiederverwendung desselben Dekorators mit verschiedenen Methodennamen, während immer noch ein einzelner Dekorator unter einem Namen erzeugt wird.
# 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)Die Verwendung von hybrid_property.inplace qualifiziert die Verwendung des Dekorators weiter, dass keine neue Kopie erstellt werden soll, wodurch der Name Interval.radius erhalten bleibt und gleichzeitig zusätzliche Methoden Interval._radius_setter und Interval._radius_expression unterschiedlich benannt werden können.
Neu in Version 2.0.4: hybrid_property.inplace hinzugefügt, um eine weniger ausführliche Konstruktion von zusammengesetzten hybrid_property-Objekten zu ermöglichen, ohne wiederholte Methodennamen verwenden zu müssen. Außerdem wurde die Verwendung von @classmethod innerhalb von hybrid_property.expression, hybrid_property.update_expression und hybrid_property.comparator erlaubt, damit Typisierungswerkzeuge cls als Klasse und nicht als Instanz in der Methodensignatur identifizieren können.
Definieren von Settern¶
Der Modifikator hybrid_property.setter() ermöglicht die Konstruktion einer benutzerdefinierten Setter-Methode, die Werte im Objekt ändern kann.
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 + valueDie Methode length(self, value) wird nun beim Set aufgerufen.
>>> i1 = Interval(5, 10)
>>> i1.length
5
>>> i1.length = 12
>>> i1.end
17Erlauben von Massen-ORM-Updates¶
Ein Hybrid kann einen benutzerdefinierten "UPDATE"-Handler definieren, wenn ORM-fähige Updates verwendet werden, wodurch der Hybrid in der SET-Klausel des Updates verwendet werden kann.
Normalerweise, wenn ein Hybrid mit update() verwendet wird, wird der SQL-Ausdruck als Spalte verwendet, die das Ziel des SET ist. Wenn unsere Interval-Klasse einen hybriden start_point hätte, der auf Interval.start verweist, könnte dies direkt ersetzt werden.
from sqlalchemy import update
stmt = update(Interval).values({Interval.start_point: 10})Wenn jedoch ein zusammengesetzter Hybrid wie Interval.length verwendet wird, repräsentiert dieser Hybrid mehr als eine Spalte. Wir können einen Handler einrichten, der einen Wert im VALUES-Ausdruck berücksichtigt, der dies beeinflussen kann, indem er den Dekorator hybrid_property.update_expression() verwendet. Ein Handler, der ähnlich wie unser Setter funktioniert, wäre:
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)]Wenn wir Interval.length in einem UPDATE-Ausdruck verwenden, erhalten wir einen hybriden SET-Ausdruck.
>>> from sqlalchemy import update
>>> print(update(Interval).values({Interval.length: 25}))
UPDATE interval SET "end"=(interval.start + :start_1)
Dieser SET-Ausdruck wird automatisch vom ORM verarbeitet.
Siehe auch
ORM-fähige INSERT-, UPDATE- und DELETE-Anweisungen - enthält Hintergründe zu ORM-fähigen UPDATE-Anweisungen
Arbeiten mit Beziehungen¶
Es gibt keinen wesentlichen Unterschied beim Erstellen von Hybriden, die mit verwandten Objekten arbeiten, im Gegensatz zu spaltenbasierten Daten. Die Notwendigkeit für unterschiedliche Ausdrücke ist tendenziell größer. Die beiden Varianten, die wir veranschaulichen werden, sind der "join-abhängige" Hybrid und der "korrelierte Subquery"-Hybrid.
Join-abhängige Beziehungs-Hybride¶
Betrachten Sie die folgende deklarative Zuordnung, die einen User mit einem SavingsAccount verknüpft.
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,
)Die obige hybride Eigenschaft balance arbeitet mit dem ersten SavingsAccount-Eintrag in der Liste der Konten für diesen Benutzer. Die In-Python-Getter-/Setter-Methoden können accounts als Python-Liste behandeln, die auf self verfügbar ist.
Tipp
Der Getter User.balance im obigen Beispiel greift auf die self.accounts-Sammlung zu, die normalerweise über die auf der User.balance relationship() konfigurierte selectinload()-Ladestrategie geladen wird. Die Standard-Ladestrategie, wenn auf relationship() nichts anderes angegeben ist, ist lazyload(), die SQL bei Bedarf ausgibt. Bei der Verwendung von asyncio werden On-Demand-Loader wie lazyload() nicht unterstützt, daher ist Vorsicht geboten, um sicherzustellen, dass die self.accounts-Sammlung für diesen hybriden Zugriff verfügbar ist, wenn asyncio verwendet wird.
Auf Ausdrucsebene wird erwartet, dass die User-Klasse in einem geeigneten Kontext verwendet wird, sodass ein entsprechender Join zu SavingsAccount vorhanden ist.
>>> from sqlalchemy import select
>>> print(
... select(User, User.balance)
... .join(User.accounts)
... .filter(User.balance > 5000)
... )
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
Beachten Sie jedoch, dass sich dieses Problem, während die Instanz-Level-Accessor berücksichtigen müssen, ob self.accounts überhaupt vorhanden ist, auf der SQL-Ausdrucksebene anders äußert, wo wir im Grunde einen äußeren Join verwenden würden.
>>> from sqlalchemy import select
>>> from sqlalchemy import or_
>>> print(
... select(User, User.balance)
... .outerjoin(User.accounts)
... .filter(or_(User.balance < 5000, User.balance == None))
... )
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
Korrelierte Subquery-Beziehungs-Hybride¶
Wir können natürlich davon absehen, von der Verwendung von Joins in der umschließenden Abfrage abhängig zu sein, zugunsten der korrelierten Subquery, die portabel in einen einzelnen Spaltenausdruck verpackt werden kann. Eine korrelierte Subquery ist portabler, aber oft schlechter auf SQL-Ebene. Unter Verwendung derselben Technik, die unter Verwenden von column_property veranschaulicht wird, können wir unser SavingsAccount-Beispiel anpassen, um die Salden für *alle* Konten zu aggregieren und eine korrelierte Subquery für den Spaltenausdruck zu verwenden.
from __future__ import annotations
from decimal import Decimal
from typing import List
from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy import Numeric
from sqlalchemy import select
from sqlalchemy import SQLColumnExpression
from sqlalchemy import String
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) -> Decimal:
return sum(
(acc.balance for acc in self.accounts), start=Decimal("0")
)
@balance.inplace.expression
@classmethod
def _balance_expression(cls) -> SQLColumnExpression[Decimal]:
return (
select(func.sum(SavingsAccount.balance))
.where(SavingsAccount.user_id == cls.id)
.label("total_balance")
)Das obige Rezept liefert uns die balance-Spalte, die eine korrelierte SELECT-Anweisung rendert.
>>> from sqlalchemy import select
>>> print(select(User).filter(User.balance > 400))
SELECT "user".id, "user".name
FROM "user"
WHERE (
SELECT sum(account.balance) AS sum_1 FROM account
WHERE account.user_id = "user".id
) > :param_1
Erstellen von benutzerdefinierten Komparatoren¶
Die Hybrid-Eigenschaft enthält auch eine Hilfsfunktion, die die Erstellung von benutzerdefinierten Komparatoren ermöglicht. Ein Komparatorobjekt ermöglicht die individuelle Anpassung des Verhaltens jedes SQLAlchemy-Ausdrucksoperators. Sie sind nützlich beim Erstellen benutzerdefinierter Typen, die auf der SQL-Seite ein sehr eigenartiges Verhalten aufweisen.
Hinweis
Der in diesem Abschnitt eingeführte Dekorator hybrid_property.comparator() ersetzt die Verwendung des Dekorators hybrid_property.expression(). Sie können nicht zusammen verwendet werden.
Die unten stehende Beispielklasse ermöglicht case-insensitive Vergleiche des Attributs mit dem Namen 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)Oben wendet die SQL-Ausdrucksauswertung gegen word_insensitive die SQL-Funktion LOWER() auf beide Seiten an.
>>> from sqlalchemy import select
>>> print(select(SearchWord).filter_by(word_insensitive="Trucks"))
SELECT searchword.id, searchword.word
FROM searchword
WHERE lower(searchword.word) = lower(:lower_1)
Die obige CaseInsensitiveComparator implementiert einen Teil der ColumnOperators-Schnittstelle. Eine "Kooperations"-Operation wie die Kleinschreibung kann mit Operators.operate() auf alle Vergleichsoperationen (d.h. eq, lt, gt usw.) angewendet werden.
class CaseInsensitiveComparator(Comparator):
def operate(self, op, other, **kwargs):
return op(
func.lower(self.__clause_element__()),
func.lower(other),
**kwargs,
)Wiederverwenden von Hybrid-Eigenschaften über Unterklassen hinweg¶
Ein Hybrid kann von einer Oberklasse referenziert werden, um Methoden wie hybrid_property.getter() und hybrid_property.setter() zu modifizieren, um diese Methoden in einer Unterklasse neu zu definieren. Dies ähnelt der Funktionsweise des standardmäßigen Python @property-Objekts.
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)Oben verweist die Klasse FirstNameLastName auf den Hybrid von FirstNameOnly.name, um dessen Getter und Setter für die Unterklasse neu zu belegen.
Beim Überschreiben von hybrid_property.expression() und hybrid_property.comparator() allein als erste Referenz zur Oberklasse, kollidieren diese Namen mit den gleichnamigen Accessoren des QueryableAttribute-Objekts auf Klassenebene, das auf Klassenebene zurückgegeben wird. Um diese Methoden beim direkten Verweis auf den übergeordneten Klassendeskriptor zu überschreiben, fügen Sie den speziellen Qualifikator hybrid_property.overrides hinzu, der das instrumentierte Attribut zurück zum Hybridobjekt dereferenziert.
class FirstNameLastName(FirstNameOnly):
# ...
last_name: Mapped[str]
@FirstNameOnly.name.overrides.expression
@classmethod
def name(cls):
return func.concat(cls.first_name, " ", cls.last_name)Hybrid-Wertobjekte¶
Beachten Sie in unserem vorherigen Beispiel, dass, wenn wir das Attribut word_insensitive einer Instanz von SearchWord mit einem einfachen Python-String vergleichen würden, der einfache Python-String nicht in Kleinschreibung konvertiert würde – die von uns erstellte CaseInsensitiveComparator, die von @word_insensitive.comparator zurückgegeben wird, gilt nur für die SQL-Seite.
Eine umfassendere Form des benutzerdefinierten Komparators ist die Konstruktion eines Hybrid Value Object. Diese Technik wendet den Zielwert oder -ausdruck auf ein Wertobjekt an, das dann von dem Accessor in allen Fällen zurückgegeben wird. Das Wertobjekt ermöglicht die Kontrolle über alle Operationen auf dem Wert sowie darüber, wie verglichene Werte behandelt werden, sowohl auf der SQL-Ausdrucksseite als auch auf der Python-Wertseite. Ersetzen der vorherigen CaseInsensitiveComparator-Klasse durch eine neue CaseInsensitiveWord-Klasse.
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"Oben repräsentiert das CaseInsensitiveWord-Objekt self.word, das eine SQL-Funktion oder ein natives Python-Objekt sein kann. Durch die Überschreibung von operate() und __clause_element__(), um im Sinne von self.word zu arbeiten, werden alle Vergleichsoperationen auf der "konvertierten" Form von word basieren, sei es auf der SQL- oder der Python-Seite. Unsere SearchWord-Klasse kann nun das CaseInsensitiveWord-Objekt bedingungslos von einem einzigen Hybridaufruf liefern.
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)Das Attribut word_insensitive hat nun universell ein case-insensitive Vergleichsverhalten, einschließlich SQL-Ausdruck gegen Python-Ausdruck (beachten Sie, dass der Python-Wert hier auf der Python-Seite in Kleinschreibung konvertiert wird).
>>> print(select(SearchWord).filter_by(word_insensitive="Trucks"))
SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
FROM searchword
WHERE lower(searchword.word) = :lower_1
SQL-Ausdruck gegen SQL-Ausdruck
>>> 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
... )
... )
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)
Nur-Python-Ausdruck
>>> ws1 = SearchWord(word="SomeWord")
>>> ws1.word_insensitive == "sOmEwOrD"
True
>>> ws1.word_insensitive == "XOmEwOrX"
False
>>> print(ws1.word_insensitive)
somewordDas Hybrid Value-Muster ist sehr nützlich für jede Art von Wert, die mehrere Darstellungen haben kann, wie z.B. Zeitstempel, Zeitdifferenzen, Maßeinheiten, Währungen und verschlüsselte Passwörter.
Siehe auch
Hybride und werteagnostische Typen – im techspot.zzzeek.org Blog
Werteagnostische Typen, Teil II – im techspot.zzzeek.org Blog
API-Referenz¶
| Objektname | Beschreibung |
|---|---|
Eine Hilfsklasse, die die einfache Erstellung von benutzerdefinierten |
|
Ein Dekorator, der die Definition einer Python-Objektmethode mit Instanz- und Klassenebene ermöglicht. |
|
Ein Dekorator, der die Definition eines Python-Deskriptors mit Instanz- und Klassenebene ermöglicht. |
|
Eine Aufzählung. |
- class sqlalchemy.ext.hybrid.hybrid_method¶
Ein Dekorator, der die Definition einer Python-Objektmethode mit Instanz- und Klassenebene ermöglicht.
Mitglieder
__init__(), expression(), extension_type, inplace, is_attribute
Klassensignatur
class
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: Callable[[Concatenate[Any, _P]], SQLCoreOperations[_R]] | None = None)¶ Erstellt ein neues
hybrid_method.Die Verwendung erfolgt typischerweise über einen Dekorator.
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)
-
methode
sqlalchemy.ext.hybrid.hybrid_method.expression(expr: Callable[[Concatenate[Any, _P]], SQLCoreOperations[_R]]) → hybrid_method[_P, _R]¶ Stellt einen modifizierenden Dekorator bereit, der eine SQL-Ausdruck erzeugende Methode definiert.
-
attribut
sqlalchemy.ext.hybrid.hybrid_method.extension_type: InspectionAttrExtensionType = 'HYBRID_METHOD'¶ Der Erweiterungstyp, falls vorhanden. Standard ist
NotExtension.NOT_EXTENSION
-
attribut
sqlalchemy.ext.hybrid.hybrid_method.inplace¶ Gibt den In-Place-Mutator für diese
hybrid_methodzurück.Die Klasse
hybrid_methodführt bereits eine "In-Place"-Mutation durch, wenn der Dekoratorhybrid_method.expression()aufgerufen wird, daher gibt dieses Attribut Self zurück.Neu seit Version 2.0.4.
-
attribut
sqlalchemy.ext.hybrid.hybrid_method.is_attribute = True¶ True, wenn dieses Objekt ein Python Deskriptor ist.
Dies kann sich auf viele Typen beziehen. Normalerweise ein
QueryableAttribute, der Attributereignisse im Auftrag einerMapperPropertybehandelt. Kann aber auch ein Erweiterungstyp sein, wieAssociationProxyoderhybrid_property. DerInspectionAttr.extension_typebezieht sich auf eine Konstante, die den spezifischen Untertyp identifiziert.Siehe auch
-
method
- Klasse sqlalchemy.ext.hybrid.hybrid_property¶
Ein Dekorator, der die Definition eines Python-Deskriptors mit Instanz- und Klassenebene ermöglicht.
Mitglieder
__init__(), comparator(), deleter(), expression(), extension_type, getter(), inplace, is_attribute, overrides, setter(), update_expression()
Klassensignatur
Klasse
sqlalchemy.ext.hybrid.hybrid_property(sqlalchemy.orm.base.InspectionAttrInfo,sqlalchemy.orm.base.ORMDescriptor)-
methode
sqlalchemy.ext.hybrid.hybrid_property.__init__(fget: _HybridGetterType[_T], fset: _HybridSetterType[_T] | None = None, fdel: _HybridDeleterType[_T] | None = None, expr: _HybridExprCallableType[_T] | None = None, custom_comparator: Comparator[_T] | None = None, update_expr: _HybridUpdaterType[_T] | None = None)¶ Erstellt eine neue
hybrid_property.Die Verwendung erfolgt typischerweise über einen Dekorator.
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
-
methode
sqlalchemy.ext.hybrid.hybrid_property.comparator(comparator: _HybridComparatorCallableType[_T]) → hybrid_property[_T]¶ Stellt einen modifizierenden Dekorator bereit, der eine benutzerdefinierte Vergleichsmethoden-erzeugende Methode definiert.
Der Rückgabewert der dekorierten Methode sollte eine Instanz von
Comparatorsein.Hinweis
Der Dekorator
hybrid_property.comparator()**ersetzt** die Verwendung des Dekoratorshybrid_property.expression(). Sie können nicht zusammen verwendet werden.Wenn ein Hybrid auf Klassenebene aufgerufen wird, wird das hier angegebene
Comparator-Objekt in ein spezialisiertesQueryableAttributeverpackt, das die gleiche Art von Objekt ist, die vom ORM zur Darstellung anderer gemappter Attribute verwendet wird. Der Grund dafür ist, dass andere klassenbezogene Attribute wie Docstrings und ein Verweis auf den Hybrid selbst innerhalb der zurückgegebenen Struktur beibehalten werden können, ohne das ursprüngliche übergebene Comparator-Objekt zu modifizieren.Hinweis
Wenn auf eine Hybrid-Eigenschaft von einer besitzenden Klasse verwiesen wird (z. B.
SomeClass.some_hybrid), wird eine Instanz vonQueryableAttributezurückgegeben, die den Ausdruck oder das Comparator-Objekt sowie dieses Hybrid-Objekt darstellt. Dieses Objekt selbst verfügt jedoch über Zugriffsfunktionen namensexpressionundcomparator; wenn Sie versuchen, diese Dekoratoren in einer Unterklasse zu überschreiben, müssen Sie sie möglicherweise zuerst mit dem Modifikatorhybrid_property.overridesqualifizieren. Weitere Details finden Sie unter diesem Modifikator.
-
methode
sqlalchemy.ext.hybrid.hybrid_property.deleter(fdel: _HybridDeleterType[_T]) → hybrid_property[_T]¶ Stellt einen modifizierenden Dekorator bereit, der eine Löschmethode definiert.
-
methode
sqlalchemy.ext.hybrid.hybrid_property.expression(expr: _HybridExprCallableType[_T]) → hybrid_property[_T]¶ Stellt einen modifizierenden Dekorator bereit, der eine SQL-Ausdruck erzeugende Methode definiert.
Wenn ein Hybrid auf Klassenebene aufgerufen wird, wird der hier angegebene SQL-Ausdruck in ein spezialisiertes
QueryableAttributeverpackt, das die gleiche Art von Objekt ist, die vom ORM zur Darstellung anderer gemappter Attribute verwendet wird. Der Grund dafür ist, dass andere klassenbezogene Attribute wie Docstrings und ein Verweis auf den Hybrid selbst innerhalb der zurückgegebenen Struktur beibehalten werden können, ohne den ursprünglichen übergebenen SQL-Ausdruck zu modifizieren.Hinweis
Wenn auf eine Hybrid-Eigenschaft von einer besitzenden Klasse verwiesen wird (z. B.
SomeClass.some_hybrid), wird eine Instanz vonQueryableAttributezurückgegeben, die den Ausdruck oder das Comparator-Objekt sowie dieses Hybrid-Objekt darstellt. Dieses Objekt selbst verfügt jedoch über Zugriffsfunktionen namensexpressionundcomparator; wenn Sie versuchen, diese Dekoratoren in einer Unterklasse zu überschreiben, müssen Sie sie möglicherweise zuerst mit dem Modifikatorhybrid_property.overridesqualifizieren. Weitere Details finden Sie unter diesem Modifikator.
-
attribut
sqlalchemy.ext.hybrid.hybrid_property.extension_type: InspectionAttrExtensionType = 'HYBRID_PROPERTY'¶ Der Erweiterungstyp, falls vorhanden. Standard ist
NotExtension.NOT_EXTENSION
-
methode
sqlalchemy.ext.hybrid.hybrid_property.getter(fget: _HybridGetterType[_T]) → hybrid_property[_T]¶ Stellt einen modifizierenden Dekorator bereit, der eine Getter-Methode definiert.
Neu seit Version 1.2.
-
attribut
sqlalchemy.ext.hybrid.hybrid_property.inplace¶ Gibt den In-Place-Mutator für diese
hybrid_propertyzurück.Dies ermöglicht die In-Place-Mutation des Hybrids, wodurch die erste Hybrid-Methode eines bestimmten Namens wiederverwendet werden kann, um weitere Methoden hinzuzufügen, ohne diese Methoden gleich benennen zu müssen, z. B.
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)
Neu seit Version 2.0.4.
-
attribut
sqlalchemy.ext.hybrid.hybrid_property.is_attribute = True¶ True, wenn dieses Objekt ein Python Deskriptor ist.
Dies kann sich auf viele Typen beziehen. Normalerweise ein
QueryableAttribute, der Attributereignisse im Auftrag einerMapperPropertybehandelt. Kann aber auch ein Erweiterungstyp sein, wieAssociationProxyoderhybrid_property. DerInspectionAttr.extension_typebezieht sich auf eine Konstante, die den spezifischen Untertyp identifiziert.Siehe auch
-
attribut
sqlalchemy.ext.hybrid.hybrid_property.overrides¶ Präfix für eine Methode, die ein vorhandenes Attribut überschreibt.
Der Zugriff
hybrid_property.overridesgibt einfach dieses Hybrid-Objekt zurück, das, wenn es auf Klassenebene von einer Elternklasse aufgerufen wird, das "instrumentierte Attribut", das normalerweise auf dieser Ebene zurückgegeben wird, de-referenziert und es den modifizierenden Dekoratoren wiehybrid_property.expression()undhybrid_property.comparator()ermöglicht, ohne Konflikte mit den gleichnamigen Attributen zu haben, die normalerweise auf demQueryableAttributevorhanden sind.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)
Neu seit Version 1.2.
-
methode
sqlalchemy.ext.hybrid.hybrid_property.setter(fset: _HybridSetterType[_T]) → hybrid_property[_T]¶ Stellt einen modifizierenden Dekorator bereit, der eine Setter-Methode definiert.
-
methode
sqlalchemy.ext.hybrid.hybrid_property.update_expression(meth: _HybridUpdaterType[_T]) → hybrid_property[_T]¶ Stellt einen modifizierenden Dekorator bereit, der eine UPDATE-Tupel erzeugende Methode definiert.
Die Methode akzeptiert einen einzelnen Wert, der der Wert ist, der in die SET-Klausel einer UPDATE-Anweisung gerendert werden soll. Die Methode sollte diesen Wert dann in einzelne Spaltenausdrücke verarbeiten, die in die endgültige SET-Klausel passen, und sie als Sequenz von 2-Tupeln zurückgeben. Jedes Tupel enthält einen Spaltenausdruck als Schlüssel und einen Wert zum Rendern.
Z. B.
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)]
Neu seit Version 1.2.
-
methode
- Klasse sqlalchemy.ext.hybrid.Comparator¶
Eine Hilfsklasse, die die einfache Erstellung von benutzerdefinierten
PropComparator-Klassen für die Verwendung mit Hybriden ermöglicht.Klassensignatur
Klasse
sqlalchemy.ext.hybrid.Comparator(sqlalchemy.orm.PropComparator)
- Klasse sqlalchemy.ext.hybrid.HybridExtensionType¶
Eine Aufzählung.
Mitglieder
Klassensignatur
Klasse
sqlalchemy.ext.hybrid.HybridExtensionType(sqlalchemy.orm.base.InspectionAttrExtensionType)-
attribut
sqlalchemy.ext.hybrid.HybridExtensionType.HYBRID_METHOD = 'HYBRID_METHOD'¶ Symbol, das eine
InspectionAttrvom Typhybrid_methodanzeigt.Wird dem Attribut
InspectionAttr.extension_typezugewiesen.Siehe auch
Mapper.all_orm_attributes
-
attribut
sqlalchemy.ext.hybrid.HybridExtensionType.HYBRID_PROPERTY = 'HYBRID_PROPERTY'¶ - Symbol, das eine
InspectionAttranzeigt, die vom Typ
hybrid_methodist.
Wird dem Attribut
InspectionAttr.extension_typezugewiesen.Siehe auch
Mapper.all_orm_attributes- Symbol, das eine
-
attribut
Die Designs von flambé! dem Drachen und Der Alchemist wurden von Rotem Yaari erstellt und großzügig gespendet.
Erstellt mit Sphinx 7.2.6. Dokumentation zuletzt generiert: Di 11 Mär 2025 14:40:17 EDT