SQLAlchemy 2.0 Dokumentation
SQLAlchemy ORM
- ORM Schnellstart
- ORM Abgebildete Klassenkonfiguration
- Übersicht über ORM-gemappte Klassen
- Klassen mit Deklarativität zuordnen
- Deklarative Mapping-Stile
- Tabellenkonfiguration mit Deklarativ
- Mapper-Konfiguration mit Deklarativ
- Mapped Hierarchien mit Mixins zusammensetzen¶
- Erweiterung der Basis
- Mischen von Spalten
- Mischen von Beziehungen
- Mischen von
column_property()und anderenMapperPropertyKlassen - Verwendung von Mixins und Basisklassen mit Mapped Inheritance Mustern
- Kombination von Tabellen-/Mapper-Argumenten aus mehreren Mixins
- Erstellung von Indizes und Constraints mit Benennungskonventionen bei Mixins
- Integration mit dataclasses und attrs
- SQL-Ausdrücke als gemappte Attribute
- Ändern des Attributverhaltens
- Zusammengesetzte Spaltentypen
- Abbildung von Klassenhierarchien
- Nicht-traditionelle Zuordnungen
- Konfigurieren eines Versionszählers
- Klassen-Mapping-API
- SQL-Ausdrücke zuordnen
- Beziehungskonfiguration
- ORM Abfragehandbuch
- Verwendung der Sitzung
- Ereignisse und Interna
- ORM Erweiterungen
- ORM Beispiele
Projektversionen
- Vorher: Mapper-Konfiguration mit Declarative
- Nächste: Integration mit dataclasses und attrs
- Nach oben: Startseite
- Auf dieser Seite
- Zusammensetzen von gemappten Hierarchien mit Mixins
- Erweiterung der Basis
- Mischen von Spalten
- Mischen von Beziehungen
- Mischen von
_orm.column_property()und anderen_orm.MapperPropertyKlassen - Verwendung von Mixins und Basisklassen mit Mapped Inheritance Mustern
- Kombination von Tabellen-/Mapper-Argumenten aus mehreren Mixins
- Erstellung von Indizes und Constraints mit Benennungskonventionen bei Mixins
Mapped Hierarchien mit Mixins zusammensetzen¶
Ein häufiger Bedarf bei der Abbildung von Klassen mit dem Declarative Stil ist die gemeinsame Nutzung von Funktionalitäten, wie z. B. bestimmten Spalten, Tabellen- oder Mapper-Optionen, Benennungsschemata oder anderen abgebildeten Eigenschaften, über viele Klassen hinweg. Bei der Verwendung von deklarativen Abbildungen wird dieses Idiom durch die Verwendung von Mixin-Klassen unterstützt, sowie durch die Erweiterung der deklarativen Basisklasse selbst.
Tipp
Zusätzlich zu Mixin-Klassen können gemeinsame Spaltenoptionen auch über viele Klassen hinweg mit PEP 593 Annotated Typen geteilt werden; siehe Abbildung mehrerer Typenkonfigurationen auf Python-Typen und Abbildung ganzer Spaltendeklarationen auf Python-Typen für Hintergrundinformationen zu diesen SQLAlchemy 2.0-Funktionen.
Ein Beispiel für einige häufig verwendete Mixin-Idiome ist unten aufgeführt
from sqlalchemy import ForeignKey
from sqlalchemy.orm import declared_attr
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 CommonMixin:
"""define a series of common elements that may be applied to mapped
classes using this class as a mixin class."""
@declared_attr.directive
def __tablename__(cls) -> str:
return cls.__name__.lower()
__table_args__ = {"mysql_engine": "InnoDB"}
__mapper_args__ = {"eager_defaults": True}
id: Mapped[int] = mapped_column(primary_key=True)
class HasLogRecord:
"""mark classes that have a many-to-one relationship to the
``LogRecord`` class."""
log_record_id: Mapped[int] = mapped_column(ForeignKey("logrecord.id"))
@declared_attr
def log_record(self) -> Mapped["LogRecord"]:
return relationship("LogRecord")
class LogRecord(CommonMixin, Base):
log_info: Mapped[str]
class MyModel(CommonMixin, HasLogRecord, Base):
name: Mapped[str]Das obige Beispiel illustriert eine Klasse MyModel, die zwei Mixins CommonMixin und HasLogRecord in ihren Basen enthält, sowie eine ergänzende Klasse LogRecord, die ebenfalls CommonMixin enthält, und demonstriert eine Vielzahl von Konstrukten, die auf Mixins und Basisklassen unterstützt werden, einschließlich
Spalten, die mit
mapped_column(),MappedoderColumndeklariert wurden, werden von Mixins oder Basisklassen auf die abzubildende Zielklasse kopiert; oben wird dies durch die SpaltenattributeCommonMixin.idundHasLogRecord.log_record_idillustriert.Deklarative Direktiven wie
__table_args__und__mapper_args__können einer Mixin- oder Basisklasse zugewiesen werden, wo sie automatisch für alle Klassen wirksam werden, die von der Mixin- oder Basisklasse erben. Das obige Beispiel illustriert dies unter Verwendung der Attribute__table_args__und__mapper_args__.Alle deklarativen Direktiven, einschließlich aller von
__tablename__,__table__,__table_args__und__mapper_args__, können mit benutzerdefinierten Klassenmethoden implementiert werden, die mit demdeclared_attrDekorator dekoriert sind (insbesondere das Unterelementdeclared_attr.directive, mehr dazu gleich). Oben wird dies durch einedef __tablename__(cls)Klassenmethode illustriert, die einenTableNamen dynamisch generiert; wenn sie auf die KlasseMyModelangewendet wird, wird der Tabellenname als"mymodel"generiert, und wenn sie auf die KlasseLogRecordangewendet wird, wird der Tabellenname als"logrecord"generiert.Andere ORM-Eigenschaften wie
relationship()können auf der abzubildenden Zielklasse mithilfe von benutzerdefinierten Klassenmethoden generiert werden, die ebenfalls mit demdeclared_attrDekorator dekoriert sind. Oben wird dies durch die Generierung einer Many-to-Onerelationship()zu einem abgebildeten Objekt namensLogRecordillustriert.
Die oben genannten Funktionen können alle mit einem select() Beispiel demonstriert werden
>>> from sqlalchemy import select
>>> print(select(MyModel).join(MyModel.log_record))
SELECT mymodel.name, mymodel.id, mymodel.log_record_id
FROM mymodel JOIN logrecord ON logrecord.id = mymodel.log_record_id
Tipp
Die Beispiele für declared_attr versuchen, die korrekten PEP 484 Annotationen für jedes Methodenbeispiel zu illustrieren. Die Verwendung von Annotationen mit declared_attr Funktionen ist vollkommen optional und wird von Declarative nicht konsumiert; diese Annotationen sind jedoch erforderlich, um die Mypy --strict Typüberprüfung zu bestehen.
Zusätzlich ist das oben gezeigte Unterelement declared_attr.directive ebenfalls optional und nur für PEP 484 Typisierungswerkzeuge relevant, da es den erwarteten Rückgabetyp anpasst, wenn Methoden zur Überschreibung von deklarativen Direktiven wie __tablename__, __mapper_args__ und __table_args__ erstellt werden.
Neu in Version 2.0: Als Teil der PEP 484 Typunterstützung für das SQLAlchemy ORM wurde declared_attr.directive zu declared_attr hinzugefügt, um zwischen Mapped Attributen und deklarativen Konfigurationsattributen zu unterscheiden
Es gibt keine feste Konvention für die Reihenfolge von Mixins und Basisklassen. Normale Python-Methodenauflösungsregeln gelten, und das obige Beispiel würde genauso gut mit
class MyModel(Base, HasLogRecord, CommonMixin):
name: Mapped[str] = mapped_column()Dies funktioniert, da Base hier keine der Variablen definiert, die CommonMixin oder HasLogRecord definieren, d.h. __tablename__, __table_args__, id usw. Wenn die Base ein Attribut mit demselben Namen definieren würde, würde die Klasse, die zuerst in der Vererbungsliste steht, bestimmen, welches Attribut in der neu definierten Klasse verwendet wird.
Tipp
Während das obige Beispiel die Annotated Declarative Table Form verwendet, die auf der Mapped Annotationsklasse basiert, funktionieren Mixin-Klassen auch perfekt mit nicht-annotierten und älteren deklarativen Formen, wie z.B. bei der direkten Verwendung von Column anstelle von mapped_column().
Geändert in Version 2.0: Für Benutzer der SQLAlchemy 1.4 Serie, die möglicherweise das mypy Plugin verwendet haben, wird der Klassen-Decorator declarative_mixin() nicht mehr benötigt, um deklarative Mixins zu kennzeichnen, vorausgesetzt, das mypy Plugin wird nicht mehr verwendet.
Erweiterung der Basisklasse¶
Zusätzlich zur Verwendung eines reinen Mixins können die meisten Techniken in diesem Abschnitt auch direkt auf die Basisklasse angewendet werden, für Muster, die für alle von einer bestimmten Basis abgeleiteten Klassen gelten sollen. Das folgende Beispiel illustriert einige der vorherigen Beispiele in Bezug auf die Base Klasse
from sqlalchemy import ForeignKey
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
"""define a series of common elements that may be applied to mapped
classes using this class as a base class."""
@declared_attr.directive
def __tablename__(cls) -> str:
return cls.__name__.lower()
__table_args__ = {"mysql_engine": "InnoDB"}
__mapper_args__ = {"eager_defaults": True}
id: Mapped[int] = mapped_column(primary_key=True)
class HasLogRecord:
"""mark classes that have a many-to-one relationship to the
``LogRecord`` class."""
log_record_id: Mapped[int] = mapped_column(ForeignKey("logrecord.id"))
@declared_attr
def log_record(self) -> Mapped["LogRecord"]:
return relationship("LogRecord")
class LogRecord(Base):
log_info: Mapped[str]
class MyModel(HasLogRecord, Base):
name: Mapped[str]Wo oben, MyModel sowie LogRecord, abgeleitet von Base, beide ihren Tabellennamen aus ihrem Klassennamen, eine Primärschlüsselspalte namens id, sowie die oben genannten Tabellen- und Mapper-Argumente, die von Base.__table_args__ und Base.__mapper_args__ definiert werden, haben werden.
Bei Verwendung des älteren declarative_base() oder registry.generate_base() kann der Parameter declarative_base.cls wie folgt verwendet werden, um einen ähnlichen Effekt zu erzielen, wie im folgenden nicht-annotierten Beispiel gezeigt
# legacy declarative_base() use
from sqlalchemy import Integer, String
from sqlalchemy import ForeignKey
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base:
"""define a series of common elements that may be applied to mapped
classes using this class as a base class."""
@declared_attr.directive
def __tablename__(cls):
return cls.__name__.lower()
__table_args__ = {"mysql_engine": "InnoDB"}
__mapper_args__ = {"eager_defaults": True}
id = mapped_column(Integer, primary_key=True)
Base = declarative_base(cls=Base)
class HasLogRecord:
"""mark classes that have a many-to-one relationship to the
``LogRecord`` class."""
log_record_id = mapped_column(ForeignKey("logrecord.id"))
@declared_attr
def log_record(self):
return relationship("LogRecord")
class LogRecord(Base):
log_info = mapped_column(String)
class MyModel(HasLogRecord, Base):
name = mapped_column(String)Mischen von Spalten¶
Spalten können in Mixins angegeben werden, vorausgesetzt, der Declarative Table Stil der Konfiguration wird verwendet (im Gegensatz zur Imperative Table Konfiguration), sodass auf dem Mixin deklarierte Spalten dann Teil der Table sein können, die der deklarative Prozess generiert. Alle drei Konstrukte mapped_column(), Mapped und Column können inline in einem deklarativen Mixin deklariert werden
class TimestampMixin:
created_at: Mapped[datetime] = mapped_column(default=func.now())
updated_at: Mapped[datetime]
class MyModel(TimestampMixin, Base):
__tablename__ = "test"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]Wo oben, werden alle deklarativen Klassen, die TimestampMixin in ihren Klassensätzen enthalten, automatisch eine Spalte created_at aufnehmen, die einen Zeitstempel auf alle Zeileneinfügungen anwendet, sowie eine Spalte updated_at, die für die Zwecke des Beispiels keinen Standardwert enthält (wenn sie einen hätte, würden wir den Parameter Column.onupdate verwenden, der von mapped_column() akzeptiert wird). Diese Spaltenkonstrukte werden immer von der ursprünglichen Mixin- oder Basisklasse kopiert, sodass dieselbe Mixin/Basisklasse auf jede beliebige Anzahl von Zielklassen angewendet werden kann, die jeweils ihre eigenen Spaltenkonstrukte haben.
Alle deklarativen Spaltenformen werden von Mixins unterstützt, einschließlich
Annotierte Attribute - mit oder ohne
mapped_column()class TimestampMixin: created_at: Mapped[datetime] = mapped_column(default=func.now()) updated_at: Mapped[datetime]
mapped_column - mit oder ohne
Mappedclass TimestampMixin: created_at = mapped_column(default=func.now()) updated_at: Mapped[datetime] = mapped_column()
Column - ältere deklarative Form
class TimestampMixin: created_at = Column(DateTime, default=func.now()) updated_at = Column(DateTime)
In jeder der oben genannten Formen behandelt Declarative die spaltenbasierten Attribute in der Mixin-Klasse, indem sie eine Kopie des Konstrukts erstellt, die dann auf die Zielklasse angewendet wird.
Geändert in Version 2.0: Die deklarative API kann nun Column Objekte sowie mapped_column() Konstrukte jeder Form aufnehmen, wenn Mixins verwendet werden, ohne dass declared_attr() verwendet werden muss. Frühere Einschränkungen, die die direkte Verwendung von Spalten mit ForeignKey Elementen in Mixins verhinderten, wurden entfernt.
Mischen von Beziehungen¶
Beziehungen, die von relationship() erstellt werden, werden mit deklarativen Mixin-Klassen ausschließlich über den declared_attr Ansatz bereitgestellt, wodurch Mehrdeutigkeiten, die bei der Kopie einer Beziehung und ihres möglicherweise spaltengebundenen Inhalts entstehen könnten, eliminiert werden. Unten ist ein Beispiel, das eine Fremdschlüsselspalte und eine Beziehung kombiniert, sodass zwei Klassen Foo und Bar beide so konfiguriert werden können, dass sie über Many-to-One auf eine gemeinsame Zielklasse verweisen
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class RefTargetMixin:
target_id: Mapped[int] = mapped_column(ForeignKey("target.id"))
@declared_attr
def target(cls) -> Mapped["Target"]:
return relationship("Target")
class Foo(RefTargetMixin, Base):
__tablename__ = "foo"
id: Mapped[int] = mapped_column(primary_key=True)
class Bar(RefTargetMixin, Base):
__tablename__ = "bar"
id: Mapped[int] = mapped_column(primary_key=True)
class Target(Base):
__tablename__ = "target"
id: Mapped[int] = mapped_column(primary_key=True)Mit der obigen Abbildung enthalten sowohl Foo als auch Bar eine Beziehung zu Target, auf die über das Attribut .target zugegriffen wird.
>>> from sqlalchemy import select
>>> print(select(Foo).join(Foo.target))
SELECT foo.id, foo.target_id
FROM foo JOIN target ON target.id = foo.target_id
>>> print(select(Bar).join(Bar.target))
SELECT bar.id, bar.target_id
FROM bar JOIN target ON target.id = bar.target_id
Spezielle Argumente wie relationship.primaryjoin können ebenfalls innerhalb von gemischten Klassenmethoden verwendet werden, die sich oft auf die abzubildende Klasse beziehen müssen. Für Schemata, die sich auf lokal abgebildete Spalten beziehen müssen, werden diese Spalten in normalen Fällen von Declarative als Attribute der abgebildeten Klasse bereitgestellt, die als cls Argument an die dekorierte Klassenmethode übergeben wird. Mit dieser Funktion könnten wir zum Beispiel die Methode RefTargetMixin.target mit einem expliziten Primaryjoin umschreiben, das auf ausstehende abgebildete Spalten sowohl in Target als auch in cls verweist
class Target(Base):
__tablename__ = "target"
id: Mapped[int] = mapped_column(primary_key=True)
class RefTargetMixin:
target_id: Mapped[int] = mapped_column(ForeignKey("target.id"))
@declared_attr
def target(cls) -> Mapped["Target"]:
# illustrates explicit 'primaryjoin' argument
return relationship("Target", primaryjoin=Target.id == cls.target_id)Mischen von column_property() und anderen MapperProperty Klassen¶
Ähnlich wie relationship() müssen auch andere MapperProperty Unterklassen wie column_property() klassenlokale Kopien generieren, wenn sie von Mixins verwendet werden, und werden daher ebenfalls innerhalb von Funktionen deklariert, die von declared_attr dekoriert sind. Innerhalb der Funktion werden andere gewöhnliche abgebildete Spalten, die mit mapped_column(), Mapped oder Column deklariert wurden, über das cls Argument verfügbar gemacht, damit sie zur Zusammensetzung neuer Attribute verwendet werden können, wie im folgenden Beispiel, das zwei Spalten addiert
from sqlalchemy.orm import column_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class SomethingMixin:
x: Mapped[int]
y: Mapped[int]
@declared_attr
def x_plus_y(cls) -> Mapped[int]:
return column_property(cls.x + cls.y)
class Something(SomethingMixin, Base):
__tablename__ = "something"
id: Mapped[int] = mapped_column(primary_key=True)Oben können wir Something.x_plus_y in einer Anweisung verwenden, wo es den vollständigen Ausdruck erzeugt
>>> from sqlalchemy import select
>>> print(select(Something.x_plus_y))
SELECT something.x + something.y AS anon_1
FROM something
Tipp
Der declared_attr Dekorator bewirkt, dass das dekorierte aufrufbare Objekt sich genau wie eine Klassenmethode verhält. Allerdings können Typisierungswerkzeuge wie Pylance dies möglicherweise nicht erkennen, was manchmal dazu führen kann, dass sie sich über den Zugriff auf die Variable cls im Körper der Funktion beschweren. Um dieses Problem zu lösen, wenn es auftritt, kann der @classmethod Dekorator direkt mit declared_attr kombiniert werden als
class SomethingMixin:
x: Mapped[int]
y: Mapped[int]
@declared_attr
@classmethod
def x_plus_y(cls) -> Mapped[int]:
return column_property(cls.x + cls.y)Neu in Version 2.0: - declared_attr kann eine mit @classmethod dekorierte Funktion aufnehmen, um bei Bedarf die PEP 484 Integration zu unterstützen.
Verwendung von Mixins und Basisklassen mit Mapped Inheritance Mustern¶
Bei der Arbeit mit Mapper-Inheritance-Mustern, wie in Mapping Class Inheritance Hierarchien dokumentiert, sind einige zusätzliche Fähigkeiten vorhanden, wenn declared_attr entweder mit Mixin-Klassen verwendet wird oder wenn sowohl gemappte als auch nicht-gemappte Oberklassen in einer Klassenhierarchie erweitert werden.
Beim Definieren von Funktionen, die von declared_attr auf Mixins oder Basisklassen dekoriert sind, um von Unterklassen in einer gemappten Vererbungshierarchie interpretiert zu werden, wird zwischen Funktionen, die spezielle von Declarative verwendete Namen wie __tablename__, __mapper_args__ generieren, und solchen, die gewöhnliche gemappte Attribute wie mapped_column() und relationship() generieren können, eine wichtige Unterscheidung getroffen. Funktionen, die deklarative Direktiven definieren, werden für jede Unterklasse in einer Hierarchie aufgerufen, während Funktionen, die gemappte Attribute generieren, nur für die erste gemappte Oberklasse in einer Hierarchie aufgerufen werden.
Die Begründung für diesen Verhaltensunterschied liegt darin, dass gemappte Eigenschaften bereits von Klassen geerbt werden können, z.B. sollte eine bestimmte Spalte in der gemappten Tabelle einer Oberklasse nicht dupliziert werden, während Elemente, die für eine bestimmte Klasse oder ihre gemappte Tabelle spezifisch sind, nicht erblich sind, wie z.B. der Name der lokal abgebildeten Tabelle.
Der Unterschied im Verhalten zwischen diesen beiden Anwendungsfällen wird in den folgenden beiden Abschnitten demonstriert.
Verwendung von declared_attr() mit ererbenden Table und Mapper Argumenten¶
Ein gängiges Rezept mit Mixins ist die Erstellung einer Funktion def __tablename__(cls), die einen Namen für die gemappte Table dynamisch generiert.
Dieses Rezept kann verwendet werden, um Tabellennamen für eine ererbende Mapper-Hierarchie zu generieren, wie im folgenden Beispiel, das einen Mixin erstellt, der jeder Klasse einen einfachen Tabellennamen basierend auf dem Klassennamen gibt. Das Rezept wird unten illustriert, wo ein Tabellenname für die gemappte Klasse Person und die Unterklasse Engineer von Person generiert wird, aber nicht für die Unterklasse Manager von Person
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class Tablename:
@declared_attr.directive
def __tablename__(cls) -> Optional[str]:
return cls.__name__.lower()
class Person(Tablename, Base):
id: Mapped[int] = mapped_column(primary_key=True)
discriminator: Mapped[str]
__mapper_args__ = {"polymorphic_on": "discriminator"}
class Engineer(Person):
id: Mapped[int] = mapped_column(ForeignKey("person.id"), primary_key=True)
primary_language: Mapped[str]
__mapper_args__ = {"polymorphic_identity": "engineer"}
class Manager(Person):
@declared_attr.directive
def __tablename__(cls) -> Optional[str]:
"""override __tablename__ so that Manager is single-inheritance to Person"""
return None
__mapper_args__ = {"polymorphic_identity": "manager"}Im obigen Beispiel haben sowohl die Basisklasse Person als auch die Klasse Engineer, die Unterklassen der Mixin-Klasse Tablename sind, die neue Tabellennamen generiert, ein generiertes Attribut __tablename__, das Declarative anzeigt, dass jede Klasse ihre eigene Table generiert bekommen soll, auf die sie abgebildet wird. Für die Unterklasse Engineer ist der angewandte Vererbungsstil Joined Table Inheritance, da sie auf eine Tabelle engineer abgebildet wird, die sich mit der Basis-Tabelle person verbindet. Jede andere Unterklasse, die von Person erbt, wird standardmäßig ebenfalls diesen Vererbungsstil anwenden (und innerhalb dieses spezifischen Beispiels müsste jede eine Primärschlüsselspalte angeben; mehr dazu im nächsten Abschnitt).
Im Gegensatz dazu überschreibt die Unterklasse Manager von Person die Klassenmethode __tablename__, um None zurückzugeben. Dies zeigt Declarative an, dass diese Klasse keine Table generiert bekommen soll und stattdessen ausschließlich die Basis- Table verwenden soll, auf die Person abgebildet wird. Für die Unterklasse Manager ist der angewandte Vererbungsstil Single Table Inheritance.
Das obige Beispiel zeigt, dass deklarative Direktiven wie __tablename__ notwendigerweise auf jede Unterklasse einzeln angewendet werden müssen, da jede abgebildete Klasse angeben muss, auf welche Table sie abgebildet werden soll, oder ob sie sich selbst auf die Table der vererbenden Oberklasse abbildet.
Wenn wir stattdessen das standardmäßige Tabellenschema, das oben dargestellt ist, umkehren möchten, sodass Single-Table-Inheritance der Standard ist und Joined-Table-Inheritance nur dann definiert werden kann, wenn eine __tablename__-Direktive zur Überschreibung bereitgestellt wird, können wir Declarative-Helfer innerhalb der obersten __tablename__()-Methode verwenden, in diesem Fall einen Helfer namens has_inherited_table(). Diese Funktion gibt True zurück, wenn eine Oberklasse bereits einer Table zugeordnet ist. Wir können diesen Helfer innerhalb der untersten __tablename__()-Klassenmethode verwenden, damit wir bedingt None für den Tabellennamen zurückgeben können, wenn eine Tabelle bereits vorhanden ist, und somit standardmäßig Single-Table-Inheritance für erbende Unterklassen anzeigen
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import has_inherited_table
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class Tablename:
@declared_attr.directive
def __tablename__(cls):
if has_inherited_table(cls):
return None
return cls.__name__.lower()
class Person(Tablename, Base):
id: Mapped[int] = mapped_column(primary_key=True)
discriminator: Mapped[str]
__mapper_args__ = {"polymorphic_on": "discriminator"}
class Engineer(Person):
@declared_attr.directive
def __tablename__(cls):
"""override __tablename__ so that Engineer is joined-inheritance to Person"""
return cls.__name__.lower()
id: Mapped[int] = mapped_column(ForeignKey("person.id"), primary_key=True)
primary_language: Mapped[str]
__mapper_args__ = {"polymorphic_identity": "engineer"}
class Manager(Person):
__mapper_args__ = {"polymorphic_identity": "manager"}Verwendung von declared_attr() zur Generierung tabellenspezifischer erbender Spalten¶
Im Gegensatz zur Behandlung von __tablename__ und anderen speziellen Namen bei der Verwendung mit declared_attr wird die Funktion bei der Mischung von Spalten und Eigenschaften (z. B. Beziehungen, Spalteneigenschaften usw.) nur für die Basisklasse in der Hierarchie aufgerufen, es sei denn, die declared_attr-Direktive wird in Kombination mit der Unterdirektive declared_attr.cascading verwendet. Im Folgenden erhält nur die Klasse Person eine Spalte namens id; die Zuordnung schlägt bei Engineer fehl, dem kein Primärschlüssel zugewiesen wird
class HasId:
id: Mapped[int] = mapped_column(primary_key=True)
class Person(HasId, Base):
__tablename__ = "person"
discriminator: Mapped[str]
__mapper_args__ = {"polymorphic_on": "discriminator"}
# this mapping will fail, as there's no primary key
class Engineer(Person):
__tablename__ = "engineer"
primary_language: Mapped[str]
__mapper_args__ = {"polymorphic_identity": "engineer"}Im Fall von Joined-Table-Inheritance möchten wir normalerweise eindeutig benannte Spalten in jeder Unterklasse haben. In diesem Fall möchten wir jedoch möglicherweise eine id-Spalte in jeder Tabelle haben und sie über Fremdschlüssel miteinander verknüpfen. Dies können wir als Mixin erreichen, indem wir den Modifikator declared_attr.cascading verwenden, der angibt, dass die Funktion für jede Klasse in der Hierarchie aufgerufen werden soll, und zwar fast (siehe Warnung unten) auf die gleiche Weise wie für __tablename__
class HasIdMixin:
@declared_attr.cascading
def id(cls) -> Mapped[int]:
if has_inherited_table(cls):
return mapped_column(ForeignKey("person.id"), primary_key=True)
else:
return mapped_column(Integer, primary_key=True)
class Person(HasIdMixin, Base):
__tablename__ = "person"
discriminator: Mapped[str]
__mapper_args__ = {"polymorphic_on": "discriminator"}
class Engineer(Person):
__tablename__ = "engineer"
primary_language: Mapped[str]
__mapper_args__ = {"polymorphic_identity": "engineer"}Warnung
Das Feature declared_attr.cascading erlaubt derzeit nicht, dass eine Unterklasse das Attribut mit einer anderen Funktion oder einem anderen Wert überschreibt. Dies ist eine aktuelle Einschränkung in der Mechanik der Auflösung von @declared_attr, und es wird eine Warnung ausgegeben, wenn diese Bedingung erkannt wird. Diese Einschränkung gilt nur für ORM-zugeordnete Spalten, Beziehungen und andere Attribute im Stil von MapperProperty. Sie gilt nicht für Declarative-Direktiven wie __tablename__, __mapper_args__ usw., die intern anders aufgelöst werden als declared_attr.cascading.
Kombinieren von Tabellen-/Mapper-Argumenten aus mehreren Mixins¶
Im Fall von __table_args__ oder __mapper_args__, die mit Declarative-Mixins angegeben werden, möchten Sie möglicherweise einige Parameter aus mehreren Mixins mit denen kombinieren, die Sie auf der Klasse selbst definieren möchten. Der Dekorator declared_attr kann hier verwendet werden, um benutzerdefinierte Kollationsroutinen zu erstellen, die aus mehreren Sammlungen ziehen
from sqlalchemy.orm import declarative_mixin, declared_attr
class MySQLSettings:
__table_args__ = {"mysql_engine": "InnoDB"}
class MyOtherMixin:
__table_args__ = {"info": "foo"}
class MyModel(MySQLSettings, MyOtherMixin, Base):
__tablename__ = "my_model"
@declared_attr.directive
def __table_args__(cls):
args = dict()
args.update(MySQLSettings.__table_args__)
args.update(MyOtherMixin.__table_args__)
return args
id = mapped_column(Integer, primary_key=True)Erstellen von Indizes und Constraints mit Namenskonventionen auf Mixins¶
Die Verwendung benannter Constraints wie Index, UniqueConstraint, CheckConstraint, wobei jedes Objekt spezifisch für eine bestimmte Tabelle sein soll, die von einem Mixin abgeleitet ist, erfordert die Erstellung einer einzelnen Instanz jedes Objekts pro tatsächlich zugeordneter Klasse.
Als einfaches Beispiel für die Definition eines benannten, potenziell mehrspaltigen Index, der für alle von einem Mixin abgeleiteten Tabellen gilt, verwenden Sie die "Inline"-Form von Index und richten Sie ihn als Teil von __table_args__ ein, indem Sie declared_attr verwenden, um __table_args__() als Klassenmethode einzurichten, die für jede Unterklasse aufgerufen wird
class MyMixin:
a = mapped_column(Integer)
b = mapped_column(Integer)
@declared_attr.directive
def __table_args__(cls):
return (Index(f"test_idx_{cls.__tablename__}", "a", "b"),)
class MyModelA(MyMixin, Base):
__tablename__ = "table_a"
id = mapped_column(Integer, primary_key=True)
class MyModelB(MyMixin, Base):
__tablename__ = "table_b"
id = mapped_column(Integer, primary_key=True)Das obige Beispiel würde zwei Tabellen "table_a" und "table_b" mit den Indizes "test_idx_table_a" und "test_idx_table_b" generieren
Typischerweise würden wir im modernen SQLAlchemy eine Namenskonvention verwenden, wie dokumentiert unter Konfigurieren von Namenskonventionen für Constraints. Während Namenskonventionen automatisch unter Verwendung von MetaData.naming_convention angewendet werden, wenn neue Constraint-Objekte erstellt werden, da diese Konvention zur Objektkonstruktionszeit basierend auf der übergeordneten Table für einen bestimmten Constraint angewendet wird, muss ein separates Constraint-Objekt für jede erbende Unterklasse mit ihrer eigenen Table erstellt werden, wieder unter Verwendung von declared_attr mit __table_args__(), unten dargestellt unter Verwendung einer abstrakten zugeordneten Basis
from uuid import UUID
from sqlalchemy import CheckConstraint
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import UniqueConstraint
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
constraint_naming_conventions = {
"ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s",
}
class Base(DeclarativeBase):
metadata = MetaData(naming_convention=constraint_naming_conventions)
class MyAbstractBase(Base):
__abstract__ = True
@declared_attr.directive
def __table_args__(cls):
return (
UniqueConstraint("uuid"),
CheckConstraint("x > 0 OR y < 100", name="xy_chk"),
)
id: Mapped[int] = mapped_column(primary_key=True)
uuid: Mapped[UUID]
x: Mapped[int]
y: Mapped[int]
class ModelAlpha(MyAbstractBase):
__tablename__ = "alpha"
class ModelBeta(MyAbstractBase):
__tablename__ = "beta"Die obige Zuordnung generiert DDL, die tabellenspezifische Namen für alle Constraints enthält, einschließlich Primärschlüssel, CHECK-Constraint und Unique-Constraint
CREATE TABLE alpha (
id INTEGER NOT NULL,
uuid CHAR(32) NOT NULL,
x INTEGER NOT NULL,
y INTEGER NOT NULL,
CONSTRAINT pk_alpha PRIMARY KEY (id),
CONSTRAINT uq_alpha_uuid UNIQUE (uuid),
CONSTRAINT ck_alpha_xy_chk CHECK (x > 0 OR y < 100)
)
CREATE TABLE beta (
id INTEGER NOT NULL,
uuid CHAR(32) NOT NULL,
x INTEGER NOT NULL,
y INTEGER NOT NULL,
CONSTRAINT pk_beta PRIMARY KEY (id),
CONSTRAINT uq_beta_uuid UNIQUE (uuid),
CONSTRAINT ck_beta_xy_chk CHECK (x > 0 OR y < 100)
)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