SQLAlchemy 2.0 Dokumentation
SQLAlchemy ORM
- ORM Schnellstart
- ORM Abgebildete Klassenkonfiguration
- Beziehungskonfiguration
- ORM Abfragehandbuch
- Verwendung der Sitzung
- Grundlagen der Session
- Zustandsverwaltung
- Kaskaden
- Transaktionen und Verbindungsverwaltung¶
- Transaktionen verwalten
- Eine Session in eine externe Transaktion einbinden (z.B. für Testsuiten)
- Zusätzliche Persistenztechniken
- Kontextuelle/Thread-lokale Sessions
- Abfragen, Objekte und Session-Änderungen mit Events verfolgen
- Session API
- Ereignisse und Interna
- ORM Erweiterungen
- ORM Beispiele
Projektversionen
- Vorherige: Kaskaden
- Nächste: Zusätzliche Persistenztechniken
- Nach oben: Startseite
- Auf dieser Seite
- Transaktionen und Verbindungsverwaltung
- Transaktionen verwalten
- Eine Session in eine externe Transaktion einbinden (z.B. für Testsuiten)
Transaktionen und Verbindungsverwaltung¶
Transaktionen verwalten¶
Geändert in Version 1.4: Das Transaktionsmanagement von Sessions wurde überarbeitet, um klarer und einfacher zu sein. Insbesondere verfügt es jetzt über den „autobegin“-Betrieb, was bedeutet, dass der Zeitpunkt, an dem eine Transaktion beginnt, gesteuert werden kann, ohne den veralteten „autocommit“-Modus zu verwenden.
Die Session verfolgt den Zustand einer einzelnen „virtuellen“ Transaktion gleichzeitig mit einem Objekt namens SessionTransaction. Dieses Objekt nutzt dann die zugrunde liegenden Engine oder Engines, an die das Session-Objekt gebunden ist, um bei Bedarf echte Transaktionen auf Verbindungsebene unter Verwendung des Connection-Objekts zu starten.
Diese „virtuelle“ Transaktion wird automatisch erstellt, wenn sie benötigt wird, oder kann alternativ mit der Methode Session.begin() gestartet werden. Soweit möglich wird die Verwendung von Python-Kontextmanagern sowohl auf der Ebene der Erstellung von Session-Objekten als auch zur Aufrechterhaltung des Geltungsbereichs der SessionTransaction unterstützt.
Im Folgenden wird angenommen, dass wir mit einer Session beginnen
from sqlalchemy.orm import Session
session = Session(engine)Wir können nun Operationen innerhalb einer abgegrenzten Transaktion mit einem Kontextmanager ausführen
with session.begin():
session.add(some_object())
session.add(some_other_object())
# commits transaction at the end, or rolls back if there
# was an exception raisedAm Ende des obigen Kontexts werden, vorausgesetzt, es wurden keine Ausnahmen ausgelöst, alle ausstehenden Objekte in die Datenbank geschrieben und die Datenbanktransaktion wird committet. Wenn innerhalb des obigen Blocks eine Ausnahme ausgelöst wurde, wird die Transaktion zurückgerollt. In beiden Fällen ist die obige Session nach dem Verlassen des Blocks bereit, in nachfolgenden Transaktionen verwendet zu werden.
Die Methode Session.begin() ist optional, und die Session kann auch in einem Commit-während-der-Arbeit-Ansatz verwendet werden, bei dem sie bei Bedarf automatisch Transaktionen beginnt; diese müssen nur committet oder zurückgerollt werden
session = Session(engine)
session.add(some_object())
session.add(some_other_object())
session.commit() # commits
# will automatically begin again
result = session.execute(text("< some select statement >"))
session.add_all([more_objects, ...])
session.commit() # commits
session.add(still_another_object)
session.flush() # flush still_another_object
session.rollback() # rolls back still_another_objectDie Session selbst verfügt über eine Methode Session.close(). Wenn die Session innerhalb einer noch nicht committeten oder zurückgerollten Transaktion gestartet wurde, verwirft diese Methode die Transaktion (d.h. rollt sie zurück) und expungiert auch alle Objekte, die sich im Zustand des Session-Objekts befinden. Wenn die Session so verwendet wird, dass ein Aufruf von Session.commit() oder Session.rollback() nicht garantiert ist (z. B. nicht innerhalb eines Kontextmanagers oder Ähnlichem), kann die Methode close verwendet werden, um sicherzustellen, dass alle Ressourcen freigegeben werden.
# expunges all objects, releases all transactions unconditionally
# (with rollback), releases all database connections back to their
# engines
session.close()Schließlich kann der Prozess der Session-Konstruktion / des Schließens selbst über einen Kontextmanager laufen. Dies ist der beste Weg, um sicherzustellen, dass der Geltungsbereich der Verwendung eines Session-Objekts innerhalb eines festen Blocks begrenzt ist. Illustriert durch den Session-Konstruktor zuerst
with Session(engine) as session:
session.add(some_object())
session.add(some_other_object())
session.commit() # commits
session.add(still_another_object)
session.flush() # flush still_another_object
session.commit() # commits
result = session.execute(text("<some SELECT statement>"))
# remaining transactional state from the .execute() call is
# discardedÄhnlich kann die sessionmaker auf die gleiche Weise verwendet werden
Session = sessionmaker(engine)
with Session() as session:
with session.begin():
session.add(some_object)
# commits
# closes the SessionDie sessionmaker selbst enthält eine Methode sessionmaker.begin(), um beide Operationen gleichzeitig durchzuführen
with Session.begin() as session:
session.add(some_object)SAVEPOINT verwenden¶
SAVEPOINT-Transaktionen, sofern vom zugrunde liegenden Engine unterstützt, können mit der Methode Session.begin_nested() abgegrenzt werden.
Session = sessionmaker()
with Session.begin() as session:
session.add(u1)
session.add(u2)
nested = session.begin_nested() # establish a savepoint
session.add(u3)
nested.rollback() # rolls back u3, keeps u1 and u2
# commits u1 and u2Jedes Mal, wenn Session.begin_nested() aufgerufen wird, wird ein neuer „BEGIN SAVEPOINT“-Befehl an die Datenbank gesendet, innerhalb des Bereichs der aktuellen Datenbanktransaktion (eine startend, wenn noch keine im Gange ist), und ein Objekt vom Typ SessionTransaction wird zurückgegeben, das einen Handle zu diesem SAVEPOINT darstellt. Wenn die Methode .commit() dieses Objekts aufgerufen wird, wird „RELEASE SAVEPOINT“ an die Datenbank gesendet, und wenn stattdessen die Methode .rollback() aufgerufen wird, wird „ROLLBACK TO SAVEPOINT“ gesendet. Die umgebende Datenbanktransaktion bleibt in Bearbeitung.
Session.begin_nested() wird typischerweise als Kontextmanager verwendet, bei dem spezifische Fehler pro Instanz abgefangen werden können, in Verbindung mit einem Rollback für diesen Teil des Transaktionszustands, ohne die gesamte Transaktion zurückzurollen, wie im folgenden Beispiel
for record in records:
try:
with session.begin_nested():
session.merge(record)
except:
print("Skipped record %s" % record)
session.commit()Wenn der vom Session.begin_nested() bereitgestellte Kontextmanager abgeschlossen ist, „committet“ er den Savepoint, was das übliche Verhalten des Flushs aller ausstehenden Zustände beinhaltet. Wenn ein Fehler auftritt, wird der Savepoint zurückgerollt und der Zustand der Session, der sich auf die geänderten Objekte bezieht, wird abgelaufen erklärt.
Dieses Muster ist ideal für Situationen wie die Verwendung von PostgreSQL und das Abfangen von IntegrityError zum Erkennen doppelter Zeilen; PostgreSQL bricht normalerweise die gesamte Transaktion ab, wenn ein solcher Fehler auftritt, jedoch wird bei Verwendung von SAVEPOINT die äußere Transaktion beibehalten. Im folgenden Beispiel werden eine Liste von Daten in die Datenbank geschrieben, wobei gelegentlich ein Datensatz mit „dupliziertem Primärschlüssel“ übersprungen wird, ohne die gesamte Operation zurückzurollen
from sqlalchemy import exc
with session.begin():
for record in records:
try:
with session.begin_nested():
obj = SomeRecord(id=record["identifier"], name=record["name"])
session.add(obj)
except exc.IntegrityError:
print(f"Skipped record {record} - row already exists")Wenn Session.begin_nested() aufgerufen wird, leert die Session zuerst alle aktuell ausstehenden Zustände in die Datenbank; dies geschieht bedingungslos, unabhängig vom Wert des Parameters Session.autoflush, der normalerweise verwendet werden kann, um automatisches Leeren zu deaktivieren. Die Begründung für dieses Verhalten ist, dass die Session bei einem Rollback dieser verschachtelten Transaktion jeden im Speicher befindlichen Zustand, der innerhalb des Geltungsbereichs des SAVEPOINT erstellt wurde, ablaufen lassen kann, während gleichzeitig sichergestellt wird, dass bei der Aktualisierung dieser abgelaufenen Objekte der Zustand des Objektgraphen vor Beginn des SAVEPOINT zum erneuten Laden aus der Datenbank verfügbar ist.
In modernen Versionen von SQLAlchemy wird, wenn ein SAVEPOINT, der von Session.begin_nested() initiiert wurde, zurückgerollt wird, der im Speicher befindliche Objektzustand, der seit der Erstellung des SAVEPOINT geändert wurde, abgelaufen erklärt, jedoch bleibt anderer Objektzustand, der seit Beginn des SAVEPOINT nicht verändert wurde, erhalten. Dies geschieht, damit nachfolgende Operationen die ansonsten unbeeinflussten Daten weiterhin nutzen können, ohne sie aus der Datenbank neu laden zu müssen.
Siehe auch
Connection.begin_nested() - Core SAVEPOINT API
Transaktionskontrolle auf Session-Ebene vs. Engine-Ebene¶
Die Connection in Core und _session.Session in ORM weisen äquivalente transaktionale Semantiken auf, sowohl auf der Ebene der sessionmaker im Vergleich zur Engine, als auch der Session im Vergleich zur Connection. Die folgenden Abschnitte beschreiben diese Szenarien basierend auf dem folgenden Schema
ORM Core
----------------------------------------- -----------------------------------
sessionmaker Engine
Session Connection
sessionmaker.begin() Engine.begin()
some_session.commit() some_connection.commit()
with some_sessionmaker() as session: with some_engine.connect() as conn:
with some_sessionmaker.begin() as session: with some_engine.begin() as conn:
with some_session.begin_nested() as sp: with some_connection.begin_nested() as sp:Commit während der Arbeit¶
Sowohl Session als auch Connection verfügen über die Methoden Connection.commit() und Connection.rollback(). Bei Verwendung des SQLAlchemy 2.0-Stils beeinflussen diese Methoden in allen Fällen die **äußerste** Transaktion. Für die Session wird davon ausgegangen, dass Session.autobegin auf seinem Standardwert True belassen wird.
engine = create_engine("postgresql+psycopg2://user:pass@host/dbname")
with engine.connect() as conn:
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
conn.commit()Session = sessionmaker(engine)
with Session() as session:
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
session.commit()Einmal beginnend¶
Sowohl sessionmaker als auch Engine verfügen über eine Methode Engine.begin(), die sowohl ein neues Objekt zur Ausführung von SQL-Anweisungen beschafft (die Session und Connection, jeweils) als auch einen Kontextmanager zurückgibt, der einen Begin/Commit/Rollback-Kontext für dieses Objekt aufrechterhält.
Engine
engine = create_engine("postgresql+psycopg2://user:pass@host/dbname")
with engine.begin() as conn:
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
# commits and closes automaticallySession
Session = sessionmaker(engine)
with Session.begin() as session:
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
# commits and closes automaticallyVerschachtelte Transaktion¶
Bei Verwendung eines SAVEPOINTs über die Methoden Session.begin_nested() oder Connection.begin_nested() muss das zurückgegebene Transaktionsobjekt verwendet werden, um den SAVEPOINT zu committen oder zurückzurollen. Das Aufrufen der Methoden Session.commit() oder Connection.commit() committet immer die **äußerste** Transaktion; dies ist ein spezifisches Verhalten von SQLAlchemy 2.0, das sich von der 1.x-Serie unterscheidet.
Engine
engine = create_engine("postgresql+psycopg2://user:pass@host/dbname")
with engine.begin() as conn:
savepoint = conn.begin_nested()
conn.execute(
some_table.insert(),
[
{"data": "some data one"},
{"data": "some data two"},
{"data": "some data three"},
],
)
savepoint.commit() # or rollback
# commits automaticallySession
Session = sessionmaker(engine)
with Session.begin() as session:
savepoint = session.begin_nested()
session.add_all(
[
SomeClass(data="some data one"),
SomeClass(data="some data two"),
SomeClass(data="some data three"),
]
)
savepoint.commit() # or rollback
# commits automaticallyExplizites Beginnen¶
Die Session verfügt über ein „autobegin“-Verhalten, was bedeutet, dass, sobald Operationen stattfinden, sichergestellt wird, dass eine SessionTransaction vorhanden ist, um laufende Operationen zu verfolgen. Diese Transaktion wird abgeschlossen, wenn Session.commit() aufgerufen wird.
Es ist oft wünschenswert, insbesondere in Framework-Integrationen, den Zeitpunkt zu kontrollieren, an dem die „begin“-Operation stattfindet. Um dies zu ermöglichen, verwendet die Session eine „autobegin“-Strategie, so dass die Methode Session.begin() direkt für eine Session aufgerufen werden kann, für die noch keine Transaktion begonnen wurde.
Session = sessionmaker(bind=engine)
session = Session()
session.begin()
try:
item1 = session.get(Item, 1)
item2 = session.get(Item, 2)
item1.foo = "bar"
item2.bar = "foo"
session.commit()
except:
session.rollback()
raiseDas obige Muster wird idiomatischer mit einem Kontextmanager aufgerufen
Session = sessionmaker(bind=engine)
session = Session()
with session.begin():
item1 = session.get(Item, 1)
item2 = session.get(Item, 2)
item1.foo = "bar"
item2.bar = "foo"Die Methode Session.begin() und der „autobegin“-Prozess der Session verwenden dieselbe Sequenz von Schritten, um die Transaktion zu beginnen. Dazu gehört, dass das Event SessionEvents.after_transaction_create() ausgelöst wird, wenn es auftritt; dieser Hook wird von Frameworks verwendet, um ihre eigenen transaktionalen Prozesse mit denen der ORM-Session zu integrieren.
Zwei-Phasen-Commit aktivieren¶
Für Backends, die den Zwei-Phasen-Betrieb unterstützen (derzeit MySQL und PostgreSQL), kann die Session angewiesen werden, Zwei-Phasen-Commit-Semantik zu verwenden. Dies koordiniert das Committen von Transaktionen über Datenbanken hinweg, sodass die Transaktion entweder in allen Datenbanken committet oder zurückgerollt wird. Sie können die Session auch mit Session.prepare() für die Interaktion mit nicht von SQLAlchemy verwalteten Transaktionen vorbereiten. Um Zwei-Phasen-Transaktionen zu verwenden, setzen Sie das Flag twophase=True auf der Session.
engine1 = create_engine("postgresql+psycopg2://db1")
engine2 = create_engine("postgresql+psycopg2://db2")
Session = sessionmaker(twophase=True)
# bind User operations to engine 1, Account operations to engine 2
Session.configure(binds={User: engine1, Account: engine2})
session = Session()
# .... work with accounts and users
# commit. session will issue a flush to all DBs, and a prepare step to all DBs,
# before committing both transactions
session.commit()Transaktionsisolationslevel / DBAPI AUTOCOMMIT einstellen¶
Die meisten DBAPIs unterstützen das Konzept konfigurierbarer Transaktions-Isolationslevel. Dies sind traditionell die vier Level „READ UNCOMMITTED“, „READ COMMITTED“, „REPEATABLE READ“ und „SERIALIZABLE“. Diese werden normalerweise auf eine DBAPI-Verbindung angewendet, bevor sie eine neue Transaktion beginnt. Beachten Sie, dass die meisten DBAPIs diese Transaktion implizit beginnen, wenn zuerst SQL-Anweisungen gesendet werden.
DBAPIs, die Isolationslevel unterstützen, unterstützen auch normalerweise das Konzept eines echten „Autocommits“, was bedeutet, dass die DBAPI-Verbindung selbst in einen nicht-transaktionalen Autocommit-Modus versetzt wird. Dies bedeutet normalerweise, dass das übliche DBAPI-Verhalten, automatisch „BEGIN“ an die Datenbank zu senden, nicht mehr auftritt, aber es kann auch andere Direktiven enthalten. Bei Verwendung dieses Modus **verwendet die DBAPI unter keinen Umständen eine Transaktion**. SQLAlchemy-Methoden wie .begin(), .commit() und .rollback() werden stillschweigend ausgeführt.
SQLAlchemy-Dialekte unterstützen einstellbare Isolationsmodi auf einer pro-Engine oder pro-Connection-Basis, unter Verwendung von Flags sowohl auf der Ebene von create_engine() als auch auf der Ebene von Connection.execution_options().
Bei Verwendung der ORM-Session fungiert diese als **Fassade** für Engines und Verbindungen, exponiert jedoch die Transaktionsisolation nicht direkt. Um also das Transaktionsisolationslevel zu beeinflussen, müssen wir uns auf die Engine oder Connection auswirken, je nachdem.
Siehe auch
Einstellen von Transaktionsisolationsleveln einschließlich DBAPI Autocommit - stellen Sie sicher, dass Sie auch überprüfen, wie Isolationslevel auf der Ebene des SQLAlchemy-Connection-Objekts funktionieren.
Isolation für eine Sessionmaker / Engine-weit einstellen¶
Um eine Session oder sessionmaker global mit einem bestimmten Isolationslevel einzurichten, ist die erste Technik, dass eine Engine gegen ein bestimmtes Isolationslevel in allen Fällen konstruiert werden kann, das dann als Quelle der Konnektivität für eine Session und/oder sessionmaker verwendet wird.
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
eng = create_engine(
"postgresql+psycopg2://scott:tiger@localhost/test",
isolation_level="REPEATABLE READ",
)
Session = sessionmaker(eng)Eine weitere Option, nützlich, wenn es zwei Engines mit unterschiedlichen Isolationsleveln gleichzeitig gibt, ist die Verwendung der Methode Engine.execution_options(), die eine flache Kopie der ursprünglichen Engine erzeugt, die denselben Connection-Pool wie die übergeordnete Engine teilt. Dies ist oft vorzuziehen, wenn Operationen in „transaktionale“ und „autocommit“-Operationen aufgeteilt werden.
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
eng = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")
autocommit_engine = eng.execution_options(isolation_level="AUTOCOMMIT")
transactional_session = sessionmaker(eng)
autocommit_session = sessionmaker(autocommit_engine)Oben teilen sich sowohl „eng“ als auch "autocommit_engine" denselben Dialekt und Connection-Pool. Der „AUTOCOMMIT“-Modus wird jedoch auf Verbindungen gesetzt, wenn sie von der autocommit_engine erworben werden. Die beiden sessionmaker-Objekte „transactional_session“ und „autocommit_session" erben dann diese Eigenschaften, wenn sie mit Datenbankverbindungen arbeiten.
Die „autocommit_session“ **behält transaktionale Semantik bei**, einschließlich der Tatsache, dass Session.commit() und Session.rollback() sich immer noch als „committend“ und „zurückrollend“ von Objekten betrachten; die Transaktion wird jedoch stillschweigend abwesend sein. Aus diesem Grund **ist es typisch, wenn auch nicht streng erforderlich, dass eine Session mit AUTOCOMMIT-Isolation in schreibgeschützter Weise verwendet wird**, d.h.
with autocommit_session() as session:
some_objects = session.execute(text("<statement>"))
some_other_objects = session.execute(text("<statement>"))
# closes connectionIsolation für einzelne Sessions einstellen¶
Wenn wir eine neue Session erstellen, entweder direkt über den Konstruktor oder wenn wir den von einer sessionmaker erzeugten Aufrufer verwenden, können wir das Argument bind direkt übergeben und damit die vordefinierte Bindung überschreiben. Wir können zum Beispiel unsere Session aus einer Standard-sessionmaker erstellen und eine für Autocommit eingerichtete Engine übergeben.
plain_engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")
autocommit_engine = plain_engine.execution_options(isolation_level="AUTOCOMMIT")
# will normally use plain_engine
Session = sessionmaker(plain_engine)
# make a specific Session that will use the "autocommit" engine
with Session(bind=autocommit_engine) as session:
# work with session
...Für den Fall, dass die Session oder sessionmaker mit mehreren „binds“ konfiguriert ist, können wir entweder das Argument binds vollständig neu angeben, oder wenn wir nur bestimmte binds ersetzen möchten, können wir die Methoden Session.bind_mapper() oder Session.bind_table() verwenden.
with Session() as session:
session.bind_mapper(User, autocommit_engine)Isolation für einzelne Transaktionen einstellen¶
Eine wichtige Einschränkung bezüglich des Isolationslevels ist, dass die Einstellung auf einer Connection, bei der bereits eine Transaktion begonnen wurde, nicht sicher geändert werden kann. Datenbanken können das Isolationslevel einer laufenden Transaktion nicht ändern, und einige DBAPIs und SQLAlchemy-Dialekte weisen in diesem Bereich inkonsistente Verhaltensweisen auf.
Daher ist es vorzuziehen, eine Session zu verwenden, die von Anfang an an eine Engine mit dem gewünschten Isolationslevel gebunden ist. Das Isolationslevel kann jedoch auf einer Pro-Verbindungs-Basis beeinflusst werden, indem die Methode Session.connection() zu Beginn einer Transaktion verwendet wird.
from sqlalchemy.orm import Session
# assume session just constructed
sess = Session(bind=engine)
# call connection() with options before any other operations proceed.
# this will procure a new connection from the bound engine and begin a real
# database transaction.
sess.connection(execution_options={"isolation_level": "SERIALIZABLE"})
# ... work with session in SERIALIZABLE isolation level...
# commit transaction. the connection is released
# and reverted to its previous isolation level.
sess.commit()
# subsequent to commit() above, a new transaction may be begun if desired,
# which will proceed with the previous default isolation level unless
# it is set again.Oben erzeugen wir zuerst eine Session entweder über den Konstruktor oder eine sessionmaker. Dann richten wir explizit den Beginn einer Datenbank-Transaktion ein, indem wir Session.connection() aufrufen, was Ausführungsoptionen bereitstellt, die an die Verbindung übergeben werden, bevor die Datenbank-Transaktion begonnen wird. Die Transaktion wird mit diesem ausgewählten Isolationslevel fortgesetzt. Wenn die Transaktion abgeschlossen ist, wird das Isolationslevel auf der Verbindung auf den Standardwert zurückgesetzt, bevor die Verbindung an den Connection-Pool zurückgegeben wird.
Die Methode Session.begin() kann auch verwendet werden, um die Transaktion auf Session-Ebene zu starten; ein Aufruf von Session.connection() nach diesem Aufruf kann verwendet werden, um die Isolationsstufe der Transaktion pro Verbindung einzurichten.
sess = Session(bind=engine)
with sess.begin():
# call connection() with options before any other operations proceed.
# this will procure a new connection from the bound engine and begin a
# real database transaction.
sess.connection(execution_options={"isolation_level": "SERIALIZABLE"})
# ... work with session in SERIALIZABLE isolation level...
# outside the block, the transaction has been committed. the connection is
# released and reverted to its previous isolation level.Verfolgung des Transaktionszustands mit Ereignissen¶
Siehe den Abschnitt Transaktionsereignisse für eine Übersicht über die verfügbaren Ereignis-Hooks für Änderungen des Transaktionszustands der Sitzung.
Beitreten einer Sitzung zu einer externen Transaktion (z. B. für Testsuiten)¶
Wenn eine Connection verwendet wird, die sich bereits in einem Transaktionszustand befindet (d. h. eine Transaction eingerichtet hat), kann eine Session dazu gebracht werden, an dieser Transaktion teilzunehmen, indem die Session an diese Connection gebunden wird. Der übliche Grund dafür ist eine Testsuite, die es ORM-Code ermöglicht, frei mit einer Session zu arbeiten, einschließlich der Möglichkeit, Session.commit() aufzurufen, wonach die gesamte Datenbankinteraktion zurückgerollt wird.
Geändert in Version 2.0: Das Rezept „in eine externe Transaktion eintreten“ wurde in 2.0 erneut verbessert; Ereignishandler zum „Zurücksetzen“ der verschachtelten Transaktion sind nicht mehr erforderlich.
Das Rezept funktioniert, indem eine Connection innerhalb einer Transaktion und optional einem SAVEPOINT eingerichtet wird, und diese dann einer Session als „Bindung“ übergeben wird; der Parameter Session.join_transaction_mode wird mit der Einstellung "create_savepoint" übergeben, was anzeigt, dass neue SAVEPOINTs erstellt werden sollen, um BEGIN/COMMIT/ROLLBACK für die Session zu implementieren, wodurch die externe Transaktion im selben Zustand hinterlassen wird, in dem sie übergeben wurde.
Wenn der Test abgebaut wird, wird die externe Transaktion zurückgerollt, sodass alle Datenänderungen während des Tests rückgängig gemacht werden.
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from unittest import TestCase
# global application scope. create Session class, engine
Session = sessionmaker()
engine = create_engine("postgresql+psycopg2://...")
class SomeTest(TestCase):
def setUp(self):
# connect to the database
self.connection = engine.connect()
# begin a non-ORM transaction
self.trans = self.connection.begin()
# bind an individual Session to the connection, selecting
# "create_savepoint" join_transaction_mode
self.session = Session(
bind=self.connection, join_transaction_mode="create_savepoint"
)
def test_something(self):
# use the session in tests.
self.session.add(Foo())
self.session.commit()
def test_something_with_rollbacks(self):
self.session.add(Bar())
self.session.flush()
self.session.rollback()
self.session.add(Foo())
self.session.commit()
def tearDown(self):
self.session.close()
# rollback - everything that happened with the
# Session above (including calls to commit())
# is rolled back.
self.trans.rollback()
# return connection to the Engine
self.connection.close()Das obige Rezept ist Teil der eigenen CI von SQLAlchemy, um sicherzustellen, dass es wie erwartet funktioniert.
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