SQLAlchemy 2.0 Dokumentation
SQLAlchemy Unified Tutorial
- Aufbau der Konnektivität - die Engine
- Arbeiten mit Transaktionen und der DBAPI
- Arbeiten mit Datenbankmetadaten¶
- Arbeiten mit Daten
- Datenmanipulation mit der ORM
- Arbeiten mit ORM-bezogenen Objekten
- Weitere Lektüre
Projektversionen
- Vorheriges: Arbeiten mit Transaktionen und DBAPI
- Nächstes: Arbeiten mit Daten
- Nach oben: Startseite
- Auf dieser Seite
Arbeiten mit Datenbankmetadaten¶
Nachdem wir uns mit Engines und SQL-Ausführung beschäftigt haben, sind wir bereit, mit der Alchemy zu beginnen. Das zentrale Element von SQLAlchemy Core und ORM ist die SQL-Ausdruckssprache, die eine flüssige, komponierbare Konstruktion von SQL-Abfragen ermöglicht. Die Grundlage für diese Abfragen sind Python-Objekte, die Datenbankkonzepte wie Tabellen und Spalten darstellen. Diese Objekte werden kollektiv als Datenbankmetadaten bezeichnet.
Die gebräuchlichsten grundlegenden Objekte für Datenbankmetadaten in SQLAlchemy sind MetaData, Table und Column. Die folgenden Abschnitte veranschaulichen, wie diese Objekte sowohl in einem Core-orientierten als auch in einem ORM-orientierten Stil verwendet werden.
ORM-Leser, bleiben Sie dran!
Wie bei anderen Abschnitten können Core-Benutzer die ORM-Abschnitte überspringen, aber ORM-Benutzer sollten mit diesen Objekten aus beiden Perspektiven vertraut sein. Das hier diskutierte Table-Objekt wird bei Verwendung des ORM auf indirektere Weise (und auch vollständig typisiert in Python) deklariert, es gibt jedoch immer noch ein Table-Objekt innerhalb der ORM-Konfiguration.
Einrichtung von MetaData mit Table-Objekten¶
Wenn wir mit einer relationalen Datenbank arbeiten, ist die grundlegende datenhaltende Struktur in der Datenbank, aus der wir Abfragen erstellen, als Tabelle bekannt. In SQLAlchemy wird die Datenbank „Tabelle“ letztendlich durch ein Python-Objekt mit dem ähnlich benannten Namen Table dargestellt.
Um mit der SQLAlchemy Expression Language zu arbeiten, benötigen wir konstruierte Table-Objekte, die alle Datenbanktabellen darstellen, an denen wir interessiert sind. Das Table wird programmatisch konstruiert, entweder direkt durch Verwendung des Table-Konstruktors oder indirekt durch Verwendung von ORM Mapped Classes (weiter unten unter Verwendung von ORM-Deklarationsformen zur Definition von Tabellenmetadaten beschrieben). Es besteht auch die Möglichkeit, einige oder alle Tabelleninformationen aus einer vorhandenen Datenbank zu laden, was als Reflektion bezeichnet wird.
Welche Art von Ansatz auch immer verwendet wird, wir beginnen immer mit einer Sammlung, in der wir unsere Tabellen platzieren werden, die als MetaData-Objekt bekannt ist. Dieses Objekt ist im Wesentlichen eine Fassade um ein Python-Dictionary, das eine Reihe von Table-Objekten speichert, die mit ihrem String-Namen indiziert sind. Während die ORM einige Optionen anbietet, wo diese Sammlung bezogen werden kann, haben wir immer die Möglichkeit, einfach eine direkt zu erstellen, was so aussieht:
>>> from sqlalchemy import MetaData
>>> metadata_obj = MetaData()Sobald wir ein MetaData-Objekt haben, können wir einige Table-Objekte deklarieren. Dieses Tutorial beginnt mit dem klassischen SQLAlchemy-Tutorialmodell, das eine Tabelle namens user_account hat, die beispielsweise die Benutzer einer Website speichert, und eine zugehörige Tabelle address, die E-Mail-Adressen speichert, die mit Zeilen in der user_account-Tabelle verknüpft sind. Wenn wir überhaupt keine ORM-Deklarationsmodelle verwenden, konstruieren wir jedes Table-Objekt direkt, und weisen es typischerweise einer Variablen zu, die wir verwenden werden, um uns in Anwendungscode auf die Tabelle zu beziehen.
>>> from sqlalchemy import Table, Column, Integer, String
>>> user_table = Table(
... "user_account",
... metadata_obj,
... Column("id", Integer, primary_key=True),
... Column("name", String(30)),
... Column("fullname", String),
... )Mit dem obigen Beispiel werden wir, wenn wir Code schreiben möchten, der sich auf die user_account-Tabelle in der Datenbank bezieht, die Python-Variable user_table verwenden, um darauf zu verweisen.
Komponenten von Table¶
Wir können beobachten, dass die Table-Konstruktion, wie sie in Python geschrieben ist, eine Ähnlichkeit mit einer SQL CREATE TABLE-Anweisung aufweist; beginnend mit dem Tabellennamen, gefolgt von jeder Spalte, wobei jede Spalte einen Namen und einen Datentyp hat. Die von uns oben verwendeten Objekte sind
Table- stellt eine Datenbanktabelle dar und ordnet sich selbst einerMetaData-Sammlung zu.Column- stellt eine Spalte in einer Datenbanktabelle dar und ordnet sich selbst einemTable-Objekt zu. DieColumnenthält normalerweise einen String-Namen und ein Typobjekt. Die Sammlung vonColumn-Objekten in Bezug auf die übergeordneteTableist typischerweise über ein assoziatives Array unterTable.czugänglich.>>> user_table.c.name Column('name', String(length=30), table=<user_account>) >>> user_table.c.keys() ['id', 'name', 'fullname']
Integer,String- diese Klassen stellen SQL-Datentypen dar und können an eineColumnübergeben werden, entweder instanziiert oder nicht. Oben möchten wir der Spalte „name“ eine Länge von „30“ geben, daher haben wirString(30)instanziiert. Für „id“ und „fullname“ haben wir diese jedoch nicht angegeben, sodass wir die Klasse selbst übergeben können.
Siehe auch
Die Referenz- und API-Dokumentation für MetaData, Table und Column finden Sie unter Datenbanken mit MetaData beschreiben. Die Referenzdokumentation für Datentypen finden Sie unter SQL-Datentypobjekte.
In einem kommenden Abschnitt werden wir eine der grundlegenden Funktionen von Table veranschaulichen, nämlich die Erzeugung von DDL für eine bestimmte Datenbankverbindung. Aber zuerst werden wir eine zweite Table deklarieren.
Einfache Constraints deklarieren¶
Die erste Column im Beispiel user_table enthält den Parameter Column.primary_key, der eine Kurzform darstellt, um anzuzeigen, dass diese Column Teil des Primärschlüssels für diese Tabelle sein soll. Der Primärschlüssel selbst wird normalerweise implizit deklariert und durch die PrimaryKeyConstraint-Konstruktion dargestellt, die wir im Attribut Table.primary_key des Table-Objekts sehen können.
>>> user_table.primary_key
PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))Das Constraint, das am häufigsten explizit deklariert wird, ist das Objekt ForeignKeyConstraint, das einem Fremdschlüssel-Constraint in der Datenbank entspricht. Wenn wir Tabellen deklarieren, die miteinander verknüpft sind, verwendet SQLAlchemy die Anwesenheit dieser Fremdschlüssel-Constraint-Deklarationen nicht nur, damit sie in CREATE-Anweisungen an die Datenbank gesendet werden, sondern auch, um die Konstruktion von SQL-Ausdrücken zu unterstützen.
Ein ForeignKeyConstraint, der nur eine einzige Spalte in der Ziel-Tabelle betrifft, wird typischerweise über eine Kurzschreibweise auf Spaltenebene mithilfe des Objekts ForeignKey deklariert. Unten deklarieren wir eine zweite Tabelle address, die einen Fremdschlüssel-Constraint haben wird, der auf die user-Tabelle verweist.
>>> from sqlalchemy import ForeignKey
>>> address_table = Table(
... "address",
... metadata_obj,
... Column("id", Integer, primary_key=True),
... Column("user_id", ForeignKey("user_account.id"), nullable=False),
... Column("email_address", String, nullable=False),
... )Die obige Tabelle weist auch eine dritte Art von Constraint auf, die in SQL der „NOT NULL“-Constraint ist, der oben über den Parameter Column.nullable angegeben wird.
Tipp
Bei der Verwendung des Objekts ForeignKey innerhalb einer Column-Definition können wir den Datentyp für diese Column weglassen; er wird automatisch vom Datentyp der zugehörigen Spalte abgeleitet, im obigen Beispiel vom Integer-Datentyp der user_account.id-Spalte.
Im nächsten Abschnitt werden wir die abgeschlossene DDL für die Tabellen user und address senden, um das Endergebnis zu sehen.
DDL an die Datenbank senden¶
Wir haben eine Objektstruktur konstruiert, die zwei Datenbanktabellen in einer Datenbank darstellt, beginnend mit dem Root MetaData-Objekt, dann in zwei Table-Objekten, die jeweils eine Sammlung von Column- und Constraint-Objekten enthalten. Diese Objektstruktur wird das Zentrum der meisten Operationen sein, die wir mit Core und ORM weiterhin durchführen.
Die erste nützliche Sache, die wir mit dieser Struktur tun können, ist das Senden von CREATE TABLE-Anweisungen oder DDL an unsere SQLite-Datenbank, damit wir Daten daraus einfügen und abfragen können. Wir haben bereits alle Werkzeuge dazu, indem wir die Methode MetaData.create_all() auf unserem MetaData aufrufen und ihr die Engine übergeben, die auf die Ziel-Datenbank verweist.
>>> metadata_obj.create_all(engine)
BEGIN (implicit)
PRAGMA main.table_...info("user_account")
...
PRAGMA main.table_...info("address")
...
CREATE TABLE user_account (
id INTEGER NOT NULL,
name VARCHAR(30),
fullname VARCHAR,
PRIMARY KEY (id)
)
...
CREATE TABLE address (
id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
email_address VARCHAR NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES user_account (id)
)
...
COMMIT
Der obige DDL-Erstellungsprozess enthält einige SQLite-spezifische PRAGMA-Anweisungen, die auf die Existenz jeder Tabelle prüfen, bevor ein CREATE gesendet wird. Die vollständige Reihe von Schritten ist auch innerhalb eines BEGIN/COMMIT-Paares enthalten, um transaktionale DDL zu ermöglichen.
Der Erstellungsprozess kümmert sich auch um das Senden von CREATE-Anweisungen in der richtigen Reihenfolge; oben hängt das FOREIGN KEY-Constraint von der Existenz der user-Tabelle ab, daher wird die address-Tabelle als zweite erstellt. In komplizierteren Abhängigkeitsszenarien können die FOREIGN KEY-Constraints auch nachträglich mithilfe von ALTER auf Tabellen angewendet werden.
Das MetaData-Objekt verfügt auch über eine Methode MetaData.drop_all(), die DROP-Anweisungen in umgekehrter Reihenfolge sendet, als sie CREATE senden würde, um Schemaelemente zu löschen.
Verwendung von ORM-Deklarationsformen zur Definition von Tabellenmetadaten¶
Bei der Verwendung des ORM wird der Prozess, mit dem wir Table-Metadaten deklarieren, normalerweise mit dem Prozess der Deklaration von gefllagten Klassen kombiniert. Die gefllagte Klasse ist jede Python-Klasse, die wir erstellen möchten, die dann Attribute hat, die mit den Spalten in einer Datenbanktabelle verknüpft werden. Obwohl es einige Variationen gibt, wie dies erreicht wird, ist der gebräuchlichste Stil als deklarativ bekannt und ermöglicht es uns, unsere benutzerdefinierten Klassen und Table-Metadaten gleichzeitig zu deklarieren.
Einrichtung einer deklarativen Basis¶
Bei Verwendung des ORM bleibt die MetaData-Sammlung bestehen, sie ist jedoch selbst mit einer ausschließlich für ORM gedachten Konstruktion verbunden, die allgemein als Deklarative Basis bezeichnet wird. Der schnellste Weg, eine neue Deklarative Basis zu erhalten, ist die Erstellung einer neuen Klasse, die von der SQLAlchemy-Klasse DeclarativeBase erbt.
>>> from sqlalchemy.orm import DeclarativeBase
>>> class Base(DeclarativeBase):
... passDie obige Base-Klasse ist das, was wir die Deklarative Basis nennen werden. Wenn wir neue Klassen erstellen, die von Base erben, werden sie zusammen mit entsprechenden Klassen-Direktiven bei der Klassenerstellung jeweils als neue ORM-gefllagte Klasse etabliert, die typischerweise (aber nicht ausschließlich) auf ein bestimmtes Table-Objekt verweist.
Die Deklarative Basis verweist auf eine MetaData-Sammlung, die automatisch für uns erstellt wird, sofern wir keine von außen übergeben haben. Auf diese MetaData-Sammlung kann über das Klassenattribut DeclarativeBase.metadata zugegriffen werden. Wenn wir neue gefllagte Klassen erstellen, werden diese jeweils auf eine Table innerhalb dieser MetaData-Sammlung verweisen.
>>> Base.metadata
MetaData()Die Deklarative Basis verweist auch auf eine Sammlung namens registry, die die zentrale „Mapper-Konfigurations“-Einheit in der SQLAlchemy ORM ist. Obwohl sie selten direkt aufgerufen wird, ist dieses Objekt zentral für den Mapper-Konfigurationsprozess, da eine Reihe von ORM-gefllagten Klassen über diese Registry miteinander koordinieren. Wie bei MetaData hat unsere Deklarative Basis auch eine registry für uns erstellt (wieder mit der Option, unsere eigene registry zu übergeben), auf die wir über die Klassenvariable DeclarativeBase.registry zugreifen können.
>>> Base.registry
<sqlalchemy.orm.decl_api.registry object at 0x...>Geflaggte Klassen deklarieren¶
Mit der etablierten Base-Klasse können wir nun ORM-gefllagte Klassen für die Tabellen user_account und address in Form von neuen Klassen User und Address definieren. Wir veranschaulichen unten die modernste Form der Deklaration, die von PEP 484-Typannotationen mithilfe eines speziellen Typs Mapped angetrieben wird, der Attribute angibt, die als bestimmte Typen zugeordnet werden sollen.
>>> from typing import List
>>> from typing import Optional
>>> from sqlalchemy.orm import Mapped
>>> from sqlalchemy.orm import mapped_column
>>> from sqlalchemy.orm import relationship
>>> class User(Base):
... __tablename__ = "user_account"
...
... id: Mapped[int] = mapped_column(primary_key=True)
... name: Mapped[str] = mapped_column(String(30))
... fullname: Mapped[Optional[str]]
...
... addresses: Mapped[List["Address"]] = relationship(back_populates="user")
...
... def __repr__(self) -> str:
... return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"
>>> class Address(Base):
... __tablename__ = "address"
...
... id: Mapped[int] = mapped_column(primary_key=True)
... email_address: Mapped[str]
... user_id = mapped_column(ForeignKey("user_account.id"))
...
... user: Mapped[User] = relationship(back_populates="addresses")
...
... def __repr__(self) -> str:
... return f"Address(id={self.id!r}, email_address={self.email_address!r})"Die beiden obigen Klassen, User und Address, werden nun als ORM-gefllagte Klassen bezeichnet und sind für die Verwendung in ORM-Persistenz- und Abfrageoperationen verfügbar, die später beschrieben werden. Details zu diesen Klassen umfassen:
Jede Klasse verweist auf ein
Table-Objekt, das als Teil des deklarativen Zuordnungsprozesses generiert wurde und durch die Zuweisung eines Strings zum AttributDeclarativeBase.__tablename__benannt wird. Sobald die Klasse erstellt ist, ist diese generierteTableüber das KlassenattributDeclarativeBase.__table__verfügbar.Wie bereits erwähnt, wird diese Form als Deklarative Tabellenkonfiguration bezeichnet. Ein von mehreren alternativen Deklarationsstilen würde uns stattdessen dazu veranlassen, das
Table-Objekt direkt zu erstellen und es direktDeclarativeBase.__table__zuzuweisen. Dieser Stil wird als Deklarativ mit imperativer Tabelle bezeichnet.Um Spalten in der
Tableanzugeben, verwenden wir diemapped_column()Konstruktion, in Kombination mit Typ-Annotationen, die auf demMappedTyp basieren. Dieses Objekt generiertColumnObjekte, die für die Konstruktion derTableverwendet werden.Für Spalten mit einfachen Datentypen und ohne weitere Optionen können wir eine
MappedTyp-Annotation allein angeben, wobei einfache Python-Typen wieintundstrfürIntegerundStringstehen. Die Anpassung, wie Python-Typen im Deklarativen Mapping-Prozess interpretiert werden, ist sehr offen gestaltet; siehe die Abschnitte Verwendung von annotierten deklarativen Tabellen (Typ-annotierte Formen für mapped_column()) und Anpassung der Typzuordnung für Hintergrundinformationen.Eine Spalte kann als „nullable“ (null-fähig) oder „not null“ (nicht null-fähig) deklariert werden, basierend auf der Anwesenheit der
Optional[<typ>]Typ-Annotation (oder ihren Entsprechungen,<typ> | NoneoderUnion[<typ>, None]). Dermapped_column.nullableParameter kann ebenfalls explizit verwendet werden (und muss nicht mit der Optionalität der Annotation übereinstimmen).Die Verwendung expliziter Typ-Annotationen ist vollständig optional. Wir können
mapped_column()auch ohne Annotationen verwenden. In diesem Fall würden wir explizitere Typ-Objekte wieIntegerundStringsowienullable=Falsenach Bedarf innerhalb jedermapped_column()Konstruktion verwenden.Zwei zusätzliche Attribute,
User.addressesundAddress.user, definieren eine andere Art von Attribut, das alsrelationship()bezeichnet wird. Dieses weist ähnliche, auf Annotationen reagierende Konfigurationsstile auf, wie oben gezeigt. Dierelationship()Konstruktion wird ausführlicher unter Arbeiten mit ORM-bezogenen Objekten besprochen.Die Klassen erhalten automatisch eine
__init__()Methode, wenn wir keine eigene deklarieren. Die Standardform dieser Methode akzeptiert alle Attributnamen als optionale Schlüsselwortargumente.>>> sandy = User(name="sandy", fullname="Sandy Cheeks")
Um automatisch eine voll funktionsfähige
__init__()Methode zu generieren, die Positionsargumente sowie Argumente mit Standard-Schlüsselwortwerten bereitstellt, kann die Dataclasses-Funktion verwendet werden, die unter Deklaratives Dataclass-Mapping eingeführt wurde. Es ist natürlich immer auch eine Option, eine explizite__init__()Methode zu verwenden.Die
__repr__()Methoden werden hinzugefügt, um eine lesbare String-Ausgabe zu erhalten; es besteht keine Anforderung, dass diese Methoden hier vorhanden sein müssen. Wie bei__init__()kann auch eine__repr__()Methode automatisch generiert werden, indem die Dataclasses-Funktion verwendet wird.
Siehe auch
ORM-Mapping-Stile - vollständiger Hintergrund zu verschiedenen ORM-Konfigurationsstilen.
Deklaratives Mapping - Überblick über das Deklarative Klassen-Mapping
Deklarative Tabelle mit mapped_column() - Details zur Verwendung von mapped_column() und Mapped zur Definition der Spalten innerhalb einer Table, die bei der Verwendung von Deklarativ gemappt werden soll.
Generieren von DDL für die Datenbank aus einem ORM-Mapping¶
Da unsere ORM-gemappten Klassen auf Table-Objekte innerhalb einer MetaData-Sammlung verweisen, erfolgt die Generierung von DDL für die Deklarative Basis auf die gleiche Weise wie zuvor unter Generieren von DDL für die Datenbank beschrieben. In unserem Fall haben wir die Tabellen user und address bereits in unserer SQLite-Datenbank erstellt. Hätten wir dies noch nicht getan, könnten wir die MetaData, die mit unserer ORM-Deklarativen Basisklasse verknüpft ist, verwenden, um dies zu tun. Dazu greifen wir über das Attribut DeclarativeBase.metadata auf die Sammlung zu und verwenden dann MetaData.create_all() wie zuvor. In diesem Fall werden PRAGMA-Anweisungen ausgeführt, aber keine neuen Tabellen generiert, da sie bereits vorhanden sind.
>>> Base.metadata.create_all(engine)
BEGIN (implicit)
PRAGMA main.table_...info("user_account")
...
PRAGMA main.table_...info("address")
...
COMMIT
Tabellen-Reflexion¶
Um den Abschnitt über die Arbeit mit Tabellenmetadaten abzuschließen, werden wir eine weitere Operation veranschaulichen, die am Anfang des Abschnitts erwähnt wurde: die Tabellen-Reflexion. Tabellen-Reflexion bezeichnet den Prozess der Generierung von Table und verwandten Objekten durch Lesen des aktuellen Zustands einer Datenbank. Während wir in den vorherigen Abschnitten Table-Objekte in Python deklariert haben, mit der Option, DDL an die Datenbank zu senden, um ein solches Schema zu generieren, führt der Reflexionsprozess diese beiden Schritte umgekehrt aus, beginnend mit einer bestehenden Datenbank und generiert In-Python-Datenstrukturen, um die Schemata innerhalb dieser Datenbank darzustellen.
Tipp
Es gibt keine Anforderung, dass Reflexion verwendet werden muss, um SQLAlchemy mit einer bereits bestehenden Datenbank zu verwenden. Es ist durchaus üblich, dass die SQLAlchemy-Anwendung alle Metadaten explizit in Python deklariert, sodass ihre Struktur der der bestehenden Datenbank entspricht. Die Metadatenstruktur muss auch keine Tabellen, Spalten oder andere Einschränkungen und Konstrukte in der bestehenden Datenbank enthalten, die für die lokale Anwendung nicht benötigt werden.
Als Beispiel für Reflexion erstellen wir ein neues Table-Objekt, das das some_table-Objekt repräsentiert, das wir in früheren Abschnitten dieses Dokuments manuell erstellt haben. Es gibt wieder einige Varianten, wie dies durchgeführt wird, jedoch ist die grundlegendste die Konstruktion eines Table-Objekts, gegeben den Namen der Tabelle und eine MetaData-Sammlung, zu der es gehören wird, und dann anstatt einzelne Column- und Constraint-Objekte anzugeben, den Ziel-Engine über den Table.autoload_with Parameter zu übergeben.
>>> some_table = Table("some_table", metadata_obj, autoload_with=engine)
BEGIN (implicit)
PRAGMA main.table_...info("some_table")
[raw sql] ()
SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE name = ? AND type in ('table', 'view')
[raw sql] ('some_table',)
PRAGMA main.foreign_key_list("some_table")
...
PRAGMA main.index_list("some_table")
...
ROLLBACK
Am Ende des Prozesses enthält das some_table-Objekt nun die Informationen über die Column-Objekte in der Tabelle, und das Objekt ist auf genau die gleiche Weise verwendbar wie eine Table, die wir explizit deklariert haben.
>>> some_table
Table('some_table', MetaData(),
Column('x', INTEGER(), table=<some_table>),
Column('y', INTEGER(), table=<some_table>),
schema=None)Siehe auch
Lesen Sie mehr über Tabellen- und Schema-Reflexion unter Reflexion von Datenbankobjekten.
Für ORM-bezogene Varianten der Tabellen-Reflexion enthält der Abschnitt Deklaratives Mapping mit reflektierten Tabellen einen Überblick über die verfügbaren Optionen.
Nächste Schritte¶
Wir haben nun eine SQLite-Datenbank mit zwei vorhandenen Tabellen und Core- und ORM-Tabellen-orientierten Konstrukten, die wir verwenden können, um über eine Connection und/oder eine ORM Session mit diesen Tabellen zu interagieren. In den folgenden Abschnitten werden wir veranschaulichen, wie Daten mit diesen Strukturen erstellt, bearbeitet und ausgewählt werden.
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