Neues in SQLAlchemy 0.4?

Über dieses Dokument

Dieses Dokument beschreibt die Änderungen zwischen SQLAlchemy Version 0.3, zuletzt veröffentlicht am 14. Oktober 2007, und SQLAlchemy Version 0.4, zuletzt veröffentlicht am 12. Oktober 2008.

Datum des Dokuments: 21. März 2008

Zuerst

Wenn Sie ORM-Funktionen verwenden, stellen Sie sicher, dass Sie aus sqlalchemy.orm importieren.

from sqlalchemy import *
from sqlalchemy.orm import *

Zweitens, wo immer Sie früher engine=, connectable=, bind_to=, something.engine, metadata.connect() sagten, verwenden Sie jetzt bind.

myengine = create_engine("sqlite://")

meta = MetaData(myengine)

meta2 = MetaData()
meta2.bind = myengine

session = create_session(bind=myengine)

statement = select([table], bind=myengine)

Das haben Sie verstanden? Gut! Sie sind nun (95%) 0.4-kompatibel. Wenn Sie 0.3.10 verwenden, können Sie diese Änderungen sofort vornehmen; sie funktionieren auch dort.

Modul-Importe

In 0.3 importierte „from sqlalchemy import *“ alle Untermodule von SQLAlchemy in Ihren Namensraum. Version 0.4 importiert Untermodule nicht mehr in den Namensraum. Dies kann bedeuten, dass Sie zusätzliche Importe in Ihren Code einfügen müssen.

In 0.3 funktionierte dieser Code

from sqlalchemy import *


class UTCDateTime(types.TypeDecorator):
    pass

In 0.4 muss man Folgendes tun

from sqlalchemy import *
from sqlalchemy import types


class UTCDateTime(types.TypeDecorator):
    pass

Objektrelationale Abbildung

Abfragen

Neue Query API

Query ist auf die generative Schnittstelle standardisiert (die alte Schnittstelle ist noch vorhanden, aber veraltet). Während die meisten generativen Schnittstellen in 0.3 verfügbar sind, verfügt die 0.4 Query über die inneren Mechanismen, die zur äußeren generativen Schnittstelle passen, und bietet viele weitere Tricks. Die gesamte Ergebnisbeschränkung erfolgt über filter() und filter_by(), Limitierung/Offset erfolgt entweder durch Array-Slices oder limit()/offset(), Joins erfolgen über join() und outerjoin() (oder manueller über select_from() sowie manuell gebildete Kriterien).

Um Veraltungswarnungen zu vermeiden, müssen Sie einige Änderungen an Ihrem 03-Code vornehmen

User.query.get_by( **kwargs )

User.query.filter_by(**kwargs).first()

User.query.select_by( **kwargs )

User.query.filter_by(**kwargs).all()

User.query.select()

User.query.filter(xxx).all()

Neue eigenschaftsbasierte Ausdruckskonstrukte

Der mit Abstand spürbarste Unterschied innerhalb des ORM ist, dass Sie nun Ihre Abfragekriterien direkt anhand klassenbasierter Attribute konstruieren können. Das „.c.“ Präfix ist bei der Arbeit mit abgebildeten Klassen nicht mehr erforderlich.

session.query(User).filter(and_(User.name == "fred", User.id > 17))

Während einfache spaltenbasierte Vergleiche keine große Sache sind, verfügen die Klassenattribute über einige neue „höherstufige“ Konstrukte, einschließlich dessen, was bisher nur in filter_by() verfügbar war.

# comparison of scalar relations to an instance
filter(Address.user == user)

# return all users who contain a particular address
filter(User.addresses.contains(address))

# return all users who *dont* contain the address
filter(~User.address.contains(address))

# return all users who contain a particular address with
# the email_address like '%foo%'
filter(User.addresses.any(Address.email_address.like("%foo%")))

# same, email address equals 'foo@bar.com'.  can fall back to keyword
# args for simple comparisons
filter(User.addresses.any(email_address="foo@bar.com"))

# return all Addresses whose user attribute has the username 'ed'
filter(Address.user.has(name="ed"))

# return all Addresses whose user attribute has the username 'ed'
# and an id > 5 (mixing clauses with kwargs)
filter(Address.user.has(User.id > 5, name="ed"))

Die Column-Sammlung bleibt auf abgebildeten Klassen unter dem Attribut .c verfügbar. Beachten Sie, dass eigenschaftsbasierte Ausdrücke nur mit abgebildeten Eigenschaften von abgebildeten Klassen verfügbar sind. .c wird weiterhin verwendet, um auf Spalten in regulären Tabellen und aus SQL-Ausdrücken erzeugten auswählbaren Objekten zuzugreifen.

Automatische Join-Aliase

Wir hatten join() und outerjoin() schon eine Weile

session.query(Order).join("items")

Jetzt können Sie ihnen Aliase zuweisen

session.query(Order).join("items", aliased=True).filter(Item.name="item 1").join(
    "items", aliased=True
).filter(Item.name == "item 3")

Das obige Beispiel erstellt zwei Joins von Orders zu Items unter Verwendung von Aliases. Der nachfolgende filter()-Aufruf passt sein Tabellenkriterium an das des Alias an. Um auf die Item-Objekte zuzugreifen, verwenden Sie add_entity() und zielen Sie jeden Join mit einer id an.

session.query(Order).join("items", id="j1", aliased=True).filter(
    Item.name == "item 1"
).join("items", aliased=True, id="j2").filter(Item.name == "item 3").add_entity(
    Item, id="j1"
).add_entity(
    Item, id="j2"
)

Gibt Tupel in der Form zurück: (Order, Item, Item).

Selbstreferenzielle Abfragen

Also kann query.join() jetzt Aliase bilden. Was bringt uns das? Selbstreferenzielle Abfragen! Joins können ohne Alias-Objekte durchgeführt werden.

# standard self-referential TreeNode mapper with backref
mapper(
    TreeNode,
    tree_nodes,
    properties={
        "children": relation(
            TreeNode, backref=backref("parent", remote_side=tree_nodes.id)
        )
    },
)

# query for node with child containing "bar" two levels deep
session.query(TreeNode).join(["children", "children"], aliased=True).filter_by(
    name="bar"
)

Um Kriterien für jede Tabelle auf dem Weg in einem aliasten Join hinzuzufügen, können Sie from_joinpoint verwenden, um weiterhin gegen dieselbe Zeile von Aliases zu joinen.

# search for the treenode along the path "n1/n12/n122"

# first find a Node with name="n122"
q = sess.query(Node).filter_by(name="n122")

# then join to parent with "n12"
q = q.join("parent", aliased=True).filter_by(name="n12")

# join again to the next parent with 'n1'.  use 'from_joinpoint'
# so we join from the previous point, instead of joining off the
# root table
q = q.join("parent", aliased=True, from_joinpoint=True).filter_by(name="n1")

node = q.first()

query.populate_existing()

Die Eager-Version von query.load() (oder session.refresh()). Jede Instanz, die aus der Abfrage geladen wird, einschließlich aller eager geladenen Elemente, wird sofort aktualisiert, wenn sie bereits in der Session vorhanden ist.

session.query(Blah).populate_existing().all()

Relationen

SQL-Klauseln eingebettet in Updates/Inserts

Für die Inline-Ausführung von SQL-Klauseln, eingebettet direkt in das UPDATE oder INSERT, während eines flush().

myobject.foo = mytable.c.value + 1

user.pwhash = func.md5(password)

order.hash = text("select hash from hashing_table")

Das Spaltenattribut wird nach der Operation mit einem verzögerten Lader eingerichtet, sodass die SQL-Anweisung zum Laden des neuen Wertes ausgegeben wird, wenn Sie das nächste Mal darauf zugreifen.

Selbstreferenzielle und zyklische Eager-Ladung

Da unsere Alias-Fähigkeiten verbessert wurden, kann relation() beliebig oft entlang derselben Tabelle joinen; Sie geben an, wie tief Sie gehen möchten. Zeigen wir die selbstreferenzielle TreeNode klarer.

nodes = Table(
    "nodes",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("parent_id", Integer, ForeignKey("nodes.id")),
    Column("name", String(30)),
)


class TreeNode(object):
    pass


mapper(
    TreeNode,
    nodes,
    properties={"children": relation(TreeNode, lazy=False, join_depth=3)},
)

Was passiert also, wenn wir sagen

create_session().query(TreeNode).all()

? Ein Join über Aliase, drei Ebenen tief vom Elternteil.

SELECT
nodes_3.id AS nodes_3_id, nodes_3.parent_id AS nodes_3_parent_id, nodes_3.name AS nodes_3_name,
nodes_2.id AS nodes_2_id, nodes_2.parent_id AS nodes_2_parent_id, nodes_2.name AS nodes_2_name,
nodes_1.id AS nodes_1_id, nodes_1.parent_id AS nodes_1_parent_id, nodes_1.name AS nodes_1_name,
nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id, nodes.name AS nodes_name
FROM nodes LEFT OUTER JOIN nodes AS nodes_1 ON nodes.id = nodes_1.parent_id
LEFT OUTER JOIN nodes AS nodes_2 ON nodes_1.id = nodes_2.parent_id
LEFT OUTER JOIN nodes AS nodes_3 ON nodes_2.id = nodes_3.parent_id
ORDER BY nodes.oid, nodes_1.oid, nodes_2.oid, nodes_3.oid

Beachten Sie auch die schönen, sauberen Aliasnamen. Der Join kümmert sich nicht darum, ob er gegen dieselbe unmittelbare Tabelle oder ein anderes Objekt joined, das dann zum Anfang zurückkehrt. Jede Art von Kette von Eager-Ladungen kann sich selbst zurückschließen, wenn join_depth angegeben ist. Wenn nicht vorhanden, stoppt die Eager-Ladung automatisch, wenn sie auf einen Zyklus trifft.

Zusammengesetzte Typen

Das ist etwas aus dem Hibernate-Lager. Composite Types lassen Sie einen benutzerdefinierten Datentyp definieren, der aus mehr als einer Spalte besteht (oder einer Spalte, wenn Sie möchten). Definieren wir einen neuen Typ, Point. Speichert eine x/y-Koordinate.

class Point(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __composite_values__(self):
        return self.x, self.y

    def __eq__(self, other):
        return other.x == self.x and other.y == self.y

    def __ne__(self, other):
        return not self.__eq__(other)

Die Art und Weise, wie das Point-Objekt definiert ist, ist spezifisch für einen benutzerdefinierten Typ; der Konstruktor nimmt eine Liste von Argumenten, und die Methode __composite_values__() erzeugt eine Sequenz dieser Argumente. Die Reihenfolge wird mit unserem Mapper übereinstimmen, wie wir gleich sehen werden.

Lassen Sie uns eine Tabelle von Scheitelpunkten erstellen, die zwei Punkte pro Zeile speichert.

vertices = Table(
    "vertices",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("x1", Integer),
    Column("y1", Integer),
    Column("x2", Integer),
    Column("y2", Integer),
)

Dann bilden wir sie ab! Wir erstellen ein Vertex-Objekt, das zwei Point-Objekte speichert.

class Vertex(object):
    def __init__(self, start, end):
        self.start = start
        self.end = end


mapper(
    Vertex,
    vertices,
    properties={
        "start": composite(Point, vertices.c.x1, vertices.c.y1),
        "end": composite(Point, vertices.c.x2, vertices.c.y2),
    },
)

Sobald Sie Ihren zusammengesetzten Typ eingerichtet haben, ist er wie jeder andere Typ verwendbar.

v = Vertex(Point(3, 4), Point(26, 15))
session.save(v)
session.flush()

# works in queries too
q = session.query(Vertex).filter(Vertex.start == Point(3, 4))

Wenn Sie die Art und Weise definieren möchten, wie die abgebildeten Attribute SQL-Klauseln generieren, wenn sie in Ausdrücken verwendet werden, erstellen Sie Ihre eigene sqlalchemy.orm.PropComparator Unterklasse, definieren Sie beliebige der gängigen Operatoren (wie __eq__(), __le__() usw.) und übergeben Sie sie an composite(). Zusammengesetzte Typen funktionieren auch als Primärschlüssel und sind in query.get() verwendbar.

# a Document class which uses a composite Version
# object as primary key
document = query.get(Version(1, "a"))

dynamic_loader() Relationen

Eine relation(), die für alle Leseoperationen ein Live- Query-Objekt zurückgibt. Schreiboperationen sind auf append() und remove() beschränkt, Änderungen an der Sammlung sind erst sichtbar, wenn die Session geflusht wird. Dieses Feature ist besonders nützlich mit einer „Autoflush“-Session, die vor jeder Abfrage flusht.

mapper(
    Foo,
    foo_table,
    properties={
        "bars": dynamic_loader(
            Bar,
            backref="foo",
            # <other relation() opts>
        )
    },
)

session = create_session(autoflush=True)
foo = session.query(Foo).first()

foo.bars.append(Bar(name="lala"))

for bar in foo.bars.filter(Bar.name == "lala"):
    print(bar)

session.commit()

Neue Optionen: undefer_group(), eagerload_all()

Ein paar nützliche Abfrageoptionen. undefer_group() markiert eine ganze Gruppe von „deferred“ Spalten als nicht deferred.

mapper(
    Class,
    table,
    properties={
        "foo": deferred(table.c.foo, group="group1"),
        "bar": deferred(table.c.bar, group="group1"),
        "bat": deferred(table.c.bat, group="group1"),
    },
)

session.query(Class).options(undefer_group("group1")).filter(...).all()

und eagerload_all() setzt eine Kette von Attributen, die in einem Durchgang eager geladen werden sollen.

mapper(Foo, foo_table, properties={"bar": relation(Bar)})
mapper(Bar, bar_table, properties={"bat": relation(Bat)})
mapper(Bat, bat_table)

# eager load bar and bat
session.query(Foo).options(eagerload_all("bar.bat")).filter(...).all()

Neue Collection API

Sammlungen werden nicht mehr von einem {{{InstrumentedList}}} Proxy proxied, und der Zugriff auf Elemente, Methoden und Attribute ist direkt. Dekoratoren fangen nun Objekte ab, die die Sammlung betreten und verlassen, und es ist nun möglich, einfach eine benutzerdefinierte Sammlungsklasse zu schreiben, die ihre eigene Mitgliedschaft verwaltet. Flexible Dekoratoren ersetzen auch die benannte Methodenschnittstelle benutzerdefinierter Sammlungen in 0.3, wodurch jede Klasse leicht als Sammlungscontainer angepasst werden kann.

Wörterbuchbasierte Sammlungen sind jetzt viel einfacher zu verwenden und vollständig dict-ähnlich. Das Ändern von __iter__ ist für dicts nicht mehr erforderlich, und neue integrierte dict-Typen decken viele Bedürfnisse ab.

# use a dictionary relation keyed by a column
relation(Item, collection_class=column_mapped_collection(items.c.keyword))
# or named attribute
relation(Item, collection_class=attribute_mapped_collection("keyword"))
# or any function you like
relation(Item, collection_class=mapped_collection(lambda entity: entity.a + entity.b))

Bestehende dict-ähnliche und frei formatierte objektbasierte Sammlungsklassen aus 0.3 müssen für die neue API aktualisiert werden. In den meisten Fällen ist dies lediglich eine Frage des Hinzufügens einiger Dekoratoren zur Klassendefinition.

Abgebildete Relationen von externen Tabellen/Unterabfragen

Dieses Feature erschien leise in 0.3, wurde aber in 0.4 dank besserer Fähigkeit, Unterabfragen gegen eine Tabelle in Unterabfragen gegen einen Alias dieser Tabelle umzuwandeln, verbessert; dies ist entscheidend für Eager-Ladung, aliastierte Joins in Abfragen usw. Es reduziert die Notwendigkeit, Mapper gegen Select-Anweisungen zu erstellen, wenn Sie nur zusätzliche Spalten oder Unterabfragen hinzufügen möchten.

mapper(
    User,
    users,
    properties={
        "fullname": column_property(
            (users.c.firstname + users.c.lastname).label("fullname")
        ),
        "numposts": column_property(
            select([func.count(1)], users.c.id == posts.c.user_id)
            .correlate(users)
            .label("posts")
        ),
    },
)

Eine typische Abfrage sieht so aus

SELECT (SELECT count(1) FROM posts WHERE users.id = posts.user_id) AS count,
users.firstname || users.lastname AS fullname,
users.id AS users_id, users.firstname AS users_firstname, users.lastname AS users_lastname
FROM users ORDER BY users.oid

API für horizontale Skalierung (Sharding)

[browser:/sqlalchemy/trunk/examples/sharding/attribute_shard .py]

Sessions

Neues Sitzungs-Erstellungsparadigma; SessionContext, assignmapper Veraltet

Das stimmt, das gesamte Paket wird durch zwei Konfigurationsfunktionen ersetzt. Die Verwendung beider wird das bisher 0.1-ähnlichste Gefühl vermitteln, das wir seit 0.1 hatten (d.h. die geringste Tipparbeit).

Konfigurieren Sie Ihre eigene Session-Klasse direkt dort, wo Sie Ihre engine definieren (oder wo auch immer).

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine("myengine://")
Session = sessionmaker(bind=engine, autoflush=True, transactional=True)

# use the new Session() freely
sess = Session()
sess.save(someobject)
sess.flush()

Wenn Sie Ihre Session nachträglich konfigurieren müssen, z. B. mit einer Engine, fügen Sie sie später mit configure() hinzu.

Session.configure(bind=create_engine(...))

Alle Verhaltensweisen von SessionContext und die Methoden query und __init__ von assignmapper sind in die neue Funktion scoped_session() verschoben, die sowohl mit sessionmaker als auch mit create_session() kompatibel ist.

from sqlalchemy.orm import scoped_session, sessionmaker

Session = scoped_session(sessionmaker(autoflush=True, transactional=True))
Session.configure(bind=engine)

u = User(name="wendy")

sess = Session()
sess.save(u)
sess.commit()

# Session constructor is thread-locally scoped.  Everyone gets the same
# Session in the thread when scope="thread".
sess2 = Session()
assert sess is sess2

Wenn Sie eine Thread-lokale Session verwenden, verfügt die zurückgegebene Klasse über die gesamte Schnittstelle von Session, die als Klassenmethoden implementiert ist, und die Funktionalität von „assignmapper“ ist über die Klassenmethode mapper verfügbar. Genau wie zu Zeiten von objectstore...

# "assignmapper"-like functionality available via ScopedSession.mapper
Session.mapper(User, users_table)

u = User(name="wendy")

Session.commit()

Sessions referenzieren standardmäßig wieder schwach

Das Flag weak_identity_map ist jetzt standardmäßig auf True in Session gesetzt. Instanzen, die extern nicht mehr referenziert werden und aus dem Gültigkeitsbereich fallen, werden automatisch aus der Session entfernt. Elemente mit „dirty“ Änderungen werden jedoch stark referenziert bleiben, bis diese Änderungen geflusht werden, wonach das Objekt wieder schwach referenziert wird (dies funktioniert auch für „mutable“ Typen wie pickelbare Attribute). Das Setzen von weak_identity_map auf False stellt das alte stark referenzierende Verhalten für diejenigen wieder her, die die Session wie einen Cache verwenden.

Auto-transaktionale Sessions

Wie Sie oben vielleicht bemerkt haben, rufen wir commit() auf Session auf. Das Flag transactional=True bedeutet, dass die Session immer in einer Transaktion ist, commit() speichert dauerhaft.

Auto-Flush-Sessions

Außerdem bedeutet autoflush=True, dass die Session vor jeder query sowie beim Aufruf von flush() oder commit() flush(). So funktioniert das jetzt

Session = sessionmaker(bind=engine, autoflush=True, transactional=True)

u = User(name="wendy")

sess = Session()
sess.save(u)

# wendy is flushed, comes right back from a query
wendy = sess.query(User).filter_by(name="wendy").one()

Transaktionsmethoden auf Sessions verschoben

commit() und rollback(), sowie begin(), sind jetzt direkt auf Session. Sie müssen SessionTransaction nicht mehr für irgendetwas verwenden (sie bleibt im Hintergrund).

Session = sessionmaker(autoflush=True, transactional=False)

sess = Session()
sess.begin()

# use the session

sess.commit()  # commit transaction

Das Teilen einer Session mit einer umschließenden Transaktion auf Engine-Ebene (d.h. nicht-ORM) ist einfach.

Session = sessionmaker(autoflush=True, transactional=False)

conn = engine.connect()
trans = conn.begin()
sess = Session(bind=conn)

# ... session is transactional

# commit the outermost transaction
trans.commit()

Verschachtelte Session-Transaktionen mit SAVEPOINT

Auf Engine- und ORM-Ebene verfügbar. Bisherige ORM-Dokumentation

https://sqlalchemy.de/docs/04/session.html#unitofwork_managing

Zwei-Phasen-Commit-Sessions

Auf Engine- und ORM-Ebene verfügbar. Bisherige ORM-Dokumentation

https://sqlalchemy.de/docs/04/session.html#unitofwork_managing

Vererbung

Polymorphe Vererbung ohne Joins oder Unions

Neue Dokumentation zur Vererbung: https://sqlalchemy.de/docs/04 /mappers.html#advdatamapping_mapper_inheritance_joined

Besseres polymorphes Verhalten mit get()

Alle Klassen innerhalb einer Joined-Table-Vererbungshierarchie erhalten einen _instance_key unter Verwendung der Basisklasse, d.h. (BaseClass, (1, ), None). Dadurch kann beim Aufruf von get() eine Query gegen die Basisklasse auf Unterklasseninstanzen im aktuellen Identitätsabbild zugreifen, ohne die Datenbank abfragen zu müssen.

Typen

Benutzerdefinierte Unterklassen von sqlalchemy.types.TypeDecorator

Es gibt eine neue API zum Ableiten von TypeDecorator. Die Verwendung der 0.3 API führt in einigen Fällen zu Kompilierungsfehlern.

SQL-Ausdrücke

Alle neuen, deterministischen Label/Alias-Generierung

Alle „anonymen“ Labels und Aliase verwenden jetzt ein einfaches <name>_<nummer> Format. SQL ist leichter zu lesen und kompatibel mit Plan-Optimizer-Caches. Schauen Sie sich die Beispiele in den Tutorials an: https://sqlalchemy.de/docs/04/ormtutorial.html https://sqlalchemy.de/docs/04/sqlexpression.html

Generative select() Konstrukte

Dies ist definitiv der Weg für select(). Siehe https://sqlalchemy.de/docs/04/sqlexpression.html#sql_transform .

Neues Operator-System

SQL-Operatoren und so ziemlich jedes existierende SQL-Schlüsselwort sind nun in die Compiler-Schicht abstrahiert. Sie agieren jetzt intelligent und sind Typ/Backend-bewusst, siehe: https://sqlalchemy.de/docs/04/sqlexpression.html#sql_operators

Alle type Keyword-Argumente umbenannt in type_

Genau wie es da steht.

b = bindparam("foo", type_=String)

in_ Funktion akzeptiert jetzt Sequenz oder Selektierbares

Die Funktion in_ nimmt jetzt eine Sequenz von Werten oder ein auswählbares Objekt als einziges Argument entgegen. Die vorherige API, bei der Werte als Positionsargumente übergeben wurden, funktioniert weiterhin, ist aber nun veraltet. Das bedeutet, dass

my_table.select(my_table.c.id.in_(1, 2, 3))
my_table.select(my_table.c.id.in_(*listOfIds))

zu geändert werden sollte

my_table.select(my_table.c.id.in_([1, 2, 3]))
my_table.select(my_table.c.id.in_(listOfIds))

Schema und Reflektion

MetaData, BoundMetaData, DynamicMetaData

In der 0.3.x-Serie wurden BoundMetaData und DynamicMetaData zugunsten von MetaData und ThreadLocalMetaData veraltet. Die älteren Namen wurden in 0.4 entfernt. Die Aktualisierung ist einfach.

+-------------------------------------+-------------------------+
|If You Had                           | Now Use                 |
+=====================================+=========================+
| ``MetaData``                        | ``MetaData``            |
+-------------------------------------+-------------------------+
| ``BoundMetaData``                   | ``MetaData``            |
+-------------------------------------+-------------------------+
| ``DynamicMetaData`` (with one       | ``MetaData``            |
| engine or threadlocal=False)        |                         |
+-------------------------------------+-------------------------+
| ``DynamicMetaData``                 | ``ThreadLocalMetaData`` |
| (with different engines per thread) |                         |
+-------------------------------------+-------------------------+

Der selten verwendete Parameter name für MetaData-Typen wurde entfernt. Der Konstruktor von ThreadLocalMetaData nimmt nun keine Argumente mehr an. Beide Typen können nun an eine Engine oder eine einzelne Connection gebunden werden.

Einzelschrittige Multi-Tabellen-Reflektion

Sie können nun Tabellendefinitionen laden und Table-Objekte aus einer gesamten Datenbank oder einem Schema in einem Durchgang automatisch erstellen.

>>> metadata = MetaData(myengine, reflect=True)
>>> metadata.tables.keys()
['table_a', 'table_b', 'table_c', '...']

MetaData erhält außerdem eine Methode .reflect(), die eine feinere Steuerung des Ladevorgangs ermöglicht, einschließlich der Angabe einer Teilmenge der verfügbaren Tabellen zum Laden.

SQL-Ausführung

engine, connectable und bind_to sind jetzt alle bind

Transactions, NestedTransactions und TwoPhaseTransactions

Connection-Pool-Events

Der Connection-Pool löst nun Events aus, wenn neue DB-API-Verbindungen erstellt, ausgecheckt und zurück in den Pool eingecheckt werden. Sie können diese beispielsweise verwenden, um sitzungsbezogene SQL-Einrichtungsanweisungen auf neuen Verbindungen auszuführen.

Oracle Engine korrigiert

In 0.3.11 gab es Fehler in der Oracle Engine, wie Primärschlüssel behandelt werden. Diese Fehler konnten dazu führen, dass Programme, die mit anderen Engines wie sqlite einwandfrei funktionierten, bei Verwendung der Oracle Engine fehlschlugen. In 0.4 wurde die Oracle Engine überarbeitet und behebt diese Primärschlüsselprobleme.

Out-Parameter für Oracle

result = engine.execute(
    text(
        "begin foo(:x, :y, :z); end;",
        bindparams=[
            bindparam("x", Numeric),
            outparam("y", Numeric),
            outparam("z", Numeric),
        ],
    ),
    x=5,
)
assert result.out_parameters == {"y": 10, "z": 75}

Verbindungsgebundene MetaData, Sessions

MetaData und Session können explizit an eine Verbindung gebunden werden.

conn = engine.connect()
sess = create_session(bind=conn)

Schnellere, narrensicherere ResultProxy Objekte