Fehlermeldungen

Dieser Abschnitt listet Beschreibungen und Hintergründe zu häufigen Fehlermeldungen und Warnungen auf, die von SQLAlchemy ausgegeben werden.

SQLAlchemy löst Fehler normalerweise im Kontext einer SQLAlchemy-spezifischen Ausnahmeklasse aus. Details zu diesen Klassen finden Sie unter Core Exceptions und ORM Exceptions.

SQLAlchemy-Fehler lassen sich grob in zwei Kategorien einteilen: Programmierzeitfehler und Laufzeitfehler. Programmierzeitfehler werden ausgelöst, weil Funktionen oder Methoden mit falschen Argumenten aufgerufen werden oder aufgrund anderer Konfiguration-orientierter Methoden wie Mapper-Konfigurationen, die nicht aufgelöst werden können. Der Programmierzeitfehler ist typischerweise unmittelbar und deterministisch. Der Laufzeitfehler hingegen stellt ein Versagen dar, das während der Ausführung eines Programms aufgrund einer beliebigen Bedingung auftritt, wie z. B. erschöpfte Datenbankverbindungen oder ein datenbezogenes Problem. Laufzeitfehler werden wahrscheinlich eher in den Protokollen einer laufenden Anwendung gesehen, da das Programm auf diese Zustände als Reaktion auf Last und angetroffene Daten stößt.

Da Laufzeitfehler nicht so leicht zu reproduzieren sind und oft als Reaktion auf eine beliebige Bedingung während der Programmausführung auftreten, sind sie schwieriger zu debuggen und beeinträchtigen auch Programme, die bereits in Produktion sind.

In diesem Abschnitt werden einige der häufigsten Laufzeit- und Programmierzeitfehler erläutert.

Verbindungen und Transaktionen

QueuePool-Limit von Größe <x> Überlauf <y> erreicht, Verbindung Zeitüberschreitung, Zeitüberschreitung <z>

Dies ist möglicherweise der häufigste Laufzeitfehler, da er direkt die Arbeitslast der Anwendung betrifft, die ein konfiguriertes Limit überschreitet, ein Limit, das typischerweise für fast alle SQLAlchemy-Anwendungen gilt.

Die folgenden Punkte fassen zusammen, was diese Fehlermeldung bedeutet, beginnend mit den grundlegendsten Punkten, mit denen die meisten SQLAlchemy-Benutzer bereits vertraut sein sollten.

  • Das SQLAlchemy Engine-Objekt verwendet standardmäßig einen Verbindungspool – Das bedeutet, wenn man eine SQL-Datenbankverbindungsressource eines Engine-Objekts nutzt und diese Ressource dann zurückgibt, bleibt die Datenbankverbindung selbst mit der Datenbank verbunden und wird in eine interne Warteschlange zurückgegeben, wo sie wieder verwendet werden kann. Obwohl der Code die Konversation mit der Datenbank zu beenden scheint, behält die Anwendung in vielen Fällen eine feste Anzahl von Datenbankverbindungen bei, die bestehen bleiben, bis die Anwendung endet oder der Pool explizit freigegeben wird.

  • Aufgrund des Pools, wenn eine Anwendung eine SQL-Datenbankverbindung nutzt, meistens entweder über Engine.connect() oder beim Ausführen von Abfragen mit einer ORM Session, stellt diese Aktivität nicht notwendigerweise eine neue Verbindung zur Datenbank her, wenn das Verbindungsobjekt erworben wird; stattdessen konsultiert sie den Verbindungspool nach einer Verbindung, die oft eine vorhandene Verbindung aus dem Pool abruft, um wiederverwendet zu werden. Wenn keine Verbindungen verfügbar sind, erstellt der Pool eine neue Datenbankverbindung, aber nur, wenn der Pool eine konfigurierte Kapazität nicht überschritten hat.

  • Der standardmäßige Pool, der in den meisten Fällen verwendet wird, heißt QueuePool. Wenn Sie diesen Pool bitten, Ihnen eine Verbindung zu geben, und keine verfügbar sind, erstellt er eine neue Verbindung, wenn die Gesamtzahl der aktiven Verbindungen unter einem konfigurierten Wert liegt. Dieser Wert entspricht der Poolgröße plus dem maximalen Überlauf. Das heißt, wenn Sie Ihre Engine wie folgt konfiguriert haben

    engine = create_engine("mysql+mysqldb://u:p@host/db", pool_size=10, max_overflow=20)

    Die obige Engine erlaubt maximal 30 Verbindungen, die zu jeder Zeit aktiv sein können, nicht eingerechnet Verbindungen, die von der Engine getrennt oder ungültig gemacht wurden. Wenn eine Anfrage nach einer neuen Verbindung eingeht und 30 Verbindungen bereits von anderen Teilen der Anwendung in Gebrauch sind, blockiert der Verbindungspool für einen bestimmten Zeitraum, bevor er einen Timeout auslöst und diese Fehlermeldung ausgibt.

    Um eine höhere Anzahl von Verbindungen gleichzeitig zulassen zu können, kann der Pool mit den Parametern create_engine.pool_size und create_engine.max_overflow angepasst werden, die an die Funktion create_engine() übergeben werden. Die Zeitüberschreitung, um auf die Verfügbarkeit einer Verbindung zu warten, wird mit dem Parameter create_engine.pool_timeout konfiguriert.

  • Der Pool kann so konfiguriert werden, dass er einen unbegrenzten Überlauf hat, indem create_engine.max_overflow auf den Wert „-1“ gesetzt wird. Mit dieser Einstellung behält der Pool einen festen Pool von Verbindungen bei, blockiert jedoch nie, wenn eine neue Verbindung angefordert wird; stattdessen erstellt er bedingungslos eine neue Verbindung, wenn keine verfügbar ist.

    Wenn die Anwendung jedoch auf diese Weise läuft und alle verfügbaren Konnektivitätsressourcen aufbraucht, erreicht sie schließlich das konfigurierte Limit an verfügbaren Verbindungen auf der Datenbank selbst, was wiederum einen Fehler zurückgibt. Schwerwiegender ist, dass, wenn die Anwendung die Datenbank an Verbindungen erschöpft, sie in der Regel eine große Menge an Ressourcen verbraucht hat, bevor sie ausfällt, und auch andere Anwendungen und Datenbankstatusmechanismen stören kann, die auf die Möglichkeit angewiesen sind, sich mit der Datenbank zu verbinden.

    Angesichts des oben Genannten kann der Verbindungspool als Sicherheitsventil für die Verbindungsnutzung betrachtet werden, das eine kritische Schutzschicht gegen eine fehlerhafte Anwendung bietet, die die gesamte Datenbank für alle anderen Anwendungen unzugänglich macht. Wenn diese Fehlermeldung empfangen wird, ist es weitaus besser, das Problem mit dem übermäßigen Verbindungsverbrauch zu beheben und/oder die Limits entsprechend zu konfigurieren, anstatt einen unbegrenzten Überlauf zuzulassen, der das zugrunde liegende Problem nicht tatsächlich löst.

Was verursacht, dass eine Anwendung alle verfügbaren Verbindungen aufbraucht?

  • Die Anwendung verarbeitet zu viele gleichzeitige Anfragen, um basierend auf dem konfigurierten Wert für den Pool zu arbeiten – Dies ist die direkteste Ursache. Wenn Sie eine Anwendung haben, die in einem Thread-Pool läuft, der 30 gleichzeitige Threads zulässt, mit einer Verbindung pro Thread, und Ihr Pool nicht so konfiguriert ist, dass er mindestens 30 Verbindungen gleichzeitig zulässt, erhalten Sie diesen Fehler, sobald Ihre Anwendung genügend gleichzeitige Anfragen erhält. Lösung: Erhöhen Sie die Limits des Pools oder verringern Sie die Anzahl der gleichzeitigen Threads.

  • Die Anwendung gibt Verbindungen nicht an den Pool zurück – Dies ist der zweithäufigste Grund, nämlich dass die Anwendung den Verbindungspool nutzt, aber das Programm es versäumt, diese Verbindungen zu zurückzugeben und sie stattdessen offen lässt. Der Verbindungspool sowie die ORM Session verfügen über Logik, die, wenn die Sitzungs- und/oder Verbindungsobjekte vom Garbage Collector bereinigt werden, dazu führt, dass die zugrunde liegenden Verbindungsressourcen freigegeben werden. Dieses Verhalten kann jedoch nicht zuverlässig zur rechtzeitigen Freigabe von Ressourcen herangezogen werden.

    Ein häufiger Grund dafür ist, dass die Anwendung ORM-Sitzungen verwendet und nach Abschluss der Arbeit mit dieser Sitzung nicht Session.close() aufruft. Lösung: Stellen Sie sicher, dass ORM-Sitzungen bei Verwendung des ORM oder von Engine-gebundenen Connection-Objekten bei Verwendung von Core am Ende der durchgeführten Arbeit explizit geschlossen werden, entweder über die entsprechende .close()-Methode oder durch Verwendung eines der verfügbaren Kontextmanager (z. B. „with:“-Anweisung), um die Ressource ordnungsgemäß freizugeben.

  • Die Anwendung versucht, lang laufende Transaktionen auszuführen – Eine Datenbanktransaktion ist eine sehr teure Ressource und sollte niemals untätig auf ein Ereignis warten. Wenn eine Anwendung auf das Drücken einer Schaltfläche durch einen Benutzer wartet, auf ein Ergebnis aus einer lang laufenden Job-Warteschlange wartet oder eine dauerhafte Verbindung zu einem Browser offen hält, halten Sie nicht die gesamte Zeit eine Datenbanktransaktion offen. Wenn die Anwendung mit der Datenbank arbeiten und mit einem Ereignis interagieren muss, öffnen Sie zu diesem Zeitpunkt eine kurzlebige Transaktion und schließen Sie sie dann.

  • Die Anwendung hängt sich auf (Deadlock) – Ebenfalls eine häufige und schwerer zu verstehende Ursache für diesen Fehler. Wenn eine Anwendung ihre Verbindung nicht abschließen kann, entweder aufgrund eines anwendungsseitigen oder datenbankseitigen Deadlocks, kann die Anwendung alle verfügbaren Verbindungen aufbrauchen, was dann dazu führt, dass zusätzliche Anfragen diesen Fehler erhalten. Gründe für Deadlocks sind

    • Verwendung eines impliziten asynchronen Systems wie gevent oder eventlet ohne ordnungsgemäße Monkeypatching aller Socket-Bibliotheken und Treiber, oder das Bugs enthält, die nicht alle Monkeypatch-Treiber-Methoden vollständig abdecken, oder seltener, wenn das asynchrone System gegen CPU-gebundene Workloads verwendet wird und Greenlets, die Datenbankressourcen nutzen, einfach zu lange warten, um sie zu bearbeiten. Weder implizite noch explizite asynchrone Programmierframeworks sind typischerweise für die überwiegende Mehrheit der relationalen Datenbankoperationen notwendig oder angemessen; wenn eine Anwendung ein asynchrones System für einen Bereich der Funktionalität verwenden muss, ist es am besten, datenbankorientierte Geschäftslogik innerhalb traditioneller Threads auszuführen, die Nachrichten an den asynchronen Teil der Anwendung weiterleiten.

    • Ein datenbankseitiger Deadlock, z. B. Zeilen sind gegenseitig blockiert

    • Threading-Fehler, wie Mutexe in einem gegenseitigen Deadlock oder der Aufruf eines bereits gesperrten Mutex im selben Thread

Beachten Sie, dass eine Alternative zur Verwendung von Pooling darin besteht, das Pooling vollständig zu deaktivieren. Siehe den Abschnitt Wechseln von Pool-Implementierungen für Hintergrundinformationen dazu. Beachten Sie jedoch, dass diese Fehlermeldung immer auf ein größeres Problem in der Anwendung selbst zurückzuführen ist; der Pool hilft nur, das Problem früher aufzudecken.

Pool-Klasse kann nicht mit asyncio-Engine (oder umgekehrt) verwendet werden

Die Pool-Klasse QueuePool verwendet intern ein thread.Lock-Objekt und ist nicht mit asyncio kompatibel. Wenn die Funktion create_async_engine() zum Erstellen einer AsyncEngine verwendet wird, ist die entsprechende Pool-Klasse AsyncAdaptedQueuePool, die automatisch verwendet wird und nicht angegeben werden muss.

Zusätzlich zu AsyncAdaptedQueuePool verwenden die Pool-Klassen NullPool und StaticPool keine Sperren und sind daher auch für die Verwendung mit asynchronen Engines geeignet.

Dieser Fehler wird auch umgekehrt ausgelöst, falls unwahrscheinlich ist, dass die Pool-Klasse AsyncAdaptedQueuePool explizit mit der Funktion create_engine() angegeben wird.

Siehe auch

Connection Pooling

Kann nicht wiederverbinden, bis ungültige Transaktion zurückgerollt wurde. Bitte rollback() vollständig, bevor Sie fortfahren

Dieser Fehlerzustand bezieht sich auf den Fall, in dem eine Connection ungültig wurde, entweder aufgrund der Erkennung einer Datenbanktrennverbindung oder aufgrund eines expliziten Aufrufs von Connection.invalidate(), aber noch eine Transaktion vorhanden ist, die entweder explizit durch die Methode Connection.begin() eingeleitet wurde oder automatisch eine Transaktion begonnen hat, wie es in der 2.x-Serie von SQLAlchemy geschieht, wenn beliebige SQL-Anweisungen ausgegeben werden. Wenn eine Verbindung ungültig wird, ist jede laufende Transaction nun in einem ungültigen Zustand und muss explizit zurückgerollt werden, um sie aus der Connection zu entfernen.

DBAPI-Fehler

Die Python Database API, oder DBAPI, ist eine Spezifikation für Datenbanktreiber, die unter Pep-249 zu finden ist. Diese API spezifiziert eine Reihe von Ausnahmeklassen, die die gesamte Bandbreite an Fehlermodi der Datenbank abdecken.

SQLAlchemy generiert diese Ausnahmen nicht direkt. Stattdessen werden sie vom Datenbanktreiber abgefangen und von der SQLAlchemy-bereitgestellten Ausnahme DBAPIError umschlossen. Die Meldung innerhalb der Ausnahme wird jedoch vom Treiber generiert, nicht von SQLAlchemy.

InterfaceError

Ausnahme für Fehler, die sich auf die Datenbankschnittstelle beziehen und nicht auf die Datenbank selbst.

Diese Ausnahme ist ein DBAPI Error und stammt vom Datenbanktreiber (DBAPI), nicht von SQLAlchemy selbst.

Der InterfaceError wird manchmal von Treibern im Kontext einer getrennten Datenbankverbindung oder der Unfähigkeit, sich mit der Datenbank zu verbinden, ausgelöst. Tipps zur Bewältigung finden Sie im Abschnitt Umgang mit Trennungen.

DatabaseError

Ausnahme für Fehler, die sich auf die Datenbank selbst beziehen und nicht auf die Schnittstelle oder die übergebenen Daten.

Diese Ausnahme ist ein DBAPI Error und stammt vom Datenbanktreiber (DBAPI), nicht von SQLAlchemy selbst.

DataError

Ausnahme für Fehler aufgrund von Problemen mit den verarbeiteten Daten, wie z. B. Division durch Null, numerischer Wert außerhalb des Bereichs usw.

Diese Ausnahme ist ein DBAPI Error und stammt vom Datenbanktreiber (DBAPI), nicht von SQLAlchemy selbst.

OperationalError

Ausnahme für Fehler, die sich auf den Betrieb der Datenbank beziehen und nicht unbedingt unter der Kontrolle des Programmierers liegen, z. B. eine unerwartete Trennung, die Datenquellenbezeichnung wird nicht gefunden, eine Transaktion konnte nicht verarbeitet werden, ein Speicherzuordnungsfehler trat während der Verarbeitung auf usw.

Diese Ausnahme ist ein DBAPI Error und stammt vom Datenbanktreiber (DBAPI), nicht von SQLAlchemy selbst.

Der OperationalError ist die häufigste (aber nicht die einzige) Fehlerklasse, die von Treibern im Kontext einer getrennten Datenbankverbindung oder der Unfähigkeit, sich mit der Datenbank zu verbinden, verwendet wird. Tipps zur Bewältigung finden Sie im Abschnitt Umgang mit Trennungen.

IntegrityError

Ausnahme, wenn die relationale Integrität der Datenbank beeinträchtigt wird, z. B. ein Fremdschlüsselprüfung schlägt fehl.

Diese Ausnahme ist ein DBAPI Error und stammt vom Datenbanktreiber (DBAPI), nicht von SQLAlchemy selbst.

InternalError

Ausnahme, wenn die Datenbank einen internen Fehler aufweist, z. B. der Cursor ist nicht mehr gültig, die Transaktion ist nicht synchron usw.

Diese Ausnahme ist ein DBAPI Error und stammt vom Datenbanktreiber (DBAPI), nicht von SQLAlchemy selbst.

Der InternalError wird manchmal von Treibern im Kontext einer getrennten Datenbankverbindung oder der Unfähigkeit, sich mit der Datenbank zu verbinden, ausgelöst. Tipps zur Bewältigung finden Sie im Abschnitt Umgang mit Trennungen.

ProgrammingError

Ausnahme für Programmierfehler, z. B. Tabelle nicht gefunden oder bereits vorhanden, Syntaxfehler in der SQL-Anweisung, falsche Anzahl von angegebenen Parametern usw.

Diese Ausnahme ist ein DBAPI Error und stammt vom Datenbanktreiber (DBAPI), nicht von SQLAlchemy selbst.

Der ProgrammingError wird manchmal von Treibern im Kontext einer getrennten Datenbankverbindung oder der Unfähigkeit, sich mit der Datenbank zu verbinden, ausgelöst. Tipps zur Bewältigung finden Sie im Abschnitt Umgang mit Trennungen.

NotSupportedError

Ausnahme, wenn eine Methode oder eine Datenbank-API verwendet wurde, die von der Datenbank nicht unterstützt wird, z. B. die Anforderung eines .rollback() für eine Verbindung, die keine Transaktionen unterstützt oder bei der Transaktionen deaktiviert sind.

Diese Ausnahme ist ein DBAPI Error und stammt vom Datenbanktreiber (DBAPI), nicht von SQLAlchemy selbst.

SQL Expression Language

Objekt erzeugt keinen Cache-Schlüssel, Leistungsauswirkungen

SQLAlchemy enthält ab Version 1.4 eine SQL-Kompilierungs-Caching-Einrichtung, die es Core- und ORM-SQL-Konstrukten ermöglicht, ihre stringifizierte Form zusammen mit anderen Strukturinformationen zum Abrufen von Ergebnissen aus der Anweisung zu cachen, wodurch der relativ kostspielige String-Kompilierungsprozess übersprungen werden kann, wenn ein strukturell äquivalentes Konstrukt als nächstes verwendet wird. Dieses System beruht auf Funktionalität, die für alle SQL-Konstrukte implementiert ist, einschließlich Objekten wie Column, select() und TypeEngine-Objekten, um einen Cache-Schlüssel zu erzeugen, der ihren Zustand vollständig repräsentiert, in einem Maße, das die SQL-Kompilierung beeinflusst.

Wenn sich die betreffenden Warnungen auf weit verbreitete Objekte wie Column-Objekte beziehen und sich auf die Mehrheit der ausgegebenen SQL-Konstrukte auswirken (mithilfe der unter Schätzung der Cache-Leistung mithilfe von Protokollierung beschriebenen Schätzmethoden), sodass das Caching für eine Anwendung im Allgemeinen nicht aktiviert ist, beeinträchtigt dies die Leistung negativ und kann in einigen Fällen zu einer Leistungseinbuße im Vergleich zu früheren SQLAlchemy-Versionen führen. Die FAQ unter Warum ist meine Anwendung nach dem Upgrade auf 1.4 und/oder 2.x langsam? behandelt dies detaillierter.

Caching deaktiviert sich selbst, wenn Zweifel bestehen

Caching beruht auf der Fähigkeit, einen Cache-Schlüssel zu generieren, der die vollständige Struktur einer Anweisung auf konsistente Weise genau darstellt. Wenn ein bestimmtes SQL-Konstrukt (oder ein Typ) nicht die geeigneten Direktiven enthält, die es ihm ermöglichen, einen ordnungsgemäßen Cache-Schlüssel zu generieren, kann das Caching nicht sicher aktiviert werden.

  • Der Cache-Schlüssel muss die vollständige Struktur repräsentieren: Wenn die Verwendung zweier separater Instanzen dieses Konstrukts zu unterschiedlichem SQL führen kann, führt das Caching des SQL gegen die erste Instanz des Elements mit einem Cache-Schlüssel, der die deutlichen Unterschiede zwischen dem ersten und dem zweiten Element nicht erfasst, zu fehlerhaftem SQL, das für das zweite Element gecached und gerendert wird.

  • Der Cache-Schlüssel muss konsistent sein: Wenn ein Konstrukt einen Zustand repräsentiert, der sich jedes Mal ändert, wie z. B. ein Literalwert, der für jede Instanz eindeutiges SQL erzeugt, ist dieses Konstrukt ebenfalls nicht sicher zu cachen, da die wiederholte Verwendung des Konstrukts den Statement-Cache schnell mit eindeutigen SQL-Strings füllen wird, die wahrscheinlich nicht wieder verwendet werden, wodurch der Zweck des Caches vereitelt wird.

Aus diesen beiden Gründen ist das Caching-System von SQLAlchemy extrem konservativ bei der Entscheidung, das entsprechende SQL für ein Objekt zu cachen.

Assertion-Attribute für Caching

Die Warnung wird basierend auf den unten stehenden Kriterien ausgegeben. Weitere Details zu jedem finden Sie im Abschnitt Warum ist meine Anwendung nach dem Upgrade auf 1.4 und/oder 2.x langsam?.

Siehe auch

Schätzung der Cache-Leistung mithilfe von Protokollierung - Hintergrundinformationen zur Beobachtung des Cache-Verhaltens und der Effizienz

Warum ist meine Anwendung nach dem Upgrade auf 1.4 und/oder 2.x langsam? - im Abschnitt Häufig gestellte Fragen

Compiler StrSQLCompiler kann Element vom Typ <Elementtyp> nicht rendern

Dieser Fehler tritt normalerweise auf, wenn versucht wird, ein SQL-Ausdruckskonstrukt in einen String umzuwandeln, das Elemente enthält, die nicht Teil der Standardkompilierung sind. In diesem Fall richtet sich der Fehler gegen die Klasse StrSQLCompiler. In selteneren Fällen kann er auch auftreten, wenn die falsche Art von SQL-Ausdruck mit einem bestimmten Datenbank-Backend verwendet wird. In diesen Fällen werden andere Arten von SQL-Compilerklassen genannt, wie z. B. SQLCompiler oder sqlalchemy.dialects.postgresql.PGCompiler. Die folgenden Anleitungen sind spezifischer für den Anwendungsfall der „Stringifizierung“, beschreiben aber auch den allgemeinen Hintergrund.

Normalerweise kann ein Core SQL-Konstrukt oder ein ORM Query-Objekt direkt in einen String umgewandelt werden, z. B. wenn wir print() verwenden

>>> from sqlalchemy import column
>>> print(column("x") == 5)
x = :x_1

Wenn der obige SQL-Ausdruck in einen String umgewandelt wird, wird die Compilerklasse StrSQLCompiler verwendet. Dies ist ein spezieller Anweisungskomiler, der aufgerufen wird, wenn ein Konstrukt ohne dialektspezifische Informationen in einen String umgewandelt wird.

Es gibt jedoch viele Konstrukte, die für eine bestimmte Art von Datenbankdialekt spezifisch sind und für die StrSQLCompiler nicht weiß, wie sie in einen String umgewandelt werden können, wie z. B. das PostgreSQL INSERT…ON CONFLICT (Upsert)-Konstrukt

>>> from sqlalchemy.dialects.postgresql import insert
>>> from sqlalchemy import table, column
>>> my_table = table("my_table", column("x"), column("y"))
>>> insert_stmt = insert(my_table).values(x="foo")
>>> insert_stmt = insert_stmt.on_conflict_do_nothing(index_elements=["y"])
>>> print(insert_stmt)
Traceback (most recent call last):

...

sqlalchemy.exc.UnsupportedCompilationError:
Compiler <sqlalchemy.sql.compiler.StrSQLCompiler object at 0x7f04fc17e320>
can't render element of type
<class 'sqlalchemy.dialects.postgresql.dml.OnConflictDoNothing'>

Um Konstrukte, die für einen bestimmten Backend spezifisch sind, in einen String umzuwandeln, muss die Methode ClauseElement.compile() verwendet werden. Dabei wird entweder ein Engine- oder ein Dialect-Objekt übergeben, das den richtigen Compiler aufruft. Im Folgenden verwenden wir einen PostgreSQL-Dialekt

>>> from sqlalchemy.dialects import postgresql
>>> print(insert_stmt.compile(dialect=postgresql.dialect()))
INSERT INTO my_table (x) VALUES (%(x)s) ON CONFLICT (y) DO NOTHING

Für ein ORM Query-Objekt kann die Anweisung über den Zugriff Query.statement abgerufen werden

statement = query.statement
print(statement.compile(dialect=postgresql.dialect()))

Weitere Details zur direkten Stringifizierung/Kompilierung von SQL-Elementen finden Sie im FAQ-Link unten.

TypeError: <Operator> wird nicht zwischen Instanzen von 'ColumnProperty' und <Etwas> unterstützt

Dies tritt häufig auf, wenn versucht wird, ein column_property()- oder deferred()-Objekt im Kontext eines SQL-Ausdrucks zu verwenden, normalerweise innerhalb von Deklarativ, wie zum Beispiel

class Bar(Base):
    __tablename__ = "bar"

    id = Column(Integer, primary_key=True)
    cprop = deferred(Column(Integer))

    __table_args__ = (CheckConstraint(cprop > 5),)

Oben wird das Attribut cprop inline verwendet, bevor es zugeordnet wurde. Dieses Attribut cprop ist jedoch kein Column, sondern eine ColumnProperty, ein temporäres Objekt, das daher nicht die volle Funktionalität eines Column-Objekts oder des InstrumentedAttribute-Objekts besitzt, das nach Abschluss des deklarativen Prozesses der Klasse Bar zugeordnet wird.

Während die ColumnProperty eine Methode __clause_element__() hat, die ihre Verwendung in einigen spaltenorientierten Kontexten ermöglicht, kann sie nicht in einem offen verglichenen Kontext wie oben verwendet werden, da sie keine Python-Methode __eq__() hat, die es ihr ermöglichen würde, den Vergleich mit der Zahl „5“ als SQL-Ausdruck und nicht als regulären Python-Vergleich zu interpretieren.

Die Lösung besteht darin, direkt auf die Column über das Attribut ColumnProperty.expression zuzugreifen

class Bar(Base):
    __tablename__ = "bar"

    id = Column(Integer, primary_key=True)
    cprop = deferred(Column(Integer))

    __table_args__ = (CheckConstraint(cprop.expression > 5),)

Für den gebundenen Parameter <x> (in Parametergruppe <y>) wird ein Wert benötigt

Dieser Fehler tritt auf, wenn eine Anweisung bindparam() implizit oder explizit verwendet und beim Ausführen der Anweisung kein Wert angegeben wird

stmt = select(table.c.column).where(table.c.id == bindparam("my_param"))

result = conn.execute(stmt)

Oben wurde kein Wert für den Parameter „my_param“ angegeben. Der richtige Ansatz ist, einen Wert anzugeben

result = conn.execute(stmt, {"my_param": 12})

Wenn die Meldung lautet „Ein Wert wird für den gebundenen Parameter <x> in Parametergruppe <y> benötigt“, bezieht sich die Meldung auf den Ausführungsstil „executemany“. In diesem Fall handelt es sich bei der Anweisung normalerweise um ein INSERT, UPDATE oder DELETE, und es wird eine Liste von Parametern übergeben. In diesem Format kann die Anweisung dynamisch generiert werden, um Parameterpositionen für jeden Parameter in der Argumentliste einzuschließen. Dabei werden die ersten Parametersätze verwendet, um festzustellen, welche das sein sollen.

Zum Beispiel wird die folgende Anweisung basierend auf dem ersten Parametersatz berechnet, der die Parameter „a“, „b“ und „c“ erfordert. Diese Namen bestimmen das endgültige Stringformat der Anweisung, die für jeden Parametersatz in der Liste verwendet wird. Da der zweite Eintrag „b“ nicht enthält, wird dieser Fehler generiert

m = MetaData()
t = Table("t", m, Column("a", Integer), Column("b", Integer), Column("c", Integer))

e.execute(
    t.insert(),
    [
        {"a": 1, "b": 2, "c": 3},
        {"a": 2, "c": 4},
        {"a": 3, "b": 4, "c": 5},
    ],
)
sqlalchemy.exc.StatementError: (sqlalchemy.exc.InvalidRequestError)
A value is required for bind parameter 'b', in parameter group 1
[SQL: u'INSERT INTO t (a, b, c) VALUES (?, ?, ?)']
[parameters: [{'a': 1, 'c': 3, 'b': 2}, {'a': 2, 'c': 4}, {'a': 3, 'c': 5, 'b': 4}]]

Da „b“ benötigt wird, geben Sie es als None an, damit das INSERT fortgesetzt werden kann

e.execute(
    t.insert(),
    [
        {"a": 1, "b": 2, "c": 3},
        {"a": 2, "b": None, "c": 4},
        {"a": 3, "b": 4, "c": 5},
    ],
)

Siehe auch

Parameter senden

FROM-Klausel erwartet, SELECT erhalten. Zum Erstellen einer FROM-Klausel verwenden Sie die Methode .subquery()

Dies bezieht sich auf eine Änderung ab SQLAlchemy 1.4, bei der eine SELECT-Anweisung, wie sie von einer Funktion wie select() generiert wird, aber auch Dinge wie Unions und textuelle SELECT-Ausdrücke, nicht mehr als FromClause-Objekte gelten und nicht direkt in die FROM-Klausel einer anderen SELECT-Anweisung eingefügt werden können, ohne dass sie zuerst in eine Subquery eingewickelt werden. Dies ist eine bedeutende konzeptionelle Änderung im Core und die vollständige Begründung wird unter Eine SELECT-Anweisung wird nicht mehr implizit als FROM-Klausel betrachtet diskutiert.

Gegeben ein Beispiel wie

m = MetaData()
t = Table("t", m, Column("a", Integer), Column("b", Integer), Column("c", Integer))
stmt = select(t)

Oben repräsentiert stmt eine SELECT-Anweisung. Der Fehler tritt auf, wenn wir versuchen, stmt direkt als FROM-Klausel in einer anderen SELECT zu verwenden, z. B. wenn wir versuchen, daraus auszuwählen

new_stmt_1 = select(stmt)

Oder wenn wir es in einer FROM-Klausel verwenden wollen, wie z. B. in einem JOIN

new_stmt_2 = select(some_table).select_from(some_table.join(stmt))

In früheren Versionen von SQLAlchemy führte die Verwendung einer SELECT-Anweisung innerhalb einer anderen SELECT-Anweisung zu einer parenthetischen, unbenannten Subquery. In den meisten Fällen ist diese Form von SQL nicht sehr nützlich, da Datenbanken wie MySQL und PostgreSQL erfordern, dass Subqueries in FROM-Klauseln benannte Aliase haben. Das bedeutet, dass die Methode SelectBase.alias() oder ab Version 1.4 die Methode SelectBase.subquery() verwendet werden muss, um dies zu erzeugen. Auf anderen Datenbanken ist es immer noch viel klarer, dass die Subquery einen Namen hat, um Mehrdeutigkeiten bei zukünftigen Verweisen auf Spaltennamen innerhalb der Subquery zu vermeiden.

Über die oben genannten praktischen Gründe hinaus gibt es viele weitere SQLAlchemy-orientierte Gründe für die Änderung. Die korrekte Form der beiden obigen Anweisungen erfordert daher die Verwendung von SelectBase.subquery()

subq = stmt.subquery()

new_stmt_1 = select(subq)

new_stmt_2 = select(some_table).select_from(some_table.join(subq))

Automatisch wird ein Alias für ein rohes ClauseElement generiert

Neu in Version 1.4.26.

Diese Deprecation-Warnung bezieht sich auf ein sehr altes und wahrscheinlich wenig bekanntes Muster, das für die Legacy-Methode Query.join() sowie für die Methode 2.0 Stil Select.join() gilt, bei der ein Join in Bezug auf eine relationship() angegeben werden kann, das Ziel jedoch die Table oder ein anderes Core-Selectable ist, dem die Klasse zugeordnet ist, anstatt eine ORM-Entität wie eine zugeordnete Klasse oder ein aliased()-Konstrukt

a1 = Address.__table__

q = (
    s.query(User)
    .join(a1, User.addresses)
    .filter(Address.email_address == "ed@foo.com")
    .all()
)

Das obige Muster erlaubt auch ein beliebiges Selectable, wie z. B. ein Core Join- oder Alias-Objekt. Allerdings gibt es keine automatische Anpassung dieses Elements, was bedeutet, dass das Core-Element direkt referenziert werden müsste

a1 = Address.__table__.alias()

q = (
    s.query(User)
    .join(a1, User.addresses)
    .filter(a1.c.email_address == "ed@foo.com")
    .all()
)

Der korrekte Weg, ein Join-Ziel anzugeben, ist immer die Verwendung der zugeordneten Klasse selbst oder eines aliased-Objekts, wobei im letzteren Fall der Modifikator PropComparator.of_type() verwendet wird, um einen Alias einzurichten

# normal join to relationship entity
q = s.query(User).join(User.addresses).filter(Address.email_address == "ed@foo.com")

# name Address target explicitly, not necessary but legal
q = (
    s.query(User)
    .join(Address, User.addresses)
    .filter(Address.email_address == "ed@foo.com")
)

Join zu einem Alias

from sqlalchemy.orm import aliased

a1 = aliased(Address)

# of_type() form; recommended
q = (
    s.query(User)
    .join(User.addresses.of_type(a1))
    .filter(a1.email_address == "ed@foo.com")
)

# target, onclause form
q = s.query(User).join(a1, User.addresses).filter(a1.email_address == "ed@foo.com")

Aufgrund überlappender Tabellen wird automatisch ein Alias generiert

Neu in Version 1.4.26.

Diese Warnung wird typischerweise generiert, wenn mit der Methode Select.join() oder der Legacy-Methode Query.join() Abfragen mit Zuordnungen durchgeführt werden, die verkettete Tabellenvererbung beinhalten. Das Problem ist, dass beim Verknüpfen zweier verketteter Vererbungsmodelle, die eine gemeinsame Basistabelle haben, ein korrekter SQL-JOIN zwischen den beiden Entitäten nicht gebildet werden kann, ohne einer Seite oder der anderen einen Alias zuzuweisen. SQLAlchemy weist der rechten Seite des Joins einen Alias zu. Angenommen, eine verkettete Vererbungszuordnung wie folgt:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    manager_id = Column(ForeignKey("manager.id"))
    name = Column(String(50))
    type = Column(String(50))

    reports_to = relationship("Manager", foreign_keys=manager_id)

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }


class Manager(Employee):
    __tablename__ = "manager"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "inherit_condition": id == Employee.id,
    }

Die obige Zuordnung enthält eine Beziehung zwischen den Klassen Employee und Manager. Da beide Klassen die Datenbanktabelle „employee“ verwenden, handelt es sich aus SQL-Sicht um eine selbstbezügliche Beziehung. Wenn wir sowohl die Modelle Employee als auch Manager über einen Join abfragen wollten, muss auf SQL-Ebene die Tabelle „employee“ zweimal in die Abfrage aufgenommen werden, was bedeutet, dass sie einen Alias erhalten muss. Wenn wir einen solchen Join über das SQLAlchemy ORM erstellen, erhalten wir SQL, das wie folgt aussieht:

>>> stmt = select(Employee, Manager).join(Employee.reports_to)
>>> print(stmt)
SELECT employee.id, employee.manager_id, employee.name, employee.type, manager_1.id AS id_1, employee_1.id AS id_2, employee_1.manager_id AS manager_id_1, employee_1.name AS name_1, employee_1.type AS type_1 FROM employee JOIN (employee AS employee_1 JOIN manager AS manager_1 ON manager_1.id = employee_1.id) ON manager_1.id = employee.manager_id

Oben wählt die SQL-Anweisung FROM die Tabelle employee aus, die die Entität Employee in der Abfrage repräsentiert. Sie verknüpft dann mit einer rechts verschachtelten Verknüpfung von employee AS employee_1 JOIN manager AS manager_1, wobei die Tabelle employee erneut angegeben wird, jedoch als anonymer Alias employee_1. Dies ist die „automatische Generierung eines Alias“, auf die sich die Warnmeldung bezieht.

Wenn SQLAlchemy ORM-Zeilen lädt, die jeweils ein Employee- und ein Manager-Objekt enthalten, muss das ORM Zeilen aus den obigen Tabellenaliassen employee_1 und manager_1 in die der nicht-aliasierten Klasse Manager anpassen. Dieser Prozess ist intern komplex und unterstützt nicht alle API-Funktionen, insbesondere wenn versucht wird, Eager-Loading-Funktionen wie contains_eager() mit tiefer verschachtelten Abfragen als hier gezeigt zu verwenden. Da das Muster für komplexere Szenarien unzuverlässig ist und implizite Entscheidungen beinhaltet, die schwer vorherzusagen und nachzuvollziehen sind, wird die Warnung ausgegeben und dieses Muster kann als Legacy-Funktion betrachtet werden. Der bessere Weg, diese Abfrage zu schreiben, ist die Verwendung derselben Muster, die für jede andere selbstbezügliche Beziehung gelten, nämlich die explizite Verwendung des aliased()-Konstrukts. Für verkettete Vererbung und andere join-orientierte Zuordnungen ist es normalerweise wünschenswert, die Verwendung des Parameters aliased.flat hinzuzufügen. Dies ermöglicht es, einen JOIN von zwei oder mehr Tabellen zu einem Alias zu machen, indem ein Alias auf die einzelnen Tabellen innerhalb des Joins angewendet wird, anstatt den Join in eine neue Subquery einzubetten.

>>> from sqlalchemy.orm import aliased
>>> manager_alias = aliased(Manager, flat=True)
>>> stmt = select(Employee, manager_alias).join(Employee.reports_to.of_type(manager_alias))
>>> print(stmt)
SELECT employee.id, employee.manager_id, employee.name, employee.type, manager_1.id AS id_1, employee_1.id AS id_2, employee_1.manager_id AS manager_id_1, employee_1.name AS name_1, employee_1.type AS type_1 FROM employee JOIN (employee AS employee_1 JOIN manager AS manager_1 ON manager_1.id = employee_1.id) ON manager_1.id = employee.manager_id

Wenn wir dann contains_eager() verwenden möchten, um das Attribut reports_to zu füllen, verweisen wir auf den Alias

>>> stmt = (
...     select(Employee)
...     .join(Employee.reports_to.of_type(manager_alias))
...     .options(contains_eager(Employee.reports_to.of_type(manager_alias)))
... )

Ohne die explizite Verwendung des aliased()-Objekts hat die Option contains_eager() in einigen verschachtelteren Fällen nicht genügend Kontext, um zu wissen, woher ihre Daten stammen sollen, falls das ORM in einem sehr verschachtelten Kontext „auto-aliasiert“. Daher ist es am besten, sich nicht auf diese Funktion zu verlassen und stattdessen die SQL-Konstruktion so explizit wie möglich zu halten.

Objektrelationale Abbildung

IllegalStateChangeError und Nebenläufigkeitsausnahmen

SQLAlchemy 2.0 führte ein neues System ein, das unter Session löst proaktiv aus, wenn illegale gleichzeitige oder reentrant Zugriffe erkannt werden beschrieben wird. Dieses System erkennt proaktiv gleichzeitige Methodenaufrufe auf einer einzelnen Instanz des Session-Objekts und im weiteren Sinne auf dem AsyncSession-Proxy-Objekt. Diese gleichzeitigen Zugriffsaufrufe treten typischerweise, aber nicht ausschließlich, auf, wenn eine einzelne Instanz von Session zwischen mehreren gleichzeitigen Threads geteilt wird, ohne dass ein solcher Zugriff synchronisiert ist, oder ähnlich, wenn eine einzelne Instanz von AsyncSession zwischen mehreren gleichzeitigen Aufgaben geteilt wird (z. B. bei Verwendung einer Funktion wie asyncio.gather()). Diese Verwendungsmuster sind keine geeignete Verwendung dieser Objekte. Ohne das proaktive Warnsystem, das SQLAlchemy implementiert, würden sie immer noch ungültige Zustände innerhalb der Objekte verursachen und schwer zu debuggende Fehler einschließlich Treiber-Level-Fehler auf den Datenbankverbindungen selbst produzieren.

Instanzen von Session und AsyncSession sind veränderliche, zustandsbehaftete Objekte ohne eingebaute Synchronisierung von Methodenaufrufen und repräsentieren eine einzelne, laufende Datenbanktransaktion auf einer einzelnen Datenbankverbindung zu einem Zeitpunkt für einen bestimmten Engine oder AsyncEngine, an den das Objekt gebunden ist (beachten Sie, dass diese Objekte beide an mehrere Engines gleichzeitig gebunden werden können, aber auch dann gibt es nur eine Verbindung pro Engine im Rahmen einer Transaktion). Eine einzelne Datenbanktransaktion ist kein geeignetes Ziel für gleichzeitige SQL-Befehle; stattdessen sollte eine Anwendung, die gleichzeitige Datenbankoperationen ausführt, gleichzeitige Transaktionen verwenden. Für diese Objekte gilt dann, dass das geeignete Muster Session pro Thread oder AsyncSession pro Task ist.

Weitere Hintergrundinformationen zur Nebenläufigkeit finden Sie im Abschnitt Ist die Session Thread-sicher? Ist AsyncSession sicher für die gemeinsame Nutzung in gleichzeitigen Aufgaben?.

Übergeordnetes Objekt <x> ist nicht an eine Session gebunden; (Lazy Load/Deferred Load/Refresh/etc.) Operation kann nicht fortgesetzt werden

Dies ist wahrscheinlich die häufigste Fehlermeldung beim Umgang mit dem ORM, und sie tritt als Folge der Natur einer Technik auf, die das ORM häufig verwendet, bekannt als Lazy Loading. Lazy Loading ist ein gängiges Objekt-Relationale Muster, bei dem ein vom ORM gespeichertes Objekt einen Proxy zur Datenbank selbst behält, so dass beim Zugriff auf verschiedene Attribute des Objekts deren Wert *lazy* (verzögert) aus der Datenbank abgerufen werden kann. Der Vorteil dieses Ansatzes ist, dass Objekte aus der Datenbank abgerufen werden können, ohne alle ihre Attribute oder zugehörigen Daten auf einmal laden zu müssen. Stattdessen kann nur die angeforderte Datenmenge geliefert werden. Der Hauptnachteil ist im Grunde das Spiegelbild des Vorteils, nämlich dass es verschwenderisch ist, zusätzliche Daten stückweise zu laden, wenn viele Objekte geladen werden, für die in allen Fällen ein bestimmtes Datenset benötigt wird.

Ein weiterer Vorbehalt des Lazy Loadings über die üblichen Effizienzbedenken hinaus ist, dass das Objekt, damit das Lazy Loading fortgesetzt werden kann, **mit einer Session assoziiert bleiben muss**, um seinen Zustand abrufen zu können. Diese Fehlermeldung bedeutet, dass ein Objekt von seiner Session getrennt wurde und aufgefordert wird, Daten aus der Datenbank lazy zu laden.

Der häufigste Grund dafür, dass Objekte von ihren Session getrennt werden, ist, dass die Session selbst geschlossen wurde, normalerweise über die Methode Session.close(). Die Objekte leben dann weiter und werden weiter verwendet, sehr oft in Webanwendungen, wo sie an eine serverseitige Template-Engine übergeben werden und weitere Attribute angefordert werden, die sie nicht laden können.

Abmilderung dieses Fehlers durch diese Techniken

  • Versuchen Sie, getrennte Objekte zu vermeiden; schließen Sie die Session nicht vorzeitig - Oft schließen Anwendungen eine Transaktion ab, bevor sie zugehörige Objekte an ein anderes System übergeben, das dann aufgrund dieses Fehlers fehlschlägt. Manchmal muss die Transaktion nicht so schnell geschlossen werden; ein Beispiel ist, dass die Webanwendung die Transaktion schließt, bevor die Ansicht gerendert wird. Dies geschieht oft im Namen der „Korrektheit“, kann aber als Fehlinterpretation von „Kapselung“ angesehen werden, da sich dieser Begriff auf die Codeorganisation bezieht und nicht auf tatsächliche Aktionen. Die Vorlage, die ein ORM-Objekt verwendet, nutzt das Proxy-Muster, das die Datenbanklogik vom Aufrufer gekapselt hält. Wenn die Session bis zum Lebensende der Objekte offen gehalten werden kann, ist dies der beste Ansatz.

  • Laden Sie andernfalls alles, was benötigt wird, im Voraus - Es ist sehr oft unmöglich, die Transaktion offen zu halten, insbesondere in komplexeren Anwendungen, die Objekte an andere Systeme weitergeben müssen, die nicht im selben Kontext ausgeführt werden können, obwohl sie im selben Prozess sind. In diesem Fall sollte die Anwendung vorbereitet sein, mit getrennten Objekten umzugehen und sollte versuchen, Eager Loading angemessen zu nutzen, um sicherzustellen, dass Objekte im Voraus alles haben, was sie benötigen.

  • Und vor allem: Setzen Sie expire_on_commit auf False - Bei der Verwendung von getrennten Objekten besteht der häufigste Grund, warum Objekte Daten neu laden müssen, darin, dass sie nach dem letzten Aufruf von Session.commit() abgelaufen sind. Dieses Ablaufen sollte nicht verwendet werden, wenn mit getrennten Objekten gearbeitet wird. Daher sollte der Parameter Session.expire_on_commit auf False gesetzt werden. Indem verhindert wird, dass die Objekte außerhalb der Transaktion ablaufen, bleiben die geladenen Daten erhalten und es werden keine zusätzlichen Lazy Loads ausgelöst, wenn auf diese Daten zugegriffen wird.

    Beachten Sie auch, dass die Methode Session.rollback() bedingungslos alle Inhalte in der Session ungültig macht und ebenfalls in Nicht-Fehler-Szenarien vermieden werden sollte.

    Siehe auch

    Relationship Loading Techniques - Detaillierte Dokumentation zu Eager Loading und anderen beziehungsbezogenen Ladetechniken

    Committing - Hintergrundinformationen zum Session Commit

    Refreshing / Expiring - Hintergrundinformationen zum Attribut-Expiry

Die Transaktion dieser Session wurde aufgrund eines vorherigen Fehlers während des Flush zurückgerollt

Der Flush-Prozess der Session, wie unter Flushing beschrieben, rollt die Datenbanktransaktion im Fehlerfall zurück, um die interne Konsistenz zu wahren. Sobald dies jedoch geschieht, ist die Transaktion der Session nun "inaktiv" und muss von der aufrufenden Anwendung explizit zurückgerollt werden, so wie sie andernfalls explizit committet werden müsste, wenn kein Fehler aufgetreten wäre.

Dies ist ein häufiger Fehler bei der Verwendung des ORM und bezieht sich typischerweise auf eine Anwendung, die noch keine korrekte "Rahmengebung" um ihre Session-Operationen hat. Weitere Details finden Sie in den FAQs unter "This Session’s transaction has been rolled back due to a previous exception during flush." (oder ähnlich).

Für die Beziehung <relationship> wird der `delete-orphan`-Cascade normalerweise nur auf der "einer" Seite einer Eins-zu-viele-Beziehung konfiguriert, und nicht auf der "viele"-Seite einer viele-zu-eins- oder viele-zu-viele-Beziehung.

Dieser Fehler tritt auf, wenn der `delete-orphan`-Cascade auf einer viele-zu-eins- oder viele-zu-viele-Beziehung gesetzt ist, z. B.

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)

    bs = relationship("B", back_populates="a")


class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))

    # this will emit the error message when the mapper
    # configuration step occurs
    a = relationship("A", back_populates="bs", cascade="all, delete-orphan")


configure_mappers()

Oben gibt die `delete-orphan`-Einstellung auf B.a die Absicht an, dass, wenn jedes B-Objekt, das auf ein bestimmtes A verweist, gelöscht wird, das A dann ebenfalls gelöscht werden soll. Das heißt, es drückt aus, dass das "verwaiste" Objekt, das gelöscht wird, ein A-Objekt wäre, und es wird "verwaist", wenn jedes B, das darauf verweist, gelöscht wird.

Das `delete-orphan`-Cascade-Modell unterstützt diese Funktionalität nicht. Die "verwaiste" Betrachtung erfolgt nur in Bezug auf die Löschung eines einzelnen Objekts, das dann auf null oder mehr Objekte verweisen würde, die durch diese einzelne Löschung "verwaist" werden, was zum Löschen dieser Objekte führt. Mit anderen Worten, es ist nur dazu gedacht, die Erzeugung von "Verwaisten" basierend auf der Entfernung eines und nur eines "Eltern"-Objekts pro Verwaisten zu verfolgen, was der natürliche Fall in einer Eins-zu-viele-Beziehung ist, bei der die Löschung des Objekts auf der "eins"-Seite zur anschließenden Löschung der zugehörigen Elemente auf der "viele"-Seite führt.

Das obige Mapping zur Unterstützung dieser Funktionalität würde stattdessen die Cascade-Einstellung auf der Eins-zu-viele-Seite platzieren, was wie folgt aussieht:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)

    bs = relationship("B", back_populates="a", cascade="all, delete-orphan")


class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))

    a = relationship("A", back_populates="bs")

Wobei die Absicht darin besteht, dass beim Löschen eines A alle B-Objekte, auf die es verweist, ebenfalls gelöscht werden.

Die Fehlermeldung schlägt dann die Verwendung des Flags relationship.single_parent vor. Dieses Flag kann verwendet werden, um zu erzwingen, dass eine Beziehung, die es mehreren Objekten erlaubt, auf ein bestimmtes Objekt zu verweisen, tatsächlich nur **ein** Objekt zur Zeit referenzieren darf. Es wird für veraltete oder andere weniger ideale Datenbankschemata verwendet, bei denen die Fremdschlüsselbeziehungen eine "viele"-Sammlung nahelegen, in der Praxis jedoch nur ein Objekt zu einem bestimmten Zeitpunkt auf ein gegebenes Zielobjekt verweisen würde. Dieses ungewöhnliche Szenario kann im Hinblick auf das obige Beispiel wie folgt demonstriert werden:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)

    bs = relationship("B", back_populates="a")


class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))

    a = relationship(
        "A",
        back_populates="bs",
        single_parent=True,
        cascade="all, delete-orphan",
    )

Die obige Konfiguration installiert dann einen Validator, der erzwingt, dass nur ein B gleichzeitig mit einem A über die B.a-Beziehung verknüpft sein darf.

>>> b1 = B()
>>> b2 = B()
>>> a1 = A()
>>> b1.a = a1
>>> b2.a = a1
sqlalchemy.exc.InvalidRequestError: Instance <A at 0x7eff44359350> is
already associated with an instance of <class '__main__.B'> via its
B.a attribute, and is only allowed a single parent.

Beachten Sie, dass dieser Validator einen begrenzten Geltungsbereich hat und die Erstellung mehrerer "Eltern" über die andere Richtung nicht verhindert. Er erkennt beispielsweise nicht dieselbe Einstellung im Hinblick auf A.bs.

>>> a1.bs = [b1, b2]
>>> session.add_all([a1, b1, b2])
>>> session.commit()
INSERT INTO a DEFAULT VALUES () INSERT INTO b (a_id) VALUES (?) (1,) INSERT INTO b (a_id) VALUES (?) (1,)

Die Dinge werden jedoch später nicht wie erwartet verlaufen, da der `delete-orphan`-Cascade weiterhin in Bezug auf ein **einzelnes** führendes Objekt funktioniert, was bedeutet, dass wir, wenn wir **eines** der B-Objekte löschen, das A löschen. Das andere B bleibt bestehen, wobei das ORM normalerweise schlau genug ist, das Fremdschlüsselattribut auf NULL zu setzen, aber das ist normalerweise nicht das, was gewünscht wird.

>>> session.delete(b1)
>>> session.commit()
UPDATE b SET a_id=? WHERE b.id = ? (None, 2) DELETE FROM b WHERE b.id = ? (1,) DELETE FROM a WHERE a.id = ? (1,) COMMIT

Für alle obigen Beispiele gilt eine ähnliche Logik für die Berechnung einer viele-zu-viele-Beziehung; wenn eine viele-zu-viele-Beziehung auf einer Seite `single_parent=True` setzt, kann diese Seite den `delete-orphan`-Cascade verwenden, dies ist jedoch sehr unwahrscheinlich, dass jemand dies tatsächlich möchte, da der Sinn einer viele-zu-viele-Beziehung darin besteht, dass viele Objekte in einer Richtung auf ein Objekt verweisen können.

Insgesamt wird der `delete-orphan`-Cascade normalerweise auf der "eins"-Seite einer Eins-zu-viele-Beziehung angewendet, so dass er Objekte auf der "viele"-Seite löscht und nicht umgekehrt.

Geändert in Version 1.3.18: Der Text der Fehlermeldung "delete-orphan", wenn er auf einer viele-zu-eins- oder viele-zu-viele-Beziehung verwendet wird, wurde aktualisiert, um beschreibender zu sein.

Instanz <instance> ist bereits über ihr Attribut <attribute> mit einer Instanz von <instance> verknüpft und darf nur einen einzigen Elternteil haben.

Dieser Fehler wird ausgegeben, wenn das Flag relationship.single_parent verwendet wird und mehr als ein Objekt gleichzeitig als "Elternteil" eines Objekts zugewiesen wird.

Bei der folgenden Zuordnung

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)


class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))

    a = relationship(
        "A",
        single_parent=True,
        cascade="all, delete-orphan",
    )

Die Absicht gibt an, dass nicht mehr als ein B-Objekt gleichzeitig auf ein bestimmtes A-Objekt verweisen darf.

>>> b1 = B()
>>> b2 = B()
>>> a1 = A()
>>> b1.a = a1
>>> b2.a = a1
sqlalchemy.exc.InvalidRequestError: Instance <A at 0x7eff44359350> is
already associated with an instance of <class '__main__.B'> via its
B.a attribute, and is only allowed a single parent.

Wenn dieser Fehler unerwartet auftritt, liegt es normalerweise daran, dass das Flag relationship.single_parent als Reaktion auf die Fehlermeldung unter Für die Beziehung <relationship> wird der `delete-orphan`-Cascade normalerweise nur auf der "einer" Seite einer Eins-zu-viele-Beziehung konfiguriert, und nicht auf der "viele"-Seite einer viele-zu-eins- oder viele-zu-viele-Beziehung. angewendet wurde und das Problem tatsächlich ein Missverständnis der `delete-orphan`-Cascade-Einstellung ist. Siehe diese Meldung für Details.

Die Beziehung X kopiert Spalte Q nach Spalte P, was mit Beziehung(en) 'Y' kollidiert

Diese Warnung bezieht sich auf den Fall, in dem zwei oder mehr Beziehungen während des Flushs Daten in dieselben Spalten schreiben, das ORM jedoch keine Möglichkeit hat, diese Beziehungen miteinander zu koordinieren. Abhängig von den Details kann die Lösung darin bestehen, dass zwei Beziehungen über relationship.back_populates aufeinander verweisen müssen, oder dass eine oder mehrere der Beziehungen mit relationship.viewonly konfiguriert werden sollten, um widersprüchliche Schreibvorgänge zu verhindern, oder manchmal, dass die Konfiguration vollständig beabsichtigt ist und relationship.overlaps konfiguriert werden sollte, um jede Warnung zu unterdrücken.

Für das typische Beispiel, bei dem relationship.back_populates fehlt, bei der folgenden Zuordnung

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key=True)
    children = relationship("Child")


class Child(Base):
    __tablename__ = "child"
    id = Column(Integer, primary_key=True)
    parent_id = Column(ForeignKey("parent.id"))
    parent = relationship("Parent")

Das obige Mapping generiert Warnungen

SAWarning: relationship 'Child.parent' will copy column parent.id to column child.parent_id,
which conflicts with relationship(s): 'Parent.children' (copies parent.id to child.parent_id).

Die Beziehungen Child.parent und Parent.children scheinen zu kollidieren. Die Lösung besteht darin, relationship.back_populates anzuwenden.

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key=True)
    children = relationship("Child", back_populates="parent")


class Child(Base):
    __tablename__ = "child"
    id = Column(Integer, primary_key=True)
    parent_id = Column(ForeignKey("parent.id"))
    parent = relationship("Parent", back_populates="children")

Für stärker angepasste Beziehungen, bei denen eine "Überlappungssituation" beabsichtigt ist und nicht aufgelöst werden kann, kann der Parameter relationship.overlaps die Namen von Beziehungen angeben, für die die Warnung nicht gelten soll. Dies geschieht typischerweise für zwei oder mehr Beziehungen zur selben zugrunde liegenden Tabelle, die benutzerdefinierte relationship.primaryjoin-Bedingungen enthalten, die die jeweiligen verknüpften Elemente einschränken.

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key=True)
    c1 = relationship(
        "Child",
        primaryjoin="and_(Parent.id == Child.parent_id, Child.flag == 0)",
        backref="parent",
        overlaps="c2, parent",
    )
    c2 = relationship(
        "Child",
        primaryjoin="and_(Parent.id == Child.parent_id, Child.flag == 1)",
        overlaps="c1, parent",
    )


class Child(Base):
    __tablename__ = "child"
    id = Column(Integer, primary_key=True)
    parent_id = Column(ForeignKey("parent.id"))

    flag = Column(Integer)

Oben erkennt das ORM, dass die Überlappung zwischen Parent.c1, Parent.c2 und Child.parent beabsichtigt ist.

Objekt kann nicht in den 'persistenten' Zustand konvertiert werden, da diese Identitätszuordnung nicht mehr gültig ist.

Neu in Version 1.4.26.

Diese Meldung wurde hinzugefügt, um den Fall zu berücksichtigen, in dem ein Result-Objekt, das ORM-Objekte liefert, iteriert wird, nachdem die ursprüngliche Session geschlossen wurde oder seine Methode Session.expunge_all() aufgerufen wurde. Wenn eine Session alle Objekte auf einmal expungiert, wird die interne Identitätszuordnung, die von dieser Session verwendet wird, durch eine neue ersetzt und die ursprüngliche verworfen. Ein unkonsumiertes und ungebuffertes Result-Objekt behält intern eine Referenz auf diese nun verworfene Identitätszuordnung. Wenn das Result konsumiert wird, können die Objekte, die geliefert werden würden, dieser Session nicht zugeordnet werden. Diese Anordnung ist beabsichtigt, da es im Allgemeinen nicht empfohlen wird, ein ungebuffertes Result-Objekt außerhalb des Transaktionskontextes zu iterieren, in dem es erstellt wurde.

# context manager creates new Session
with Session(engine) as session_obj:
    result = sess.execute(select(User).where(User.id == 7))

# context manager is closed, so session_obj above is closed, identity
# map is replaced

# iterating the result object can't associate the object with the
# Session, raises this error.
user = result.first()

Die obige Situation tritt typischerweise **nicht** auf, wenn die asyncio ORM-Erweiterung verwendet wird, da, wenn AsyncSession ein synchrone Result zurückgibt, die Ergebnisse beim Ausführen der Anweisung vorab gepuffert wurden. Dies geschieht, damit sekundäre Eager Loader aufgerufen werden können, ohne dass ein zusätzlicher await-Aufruf erforderlich ist.

Um Ergebnisse in der obigen Situation mit der regulären Session auf die gleiche Weise vorab zu puffern, wie es die asyncio-Erweiterung tut, kann die Ausführungsoption prebuffer_rows wie folgt verwendet werden:

# context manager creates new Session
with Session(engine) as session_obj:
    # result internally pre-fetches all objects
    result = sess.execute(
        select(User).where(User.id == 7), execution_options={"prebuffer_rows": True}
    )

# context manager is closed, so session_obj above is closed, identity
# map is replaced

# pre-buffered objects are returned
user = result.first()

# however they are detached from the session, which has been closed
assert inspect(user).detached
assert inspect(user).session is None

Oben werden die ausgewählten ORM-Objekte vollständig innerhalb des session_obj-Blocks generiert, mit session_obj verknüpft und im Result-Objekt zum Iterieren gepuffert. Außerhalb des Blocks wird session_obj geschlossen und diese ORM-Objekte expungiert. Das Iterieren des Result-Objekts liefert diese ORM-Objekte, aber da ihre ursprüngliche Session sie expungiert hat, werden sie im detached-Zustand geliefert.

Hinweis

Der obige Verweis auf ein "vorab gepuffertes" vs. "unverbuffertes" Result-Objekt bezieht sich auf den Prozess, durch den das ORM rohe Datenbankzeilen vom DBAPI in ORM-Objekte konvertiert. Dies impliziert nicht, ob das zugrunde liegende cursor-Objekt selbst, das ausstehende Ergebnisse vom DBAPI repräsentiert, gepuffert oder ungepuffert ist, da dies im Wesentlichen eine niedrigere Pufferungsebene ist. Hintergrundinformationen zur Pufferung der cursor-Ergebnisse selbst finden Sie im Abschnitt Server-seitige Cursor verwenden (auch bekannt als Stream Results).

Typ-Annotation kann für Annotated Declarative Table-Form nicht interpretiert werden

SQLAlchemy 2.0 führt ein neues deklaratives System für Annotated Declarative Table ein, das ORM-abgebildete Attributinformationen aus PEP 484-Annotationen innerhalb von Klassendefinitionen zur Laufzeit ableitet. Eine Anforderung dieser Form ist, dass alle ORM-Annotationen einen generischen Container namens Mapped verwenden müssen, um ordnungsgemäß annotiert zu werden. Veraltete SQLAlchemy-Mappings, die explizite PEP 484-Typ-Annotationen enthalten, wie z. B. solche, die die veraltete Mypy-Erweiterung für Typ-Unterstützung verwenden, können Direktiven wie die für relationship() enthalten, die diesen generischen Container nicht einschließen.

Zur Behebung können die Klassen mit dem booleschen Attribut __allow_unmapped__ markiert werden, bis sie vollständig auf die 2.0-Syntax migriert sind. Sehen Sie sich die Migrationshinweise unter Migration zu 2.0 Schritt Sechs - __allow_unmapped__ zu explizit typisierten ORM-Modellen hinzufügen für ein Beispiel an.

Beim Umwandeln von <cls> in eine Dataclass stammen Attribute von der Oberklasse <cls>, die keine Dataclass ist.

Diese Warnung tritt auf, wenn die SQLAlchemy ORM Mapped Dataclasses-Funktion, die unter Declarative Dataclass Mapping beschrieben ist, in Verbindung mit einer Mixin-Klasse oder einer abstrakten Basisklasse verwendet wird, die selbst nicht als Dataclass deklariert ist, wie im folgenden Beispiel:

from __future__ import annotations

import inspect
from typing import Optional
from uuid import uuid4

from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import MappedAsDataclass


class Mixin:
    create_user: Mapped[int] = mapped_column()
    update_user: Mapped[Optional[int]] = mapped_column(default=None, init=False)


class Base(DeclarativeBase, MappedAsDataclass):
    pass


class User(Base, Mixin):
    __tablename__ = "sys_user"

    uid: Mapped[str] = mapped_column(
        String(50), init=False, default_factory=uuid4, primary_key=True
    )
    username: Mapped[str] = mapped_column()
    email: Mapped[str] = mapped_column()

Da Mixin selbst nicht von MappedAsDataclass erbt, wird die folgende Warnung generiert:

SADeprecationWarning: When transforming <class '__main__.User'> to a
dataclass, attribute(s) "create_user", "update_user" originates from
superclass <class
'__main__.Mixin'>, which is not a dataclass. This usage is deprecated and
will raise an error in SQLAlchemy 2.1. When declaring SQLAlchemy
Declarative Dataclasses, ensure that all mixin classes and other
superclasses which include attributes are also a subclass of
MappedAsDataclass.

Die Lösung besteht darin, MappedAsDataclass auch zur Signatur von Mixin hinzuzufügen.

class Mixin(MappedAsDataclass):
    create_user: Mapped[int] = mapped_column()
    update_user: Mapped[Optional[int]] = mapped_column(default=None, init=False)

Die PEP 681-Spezifikation von Python unterstützt keine Attribute, die in Oberklassen von Dataclasses deklariert sind, die selbst keine Dataclasses sind; gemäß dem Verhalten von Python-Dataclasses werden solche Felder ignoriert, wie im folgenden Beispiel:

from dataclasses import dataclass
from dataclasses import field
import inspect
from typing import Optional
from uuid import uuid4


class Mixin:
    create_user: int
    update_user: Optional[int] = field(default=None)


@dataclass
class User(Mixin):
    uid: str = field(init=False, default_factory=lambda: str(uuid4()))
    username: str
    password: str
    email: str

Oben wird die Klasse User create_user nicht in ihren Konstruktor aufnehmen und auch nicht versuchen, update_user als Dataclass-Attribut zu interpretieren. Dies liegt daran, dass Mixin keine Dataclass ist.

SQLAlchemy's Dataclasses-Funktion in der 2.0-Serie honoriert dieses Verhalten nicht korrekt; stattdessen werden Attribute in Nicht-Dataclass-Mixins und Oberklassen als Teil der endgültigen Dataclass-Konfiguration behandelt. Typ-Checker wie Pyright und Mypy werden diese Felder jedoch nicht als Teil des Dataclass-Konstruktors betrachten, da sie gemäß PEP 681 ignoriert werden sollen. Da ihre Anwesenheit ansonsten mehrdeutig ist, verlangt SQLAlchemy 2.1, dass Mixin-Klassen mit SQLAlchemy-abgebildeten Attributen innerhalb einer Dataclass-Hierarchie selbst Dataclasses sein müssen.

Python-Dataclass-Fehler beim Erstellen einer Dataclass für <classname>

Bei Verwendung der Mixin-Klasse MappedAsDataclass oder des Dekorators registry.mapped_as_dataclass() verwendet SQLAlchemy das tatsächliche Python-Dataclasses-Modul aus der Python-Standardbibliothek, um Dataclass-Verhalten auf die Zielklasse anzuwenden. Diese API hat ihre eigenen Fehlerszenarien, von denen die meisten die Konstruktion einer `__init__()`-Methode auf der benutzerdefinierten Klasse betreffen; die Reihenfolge der auf der Klasse deklarierten Attribute sowie auf Oberklassen bestimmt, wie die `__init__()`-Methode konstruiert wird, und es gibt spezifische Regeln für die Organisation der Attribute sowie deren Verwendung von Parametern wie `init=False`, `kw_only=True` usw. **SQLAlchemy kontrolliert oder implementiert diese Regeln nicht**. Konsultieren Sie daher für Fehler dieser Art die Python-Dataclasses-Dokumentation, mit besonderem Augenmerk auf die Regeln, die für Vererbung gelten.

Siehe auch

Declarative Dataclass Mapping - SQLAlchemy-Dataclasses-Dokumentation

Python-Dataclasses - auf der python.org-Website

inheritance - auf der python.org-Website

Per-Row ORM Bulk Update nach Primärschlüssel erfordert, dass Datensätze Primärschlüsselwerte enthalten

Dieser Fehler tritt auf, wenn die Funktion ORM Bulk UPDATE nach Primärschlüssel verwendet wird, ohne Primärschlüsselwerte in den gegebenen Datensätzen anzugeben, wie z. B.:

>>> session.execute(
...     update(User).where(User.name == bindparam("u_name")),
...     [
...         {"u_name": "spongebob", "fullname": "Spongebob Squarepants"},
...         {"u_name": "patrick", "fullname": "Patrick Star"},
...     ],
... )

Das Vorhandensein einer Liste von Parameterwörterbüchern in Kombination mit der Verwendung der Session zum Ausführen einer ORM-fähigen UPDATE-Anweisung verwendet automatisch ORM Bulk Update nach Primärschlüssel, das erwartet, dass Parameterwörterbücher Primärschlüsselwerte enthalten, z. B.:

>>> session.execute(
...     update(User),
...     [
...         {"id": 1, "fullname": "Spongebob Squarepants"},
...         {"id": 3, "fullname": "Patrick Star"},
...         {"id": 5, "fullname": "Eugene H. Krabs"},
...     ],
... )

Um die UPDATE-Anweisung ohne Angabe von Pro-Datensatz-Primärschlüsselwerten aufzurufen, verwenden Sie Session.connection(), um die aktuelle Connection zu erwerben, und rufen Sie dann damit auf:

>>> session.connection().execute(
...     update(User).where(User.name == bindparam("u_name")),
...     [
...         {"u_name": "spongebob", "fullname": "Spongebob Squarepants"},
...         {"u_name": "patrick", "fullname": "Patrick Star"},
...     ],
... )

AsyncIO-Ausnahmen

AwaitRequired

Der SQLAlchemy-Async-Modus erfordert die Verwendung eines asynchronen Treibers, um eine Verbindung zur Datenbank herzustellen. Dieser Fehler wird normalerweise ausgelöst, wenn versucht wird, die asynchrone Version von SQLAlchemy mit einem nicht kompatiblen DBAPI zu verwenden.

MissingGreenlet

Ein Aufruf des asynchronen DBAPI wurde außerhalb des von den SQLAlchemy AsyncIO-Proxy-Klassen eingerichteten Greenlet-Spawn-Kontexts initiiert. Normalerweise tritt dieser Fehler auf, wenn E/A an einer unerwarteten Stelle versucht wurde, unter Verwendung eines Aufruf-Musters, das keine direkte Verwendung des `await`-Schlüsselworts vorsieht. Bei Verwendung des ORM ist dies fast immer auf die Verwendung von Lazy Loading zurückzuführen, das ohne zusätzliche Schritte und/oder alternative Loader-Muster unter asyncio nicht direkt unterstützt wird, um es erfolgreich nutzen zu können.

Siehe auch

Implizite E/A bei Verwendung von AsyncSession vermeiden - behandelt die meisten ORM-Szenarien, in denen dieses Problem auftreten kann und wie es gemindert werden kann, einschließlich spezifischer Muster für Lazy Load-Szenarien.

Keine Inspektion verfügbar

Die direkte Verwendung der Funktion inspect() für ein AsyncConnection- oder AsyncEngine-Objekt wird derzeit nicht unterstützt, da noch keine awaitable Form des Inspector-Objekts verfügbar ist. Stattdessen wird das Objekt durch den Erwerb über die Funktion inspect() so verwendet, dass es auf das zugrunde liegende Attribut AsyncConnection.sync_connection des AsyncConnection-Objekts verweist; der Inspector wird dann in einem "synchronen" Aufrufstil verwendet, indem die Methode AsyncConnection.run_sync() zusammen mit einer benutzerdefinierten Funktion verwendet wird, die die gewünschten Operationen ausführt.

async def async_main():
    async with engine.connect() as conn:
        tables = await conn.run_sync(
            lambda sync_conn: inspect(sync_conn).get_table_names()
        )

Siehe auch

Verwendung des Inspectors zur Inspektion von Schemainjekten - zusätzliche Beispiele für die Verwendung von inspect() mit der asyncio-Erweiterung.

Core-Ausnahmeklassen

Siehe Core Exceptions für Core-Ausnahmeklassen.

ORM-Ausnahmeklassen

Siehe ORM Exceptions für ORM-Ausnahmeklassen.

Veraltete Ausnahmen

Ausnahmen in diesem Abschnitt werden nicht von aktuellen SQLAlchemy-Versionen generiert, sind aber hier aufgeführt, um Hyperlinks zu Ausnahmemeldungen zu ermöglichen.

Die Funktion <some function> in SQLAlchemy 2.0 wird <something> nicht mehr

SQLAlchemy 2.0 stellt einen großen Umbruch für eine Vielzahl von Schlüsselmustern der SQLAlchemy-Nutzung sowohl in den Core- als auch in den ORM-Komponenten dar. Ziel der 2.0-Version ist es, einige der grundlegendsten Annahmen von SQLAlchemy seit seinen Anfängen leicht anzupassen und ein neu gestrafftes Nutzungsmodell zu liefern, das hoffentlich deutlich minimalistischer und konsistenter zwischen den Core- und ORM-Komponenten sowie leistungsfähiger ist.

Eingeführt in SQLAlchemy 2.0 - Major Migration Guide, enthält das SQLAlchemy 2.0-Projekt ein umfassendes System zur zukünftigen Kompatibilität, das in die 1.4er-Serie von SQLAlchemy integriert ist, sodass Anwendungen einen klaren, eindeutigen und inkrementellen Upgrade-Pfad haben, um Anwendungen vollständig mit 2.0 kompatibel zu machen. Die Deprecation-Warnung RemovedIn20Warning steht am Anfang dieses Systems, um Anleitungen zu geben, welche Verhaltensweisen in einer bestehenden Codebasis geändert werden müssen. Eine Übersicht, wie diese Warnung aktiviert wird, finden Sie unter SQLAlchemy 2.0 Deprecations Mode.

Siehe auch

SQLAlchemy 2.0 - Major Migration Guide - Eine Übersicht über den Upgrade-Prozess aus der 1.x-Serie sowie die aktuellen Ziele und Fortschritte von SQLAlchemy 2.0.

SQLAlchemy 2.0 Deprecations Mode - Spezifische Richtlinien zur Verwendung des „2.0 Deprecations Mode“ in SQLAlchemy 1.4.

Objekt wird zusammen mit dem Backref-Cascade in eine Session gemerged

Diese Meldung bezieht sich auf das „Backref-Cascade“-Verhalten von SQLAlchemy, das in Version 2.0 entfernt wurde. Dies bezieht sich auf die Aktion, dass ein Objekt in eine Session aufgenommen wird, als Ergebnis eines anderen Objekts, das sich bereits in dieser Session befindet und mit ihm verbunden ist. Da sich dieses Verhalten als verwirrender als hilfreich erwiesen hat, wurden die Parameter relationship.cascade_backrefs und backref.cascade_backrefs hinzugefügt, die auf False gesetzt werden können, um sie zu deaktivieren. In SQLAlchemy 2.0 wurde das Verhalten „cascade backrefs“ vollständig entfernt.

Für ältere SQLAlchemy-Versionen muss zum Setzen von relationship.cascade_backrefs auf False bei einem Backref, der derzeit mit dem String-Parameter relationship.backref konfiguriert ist, der Backref zuerst mit der Funktion backref() deklariert werden, damit der Parameter backref.cascade_backrefs übergeben werden kann.

Alternativ kann das gesamte „cascade backrefs“-Verhalten pauschal deaktiviert werden, indem die Session im „future“-Modus verwendet wird, indem True für den Parameter Session.future übergeben wird.

Siehe auch

cascade_backrefs-Verhalten für die Entfernung in 2.0 als veraltet markiert - Hintergrund zur Änderung für SQLAlchemy 2.0.

select()-Konstrukt im „legacy“-Modus erstellt; Schlüsselwortargumente usw.

Das Konstrukt select() wurde ab SQLAlchemy 1.4 aktualisiert, um den neueren Aufrufstil zu unterstützen, der in SQLAlchemy 2.0 Standard ist. Zur Abwärtskompatibilität innerhalb der 1.4er-Serie akzeptiert das Konstrukt Argumente sowohl im „legacy“-Stil als auch im „neuen“ Stil.

Der „neue“ Stil zeichnet sich dadurch aus, dass Spalten- und Tabellenausdrücke nur positionell an das Konstrukt select() übergeben werden; alle anderen Modifikatoren des Objekts müssen durch anschließendes Method Chaining übergeben werden.

# this is the way to do it going forward
stmt = select(table1.c.myid).where(table1.c.myid == table2.c.otherid)

Zum Vergleich würde ein select() in Legacy-Formen von SQLAlchemy, bevor Methoden wie Select.where() überhaupt hinzugefügt wurden, so aussehen

# this is how it was documented in original SQLAlchemy versions
# many years ago
stmt = select([table1.c.myid], whereclause=table1.c.myid == table2.c.otherid)

Oder sogar, dass die „whereclause“ positionell übergeben würde

# this is also how it was documented in original SQLAlchemy versions
# many years ago
stmt = select([table1.c.myid], table1.c.myid == table2.c.otherid)

Seit einigen Jahren wurden die zusätzlichen „whereclause“- und anderen akzeptierten Argumente aus den meisten narrativen Dokumentationen entfernt, was zu einem Aufrufstil führte, der am vertrautesten ist als die Liste der Spaltenargumente, die als Liste übergeben werden, aber keine weiteren Argumente.

# this is how it's been documented since around version 1.0 or so
stmt = select([table1.c.myid]).where(table1.c.myid == table2.c.otherid)

Das Dokument unter select() akzeptiert keine unterschiedlichen Konstruktorargumente mehr, Spalten werden positionell übergeben beschreibt diese Änderung im Kontext von 2.0 Migration.

Ein Bind wurde über Legacy-gebundene Metadaten gefunden, aber da future=True auf dieser Session gesetzt ist, wird dieses Bind ignoriert.

Das Konzept der „gebundenen Metadaten“ ist bis SQLAlchemy 1.4 vorhanden; ab SQLAlchemy 2.0 wurde es entfernt.

Diese Fehlermeldung bezieht sich auf den Parameter MetaData.bind des MetaData-Objekts, das wiederum Objekten wie der ORM-Session erlaubt, eine bestimmte gemappte Klasse mit einer Engine zu assoziieren. In SQLAlchemy 2.0 muss die Session direkt mit jeder Engine verknüpft werden. Das heißt, anstatt die Session oder sessionmaker ohne Argumente zu instanziieren und die Engine mit den MetaData zu assoziieren

engine = create_engine("sqlite://")
Session = sessionmaker()
metadata_obj = MetaData(bind=engine)
Base = declarative_base(metadata=metadata_obj)


class MyClass(Base): ...


session = Session()
session.add(MyClass())
session.commit()

Die Engine muss stattdessen direkt mit dem sessionmaker oder der Session assoziiert werden. Das MetaData-Objekt sollte nicht mehr mit einer Engine assoziiert werden.

engine = create_engine("sqlite://")
Session = sessionmaker(engine)
Base = declarative_base()


class MyClass(Base): ...


session = Session()
session.add(MyClass())
session.commit()

In SQLAlchemy 1.4 wird dieses 2.0-Stil-Verhalten aktiviert, wenn das Flag Session.future auf sessionmaker oder Session gesetzt wird.

Dieses Compiled-Objekt ist an keine Engine oder Verbindung gebunden

Diese Fehlermeldung bezieht sich auf das Konzept der „gebundenen Metadaten“, ein Legacy-SQLAlchemy-Muster, das nur in 1.x-Versionen vorhanden ist. Das Problem tritt auf, wenn die Methode Executable.execute() direkt von einem Core-Ausdrucksobjekt aufgerufen wird, das mit keiner Engine assoziiert ist.

metadata_obj = MetaData()
table = Table("t", metadata_obj, Column("q", Integer))

stmt = select(table)
result = stmt.execute()  # <--- raises

Die Logik erwartet, dass das MetaData-Objekt an eine Engine **gebunden** ist.

engine = create_engine("mysql+pymysql://user:pass@host/db")
metadata_obj = MetaData(bind=engine)

Während oben jede Anweisung, die von einer Table abgeleitet wird, die wiederum von diesem MetaData abgeleitet wird, implizit die gegebene Engine zur Ausführung der Anweisung verwendet.

Beachten Sie, dass das Konzept der gebundenen Metadaten **in SQLAlchemy 2.0 nicht vorhanden** ist. Der richtige Weg zur Ausführung von Anweisungen ist über die Methode Connection.execute() einer Connection.

with engine.connect() as conn:
    result = conn.execute(stmt)

Bei Verwendung des ORM ist eine ähnliche Einrichtung über die Session verfügbar.

result = session.execute(stmt)

Diese Verbindung befindet sich in einer inaktiven Transaktion. Bitte führen Sie zuerst ein rollback() aus, bevor Sie fortfahren.

Diese Fehlerbedingung wurde in SQLAlchemy ab Version 1.4 hinzugefügt und gilt nicht für SQLAlchemy 2.0. Die Fehlermeldung bezieht sich auf den Zustand, in dem eine Connection mit einer Methode wie Connection.begin() in eine Transaktion versetzt wird und dann eine weitere „Marker“-Transaktion innerhalb dieses Geltungsbereichs erstellt wird; die „Marker“-Transaktion wird dann mit Transaction.rollback() zurückgerollt oder mit Transaction.close() geschlossen, jedoch ist die äußere Transaktion noch in einem „inaktiven“ Zustand vorhanden und muss zurückgerollt werden.

Das Muster sieht so aus

engine = create_engine(...)

connection = engine.connect()
transaction1 = connection.begin()

# this is a "sub" or "marker" transaction, a logical nesting
# structure based on "real" transaction transaction1
transaction2 = connection.begin()
transaction2.rollback()

# transaction1 is still present and needs explicit rollback,
# so this will raise
connection.execute(text("select 1"))

Oben ist transaction2 eine „Marker“-Transaktion, die eine logische Verschachtelung von Transaktionen innerhalb einer äußeren Transaktion anzeigt; während die innere Transaktion die gesamte Transaktion über ihre rollback()-Methode zurückrollen kann, hat ihre commit()-Methode keine andere Auswirkung, als den Geltungsbereich der „Marker“-Transaktion selbst zu schließen. Der Aufruf von transaction2.rollback() hat die Auswirkung, dass transaction1 **deaktiviert** wird, was bedeutet, dass sie auf Datenbankebene im Wesentlichen zurückgerollt wird, aber immer noch vorhanden ist, um ein konsistentes Verschachtelungsmuster von Transaktionen zu ermöglichen.

Die korrekte Auflösung besteht darin, sicherzustellen, dass auch die äußere Transaktion zurückgerollt wird.

transaction1.rollback()

Dieses Muster wird in Core nicht häufig verwendet. Innerhalb des ORM kann ein ähnliches Problem auftreten, das aus der „logischen“ Transaktionsstruktur des ORM resultiert; dies wird im FAQ-Eintrag unter „Diese Transaktion der Session wurde aufgrund einer vorherigen Ausnahme während des Flushs zurückgerollt.“ (oder ähnlich) beschrieben.

Das „Subtransaktion“-Muster wird in SQLAlchemy 2.0 entfernt, sodass dieses spezielle Programmiermuster nicht mehr verfügbar ist und diese Fehlermeldung verhindert.