Was ist neu in SQLAlchemy 0.8?

Über dieses Dokument

Dieses Dokument beschreibt die Änderungen zwischen SQLAlchemy Version 0.7, die als Wartungsversionen im Oktober 2012 veröffentlicht wurde, und SQLAlchemy Version 0.8, die für Anfang 2013 erwartet wird.

Dokumentdatum: 25. Oktober 2012 Aktualisiert: 9. März 2013

Einleitung

Diese Anleitung stellt die Neuerungen in SQLAlchemy Version 0.8 vor und dokumentiert auch Änderungen, die Benutzer beträfen, die ihre Anwendungen von der 0.7er-Reihe von SQLAlchemy auf 0.8 migrieren.

SQLAlchemy-Versionen nähern sich der 1.0. und jede neue Version seit 0.5 weist weniger größere Nutzungsänderungen auf. Die meisten Anwendungen, die sich an modernen 0.7er-Mustern orientieren, sollten ohne Änderungen auf 0.8 umstellbar sein. Anwendungen, die 0.6er und sogar 0.5er Muster verwenden, sollten ebenfalls direkt auf 0.8 migrierbar sein, obwohl größere Anwendungen möglicherweise mit jeder Zwischenversion testen möchten.

Plattformunterstützung

Zielgruppe: Python 2.5 und höher

SQLAlchemy 0.8 zielt auf Python 2.5 und höher ab; die Kompatibilität für Python 2.4 wird gestrichen.

Die Interna werden Python-Ternaries nutzen können (d. h. x if y else z), was die Nutzung von y and x or z verbessert, was natürlich die Quelle einiger Fehler war, sowie Kontextmanager (d. h. with:) und möglicherweise in einigen Fällen try:/except:/else:-Blöcke, die zur Lesbarkeit des Codes beitragen.

SQLAlchemy wird schließlich auch die 2.5er-Unterstützung einstellen – wenn 2.6 als Basis erreicht wird, wird SQLAlchemy zur In-Place-Kompatibilität mit 2.6/3.3 wechseln, die Nutzung des 2to3-Tools entfernen und eine Quellbasis beibehalten, die gleichzeitig mit Python 2 und 3 funktioniert.

Neue ORM-Funktionen

Neu geschriebene relationship()-Mechanismen

0.8 bietet ein wesentlich verbessertes und leistungsfähigeres System für die Bestimmung, wie relationship() zwischen zwei Entitäten verbinden soll. Das neue System umfasst folgende Funktionen

  • Das Argument primaryjoin ist bei der Konstruktion einer relationship() zu einer Klasse, die mehrere Fremdschlüsselpfade zum Ziel hat, **nicht mehr erforderlich**. Nur das Argument foreign_keys wird benötigt, um die Spalten anzugeben, die einbezogen werden sollen.

    class Parent(Base):
        __tablename__ = "parent"
        id = Column(Integer, primary_key=True)
        child_id_one = Column(Integer, ForeignKey("child.id"))
        child_id_two = Column(Integer, ForeignKey("child.id"))
    
        child_one = relationship("Child", foreign_keys=child_id_one)
        child_two = relationship("Child", foreign_keys=child_id_two)
    
    
    class Child(Base):
        __tablename__ = "child"
        id = Column(Integer, primary_key=True)
  • Beziehungen zu selbstbezüglichen, zusammengesetzten Fremdschlüsseln, bei denen **eine Spalte auf sich selbst verweist**, werden jetzt unterstützt. Der kanonische Fall ist wie folgt

    class Folder(Base):
        __tablename__ = "folder"
        __table_args__ = (
            ForeignKeyConstraint(
                ["account_id", "parent_id"], ["folder.account_id", "folder.folder_id"]
            ),
        )
    
        account_id = Column(Integer, primary_key=True)
        folder_id = Column(Integer, primary_key=True)
        parent_id = Column(Integer)
        name = Column(String)
    
        parent_folder = relationship(
            "Folder", backref="child_folders", remote_side=[account_id, folder_id]
        )

    Oben bezieht sich die Folder auf ihre übergeordnete Folder und verbindet account_id mit sich selbst und parent_id mit folder_id. Wenn SQLAlchemy einen automatischen Join erstellt, kann es nicht mehr davon ausgehen, dass alle Spalten auf der „entfernten“ Seite aliasisiert sind und alle Spalten auf der „lokalen“ Seite nicht – die Spalte account_id ist **auf beiden Seiten** vorhanden. Daher wurden die internen Relationship-Mechanismen komplett neu geschrieben, um ein völlig anderes System zu unterstützen, bei dem zwei Kopien von account_id generiert werden, die jeweils unterschiedliche *Annotationen* enthalten, um ihre Rolle innerhalb der Anweisung zu bestimmen. Beachten Sie die Join-Bedingung in einem einfachen Eager Load

    SELECT
        folder.account_id AS folder_account_id,
        folder.folder_id AS folder_folder_id,
        folder.parent_id AS folder_parent_id,
        folder.name AS folder_name,
        folder_1.account_id AS folder_1_account_id,
        folder_1.folder_id AS folder_1_folder_id,
        folder_1.parent_id AS folder_1_parent_id,
        folder_1.name AS folder_1_name
    FROM folder
        LEFT OUTER JOIN folder AS folder_1
        ON
            folder_1.account_id = folder.account_id
            AND folder.folder_id = folder_1.parent_id
    
    WHERE folder.folder_id = ? AND folder.account_id = ?
  • Zuvor schwierige benutzerdefinierte Join-Bedingungen, wie solche, die Funktionen und/oder CASTs von Datentypen umfassen, funktionieren jetzt in den meisten Fällen wie erwartet.

    class HostEntry(Base):
        __tablename__ = "host_entry"
    
        id = Column(Integer, primary_key=True)
        ip_address = Column(INET)
        content = Column(String(50))
    
        # relationship() using explicit foreign_keys, remote_side
        parent_host = relationship(
            "HostEntry",
            primaryjoin=ip_address == cast(content, INET),
            foreign_keys=content,
            remote_side=ip_address,
        )

    Die neuen relationship()-Mechanismen nutzen ein von SQLAlchemy bekanntes Konzept namens Annotationen. Diese Annotationen sind auch für Anwendungscode explizit über die Funktionen foreign() und remote() verfügbar, entweder zur Verbesserung der Lesbarkeit für fortgeschrittene Konfigurationen oder zur direkten Einspeisung einer exakten Konfiguration, wodurch die üblichen Join-Inspektionsheuristiken umgangen werden.

    from sqlalchemy.orm import foreign, remote
    
    
    class HostEntry(Base):
        __tablename__ = "host_entry"
    
        id = Column(Integer, primary_key=True)
        ip_address = Column(INET)
        content = Column(String(50))
    
        # relationship() using explicit foreign() and remote() annotations
        # in lieu of separate arguments
        parent_host = relationship(
            "HostEntry",
            primaryjoin=remote(ip_address) == cast(foreign(content), INET),
        )

Siehe auch

Konfigurieren von Relationship-Joins – ein neu überarbeiteter Abschnitt zu relationship(), der die neuesten Techniken zur Anpassung verwandter Attribute und Sammlungszugriffe beschreibt.

#1401 #610

Neues System zur Inspektion von Klassen/Objekten

Viele SQLAlchemy-Benutzer schreiben Systeme, die die Fähigkeit erfordern, die Attribute einer zugeordneten Klasse zu inspizieren, einschließlich des Zugriffs auf Primärschlüsselspalten, Objektbeziehungen, einfache Attribute usw., typischerweise zum Zweck des Erstellens von Datenmarshalling-Systemen wie JSON/XML-Konvertierungsschemata und natürlich einer Vielzahl von Formularbibliotheken.

Ursprünglich waren das Table- und Column-Modell die ursprünglichen Inspektionpunkte, die ein gut dokumentiertes System haben. Während SQLAlchemy ORM-Modelle ebenfalls vollständig introspektierbar sind, war dies nie eine vollständig stabile und unterstützte Funktion, und Benutzer hatten tendenziell keine klare Vorstellung davon, wie sie an diese Informationen gelangen sollten.

0.8 bietet jetzt eine konsistente, stabile und vollständig dokumentierte API für diesen Zweck, einschließlich eines Inspektionssystems, das auf zugeordneten Klassen, Instanzen, Attributen und anderen Core- und ORM-Konstrukten funktioniert. Der Einstiegspunkt zu diesem System ist die Core-Funktion inspect(). In den meisten Fällen ist das zu inspizierende Objekt eines, das bereits Teil des SQLAlchemy-Systems ist, wie Mapper, InstanceState, Inspector. In einigen Fällen wurden neue Objekte hinzugefügt, die die Aufgabe haben, die Inspektions-API in bestimmten Kontexten bereitzustellen, wie z. B. AliasedInsp und AttributeState.

Ein Überblick über einige Schlüsselfunktionen folgt

>>> class User(Base):
...     __tablename__ = "user"
...     id = Column(Integer, primary_key=True)
...     name = Column(String)
...     name_syn = synonym(name)
...     addresses = relationship("Address")

>>> # universal entry point is inspect()
>>> b = inspect(User)

>>> # b in this case is the Mapper
>>> b
<Mapper at 0x101521950; User>

>>> # Column namespace
>>> b.columns.id
Column('id', Integer(), table=<user>, primary_key=True, nullable=False)

>>> # mapper's perspective of the primary key
>>> b.primary_key
(Column('id', Integer(), table=<user>, primary_key=True, nullable=False),)

>>> # MapperProperties available from .attrs
>>> b.attrs.keys()
['name_syn', 'addresses', 'id', 'name']

>>> # .column_attrs, .relationships, etc. filter this collection
>>> b.column_attrs.keys()
['id', 'name']

>>> list(b.relationships)
[<sqlalchemy.orm.properties.RelationshipProperty object at 0x1015212d0>]

>>> # they are also namespaces
>>> b.column_attrs.id
<sqlalchemy.orm.properties.ColumnProperty object at 0x101525090>

>>> b.relationships.addresses
<sqlalchemy.orm.properties.RelationshipProperty object at 0x1015212d0>

>>> # point inspect() at a mapped, class level attribute,
>>> # returns the attribute itself
>>> b = inspect(User.addresses)
>>> b
<sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x101521fd0>

>>> # From here we can get the mapper:
>>> b.mapper
<Mapper at 0x101525810; Address>

>>> # the parent inspector, in this case a mapper
>>> b.parent
<Mapper at 0x101521950; User>

>>> # an expression
>>> print(b.expression)
"user".id = address.user_id
>>> # inspect works on instances >>> u1 = User(id=3, name="x") >>> b = inspect(u1) >>> # it returns the InstanceState >>> b <sqlalchemy.orm.state.InstanceState object at 0x10152bed0> >>> # similar attrs accessor refers to the >>> b.attrs.keys() ['id', 'name_syn', 'addresses', 'name'] >>> # attribute interface - from attrs, you get a state object >>> b.attrs.id <sqlalchemy.orm.state.AttributeState object at 0x10152bf90> >>> # this object can give you, current value... >>> b.attrs.id.value 3 >>> # ... current history >>> b.attrs.id.history History(added=[3], unchanged=(), deleted=()) >>> # InstanceState can also provide session state information >>> # lets assume the object is persistent >>> s = Session() >>> s.add(u1) >>> s.commit() >>> # now we can get primary key identity, always >>> # works in query.get() >>> b.identity (3,) >>> # the mapper level key >>> b.identity_key (<class '__main__.User'>, (3,)) >>> # state within the session >>> b.persistent, b.transient, b.deleted, b.detached (True, False, False, False) >>> # owning session >>> b.session <sqlalchemy.orm.session.Session object at 0x101701150>

#2208

Neue with_polymorphic()-Funktion, überall verwendbar

Die Methode Query.with_polymorphic() ermöglicht dem Benutzer die Angabe, welche Tabellen beim Abfragen einer Joined-Table-Entität vorhanden sein sollen. Leider ist die Methode umständlich und gilt nur für die erste Entität in der Liste und weist ansonsten umständliche Verhaltensweisen sowohl in der Anwendung als auch in den Interna auf. Eine neue Erweiterung für den aliased()-Konstrukt wurde hinzugefügt, namens with_polymorphic(), die es jeder Entität ermöglicht, in eine „polymorphe“ Version von sich selbst „aliasisiert“ zu werden, frei überall verwendbar.

from sqlalchemy.orm import with_polymorphic

palias = with_polymorphic(Person, [Engineer, Manager])
session.query(Company).join(palias, Company.employees).filter(
    or_(Engineer.language == "java", Manager.hair == "pointy")
)

Siehe auch

Verwendung von with_polymorphic() – neu aktualisierte Dokumentation für die polymorphe Ladekontrolle.

#2333

of_type() funktioniert mit alias(), with_polymorphic(), any(), has(), joinedload(), subqueryload(), contains_eager()

Die Methode PropComparator.of_type() wird verwendet, um einen bestimmten Subtyp anzugeben, der beim Erstellen von SQL-Ausdrücken entlang einer relationship() verwendet werden soll, die eine polymorphe Zuordnung als Ziel hat. Diese Methode kann jetzt eine beliebige Anzahl von Ziel-Subtypen adressieren, indem sie mit der neuen Funktion with_polymorphic() kombiniert wird.

# use eager loading in conjunction with with_polymorphic targets
Job_P = with_polymorphic(Job, [SubJob, ExtraJob], aliased=True)
q = (
    s.query(DataContainer)
    .join(DataContainer.jobs.of_type(Job_P))
    .options(contains_eager(DataContainer.jobs.of_type(Job_P)))
)

Die Methode funktioniert jetzt genauso gut an den meisten Stellen, an denen ein reguläres Beziehungattribut akzeptiert wird, einschließlich mit Loader-Funktionen wie joinedload(), subqueryload(), contains_eager(), und Vergleichsmethoden wie PropComparator.any() und PropComparator.has()

# use eager loading in conjunction with with_polymorphic targets
Job_P = with_polymorphic(Job, [SubJob, ExtraJob], aliased=True)
q = (
    s.query(DataContainer)
    .join(DataContainer.jobs.of_type(Job_P))
    .options(contains_eager(DataContainer.jobs.of_type(Job_P)))
)

# pass subclasses to eager loads (implicitly applies with_polymorphic)
q = s.query(ParentThing).options(
    joinedload_all(ParentThing.container, DataContainer.jobs.of_type(SubJob))
)

# control self-referential aliasing with any()/has()
Job_A = aliased(Job)
q = (
    s.query(Job)
    .join(DataContainer.jobs)
    .filter(
        DataContainer.jobs.of_type(Job_A).any(
            and_(Job_A.id < Job.id, Job_A.type == "fred")
        )
    )
)

#2438 #1106

Ereignisse können auf nicht zugeordnete Oberklassen angewendet werden

Mapper- und Instanzeignisse können jetzt mit einer nicht zugeordneten Oberklasse verknüpft werden, wobei diese Ereignisse auf Unterklassen propagiert werden, sobald diese zugeordnet sind. Das Flag propagate=True sollte verwendet werden. Diese Funktion ermöglicht die Verknüpfung von Ereignissen mit einer deklarativen Basisklasse.

from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


@event.listens_for("load", Base, propagate=True)
def on_load(target, context):
    print("New instance loaded:", target)


# on_load() will be applied to SomeClass
class SomeClass(Base):
    __tablename__ = "sometable"

    # ...

#2585

Deklarative unterscheidet zwischen Modulen/Paketen

Eine Schlüsselfunktion von Declarative ist die Möglichkeit, auf andere zugeordnete Klassen über deren Namen als String zu verweisen. Die Registrierung von Klassennamen ist nun empfindlich für das besitzende Modul und Paket einer gegebenen Klasse. Die Klassen können über den Punktnamen in Ausdrücken referenziert werden.

class Snack(Base):
    # ...

    peanuts = relationship(
        "nuts.Peanut", primaryjoin="nuts.Peanut.snack_id == Snack.id"
    )

Die Auflösung ermöglicht, dass jeder vollständige oder teilweise auflösende Paketname verwendet werden kann. Wenn der Pfad zu einer bestimmten Klasse immer noch mehrdeutig ist, wird ein Fehler ausgelöst.

#2338

Neue DeferredReflection-Funktion in Declarative

Das Beispiel „deferred reflection“ wurde in eine unterstützte Funktion innerhalb von Declarative verschoben. Diese Funktion ermöglicht die Erstellung von deklarativen zugeordneten Klassen mit nur Platzhalter-Table-Metadaten, bis ein prepare()-Schritt aufgerufen wird, der mit einer Engine versorgt wird, um alle Tabellen vollständig zu spiegeln und tatsächliche Zuordnungen herzustellen. Das System unterstützt das Überschreiben von Spalten, Single- und Joined-Vererbung sowie unterschiedliche Basen pro Engine. Eine vollständige deklarative Konfiguration kann nun gegen eine vorhandene Tabelle erstellt werden, die zum Zeitpunkt der Engine-Erstellung in einem Schritt zusammengestellt wird.

class ReflectedOne(DeferredReflection, Base):
    __abstract__ = True


class ReflectedTwo(DeferredReflection, Base):
    __abstract__ = True


class MyClass(ReflectedOne):
    __tablename__ = "mytable"


class MyOtherClass(ReflectedOne):
    __tablename__ = "myothertable"


class YetAnotherClass(ReflectedTwo):
    __tablename__ = "yetanothertable"


ReflectedOne.prepare(engine_one)
ReflectedTwo.prepare(engine_two)

Siehe auch

DeferredReflection

#2485

ORM-Klassen werden jetzt von Core-Konstrukten akzeptiert

Obwohl die SQL-Ausdrücke, die mit Query.filter() verwendet werden, wie z. B. User.id == 5, schon immer kompatibel für die Verwendung mit Core-Konstrukten wie select() waren, wurde die zugeordnete Klasse selbst nicht erkannt, wenn sie an select(), Select.select_from() oder Select.correlate() übergeben wurde. Ein neues SQL-Registrierungssystem ermöglicht, dass eine zugeordnete Klasse als FROM-Klausel im Core akzeptiert wird.

from sqlalchemy import select

stmt = select([User]).where(User.id == 5)

Oben erweitert die zugeordnete Klasse User die Table, der User zugeordnet ist.

#2245

Query.update() unterstützt UPDATE..FROM

Die neue UPDATE..FROM-Mechanik funktioniert in query.update(). Unten geben wir ein UPDATE gegen SomeEntity aus, fügen eine FROM-Klausel hinzu (oder Äquivalent, je nach Backend) gegen SomeOtherEntity.

query(SomeEntity).filter(SomeEntity.id == SomeOtherEntity.id).filter(
    SomeOtherEntity.foo == "bar"
).update({"data": "x"})

Insbesondere werden Updates für Joined-Inheritance-Entitäten unterstützt, vorausgesetzt, das Ziel des UPDATE ist lokal zur gefilterten Tabelle, oder wenn die Eltern- und Kindtabellen gemischt sind, werden sie explizit in der Abfrage verbunden. Unten, gegeben Engineer als Joined-Unterklasse von Person.

query(Engineer).filter(Person.id == Engineer.id).filter(
    Person.name == "dilbert"
).update({"engineer_data": "java"})

würde ergeben

UPDATE engineer SET engineer_data='java' FROM person
WHERE person.id=engineer.id AND person.name='dilbert'

#2365

rollback() rollt nur "schmutzige" Objekte von einem begin_nested() zurück

Eine Verhaltensänderung, die die Effizienz für Benutzer verbessern sollte, die SAVEPOINT über Session.begin_nested() verwenden – nach einem rollback() werden nur die Objekte, die seit dem letzten Flush schmutzig geworden sind, abgelaufen, der Rest der Session bleibt intakt. Dies liegt daran, dass ein ROLLBACK zu einem SAVEPOINT die Isolation der enthaltenden Transaktion nicht beendet, sodass keine Ablauffunktion erforderlich ist, außer für die Änderungen, die in der aktuellen Transaktion nicht geleert wurden.

#2452

Caching-Beispiel verwendet jetzt dogpile.cache

Das Caching-Beispiel verwendet jetzt dogpile.cache. Dogpile.cache ist eine Neufassung des Caching-Teils von Beaker, die eine wesentlich einfachere und schnellere Bedienung sowie Unterstützung für verteilte Sperren bietet.

Beachten Sie, dass sich die von Dogpile-Beispiel und auch vom vorherigen Beaker-Beispiel verwendeten SQLAlchemy-APIs leicht geändert haben, insbesondere ist diese Änderung erforderlich, wie im Beaker-Beispiel gezeigt.

--- examples/beaker_caching/caching_query.py
+++ examples/beaker_caching/caching_query.py
@@ -222,7 +222,8 @@

         """
         if query._current_path:
-            mapper, key = query._current_path[-2:]
+            mapper, prop = query._current_path[-2:]
+            key = prop.key

             for cls in mapper.class_.__mro__:
                 if (cls, key) in self._relationship_options:

Siehe auch

Dogpile Caching

#2589

Neue Core-Funktionen

Vollständig erweiterbare, typbasierte Operatorunterstützung in Core

Core hatte bisher kein System zum Hinzufügen von Unterstützung für neue SQL-Operatoren zu Column und anderen Ausdruckskonstrukten, außer der Methode ColumnOperators.op(), die „gerade genug“ ist, um Dinge zum Laufen zu bringen. Es gab auch nie ein System für Core, das es ermöglicht, das Verhalten bestehender Operatoren zu überschreiben. Bis jetzt war der einzige Weg, Operatoren flexibel neu zu definieren, in der ORM-Schicht, unter Verwendung von column_property() mit einem Argument comparator_factory. Drittanbieterbibliotheken wie GeoAlchemy waren daher gezwungen, ORM-zentriert zu sein und sich auf eine Reihe von Hacks zu verlassen, um neue Operationen anzuwenden und sie korrekt propagieren zu lassen.

Das neue Operatorsystem in Core fügt den fehlenden Haken hinzu, nämlich die Verknüpfung neuer und überschriebener Operatoren mit *Typen*. Denn letztendlich sind es nicht wirklich eine Spalte, ein CAST-Operator oder eine SQL-Funktion, die treiben, welche Arten von Operationen vorhanden sind, sondern der *Typ* des Ausdrucks. Die Implementierungsdetails sind minimal – nur ein paar zusätzliche Methoden werden zum Kern-ColumnElement-Typ hinzugefügt, damit dieser sein TypeEngine-Objekt für eine optionale Menge von Operatoren konsultiert. Neue oder überarbeitete Operationen können mit jedem Typ verknüpft werden, entweder durch Unterklassenbildung eines bestehenden Typs, durch Verwendung von TypeDecorator oder „global übergreifend“, indem ein neues Comparator-Objekt an eine bestehende Typklasse angehängt wird.

Zum Beispiel, um Logarithmusunterstützung für Numeric-Typen hinzuzufügen.

from sqlalchemy.types import Numeric
from sqlalchemy.sql import func


class CustomNumeric(Numeric):
    class comparator_factory(Numeric.Comparator):
        def log(self, other):
            return func.log(self.expr, other)

Der neue Typ ist wie jeder andere Typ verwendbar.

data = Table(
    "data",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("x", CustomNumeric(10, 5)),
    Column("y", CustomNumeric(10, 5)),
)

stmt = select([data.c.x.log(data.c.y)]).where(data.c.x.log(2) < value)
print(conn.execute(stmt).fetchall())

Neue Funktionen, die sich daraus unmittelbar ergeben, sind die Unterstützung für den PostgreSQL-HSTORE-Typ sowie neue Operationen für den PostgreSQL-ARRAY-Typ. Es ebnet auch den Weg dafür, dass bestehende Typen viele weitere, typspezifische Operatoren erhalten, wie z. B. weitere String-, Integer- und Datumsoperatoren.

#2547

Unterstützung für Multiple-VALUES bei Insert

Die Methode Insert.values() unterstützt jetzt eine Liste von Dictionaries, die eine Multi-VALUES-Anweisung rendern wird, wie z. B. VALUES (<row1>), (<row2>), .... Dies ist nur für Backends relevant, die diese Syntax unterstützen, einschließlich PostgreSQL, SQLite und MySQL. Es ist nicht dasselbe wie der übliche executemany()-Stil von INSERT, der unverändert bleibt.

users.insert().values(
    [
        {"name": "some name"},
        {"name": "some other name"},
        {"name": "yet another name"},
    ]
)

Siehe auch

Insert.values()

#2623

Typ-Ausdrücke

SQL-Ausdrücke können jetzt Typen zugeordnet werden. Historisch gesehen erlaubte TypeEngine schon immer Python-seitige Funktionen, die sowohl gebundene Parameter als auch Ergebniszeilenwerte erhalten und diese über eine Python-seitige Konvertierungsfunktion auf dem Weg zum/vom Datenbank durchlaufen lassen. Die neue Funktion ermöglicht ähnliche Funktionalität, nur eben auf der Datenbankseite.

from sqlalchemy.types import String
from sqlalchemy import func, Table, Column, MetaData


class LowerString(String):
    def bind_expression(self, bindvalue):
        return func.lower(bindvalue)

    def column_expression(self, col):
        return func.lower(col)


metadata = MetaData()
test_table = Table("test_table", metadata, Column("data", LowerString))

Oben definiert der Typ LowerString einen SQL-Ausdruck, der jedes Mal ausgegeben wird, wenn die Spalte test_table.c.data in der Spaltenklausel einer SELECT-Anweisung gerendert wird.

>>> print(select([test_table]).where(test_table.c.data == "HI"))
SELECT lower(test_table.data) AS data FROM test_table WHERE test_table.data = lower(:data_1)

Diese Funktion wird auch intensiv von der neuen Version von GeoAlchemy verwendet, um PostGIS-Ausdrücke basierend auf Typregeln inline in SQL einzubetten.

#1534

Core-Inspektionssystem

Die in Neues System zur Inspektion von Klassen/Objekten eingeführte Funktion inspect() gilt auch für Core. Angewendet auf eine Engine erzeugt sie ein Inspector-Objekt.

from sqlalchemy import inspect
from sqlalchemy import create_engine

engine = create_engine("postgresql://scott:tiger@localhost/test")
insp = inspect(engine)
print(insp.get_table_names())

Sie kann auch auf jedes ClauseElement angewendet werden, was das ClauseElement selbst zurückgibt, wie z. B. Table, Column, Select, etc. Dies ermöglicht eine reibungslose Zusammenarbeit zwischen Core- und ORM-Konstrukten.

Neue Methode Select.correlate_except()

select() hat jetzt eine Methode Select.correlate_except(), die „korreliert für alle FROM-Klauseln außer den angegebenen“ spezifiziert. Sie kann für Szenarien verwendet werden, in denen eine zugehörige Subquery normal korrelieren soll, außer gegen ein bestimmtes Ziel-Selectable.

class SnortEvent(Base):
    __tablename__ = "event"

    id = Column(Integer, primary_key=True)
    signature = Column(Integer, ForeignKey("signature.id"))

    signatures = relationship("Signature", lazy=False)


class Signature(Base):
    __tablename__ = "signature"

    id = Column(Integer, primary_key=True)

    sig_count = column_property(
        select([func.count("*")])
        .where(SnortEvent.signature == id)
        .correlate_except(SnortEvent)
    )

PostgreSQL HSTORE-Typ

Die Unterstützung für den PostgreSQL-Typ HSTORE ist jetzt als HSTORE verfügbar. Dieser Typ nutzt das neue Operatorsystem intensiv, um eine vollständige Palette von Operatoren für HSTORE-Typen bereitzustellen, einschließlich Indexzugriff, Verkettung und Enthaltungsfunktionen wie comparator_factory.has_key(), comparator_factory.has_any() und comparator_factory.matrix().

from sqlalchemy.dialects.postgresql import HSTORE

data = Table(
    "data_table",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("hstore_data", HSTORE),
)

engine.execute(select([data.c.hstore_data["some_key"]])).scalar()

engine.execute(select([data.c.hstore_data.matrix()])).scalar()

Siehe auch

HSTORE

hstore

#2606

Erweiterter PostgreSQL ARRAY-Typ

Der Typ ARRAY akzeptiert ein optionales „dimension“-Argument, das ihn auf eine feste Anzahl von Dimensionen festlegt und die Effizienz bei der Abfrage von Ergebnissen erheblich verbessert.

# old way, still works since PG supports N-dimensions per row:
Column("my_array", postgresql.ARRAY(Integer))

# new way, will render ARRAY with correct number of [] in DDL,
# will process binds and results more efficiently as we don't need
# to guess how many levels deep to go
Column("my_array", postgresql.ARRAY(Integer, dimensions=2))

Der Typ führt auch neue Operatoren ein, die das neue typspezifische Operator-Framework nutzen. Neue Operationen umfassen indexierten Zugriff.

result = conn.execute(select([mytable.c.arraycol[2]]))

Slice-Zugriff in SELECT

result = conn.execute(select([mytable.c.arraycol[2:4]]))

Slice-Updates in UPDATE

conn.execute(mytable.update().values({mytable.c.arraycol[2:3]: [7, 8]}))

Freistehende Array-Literale

>>> from sqlalchemy.dialects import postgresql
>>> conn.scalar(select([postgresql.array([1, 2]) + postgresql.array([3, 4, 5])]))
[1, 2, 3, 4, 5]

Array-Verkettung, bei der unten die rechte Seite [4, 5, 6] in ein Array-Literal umgewandelt wird.

select([mytable.c.arraycol + [4, 5, 6]])

Siehe auch

ARRAY

array

#2441

Neue, konfigurierbare DATE-, TIME-Typen für SQLite

SQLite hat keine eingebauten DATE-, TIME- oder DATETIME-Typen und bietet stattdessen eine gewisse Unterstützung für die Speicherung von Datums- und Zeitwerten entweder als Strings oder Ganzzahlen. Die Datums- und Zeit-Typen für SQLite werden in 0.8 verbessert, um sie wesentlich besser konfigurierbar hinsichtlich des spezifischen Formats zu machen, einschließlich der optionalen „Mikrosekunden“-Komponente und praktisch allem anderen.

Column("sometimestamp", sqlite.DATETIME(truncate_microseconds=True))
Column(
    "sometimestamp",
    sqlite.DATETIME(
        storage_format=(
            "%(year)04d%(month)02d%(day)02d"
            "%(hour)02d%(minute)02d%(second)02d%(microsecond)06d"
        ),
        regexp="(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{6})",
    ),
)
Column(
    "somedate",
    sqlite.DATE(
        storage_format="%(month)02d/%(day)02d/%(year)04d",
        regexp="(?P<month>\d+)/(?P<day>\d+)/(?P<year>\d+)",
    ),
)

Großer Dank geht an Nate Dub für das Sprinten hierauf auf der Pycon 2012.

Siehe auch

DATETIME

DATE

TIME

#2363

„COLLATE“ wird über alle Dialekte unterstützt; insbesondere MySQL, PostgreSQL, SQLite

Das Schlüsselwort „collate“, das lange vom MySQL-Dialekt akzeptiert wurde, ist nun in allen String-Typen etabliert und wird auf jedem Backend gerendert, auch wenn Funktionen wie MetaData.create_all() und cast() verwendet werden.

>>> stmt = select([cast(sometable.c.somechar, String(20, collation="utf8"))])
>>> print(stmt)
SELECT CAST(sometable.somechar AS VARCHAR(20) COLLATE "utf8") AS anon_1 FROM sometable

Siehe auch

String

#2276

„Prefixes“ werden jetzt für update(), delete() unterstützt

Gerichtet auf MySQL, kann ein „prefix“ innerhalb jeder dieser Konstrukte gerendert werden. Z. B.

stmt = table.delete().prefix_with("LOW_PRIORITY", dialect="mysql")


stmt = table.update().prefix_with("LOW_PRIORITY", dialect="mysql")

Die Methode ist neu zusätzlich zu denen, die bereits auf insert(), select() und Query existierten.

Siehe auch

Update.prefix_with()

Delete.prefix_with()

Insert.prefix_with()

Select.prefix_with()

Query.prefix_with()

#2431

Verhaltensänderungen

Die Berücksichtigung eines „pending“-Objekts als „orphan“ wurde aggressiver gestaltet

Dies ist eine späte Ergänzung zur 0.8er-Serie, es wird jedoch gehofft, dass das neue Verhalten in einer breiteren Palette von Situationen im Allgemeinen konsistenter und intuitiver ist. Die ORM hat seit mindestens Version 0.4 ein Verhalten, bei dem ein Objekt, das sich im „Pending“-Status befindet, d. h. mit einer Session verbunden ist, aber noch nicht in die Datenbank eingefügt wurde, automatisch aus der Session entfernt wird, wenn es zu einem „Waisenkind“ wird. Das bedeutet, dass es von einem übergeordneten Objekt, das darauf verweist, mit der delete-orphan Kaskade in der konfigurierten relationship() getrennt wurde. Dieses Verhalten soll das Verhalten eines persistenten (d. h. bereits eingefügten) Objekts ungefähr spiegeln, bei dem die ORM eine DELETE-Anweisung für solche Objekte ausgibt, die zu Waisenkindern werden, basierend auf der Abfangung von Detachment-Ereignissen.

Die Verhaltensänderung kommt bei Objekten zum Tragen, auf die von mehreren Arten von übergeordneten Elementen verwiesen wird, die jeweils delete-orphan angeben; das typische Beispiel ist ein Assoziations-Objekt, das zwei andere Arten von Objekten in einem Many-to-Many-Muster verbindet. Zuvor wurde das Verhalten so interpretiert, dass das ausstehende Objekt erst dann aus der Session entfernt wurde, wenn es von *allen* seinen übergeordneten Elementen getrennt wurde. Mit der Verhaltensänderung wird das ausstehende Objekt sofort aus der Session entfernt, sobald es von *einem* der übergeordneten Elemente, mit dem es zuvor verbunden war, getrennt wird. Dieses Verhalten soll dem von persistenten Objekten, die gelöscht werden, sobald sie von einem übergeordneten Element getrennt werden, stärker entsprechen.

Der Grund für das ältere Verhalten reicht mindestens bis Version 0.4 zurück und war im Grunde eine defensive Entscheidung, um Verwirrung zu vermeiden, wenn ein Objekt noch für die Einfügung konstruiert wurde. Die Realität ist jedoch, dass das Objekt in jedem Fall wieder mit der Session verbunden wird, sobald es an ein neues übergeordnetes Element angehängt wird.

Es ist immer noch möglich, ein Objekt zu flushen, das nicht mit allen seinen erforderlichen übergeordneten Elementen verbunden ist, wenn das Objekt entweder von vornherein nicht mit diesen übergeordneten Elementen verbunden war oder wenn es entfernt wurde, dann aber über ein nachfolgendes Anhangsereignis wieder mit einer Session verbunden wurde, aber immer noch nicht vollständig verbunden war. In dieser Situation wird erwartet, dass die Datenbank einen Integritätsfehler ausgibt, da es wahrscheinlich NOT NULL Fremdschlüsselspalten gibt, die nicht gefüllt sind. Die ORM trifft die Entscheidung, diese INSERT-Versuche zuzulassen, basierend auf der Einschätzung, dass ein Objekt, das nur teilweise mit seinen erforderlichen übergeordneten Elementen verbunden ist, aber aktiv mit einigen von ihnen verbunden war, meistens ein Benutzerfehler ist und keine absichtliche Auslassung, die stillschweigend übersprungen werden sollte. Das stillschweigende Überspringen des INSERT hier würde Benutzerfehler dieser Art sehr schwer zu debuggen machen.

Das alte Verhalten kann für Anwendungen, die sich möglicherweise darauf verlassen haben, für jede Mapper neu aktiviert werden, indem das Flag legacy_is_orphan als Mapper-Option angegeben wird.

Das neue Verhalten ermöglicht es dem folgenden Testfall zu funktionieren

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary_key=True)
    name = Column(String(64))


class UserKeyword(Base):
    __tablename__ = "user_keyword"
    user_id = Column(Integer, ForeignKey("user.id"), primary_key=True)
    keyword_id = Column(Integer, ForeignKey("keyword.id"), primary_key=True)

    user = relationship(
        User, backref=backref("user_keywords", cascade="all, delete-orphan")
    )

    keyword = relationship(
        "Keyword", backref=backref("user_keywords", cascade="all, delete-orphan")
    )

    # uncomment this to enable the old behavior
    # __mapper_args__ = {"legacy_is_orphan": True}


class Keyword(Base):
    __tablename__ = "keyword"
    id = Column(Integer, primary_key=True)
    keyword = Column("keyword", String(64))


from sqlalchemy import create_engine
from sqlalchemy.orm import Session

# note we're using PostgreSQL to ensure that referential integrity
# is enforced, for demonstration purposes.
e = create_engine("postgresql://scott:tiger@localhost/test", echo=True)

Base.metadata.drop_all(e)
Base.metadata.create_all(e)

session = Session(e)

u1 = User(name="u1")
k1 = Keyword(keyword="k1")

session.add_all([u1, k1])

uk1 = UserKeyword(keyword=k1, user=u1)

# previously, if session.flush() were called here,
# this operation would succeed, but if session.flush()
# were not called here, the operation fails with an
# integrity error.
# session.flush()
del u1.user_keywords[0]

session.commit()

#2655

Das `after_attach`-Ereignis wird ausgelöst, nachdem das Element mit der Session verbunden wurde, anstatt davor; `before_attach` wurde hinzugefügt

Event-Handler, die `after_attach` verwenden, können nun davon ausgehen, dass die gegebene Instanz mit der gegebenen Session verbunden ist

@event.listens_for(Session, "after_attach")
def after_attach(session, instance):
    assert instance in session

Einige Anwendungsfälle erfordern, dass es so funktioniert. Andere Anwendungsfälle erfordern jedoch, dass das Element *noch nicht* Teil der Session ist, z. B. wenn eine Abfrage, die dazu dient, einige für eine Instanz erforderliche Daten zu laden, zuerst `autoflush` auslöst und andernfalls das Zielobjekt vorzeitig flushen würde. Diese Anwendungsfälle sollten das neue `before_attach`-Ereignis verwenden

@event.listens_for(Session, "before_attach")
def before_attach(session, instance):
    instance.some_necessary_attribute = (
        session.query(Widget).filter_by(instance.widget_name).first()
    )

#2464

Query korreliert jetzt automatisch wie ein `select()`

Zuvor war es notwendig, Query.correlate() aufzurufen, um eine Spalten- oder WHERE-Subquery mit dem übergeordneten Element zu korrelieren

subq = (
    session.query(Entity.value)
    .filter(Entity.id == Parent.entity_id)
    .correlate(Parent)
    .as_scalar()
)
session.query(Parent).filter(subq == "some value")

Dies war das entgegengesetzte Verhalten eines einfachen select()-Konstrukts, das standardmäßig die automatische Korrelation annahm. Die obige Aussage in 0.8 korreliert automatisch

subq = session.query(Entity.value).filter(Entity.id == Parent.entity_id).as_scalar()
session.query(Parent).filter(subq == "some value")

Ähnlich wie bei select() kann die Korrelation deaktiviert werden, indem query.correlate(None) aufgerufen wird, oder manuell festgelegt werden, indem eine Entität übergeben wird, query.correlate(someentity).

#2179

Die Korrelation ist jetzt immer kontextabhängig

Um eine breitere Palette von Korrelationsszenarien zu ermöglichen, hat sich das Verhalten von Select.correlate() und Query.correlate() geringfügig geändert, sodass die SELECT-Anweisung das „korrelierte“ Ziel aus der FROM-Klausel nur dann weglässt, wenn die Anweisung tatsächlich in diesem Kontext verwendet wird. Außerdem ist es für eine SELECT-Anweisung, die als FROM in einer umschließenden SELECT-Anweisung platziert ist, nicht mehr möglich, eine FROM-Klausel zu „korrelieren“ (d. h. wegzulassen).

Diese Änderung verbessert die SQL-Ausgabe nur insoweit, als es nicht mehr möglich ist, illegales SQL zu rendern, bei dem es zu wenige FROM-Objekte im Verhältnis zu dem gibt, was ausgewählt wird

from sqlalchemy.sql import table, column, select

t1 = table("t1", column("x"))
t2 = table("t2", column("y"))
s = select([t1, t2]).correlate(t1)

print(s)

Vor dieser Änderung würde das Folgende zurückgegeben werden

SELECT t1.x, t2.y FROM t2

was ungültiges SQL ist, da „t1“ in keiner FROM-Klausel referenziert wird.

Jetzt, in Abwesenheit eines umschließenden SELECT, wird Folgendes zurückgegeben

SELECT t1.x, t2.y FROM t1, t2

Innerhalb eines SELECT tritt die Korrelation wie erwartet in Kraft

s2 = select([t1, t2]).where(t1.c.x == t2.c.y).where(t1.c.x == s)
print(s2)
SELECT t1.x, t2.y FROM t1, t2
WHERE t1.x = t2.y AND t1.x =
    (SELECT t1.x, t2.y FROM t2)

Diese Änderung wird voraussichtlich keine bestehenden Anwendungen beeinträchtigen, da das Korrelationsverhalten für ordnungsgemäß konstruierte Ausdrücke identisch bleibt. Nur eine Anwendung, die sich höchstwahrscheinlich in einem Testszenario auf die ungültige String-Ausgabe eines korrelierten SELECT verlässt, das in einem nicht-korrelierenden Kontext verwendet wird, würde eine Änderung erfahren.

#2668

`create_all()` und `drop_all()` berücksichtigen jetzt eine leere Liste als solche

Die Methoden MetaData.create_all() und MetaData.drop_all() akzeptieren nun eine Liste von Table-Objekten, die leer ist, und geben keine CREATE- oder DROP-Anweisungen aus. Zuvor wurde eine leere Liste genauso interpretiert wie das Übergeben von None für eine Sammlung, und CREATE/DROP würde bedingungslos für alle Elemente ausgegeben.

Dies ist eine Fehlerbehebung, aber einige Anwendungen haben sich möglicherweise auf das vorherige Verhalten verlassen.

#2664

Repariertes Event-Targeting von InstrumentationEvents

Die Reihe von Event-Targets InstrumentationEvents hat dokumentiert, dass die Events nur entsprechend der tatsächlich als Target übergebenen Klasse ausgelöst werden. Bis 0.7 war dies nicht der Fall, und jeder Event-Listener, der auf InstrumentationEvents angewendet wurde, wurde für alle abgebildeten Klassen aufgerufen. In 0.8 wurde zusätzliche Logik hinzugefügt, so dass die Events nur für die übergebenen Klassen aufgerufen werden. Das Flag propagate ist hier standardmäßig auf True gesetzt, da Klassen-Instrumentierungs-Events typischerweise verwendet werden, um Klassen abzufangen, die noch nicht erstellt wurden.

#2590

Keine Magie-Konvertierung mehr von „=“ zu IN beim Vergleichen mit Subquery in MS-SQL

Wir haben ein sehr altes Verhalten im MSSQL-Dialekt gefunden, das versuchte, Benutzer vor sich selbst zu retten, wenn sie etwas wie dieses taten

scalar_subq = select([someothertable.c.id]).where(someothertable.c.data == "foo")
select([sometable]).where(sometable.c.id == scalar_subq)

SQL Server erlaubt keinen Gleichheitsvergleich mit einem skalaren SELECT, d.h. „x = (SELECT something)“. Der MSSQL-Dialekt würde dies in ein IN konvertieren. Dasselbe würde jedoch bei einem Vergleich wie „(SELECT something) = x“ passieren, und insgesamt liegt diese Art von Raten außerhalb des üblichen Umfangs von SQLAlchemy, sodass das Verhalten entfernt wurde.

#2277

Verhalten von Session.is_modified() korrigiert

Die Methode Session.is_modified() akzeptiert ein Argument passive, das im Grunde nicht notwendig sein sollte, das Argument sollte in allen Fällen der Wert True sein – wenn es bei seinem Standardwert von False belassen wird, hätte es den Effekt, die Datenbank zu treffen und oft `autoflush` auszulösen, was die Ergebnisse selbst ändern würde. In 0.8 hat das Argument passive keine Auswirkung, und nicht geladene Attribute werden nie auf Änderungen überprüft, da per Definition keine ausstehende Zustandsänderung bei einem nicht geladenen Attribut vorhanden sein kann.

#2320

``Column.key`` wird im ``Select.c``-Attribut von ``select()`` mit ``Select.apply_labels()`` berücksichtigt

Benutzer des Expression-Systems wissen, dass Select.apply_labels() dem Spaltennamen den Tabellennamen voranstellt, was die Namen beeinflusst, die von Select.c verfügbar sind

s = select([table1]).apply_labels()
s.c.table1_col1
s.c.table1_col2

Vor 0.8 wurde, wenn die Column einen anderen Column.key hatte, dieser Schlüssel ignoriert, inkonsistent im Vergleich dazu, wenn Select.apply_labels() nicht verwendet wurde

# before 0.8
table1 = Table("t1", metadata, Column("col1", Integer, key="column_one"))
s = select([table1])
s.c.column_one  # would be accessible like this
s.c.col1  # would raise AttributeError

s = select([table1]).apply_labels()
s.c.table1_column_one  # would raise AttributeError
s.c.table1_col1  # would be accessible like this

In 0.8 wird Column.key in beiden Fällen berücksichtigt

# with 0.8
table1 = Table("t1", metadata, Column("col1", Integer, key="column_one"))
s = select([table1])
s.c.column_one  # works
s.c.col1  # AttributeError

s = select([table1]).apply_labels()
s.c.table1_column_one  # works
s.c.table1_col1  # AttributeError

Alle anderen Verhaltensweisen bezüglich „name“ und „key“ bleiben gleich, einschließlich der Tatsache, dass der gerenderte SQL immer noch die Form <tablename>_<colname> verwendet – der Schwerpunkt lag hier darauf, zu verhindern, dass der Inhalt von Column.key in die SELECT-Anweisung gerendert wird, damit es keine Probleme mit Sonderzeichen/nicht-ASCII-Zeichen gibt, die im Column.key verwendet werden.

#2397

Warnung `single_parent` ist jetzt ein Fehler

Eine relationship(), die Many-to-One oder Many-to-Many ist und „cascade=’all, delete-orphan‘“ angibt, was ein umständlicher, aber dennoch unterstützter Anwendungsfall ist (mit Einschränkungen), löst nun einen Fehler aus, wenn die Beziehung nicht die Option single_parent=True angibt. Zuvor wurde nur eine Warnung ausgegeben, aber ein Fehler würde sowieso fast sofort im Attributsystem folgen.

#2405

Hinzufügen des Arguments ``inspector`` zum ``column_reflect``-Ereignis

0.7 hat ein neues Ereignis namens column_reflect hinzugefügt, das bereitgestellt wurde, damit die Reflexion von Spalten erweitert werden kann, während jede einzelne reflektiert wird. Wir haben dieses Ereignis leicht falsch gemacht, da das Ereignis keine Möglichkeit bot, auf den aktuellen Inspector und Connection zuzugreifen, die für die Reflexion verwendet wurden, falls zusätzliche Informationen aus der Datenbank benötigt werden. Da dies ein neues, noch nicht weit verbreitetes Ereignis ist, werden wir das Argument inspector direkt hinzufügen.

@event.listens_for(Table, "column_reflect")
def listen_for_col(inspector, table, column_info): ...

#2418

Deaktivierung der automatischen Erkennung von Collations und Groß-/Kleinschreibung für MySQL

Der MySQL-Dialekt führt zwei Aufrufe durch, einen sehr teuren, um alle möglichen Collations aus der Datenbank sowie Informationen zur Groß-/Kleinschreibung zu laden, wenn eine Engine zum ersten Mal verbunden wird. Keine dieser Sammlungen wird für SQLAlchemy-Funktionen verwendet, daher werden diese Aufrufe geändert, um nicht mehr automatisch ausgelöst zu werden. Anwendungen, die sich möglicherweise auf die Verfügbarkeit dieser Sammlungen auf engine.dialect verlassen haben, müssen nun direkt _detect_collations() und _detect_casing() aufrufen.

#2404

Warnung „Unconsumed column names“ wird zur Ausnahme

Das Verweisen auf eine nicht existierende Spalte in einem insert()- oder update()-Konstrukt löst nun einen Fehler statt einer Warnung aus

t1 = table("t1", column("x"))
t1.insert().values(x=5, z=5)  # raises "Unconsumed column names: z"

#2415

Inspector.get_primary_keys() ist veraltet, verwenden Sie Inspector.get_pk_constraint

Diese beiden Methoden von Inspector waren redundant, wobei get_primary_keys() die gleichen Informationen wie get_pk_constraint() zurückgab, abzüglich des Namens der Einschränkung

>>> insp.get_primary_keys()
["a", "b"]

>>> insp.get_pk_constraint()
{"name":"pk_constraint", "constrained_columns":["a", "b"]}

#2422

Groß-/Kleinschreibungs-unempfindliche Ergebnisszeilennamen werden in den meisten Fällen deaktiviert

Ein sehr altes Verhalten: Die Spaltennamen in RowProxy wurden immer groß-/kleinschreibungsunempfindlich verglichen

>>> row = result.fetchone()
>>> row["foo"] == row["FOO"] == row["Foo"]
True

Dies diente einigen Dialekten, die dies in der Anfangszeit benötigten, wie Oracle und Firebird. Im modernen Gebrauch haben wir jedoch genauere Wege, um mit dem Groß-/Kleinschreibungs-unempfindlichen Verhalten dieser beiden Plattformen umzugehen.

Zukünftig wird dieses Verhalten nur noch optional verfügbar sein, indem das Flag `case_sensitive=False` an `create_engine()` übergeben wird, ansonsten müssen die aus der Zeile angeforderten Spaltennamen in Bezug auf die Groß-/Kleinschreibung übereinstimmen.

#2423

``InstrumentationManager`` und alternative Klassen-Instrumentierung ist jetzt eine Erweiterung

Die Klasse sqlalchemy.orm.interfaces.InstrumentationManager wurde nach sqlalchemy.ext.instrumentation.InstrumentationManager verschoben. Das „alternative Instrumentierungs“-System wurde für eine sehr kleine Anzahl von Installationen entwickelt, die mit vorhandenen oder ungewöhnlichen Klassen-Instrumentierungssystemen arbeiten mussten, und wird generell sehr selten verwendet. Die Komplexität dieses Systems wurde in ein ext. Modul exportiert. Es bleibt ungenutzt, bis es einmal importiert wird, typischerweise wenn eine Drittanbieterbibliothek InstrumentationManager importiert, zu diesem Zeitpunkt wird es in sqlalchemy.orm injiziert, indem die Standard-InstrumentationFactory durch ExtendedInstrumentationRegistry ersetzt wird.

Entfernt

SQLSoup

SQLSoup ist ein praktisches Paket, das eine alternative Schnittstelle über der SQLAlchemy ORM bereitstellt. SQLSoup wurde nun in ein eigenes Projekt verschoben und separat dokumentiert/veröffentlicht; siehe https://github.com/zzzeek/sqlsoup.

SQLSoup ist ein sehr einfaches Werkzeug, das auch von Mitwirkenden profitieren könnte, die an seinem Nutzungsstil interessiert sind.

#2262

MutableType

Das ältere „mutable“-System innerhalb der SQLAlchemy ORM wurde entfernt. Dies bezieht sich auf die Schnittstelle MutableType, die auf Typen wie PickleType und bedingt auf TypeDecorator angewendet wurde und seit sehr frühen SQLAlchemy-Versionen eine Möglichkeit bot, damit die ORM Änderungen in sogenannten „mutablen“ Datenstrukturen wie JSON-Strukturen und gepickelten Objekten erkennen kann. Die Implementierung war jedoch nie vernünftig und zwang die Unit-of-Work zu einem sehr ineffizienten Nutzungsmodus, der während des Flushens einen teuren Scan aller Objekte verursachte. In 0.7 wurde die Erweiterung sqlalchemy.ext.mutable eingeführt, damit benutzerdefinierte Datentypen Änderungen an die Unit-of-Work entsprechend weitergeben können, wenn sie auftreten.

Heute wird erwartet, dass die Nutzung von MutableType gering ist, da seit einigen Jahren Warnungen bezüglich seiner Ineffizienz bestehen.

#2442

sqlalchemy.exceptions (war seit Jahren sqlalchemy.exc)

Wir hatten einen Alias sqlalchemy.exceptions belassen, um es älteren Bibliotheken, die noch nicht auf sqlalchemy.exc aktualisiert wurden, etwas einfacher zu machen. Einige Benutzer sind dadurch jedoch immer noch verwirrt, sodass wir ihn in 0.8 vollständig entfernen, um diese Verwirrung zu beseitigen.

#2433