Tabellenkonfiguration mit Deklarativ

Wie bereits in Deklarative Zuordnung eingeführt, beinhaltet der Deklarative Stil die Möglichkeit, gleichzeitig ein zugeordnetes Table-Objekt zu generieren oder direkt eine Table oder ein anderes FromClause-Objekt zu verwenden.

Die folgenden Beispiele gehen von einer deklarativen Basisklasse wie folgt aus:

from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass

Alle folgenden Beispiele illustrieren eine Klasse, die von der obigen Base erbt. Der im Abschnitt Deklarative Zuordnung mit einem Dekorator (keine deklarative Basis) eingeführte Dekorator-Stil wird ebenfalls mit allen folgenden Beispielen vollständig unterstützt, ebenso wie ältere Formen der Deklarativen Basis, einschließlich der von declarative_base() generierten Basisklassen.

Deklarative Tabelle mit mapped_column()

Bei der Verwendung von Deklarativ beinhaltet der Körper der abzubildenden Klasse in den meisten Fällen ein Attribut __tablename__, das den String-Namen einer zu generierenden Table neben der Zuordnung angibt. Das mapped_column()-Konstrukt, das zusätzliche ORM-spezifische Konfigurationsfähigkeiten aufweist, die in der einfachen Column-Klasse nicht vorhanden sind, wird dann innerhalb des Klassenkörpers verwendet, um Spalten in der Tabelle anzugeben. Das folgende Beispiel illustriert die grundlegendste Verwendung dieses Konstrukts innerhalb einer deklarativen Zuordnung

from sqlalchemy import Integer, String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import mapped_column


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "user"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50), nullable=False)
    fullname = mapped_column(String)
    nickname = mapped_column(String(30))

Oben sind die mapped_column()-Konstrukte inline innerhalb der Klassendefinition als Klassenebene-Attribute platziert. Zum Zeitpunkt der Klassendeklaration wird der deklarative Zuordnungsprozess eine neue Table-Objekt gegen die Metadatensammlung erstellen, die der deklarativen Base zugeordnet ist. Jede Instanz von mapped_column() wird dann während dieses Prozesses verwendet, um ein Column-Objekt zu generieren, das Teil der Table.columns-Sammlung dieses Table-Objekts wird.

Im obigen Beispiel erstellt Deklarativ ein Table-Konstrukt, das dem folgenden entspricht

# equivalent Table object produced
user_table = Table(
    "user",
    Base.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("fullname", String()),
    Column("nickname", String(30)),
)

Wenn die obige User-Klasse zugeordnet ist, kann dieses Table-Objekt direkt über das Attribut __table__ zugegriffen werden; dies wird weiter unter Zugriff auf Tabellen und Metadaten beschrieben.

Das Konstrukt mapped_column() akzeptiert alle Argumente, die vom Column-Konstrukt akzeptiert werden, sowie zusätzliche ORM-spezifische Argumente. Das Feld mapped_column.__name, das den Namen der Datenbankspalte angibt, wird normalerweise weggelassen, da der deklarative Prozess den dem Konstrukt gegebenen Attributnamen verwendet und diesen als Spaltennamen zuweist (im obigen Beispiel bezieht sich dies auf die Namen id, name, fullname, nickname). Die Zuweisung eines alternativen mapped_column.__name ist ebenfalls gültig, wobei die resultierende Column den angegebenen Namen in SQL- und DDL-Anweisungen verwendet, während die zugeordnete Klasse User weiterhin den Zugriff auf das Attribut unter Verwendung des angegebenen Attributnamens erlaubt, unabhängig vom Namen der Spalte selbst (mehr dazu unter Explizites Benennen von deklarativ zugeordneten Spalten).

Tipp

Das Konstrukt mapped_column() ist **nur innerhalb einer deklarativen Klassenzuordnung gültig**. Beim Erstellen einer Table-Objekts mit Core sowie bei der Verwendung von imperativer Tabellenkonfiguration ist die Column-Konstruktion weiterhin erforderlich, um die Anwesenheit einer Datenbankspalte anzuzeigen.

Siehe auch

Zuordnung von Tabellenspalten - enthält zusätzliche Hinweise zur Beeinflussung, wie der Mapper eingehende Column-Objekte interpretiert.

Verwendung von annotierter deklarativer Tabelle (typspezifische annotierte Formen für mapped_column())

Das Konstrukt mapped_column() kann seine Spaltenkonfigurationsinformationen aus PEP 484-Typannotationen ableiten, die mit dem Attribut verbunden sind, wie es in der deklarativ zugeordneten Klasse deklariert ist. Diese Typannotationen müssen, wenn sie verwendet werden, innerhalb eines speziellen SQLAlchemy-Typs namens Mapped vorhanden sein, der ein generischer Typ ist und darin einen spezifischen Python-Typ angibt.

Unten ist die Zuordnung aus dem vorherigen Abschnitt dargestellt, wobei die Verwendung von Mapped hinzugefügt wurde

from typing import Optional

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


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(50))
    fullname: Mapped[Optional[str]]
    nickname: Mapped[Optional[str]] = mapped_column(String(30))

Oben leitet bei der Verarbeitung jeder Klassenattribut durch Deklarativ jede mapped_column() zusätzliche Argumente aus der entsprechenden Mapped-Typannotation auf der linken Seite ab, falls vorhanden. Zusätzlich generiert Deklarativ implizit eine leere mapped_column()-Anweisung, wann immer eine Mapped-Typannotation angetroffen wird, der kein Wert dem Attribut zugewiesen ist (diese Form ist vom ähnlichen Stil in Python Dataclasses inspiriert); dieses mapped_column()-Konstrukt leitet seine Konfiguration von der vorhandenen Mapped-Annotation ab.

mapped_column() leitet den Datentyp und die Nullbarkeit aus der Mapped-Annotation ab

Die beiden Eigenschaften, die mapped_column() aus der Mapped-Annotation ableitet, sind

  • Datentyp - Der Python-Typ, der innerhalb von Mapped angegeben ist, wie er innerhalb des typing.Optional-Konstrukts enthalten ist, falls vorhanden, wird mit einer TypeEngine-Unterklasse wie Integer, String, DateTime oder Uuid, um nur einige gängige Typen zu nennen, assoziiert.

    Der Datentyp wird anhand eines Wörterbuchs von Python-Typen zu SQLAlchemy-Datentypen bestimmt. Dieses Wörterbuch ist vollständig anpassbar, wie im nächsten Abschnitt Anpassen der Typzuordnung beschrieben. Die Standard-Typzuordnung ist im folgenden Codebeispiel implementiert

    from typing import Any
    from typing import Dict
    from typing import Type
    
    import datetime
    import decimal
    import uuid
    
    from sqlalchemy import types
    
    # default type mapping, deriving the type for mapped_column()
    # from a Mapped[] annotation
    type_map: Dict[Type[Any], TypeEngine[Any]] = {
        bool: types.Boolean(),
        bytes: types.LargeBinary(),
        datetime.date: types.Date(),
        datetime.datetime: types.DateTime(),
        datetime.time: types.Time(),
        datetime.timedelta: types.Interval(),
        decimal.Decimal: types.Numeric(),
        float: types.Float(),
        int: types.Integer(),
        str: types.String(),
        uuid.UUID: types.Uuid(),
    }

    Wenn das Konstrukt mapped_column() einen expliziten Typ angibt, der dem Argument mapped_column.__type übergeben wird, dann wird der angegebene Python-Typ ignoriert.

  • Nullbarkeit - Das Konstrukt mapped_column() wird seine Column als NULL oder NOT NULL kennzeichnen, in erster Linie durch die Anwesenheit des Parameters mapped_column.nullable, der entweder als True oder False übergeben wird. Zusätzlich, wenn der Parameter mapped_column.primary_key vorhanden ist und auf True gesetzt ist, impliziert dies ebenfalls, dass die Spalte NOT NULL sein sollte.

    In Abwesenheit **beider** dieser Parameter wird die Anwesenheit von typing.Optional[] innerhalb der Mapped-Typannotation verwendet, um die Nullbarkeit zu bestimmen, wobei typing.Optional[] für NULL steht und die Abwesenheit von typing.Optional[] für NOT NULL steht. Wenn keine Mapped[]-Annotation vorhanden ist und kein Parameter mapped_column.nullable oder mapped_column.primary_key vorhanden ist, wird die übliche Standardeinstellung von SQLAlchemy für Column von NULL verwendet.

    Im folgenden Beispiel werden die Spalten id und data als NOT NULL und die Spalte additional_info als NULL behandelt.

    from typing import Optional
    
    from sqlalchemy.orm import DeclarativeBase
    from sqlalchemy.orm import Mapped
    from sqlalchemy.orm import mapped_column
    
    
    class Base(DeclarativeBase):
        pass
    
    
    class SomeClass(Base):
        __tablename__ = "some_table"
    
        # primary_key=True, therefore will be NOT NULL
        id: Mapped[int] = mapped_column(primary_key=True)
    
        # not Optional[], therefore will be NOT NULL
        data: Mapped[str]
    
        # Optional[], therefore will be NULL
        additional_info: Mapped[Optional[str]]

    Es ist auch vollkommen gültig, eine mapped_column() zu haben, deren Nullbarkeit **anders** ist als das, was die Annotation impliziert. Zum Beispiel kann ein ORM-zugeordnetes Attribut als zulässig für None innerhalb des Python-Codes annotiert sein, der mit dem Objekt arbeitet, während es gerade erstellt und befüllt wird. Der Wert wird jedoch letztendlich in eine Datenbankspalte geschrieben, die NOT NULL ist. Der Parameter mapped_column.nullable hat, wenn er vorhanden ist, immer Vorrang.

    class SomeClass(Base):
        # ...
    
        # will be String() NOT NULL, but can be None in Python
        data: Mapped[Optional[str]] = mapped_column(nullable=False)

    Ebenso kann ein Nicht-None-Attribut, das in eine Datenbankspalte geschrieben wird, die aus irgendeinem Grund auf Schemadebene NULL sein muss, mapped_column.nullable auf True gesetzt werden.

    class SomeClass(Base):
        # ...
    
        # will be String() NULL, but type checker will not expect
        # the attribute to be None
        data: Mapped[str] = mapped_column(nullable=True)

Anpassen der Typzuordnung

Die Zuordnung von Python-Typen zu SQLAlchemy TypeEngine-Typen, die im vorherigen Abschnitt beschrieben wurde, basiert standardmäßig auf einem fest kodierten Wörterbuch im Modul sqlalchemy.sql.sqltypes. Das registry-Objekt, das den deklarativen Zuordnungsprozess koordiniert, wird jedoch zuerst ein lokales, benutzerdefiniertes Wörterbuch von Typen konsultieren, das als Parameter registry.type_annotation_map übergeben werden kann, wenn das registry konstruiert wird, das mit der DeclarativeBase-Oberklasse beim ersten Gebrauch assoziiert werden kann.

Wenn wir beispielsweise den Datentyp BIGINT für int, den Datentyp TIMESTAMP mit timezone=True für datetime.datetime verwenden möchten und dann nur unter Microsoft SQL Server den Datentyp NVARCHAR für Python str verwenden möchten, könnten die Registry und die deklarative Basis wie folgt konfiguriert werden:

import datetime

from sqlalchemy import BIGINT, NVARCHAR, String, TIMESTAMP
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column


class Base(DeclarativeBase):
    type_annotation_map = {
        int: BIGINT,
        datetime.datetime: TIMESTAMP(timezone=True),
        str: String().with_variant(NVARCHAR, "mssql"),
    }


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    date: Mapped[datetime.datetime]
    status: Mapped[str]

Unten wird die für die obige Zuordnung generierte CREATE TABLE-Anweisung dargestellt, zuerst auf dem Microsoft SQL Server-Backend, was den NVARCHAR-Datentyp illustriert

>>> from sqlalchemy.schema import CreateTable
>>> from sqlalchemy.dialects import mssql, postgresql
>>> print(CreateTable(SomeClass.__table__).compile(dialect=mssql.dialect()))
CREATE TABLE some_table ( id BIGINT NOT NULL IDENTITY, date TIMESTAMP NOT NULL, status NVARCHAR(max) NOT NULL, PRIMARY KEY (id) )

Dann auf dem PostgreSQL-Backend, was TIMESTAMP WITH TIME ZONE illustriert

>>> print(CreateTable(SomeClass.__table__).compile(dialect=postgresql.dialect()))
CREATE TABLE some_table ( id BIGSERIAL NOT NULL, date TIMESTAMP WITH TIME ZONE NOT NULL, status VARCHAR NOT NULL, PRIMARY KEY (id) )

Durch die Verwendung von Methoden wie TypeEngine.with_variant() können wir eine Typzuordnung erstellen, die für verschiedene Backends angepasst ist, während wir weiterhin prägnante mapped_column()-Konfigurationen nur mit Annotationen verwenden können. Zwei weitere Ebenen der Python-Typenkonfigurierbarkeit sind über dies hinaus verfügbar und werden in den nächsten beiden Abschnitten beschrieben.

Union-Typen in der Typzuordnung

Geändert in Version 2.0.37: Die in diesem Abschnitt beschriebenen Funktionen wurden repariert und konsistent überarbeitet. Vor dieser Änderung wurden Union-Typen in type_annotation_map unterstützt, jedoch wies die Funktion inkonsistente Verhaltensweisen zwischen Union-Syntaxen und in der Behandlung von None auf. Stellen Sie sicher, dass SQLAlchemy auf dem neuesten Stand ist, bevor Sie versuchen, die in diesem Abschnitt beschriebenen Funktionen zu verwenden.

SQLAlchemy unterstützt die Zuordnung von Union-Typen innerhalb von type_annotation_map, um Datenbanktypen zuzuordnen, die mehrere Python-Typen unterstützen können, wie z. B. JSON oder JSONB.

from typing import Union
from sqlalchemy import JSON
from sqlalchemy.dialects import postgresql
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.schema import CreateTable

# new style Union using a pipe operator
json_list = list[int] | list[str]

# old style Union using Union explicitly
json_scalar = Union[float, str, bool]


class Base(DeclarativeBase):
    type_annotation_map = {
        json_list: postgresql.JSONB,
        json_scalar: JSON,
    }


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    list_col: Mapped[list[str] | list[int]]

    # uses JSON
    scalar_col: Mapped[json_scalar]

    # uses JSON and is also nullable=True
    scalar_col_nullable: Mapped[json_scalar | None]

    # these forms all use JSON as well due to the json_scalar entry
    scalar_col_newstyle: Mapped[float | str | bool]
    scalar_col_oldstyle: Mapped[Union[float, str, bool]]
    scalar_col_mixedstyle: Mapped[Optional[float | str | bool]]

Das obige Beispiel ordnet die Union von list[int] und list[str] dem Postgresql JSONB-Datentyp zu, während eine Union von float, str, bool dem JSON-Datentyp zugeordnet wird. Eine äquivalente Union, die im Mapped-Konstrukt angegeben ist, wird mit dem entsprechenden Eintrag in der Typzuordnung übereinstimmen.

Der Abgleich eines Union-Typs basiert auf dem Inhalt des Unions, unabhängig davon, wie die einzelnen Typen benannt sind, und schließt zusätzlich die Verwendung des None-Typs aus. Das heißt, json_scalar wird auch mit str | bool | float | None übereinstimmen. Er stimmt **nicht** mit einem Union überein, das eine Teilmenge oder Obermenge dieses Unions ist; das heißt, str | bool würde nicht übereinstimmen, ebenso wenig wie str | bool | float | int. Die einzelnen Inhalte des Unions, abgesehen von None, müssen exakt übereinstimmen.

Der Wert None ist niemals signifikant für den Abgleich von type_annotation_map zu Mapped, ist jedoch als Indikator für die Nullbarkeit der Column signifikant. Wenn None im Union vorhanden ist, entweder so, wie es in der Mapped-Konstruktion platziert wird. Wenn es in Mapped vorhanden ist, deutet es darauf hin, dass die Column nullbar wäre, in Abwesenheit spezifischerer Indikatoren. Diese Logik funktioniert genauso wie die Angabe eines Optional-Typs, wie unter mapped_column() leitet den Datentyp und die Nullbarkeit von der Mapped-Annotation ab beschrieben.

Die CREATE TABLE-Anweisung für das obige Mapping sieht wie folgt aus

>>> print(CreateTable(SomeClass.__table__).compile(dialect=postgresql.dialect()))
CREATE TABLE some_table ( id SERIAL NOT NULL, list_col JSONB NOT NULL, scalar_col JSON, scalar_col_not_null JSON NOT NULL, PRIMARY KEY (id) )

Während Union-Typen einen "lockeren" Abgleich verwenden, der auf jeder äquivalenten Menge von Subtypen basiert, gibt es im Python-Typensystem auch die Möglichkeit, "Typ-Aliase" zu erstellen, die als unterschiedliche Typen behandelt werden, die nicht äquivalent zu einem anderen Typ mit derselben Zusammensetzung sind. Die Integration dieser Typen in type_annotation_map wird im nächsten Abschnitt beschrieben: Unterstützung für Typ-Alias-Typen (definiert durch PEP 695) und NewType.

Unterstützung für Typ-Alias-Typen (definiert durch PEP 695) und NewType

Im Gegensatz zur Typenermittlung, die unter Union-Typen in der Typen-Map beschrieben wird, enthält das Python-Typensystem auch zwei Möglichkeiten, einen zusammengesetzten Typ auf formellere Weise zu erstellen, und zwar mit typing.NewType sowie mit dem type-Schlüsselwort, das in PEP 695 eingeführt wurde. Diese Typen verhalten sich anders als normale Typ-Aliase (d.h. Zuweisung eines Typs zu einem Variablennamen), und dieser Unterschied wird berücksichtigt, wie SQLAlchemy diese Typen aus der Typen-Map auflöst.

Geändert in Version 2.0.37: Die in diesem Abschnitt beschriebenen Verhaltensweisen für typing.NewType sowie PEP 695 type wurden formalisiert und korrigiert. Für "lockere" Abgleichmuster, die in einigen 2.0-Releases funktionierten, aber in SQLAlchemy 2.1 entfernt werden, werden nun Deprecation-Warnungen ausgegeben. Stellen Sie sicher, dass SQLAlchemy auf dem neuesten Stand ist, bevor Sie versuchen, die in diesem Abschnitt beschriebenen Funktionen zu verwenden.

Das Modul `typing` erlaubt die Erstellung von "neuen Typen" mit typing.NewType

from typing import NewType

nstr30 = NewType("nstr30", str)
nstr50 = NewType("nstr50", str)

Zusätzlich wurde in Python 3.12 mit PEP 695 eine neue Funktion eingeführt, die das type-Schlüsselwort zur Erreichung eines ähnlichen Ziels bietet; die Verwendung von type erzeugt ein Objekt, das in vielerlei Hinsicht typing.NewType ähnelt und intern als typing.TypeAliasType bezeichnet wird.

type SmallInt = int
type BigInt = int
type JsonScalar = str | float | bool | None

Für die Zwecke, wie SQLAlchemy diese Typobjekte behandelt, wenn sie für die SQL-Typenermittlung innerhalb von Mapped verwendet werden, ist es wichtig zu beachten, dass Python zwei äquivalente typing.TypeAliasType- oder typing.NewType-Objekte nicht als gleich betrachtet.

# two typing.NewType objects are not equal even if they are both str
>>> nstr50 == nstr30
False

# two TypeAliasType objects are not equal even if they are both int
>>> SmallInt == BigInt
False

# an equivalent union is not equal to JsonScalar
>>> JsonScalar == str | float | bool | None
False

Dies ist das gegenteilige Verhalten im Vergleich zu gewöhnlichen Unions, und es informiert das korrekte Verhalten für die type_annotation_map von SQLAlchemy. Bei Verwendung von typing.NewType oder PEP 695 type-Objekten wird erwartet, dass das Typobjekt explizit in der type_annotation_map vorhanden ist, damit es von einem Mapped-Typ abgeglichen werden kann, wobei dasselbe Objekt angegeben werden muss, damit ein Abgleich erfolgt (abgesehen davon, ob der Typ innerhalb von Mapped auch mit None unioniert ist). Dies unterscheidet sich vom Verhalten, das unter Union-Typen in der Typen-Map beschrieben wird, wo ein direkt referenziertes, einfaches Union mit anderen Unions basierend auf der Zusammensetzung, nicht auf der Objektidentität, eines bestimmten Typs in type_annotation_map übereinstimmt.

Im folgenden Beispiel haben die zusammengesetzten Typen für nstr30, nstr50, SmallInt, BigInt und JsonScalar keine Überschneidung miteinander und können innerhalb jeder Mapped-Konstruktion unterschiedlich benannt werden und sind alle explizit in type_annotation_map aufgeführt. Jede dieser Typen kann auch mit None unioniert oder als Optional[] deklariert werden, ohne die Suche zu beeinflussen, sondern nur die Spalten-Nullbarkeit abzuleiten.

from typing import NewType

from sqlalchemy import SmallInteger, BigInteger, JSON, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.schema import CreateTable

nstr30 = NewType("nstr30", str)
nstr50 = NewType("nstr50", str)
type SmallInt = int
type BigInt = int
type JsonScalar = str | float | bool | None


class TABase(DeclarativeBase):
    type_annotation_map = {
        nstr30: String(30),
        nstr50: String(50),
        SmallInt: SmallInteger,
        BigInteger: BigInteger,
        JsonScalar: JSON,
    }


class SomeClass(TABase):
    __tablename__ = "some_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    normal_str: Mapped[str]

    short_str: Mapped[nstr30]
    long_str_nullable: Mapped[nstr50 | None]

    small_int: Mapped[SmallInt]
    big_int: Mapped[BigInteger]
    scalar_col: Mapped[JsonScalar]

Eine CREATE TABLE-Anweisung für das obige Mapping veranschaulicht die verschiedenen Varianten von Ganzzahlen und Zeichenketten, die wir konfiguriert haben, und sieht so aus:

>>> print(CreateTable(SomeClass.__table__))
CREATE TABLE some_table ( id INTEGER NOT NULL, normal_str VARCHAR NOT NULL, short_str VARCHAR(30) NOT NULL, long_str_nullable VARCHAR(50), small_int SMALLINT NOT NULL, big_int BIGINT NOT NULL, scalar_col JSON, PRIMARY KEY (id) )

Hinsichtlich der Nullbarkeit enthält der Typ JsonScalar None in seiner Definition, was eine nullbare Spalte anzeigt. Ebenso wendet die Spalte long_str_nullable ein Union von None auf nstr50 an, was mit dem Typ nstr50 in der type_annotation_map übereinstimmt und gleichzeitig die Nullbarkeit auf die zugeordnete Spalte anwendet. Die anderen Spalten bleiben NOT NULL, da sie nicht als optional gekennzeichnet sind.

Zuordnung mehrerer Typkonfigurationen zu Python-Typen

Da einzelne Python-Typen mit TypeEngine-Konfigurationen beliebiger Art verknüpft werden können, indem der Parameter registry.type_annotation_map verwendet wird, besteht eine zusätzliche Möglichkeit, einen einzelnen Python-Typ mit verschiedenen Varianten eines SQL-Typs basierend auf zusätzlichen Typqualifizierern zu verknüpfen. Ein typisches Beispiel hierfür ist die Zuordnung des Python-Datentyps str zu VARCHAR-SQL-Typen unterschiedlicher Längen. Eine weitere ist die Zuordnung verschiedener Varianten von decimal.Decimal zu NUMERIC-Spalten unterschiedlicher Größe.

Das Python-Typsystem bietet eine großartige Möglichkeit, einem Python-Typ zusätzliche Metadaten hinzuzufügen, indem der PEP 593 Annotated generische Typ verwendet wird, der zusätzliche Informationen zusammen mit einem Python-Typ bündeln kann. Die mapped_column()-Konstruktion interpretiert ein Annotated-Objekt korrekt nach Identität, wenn es in der registry.type_annotation_map aufgelöst wird, wie im folgenden Beispiel, wo wir zwei Varianten von String und Numeric deklarieren.

from decimal import Decimal

from typing_extensions import Annotated

from sqlalchemy import Numeric
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import registry

str_30 = Annotated[str, 30]
str_50 = Annotated[str, 50]
num_12_4 = Annotated[Decimal, 12]
num_6_2 = Annotated[Decimal, 6]


class Base(DeclarativeBase):
    registry = registry(
        type_annotation_map={
            str_30: String(30),
            str_50: String(50),
            num_12_4: Numeric(12, 4),
            num_6_2: Numeric(6, 2),
        }
    )

Der Python-Typ, der dem Annotated-Container übergeben wird, im obigen Beispiel die Typen str und Decimal, ist nur für Typ-Tools wichtig; was die mapped_column()-Konstruktion betrifft, muss sie nur eine Suche jedes Typobjekts im Wörterbuch registry.type_annotation_map durchführen, ohne tatsächlich in das Annotated-Objekt zu schauen, zumindest in diesem speziellen Kontext. Ebenso sind die Argumente, die über den zugrunde liegenden Python-Typ selbst hinaus an Annotated übergeben werden, nicht wichtig; es ist nur wichtig, dass mindestens ein Argument vorhanden ist, damit die Annotated-Konstruktion gültig ist. Wir können dann diese erweiterten Typen direkt in unserem Mapping verwenden, wo sie mit den spezifischeren Typkonstruktionen abgeglichen werden, wie im folgenden Beispiel

class SomeClass(Base):
    __tablename__ = "some_table"

    short_name: Mapped[str_30] = mapped_column(primary_key=True)
    long_name: Mapped[str_50]
    num_value: Mapped[num_12_4]
    short_num_value: Mapped[num_6_2]

Eine CREATE TABLE-Anweisung für das obige Mapping veranschaulicht die verschiedenen Varianten von VARCHAR und NUMERIC, die wir konfiguriert haben, und sieht so aus:

>>> from sqlalchemy.schema import CreateTable
>>> print(CreateTable(SomeClass.__table__))
CREATE TABLE some_table ( short_name VARCHAR(30) NOT NULL, long_name VARCHAR(50) NOT NULL, num_value NUMERIC(12, 4) NOT NULL, short_num_value NUMERIC(6, 2) NOT NULL, PRIMARY KEY (short_name) )

Während die Vielfalt bei der Verknüpfung von Annotated-Typen mit verschiedenen SQL-Typen uns ein hohes Maß an Flexibilität verleiht, veranschaulicht der nächste Abschnitt eine zweite Möglichkeit, wie Annotated mit Declarative verwendet werden kann, die noch offener ist.

Zuordnung ganzer Spaltendeklarationen zu Python-Typen

Der vorherige Abschnitt zeigte die Verwendung von PEP 593 Annotated-Typinstanzen als Schlüssel im Wörterbuch registry.type_annotation_map. In dieser Form schaut die mapped_column()-Konstruktion tatsächlich nicht in das Annotated-Objekt selbst, sondern wird nur als Wörterbuchschlüssel verwendet. Declarative kann jedoch auch eine gesamte vorab eingerichtete mapped_column()-Konstruktion direkt aus einem Annotated-Objekt extrahieren. Mit dieser Form können wir nicht nur verschiedene Varianten von SQL-Datentypen definieren, die mit Python-Typen verknüpft sind, ohne das Wörterbuch registry.type_annotation_map zu verwenden, sondern auch beliebige Argumente wie Nullbarkeit, Spaltendefaultwerte und Einschränkungen wiederverwendbar einrichten.

Eine Reihe von ORM-Modellen wird normalerweise eine Art Primärschlüsselstil aufweisen, der für alle zugeordneten Klassen gemeinsam ist. Es können auch gemeinsame Spaltenkonfigurationen wie Zeitstempel mit Standardwerten und andere Felder mit vordefinierten Größen und Konfigurationen vorhanden sein. Wir können diese Konfigurationen in mapped_column()-Instanzen komponieren, die wir dann direkt in Instanzen von Annotated bündeln, die dann in beliebiger Anzahl von Klassendeklarationen wiederverwendet werden. Declarative wird ein Annotated-Objekt entpacken, wenn es auf diese Weise bereitgestellt wird, und alle anderen Direktiven überspringen, die nicht für SQLAlchemy gelten, und nur nach SQLAlchemy ORM-Konstrukten suchen.

Das folgende Beispiel veranschaulicht eine Vielzahl von vorkonfigurierten Feldtypen, die auf diese Weise verwendet werden, wobei wir intpk definieren, das für eine Integer-Primärschlüsselspalte steht, timestamp, das für einen DateTime-Typ steht, der CURRENT_TIMESTAMP als DDL-Level-Spaltendefault verwendet, und required_name, das eine String mit einer Länge von 30 ist, die NOT NULL ist.

import datetime

from typing_extensions import Annotated

from sqlalchemy import func
from sqlalchemy import String
from sqlalchemy.orm import mapped_column


intpk = Annotated[int, mapped_column(primary_key=True)]
timestamp = Annotated[
    datetime.datetime,
    mapped_column(nullable=False, server_default=func.CURRENT_TIMESTAMP()),
]
required_name = Annotated[str, mapped_column(String(30), nullable=False)]

Die obigen Annotated-Objekte können dann direkt innerhalb von Mapped verwendet werden, wo die vorkonfigurierten mapped_column()-Konstrukte extrahiert und in eine neue Instanz kopiert werden, die spezifisch für jedes Attribut ist.

class Base(DeclarativeBase):
    pass


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[intpk]
    name: Mapped[required_name]
    created_at: Mapped[timestamp]

CREATE TABLE für unser obiges Mapping sieht wie folgt aus:

>>> from sqlalchemy.schema import CreateTable
>>> print(CreateTable(SomeClass.__table__))
CREATE TABLE some_table ( id INTEGER NOT NULL, name VARCHAR(30) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY (id) )

Bei der Verwendung von Annotated-Typen auf diese Weise kann die Konfiguration des Typs auch pro Attribut beeinflusst werden. Für die Typen im obigen Beispiel, die die explizite Verwendung von mapped_column.nullable aufweisen, können wir den generischen Modifikator Optional[] auf jeden unserer Typen anwenden, damit das Feld auf Python-Ebene optional ist oder nicht, was unabhängig von der Einstellung NULL / NOT NULL ist, die in der Datenbank stattfindet.

from typing_extensions import Annotated

import datetime
from typing import Optional

from sqlalchemy.orm import DeclarativeBase

timestamp = Annotated[
    datetime.datetime,
    mapped_column(nullable=False),
]


class Base(DeclarativeBase):
    pass


class SomeClass(Base):
    # ...

    # pep-484 type will be Optional, but column will be
    # NOT NULL
    created_at: Mapped[Optional[timestamp]]

Die mapped_column()-Konstruktion wird auch mit einer explizit übergebenen mapped_column()-Konstruktion abgeglichen, deren Argumente Vorrang vor denen der Annotated-Konstruktion haben. Unten fügen wir eine ForeignKey-Einschränkung zu unserem ganzzahligen Primärschlüssel hinzu und verwenden auch einen alternativen Server-Default für die Spalte created_at.

import datetime

from typing_extensions import Annotated

from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.schema import CreateTable

intpk = Annotated[int, mapped_column(primary_key=True)]
timestamp = Annotated[
    datetime.datetime,
    mapped_column(nullable=False, server_default=func.CURRENT_TIMESTAMP()),
]


class Base(DeclarativeBase):
    pass


class Parent(Base):
    __tablename__ = "parent"

    id: Mapped[intpk]


class SomeClass(Base):
    __tablename__ = "some_table"

    # add ForeignKey to mapped_column(Integer, primary_key=True)
    id: Mapped[intpk] = mapped_column(ForeignKey("parent.id"))

    # change server default from CURRENT_TIMESTAMP to UTC_TIMESTAMP
    created_at: Mapped[timestamp] = mapped_column(server_default=func.UTC_TIMESTAMP())

Die CREATE TABLE-Anweisung veranschaulicht diese Attribut-spezifischen Einstellungen, fügt eine FOREIGN KEY-Einschränkung hinzu und ersetzt UTC_TIMESTAMP durch CURRENT_TIMESTAMP.

>>> from sqlalchemy.schema import CreateTable
>>> print(CreateTable(SomeClass.__table__))
CREATE TABLE some_table ( id INTEGER NOT NULL, created_at DATETIME DEFAULT UTC_TIMESTAMP() NOT NULL, PRIMARY KEY (id), FOREIGN KEY(id) REFERENCES parent (id) )

Hinweis

Die gerade beschriebene Funktion von mapped_column(), bei der ein vollständig konstruierter Satz von Spaltenargumenten mit PEP 593 Annotated-Objekten angegeben werden kann, die ein "Vorlage"-mapped_column()-Objekt enthalten, das in das Attribut kopiert werden soll, ist derzeit nicht für andere ORM-Konstrukte wie relationship() und composite() implementiert. Obwohl diese Funktionalität theoretisch möglich ist, führt der Versuch, Annotated zur Angabe weiterer Argumente für relationship() und ähnliche zu verwenden, derzeit zu einer NotImplementedError-Ausnahme zur Laufzeit, könnte aber in zukünftigen Versionen implementiert werden.

Verwendung von Python Enum oder pep-586 Literal-Typen in der Typen-Map

Neu in Version 2.0.0b4: - Enum-Unterstützung hinzugefügt

Neu in Version 2.0.1: - Literal-Unterstützung hinzugefügt

Benutzerdefinierte Python-Typen, die vom Python-Built-in-Typ enum.Enum abgeleitet sind, sowie von der Klasse typing.Literal, werden automatisch mit dem SQLAlchemy Enum-Datentyp verknüpft, wenn sie in einem ORM-Deklarativ-Mapping verwendet werden. Das folgende Beispiel verwendet eine benutzerdefinierte enum.Enum innerhalb des Mapped[]-Konstruktors.

import enum

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


class Base(DeclarativeBase):
    pass


class Status(enum.Enum):
    PENDING = "pending"
    RECEIVED = "received"
    COMPLETED = "completed"


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    status: Mapped[Status]

Im obigen Beispiel wird das zugeordnete Attribut SomeClass.status mit einer Column vom Datentyp Enum(Status) verknüpft. Dies können wir zum Beispiel in der CREATE TABLE-Ausgabe für die PostgreSQL-Datenbank sehen.

CREATE TYPE status AS ENUM ('PENDING', 'RECEIVED', 'COMPLETED')

CREATE TABLE some_table (
  id SERIAL NOT NULL,
  status status NOT NULL,
  PRIMARY KEY (id)
)

In ähnlicher Weise kann typing.Literal anstelle dessen verwendet werden, wobei ein typing.Literal verwendet wird, das ausschließlich aus Zeichenketten besteht.

from typing import Literal

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


class Base(DeclarativeBase):
    pass


Status = Literal["pending", "received", "completed"]


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    status: Mapped[Status]

Die in registry.type_annotation_map verwendeten Einträge verknüpfen den Basis-Python-Typ enum.Enum sowie den Typ typing.Literal mit dem SQLAlchemy Enum-SQL-Typ unter Verwendung einer speziellen Form, die dem Enum-Datentyp anzeigt, dass er sich automatisch gegen einen beliebigen aufgezählten Typ konfigurieren soll. Diese Konfiguration, die standardmäßig implizit ist, würde explizit wie folgt angegeben:

import enum
import typing

import sqlalchemy
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    type_annotation_map = {
        enum.Enum: sqlalchemy.Enum(enum.Enum),
        typing.Literal: sqlalchemy.Enum(enum.Enum),
    }

Die Auflösungslogik innerhalb von Declarative kann Unterklassen von enum.Enum sowie Instanzen von typing.Literal auflösen, um dem Eintrag enum.Enum oder typing.Literal im Wörterbuch registry.type_annotation_map zu entsprechen. Der Enum-SQL-Typ weiß dann, wie er eine konfigurierte Version von sich selbst mit den entsprechenden Einstellungen, einschließlich der Standard-Zeichenkettenlänge, erzeugen kann. Wenn ein typing.Literal übergeben wird, das nicht nur aus Zeichenkettenwerten besteht, wird eine informative Fehlermeldung ausgegeben.

typing.TypeAliasType kann auch zur Erstellung von Enums verwendet werden, indem es einem typing.Literal von Zeichenketten zugewiesen wird.

from typing import Literal

type Status = Literal["on", "off", "unknown"]

Da dies ein typing.TypeAliasType ist, repräsentiert es ein eindeutiges Typobjekt und muss daher in der type_annotation_map platziert werden, um erfolgreich nachgeschlagen zu werden, indexiert am Enum-Typ wie folgt:

import enum
import sqlalchemy


class Base(DeclarativeBase):
    type_annotation_map = {Status: sqlalchemy.Enum(enum.Enum)}

Da SQLAlchemy die Zuordnung verschiedener typing.TypeAliasType-Objekte unterstützt, die ansonsten strukturell äquivalent sind, müssen diese in type_annotation_map vorhanden sein, um Mehrdeutigkeiten zu vermeiden.

Native Enums und Benennung

Der Parameter Enum.native_enum gibt an, ob der Enum-Datentyp ein sogenanntes "natives" Enum erstellen soll, was bei MySQL/MariaDB das ENUM-Datentyp ist und bei PostgreSQL ein neues TYPE-Objekt ist, das mit CREATE TYPE erstellt wird, oder ein "nicht-natives" Enum, was bedeutet, dass VARCHAR zur Erstellung des Datentyps verwendet wird. Für Backends außer MySQL/MariaDB oder PostgreSQL wird in allen Fällen VARCHAR verwendet (Drittanbieter-Dialekte können eigene Verhaltensweisen haben).

Da PostgreSQLs CREATE TYPE erfordert, dass der zu erstellende Typ einen expliziten Namen hat, gibt es eine spezielle Fallback-Logik bei der Arbeit mit implizit generierten Enum, ohne einen expliziten Enum-Datentyp innerhalb eines Mappings anzugeben.

  1. Wenn das Enum mit einem enum.Enum-Objekt verknüpft ist, ist der Parameter Enum.native_enum standardmäßig auf True gesetzt und der Name des Enums wird vom Namen des enum.Enum-Datentyps übernommen. Das PostgreSQL-Backend geht von CREATE TYPE mit diesem Namen aus.

  2. Wenn das Enum mit einem typing.Literal-Objekt verknüpft ist, ist der Parameter Enum.native_enum standardmäßig auf False gesetzt; es wird kein Name generiert und VARCHAR wird angenommen.

Um typing.Literal mit einem PostgreSQL CREATE TYPE-Typ zu verwenden, muss ein explizites Enum verwendet werden, entweder innerhalb der Typen-Map

import enum
import typing

import sqlalchemy
from sqlalchemy.orm import DeclarativeBase

Status = Literal["pending", "received", "completed"]


class Base(DeclarativeBase):
    type_annotation_map = {
        Status: sqlalchemy.Enum("pending", "received", "completed", name="status_enum"),
    }

Oder alternativ innerhalb von mapped_column()

import enum
import typing

import sqlalchemy
from sqlalchemy.orm import DeclarativeBase

Status = Literal["pending", "received", "completed"]


class Base(DeclarativeBase):
    pass


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    status: Mapped[Status] = mapped_column(
        sqlalchemy.Enum("pending", "received", "completed", name="status_enum")
    )
Änderung der Konfiguration des Standard-Enums

Um die feste Konfiguration des implizit generierten Enum-Datentyps zu ändern, geben Sie neue Einträge in der registry.type_annotation_map an, die zusätzliche Argumente angeben. Um beispielsweise "nicht-native Enumerationen" bedingungslos zu verwenden, kann der Parameter Enum.native_enum für alle Typen auf False gesetzt werden.

import enum
import typing
import sqlalchemy
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    type_annotation_map = {
        enum.Enum: sqlalchemy.Enum(enum.Enum, native_enum=False),
        typing.Literal: sqlalchemy.Enum(enum.Enum, native_enum=False),
    }

Geändert in Version 2.0.1: Unterstützung für das Überschreiben von Parametern wie Enum.native_enum innerhalb des Enum-Datentyps bei der Einrichtung der registry.type_annotation_map implementiert. Zuvor funktionierte diese Funktionalität nicht.

Um eine spezifische Konfiguration für einen bestimmten enum.Enum-Subtyp zu verwenden, z.B. die Zeichenkettenlänge auf 50 zu setzen, wenn der Beispiel-Datentyp Status verwendet wird.

import enum
import sqlalchemy
from sqlalchemy.orm import DeclarativeBase


class Status(enum.Enum):
    PENDING = "pending"
    RECEIVED = "received"
    COMPLETED = "completed"


class Base(DeclarativeBase):
    type_annotation_map = {
        Status: sqlalchemy.Enum(Status, length=50, native_enum=False)
    }

Standardmäßig sind Enum, die automatisch generiert werden, nicht mit der MetaData-Instanz verknüpft, die von Base verwendet wird. Wenn die Metadaten ein Schema definieren, wird dieses also nicht automatisch mit dem Enum verknüpft. Um das Enum automatisch mit dem Schema in den Metadaten oder der Tabelle, zu der es gehört, zu verknüpfen, kann Enum.inherit_schema gesetzt werden.

from enum import Enum
import sqlalchemy as sa
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    metadata = sa.MetaData(schema="my_schema")
    type_annotation_map = {Enum: sa.Enum(Enum, inherit_schema=True)}
Verknüpfung spezifischer enum.Enum- oder typing.Literal-Typen mit anderen Datentypen

Die obigen Beispiele zeigen die Verwendung einer Enum, die sich automatisch an die Argumente / Attribute eines enum.Enum oder typing.Literal Typs konfiguriert. Für Anwendungsfälle, bei denen bestimmte Arten von enum.Enum oder typing.Literal mit anderen Typen verknüpft werden sollen, können diese spezifischen Typen auch in der Typzuordnung platziert werden. Im folgenden Beispiel wird ein Eintrag für Literal[], der Nicht-String-Typen enthält, mit dem JSON-Datentyp verknüpft.

from typing import Literal

from sqlalchemy import JSON
from sqlalchemy.orm import DeclarativeBase

my_literal = Literal[0, 1, True, False, "true", "false"]


class Base(DeclarativeBase):
    type_annotation_map = {my_literal: JSON}

In der obigen Konfiguration wird der Datentyp my_literal zu einer JSON-Instanz aufgelöst. Andere Literal-Varianten werden weiterhin zu Enum-Datentypen aufgelöst.

Dataclass-Funktionen in mapped_column()

Die Konstruktion mapped_column() integriert sich in die "nativen Dataclasses"-Funktion von SQLAlchemy, die unter Deklaratives Dataclass-Mapping beschrieben wird. Weitere Anweisungen, die von mapped_column() unterstützt werden, finden Sie in diesem Abschnitt.

Zugriff auf Tabellen und Metadaten

Eine deklarativ gemappte Klasse enthält immer ein Attribut namens __table__; wenn die obige Konfiguration mit __tablename__ abgeschlossen ist, macht der deklarative Prozess die Table über das Attribut __table__ verfügbar.

# access the Table
user_table = User.__table__

Die obige Tabelle ist letztendlich dieselbe, die dem Attribut Mapper.local_table entspricht, was wir über das Laufzeit-Inspektionssystem sehen können.

from sqlalchemy import inspect

user_table = inspect(User).local_table

Die MetaData-Sammlung, die sowohl der deklarativen registry als auch der Basisklasse zugeordnet ist, ist häufig erforderlich, um DDL-Operationen wie CREATE auszuführen, sowie in Verbindung mit Migrationswerkzeugen wie Alembic. Dieses Objekt ist über das Attribut .metadata der registry sowie der deklarativen Basisklasse verfügbar. Unten, für ein kleines Skript, möchten wir möglicherweise ein CREATE für alle Tabellen gegen eine SQLite-Datenbank ausgeben.

engine = create_engine("sqlite://")

Base.metadata.create_all(engine)

Deklarative Tabellenkonfiguration

Bei Verwendung der deklarativen Tabellenkonfiguration mit dem deklarativen Klassenattribut __tablename__ sollten zusätzliche Argumente, die dem Konstruktor Table übergeben werden sollen, über das deklarative Klassenattribut __table_args__ bereitgestellt werden.

Dieses Attribut unterstützt sowohl Positions- als auch Schlüsselwortargumente, die normalerweise an den Konstruktor Table übergeben werden. Das Attribut kann in einer von zwei Formen angegeben werden. Eine ist als Wörterbuch.

class MyClass(Base):
    __tablename__ = "sometable"
    __table_args__ = {"mysql_engine": "InnoDB"}

Die andere, ein Tupel, bei dem jedes Argument ein Positionsargument ist (normalerweise Einschränkungen).

class MyClass(Base):
    __tablename__ = "sometable"
    __table_args__ = (
        ForeignKeyConstraint(["id"], ["remote_table.id"]),
        UniqueConstraint("foo"),
    )

Schlüsselwortargumente können mit der obigen Form angegeben werden, indem das letzte Argument als Wörterbuch angegeben wird.

class MyClass(Base):
    __tablename__ = "sometable"
    __table_args__ = (
        ForeignKeyConstraint(["id"], ["remote_table.id"]),
        UniqueConstraint("foo"),
        {"autoload": True},
    )

Eine Klasse kann auch das deklarative Attribut __table_args__ sowie das Attribut __tablename__ in einem dynamischen Stil unter Verwendung der Methode declared_attr() angeben. Weitere Informationen finden Sie unter Zusammensetzen von gemappten Hierarchien mit Mixins.

Expliziter Schemaname mit deklarativer Tabelle

Der Schemaname für eine Table, wie unter Festlegen des Schemas-Namens dokumentiert, wird auf eine einzelne Table unter Verwendung des Arguments Table.schema angewendet. Bei Verwendung von deklarativen Tabellen wird diese Option wie jede andere an das Wörterbuch __table_args__ übergeben.

from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


class MyClass(Base):
    __tablename__ = "sometable"
    __table_args__ = {"schema": "some_schema"}

Der Schemaname kann auch global auf alle Table-Objekte angewendet werden, indem der Parameter MetaData.schema verwendet wird, wie unter Festlegen eines Standard-Schemas-Namens mit MetaData dokumentiert. Das MetaData-Objekt kann separat erstellt und einer DeclarativeBase-Unterklasse zugeordnet werden, indem direkt dem Attribut metadata zugewiesen wird.

from sqlalchemy import MetaData
from sqlalchemy.orm import DeclarativeBase

metadata_obj = MetaData(schema="some_schema")


class Base(DeclarativeBase):
    metadata = metadata_obj


class MyClass(Base):
    # will use "some_schema" by default
    __tablename__ = "sometable"

Festlegen von Lade- und Persistenzoptionen für deklarativ gemappte Spalten

Die Konstruktion mapped_column() akzeptiert zusätzliche ORM-spezifische Argumente, die beeinflussen, wie die generierte Column gemappt wird und sich auf ihr Lade- und Persistenzverhalten auswirken. Häufig verwendete Optionen sind:

  • verzögertes Spaltenladen – Das boolesche mapped_column.deferred etabliert die Column standardmäßig mit verzögertem Spaltenladen. Im folgenden Beispiel wird die Spalte User.bio standardmäßig nicht geladen, sondern nur, wenn darauf zugegriffen wird.

    class User(Base):
        __tablename__ = "user"
    
        id: Mapped[int] = mapped_column(primary_key=True)
        name: Mapped[str]
        bio: Mapped[str] = mapped_column(Text, deferred=True)

    Siehe auch

    Begrenzen, welche Spalten mit verzögertem Spaltenladen geladen werden – Vollständige Beschreibung des verzögerten Spaltenladens.

  • aktive Historiemapped_column.active_history stellt sicher, dass bei Änderung des Attributwerts der vorherige Wert geladen wurde und Teil der AttributeState.history-Sammlung ist, wenn die Historie des Attributs inspiziert wird. Dies kann zusätzliche SQL-Anweisungen verursachen.

    class User(Base):
        __tablename__ = "user"
    
        id: Mapped[int] = mapped_column(primary_key=True)
        important_identifier: Mapped[str] = mapped_column(active_history=True)

Eine Liste der unterstützten Parameter finden Sie im Docstring von mapped_column().

Siehe auch

Anwenden von Lade-, Persistenz- und Mapping-Optionen für imperative Tabellenspalten – beschreibt die Verwendung von column_property() und deferred() für die Verwendung mit imperativer Tabellenkonfiguration.

Explizites Benennen deklarativ gemappter Spalten

Alle bisherigen Beispiele zeigen die Konstruktion mapped_column(), die mit einem ORM-gemappten Attribut verknüpft ist, wobei der Python-Attributname, der der mapped_column() gegeben wird, auch der Spaltenname ist, wie wir in CREATE TABLE-Anweisungen und Abfragen sehen. Der Name einer Spalte, wie er in SQL ausgedrückt wird, kann durch Übergabe des Positionsarguments als Zeichenkette mapped_column.__name als erstes Positionsargument angegeben werden. Im folgenden Beispiel wird die Klasse User mit alternativen Namen für die Spalten selbst gemappt.

class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column("user_id", primary_key=True)
    name: Mapped[str] = mapped_column("user_name")

Wo oben User.id zu einer Spalte namens user_id aufgelöst wird und User.name zu einer Spalte namens user_name aufgelöst wird. Wir können eine select()-Anweisung mit unseren Python-Attributnamen schreiben und sehen die generierten SQL-Namen.

>>> from sqlalchemy import select
>>> print(select(User.id, User.name).where(User.name == "x"))
SELECT "user".user_id, "user".user_name FROM "user" WHERE "user".user_name = :user_name_1

Siehe auch

Alternative Attributnamen für das Mapping von Tabellenspalten – gilt für die imperative Tabelle.

Anhängen zusätzlicher Spalten an eine bestehende deklarativ gemappte Klasse

Eine deklarative Tabellenkonfiguration ermöglicht das Hinzufügen neuer Column-Objekte zu einem bestehenden Mapping, nachdem die Table-Metadaten bereits generiert wurden.

Für eine deklarative Klasse, die unter Verwendung einer deklarativen Basisklasse deklariert wird, enthält die zugrunde liegende Metaklasse DeclarativeMeta eine Methode __setattr__(), die zusätzliche mapped_column()- oder Core-Column-Objekte abfängt und sie sowohl zur Table mittels Table.append_column() als auch zum bestehenden Mapper mittels Mapper.add_property() hinzufügt.

MyClass.some_new_column = mapped_column(String)

Verwendung von Core Column

MyClass.some_new_column = Column(String)

Alle Argumente werden unterstützt, einschließlich eines alternativen Namens, wie z. B. MyClass.some_new_column = mapped_column("some_name", String). Der SQL-Typ muss jedoch explizit an die mapped_column() oder das Column-Objekt übergeben werden, wie in den obigen Beispielen, wo der String-Typ übergeben wird. Der Mapped-Annotationstyp kann keine Rolle bei der Operation spielen.

Zusätzliche Column-Objekte können auch in dem speziellen Fall der Single Table Inheritance einem Mapping hinzugefügt werden, bei dem Unterklassen, die keine eigene Table haben, zusätzliche Spalten aufweisen. Dies wird im Abschnitt Single Table Inheritance veranschaulicht.

Hinweis

Die Zuweisung von gemappten Eigenschaften zu einer bereits gemappten Klasse funktioniert nur dann korrekt, wenn die „deklarative Basisklasse“ verwendet wird, d. h. die benutzerdefinierte Unterklasse von DeclarativeBase oder die dynamisch generierte Klasse, die von declarative_base() oder registry.generate_base() zurückgegeben wird. Diese „Basisklasse“ enthält eine Python-Metaklasse, die eine spezielle Methode __setattr__() implementiert, die diese Operationen abfängt.

Laufzeit-Zuweisung von klassengebildeten Attributen zu einer gemappten Klasse funktioniert **nicht**, wenn die Klasse mit Dekoratoren wie registry.mapped() oder imperativen Funktionen wie registry.map_imperatively() gemappt wird.

Deklarativ mit imperativer Tabelle (auch bekannt als Hybrid-Deklarativ)

Deklarative Mappings können auch mit einem bereits existierenden Table-Objekt oder anderweitig einer Table oder einer anderen beliebigen FromClause-Konstruktion (wie z. B. einem Join oder Subquery), die separat konstruiert wird, versehen werden.

Dies wird als "hybrid-deklaratives" Mapping bezeichnet, da die Klasse für alles, was die Mapper-Konfiguration betrifft, im deklarativen Stil gemappt wird, jedoch das gemappte Table-Objekt separat erstellt und direkt an den deklarativen Prozess übergeben wird.

from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


# construct a Table directly.  The Base.metadata collection is
# usually a good choice for MetaData but any MetaData
# collection may be used.

user_table = Table(
    "user",
    Base.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String),
    Column("fullname", String),
    Column("nickname", String),
)


# construct the User class using this table.
class User(Base):
    __table__ = user_table

Oben wird ein Table-Objekt mit dem unter Datenbanken mit MetaData beschreiben beschriebenen Ansatz konstruiert. Es kann dann direkt auf eine Klasse angewendet werden, die deklarativ gemappt wird. Die deklarativen Klassenattribute __tablename__ und __table_args__ werden in dieser Form nicht verwendet. Die obige Konfiguration ist oft als Inline-Definition lesbarer.

class User(Base):
    __table__ = Table(
        "user",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String),
        Column("fullname", String),
        Column("nickname", String),
    )

Eine natürliche Folge des obigen Stils ist, dass das Attribut __table__ selbst im Klassendefinitionsblock definiert ist. Daher kann es in nachfolgenden Attributen sofort referenziert werden, wie im folgenden Beispiel, das das Referenzieren der Spalte type in einer polymorphen Mapper-Konfiguration veranschaulicht.

class Person(Base):
    __table__ = Table(
        "person",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String(50)),
        Column("type", String(50)),
    )

    __mapper_args__ = {
        "polymorphic_on": __table__.c.type,
        "polymorphic_identity": "person",
    }

Die Form "imperative Tabelle" wird auch verwendet, wenn eine Nicht-Table-Konstruktion, wie z. B. ein Join- oder Subquery-Objekt, gemappt werden soll. Ein Beispiel unten.

from sqlalchemy import func, select

subq = (
    select(
        func.count(orders.c.id).label("order_count"),
        func.max(orders.c.price).label("highest_order"),
        orders.c.customer_id,
    )
    .group_by(orders.c.customer_id)
    .subquery()
)

customer_select = (
    select(customers, subq)
    .join_from(customers, subq, customers.c.id == subq.c.customer_id)
    .subquery()
)


class Customer(Base):
    __table__ = customer_select

Hintergrundinformationen zum Mapping auf Nicht-Table-Konstruktionen finden Sie in den Abschnitten Mapping einer Klasse gegen mehrere Tabellen und Mapping einer Klasse gegen beliebige Subqueries.

Die Form "imperative Tabelle" ist besonders nützlich, wenn die Klasse selbst eine alternative Form der Attributdeklaration verwendet, wie z. B. Python Dataclasses. Weitere Details finden Sie im Abschnitt Anwenden von ORM-Mappings auf eine bestehende Dataclass (Legacy-Dataclass-Verwendung).

Alternative Attributnamen für das Mapping von Tabellenspalten

Der Abschnitt Explizites Benennen deklarativ gemappter Spalten zeigte, wie mapped_column() verwendet werden kann, um einen spezifischen Namen für das generierte Column-Objekt zu vergeben, getrennt vom Attributnamen, unter dem es gemappt wird.

Bei Verwendung der imperativen Tabellenkonfiguration haben wir bereits vorhandene Column-Objekte. Um diese zu alternativen Namen zu mappen, können wir die Column-Objekte direkt den gewünschten Attributen zuweisen.

user_table = Table(
    "user",
    Base.metadata,
    Column("user_id", Integer, primary_key=True),
    Column("user_name", String),
)


class User(Base):
    __table__ = user_table

    id = user_table.c.user_id
    name = user_table.c.user_name

Das obige User-Mapping wird auf die Spalten "user_id" und "user_name" über die Attribute User.id und User.name verweisen, auf die gleiche Weise wie im Abschnitt Explizites Benennen deklarativ gemappter Spalten gezeigt.

Eine Einschränkung des obigen Mappings ist, dass die direkte Inline-Verknüpfung mit Column bei Verwendung von PEP 484-Typwerkzeugen nicht korrekt typisiert wird. Eine Strategie zur Lösung dieses Problems besteht darin, die Column-Objekte innerhalb der Funktion column_property() anzuwenden; obwohl der Mapper dieses Eigenschaftsobjekt für seine interne Verwendung bereits automatisch generiert, können Typwerkzeuge durch Benennung im Klassen-Deklaration das Attribut mit der Mapped-Annotation abgleichen.

from sqlalchemy.orm import column_property
from sqlalchemy.orm import Mapped


class User(Base):
    __table__ = user_table

    id: Mapped[int] = column_property(user_table.c.user_id)
    name: Mapped[str] = column_property(user_table.c.user_name)

Siehe auch

Explizites Benennen deklarativ gemappter Spalten – gilt für die deklarative Tabelle.

Anwenden von Lade-, Persistenz- und Mapping-Optionen für imperative Tabellenspalten

Der Abschnitt Festlegen von Lade- und Persistenzoptionen für deklarativ gemappte Spalten untersuchte, wie Lade- und Persistenzoptionen bei der Verwendung der Konstruktion mapped_column() mit deklarativer Tabellenkonfiguration gesetzt werden. Bei der Verwendung der imperativen Tabellenkonfiguration haben wir bereits vorhandene Column-Objekte, die gemappt werden. Um diese Column-Objekte zusammen mit zusätzlichen Parametern, die spezifisch für das ORM-Mapping sind, zu mappen, können wir die Konstruktionen column_property() und deferred() verwenden, um zusätzliche Parameter der Spalte zuzuordnen. Optionen umfassen:

  • verzögertes Spaltenladen – Die Funktion deferred() ist eine Kurzform für den Aufruf von column_property() mit dem Parameter column_property.deferred auf True gesetzt; diese Konstruktion etabliert die Column standardmäßig mit verzögertem Spaltenladen. Im folgenden Beispiel wird die Spalte User.bio standardmäßig nicht geladen, sondern nur, wenn darauf zugegriffen wird.

    from sqlalchemy.orm import deferred
    
    user_table = Table(
        "user",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String),
        Column("bio", Text),
    )
    
    
    class User(Base):
        __table__ = user_table
    
        bio = deferred(user_table.c.bio)

Siehe auch

Begrenzen, welche Spalten mit verzögertem Spaltenladen geladen werden – Vollständige Beschreibung des verzögerten Spaltenladens.

  • aktive Historiecolumn_property.active_history stellt sicher, dass bei Änderung des Attributwerts der vorherige Wert geladen wurde und Teil der AttributeState.history-Sammlung ist, wenn die Historie des Attributs inspiziert wird. Dies kann zusätzliche SQL-Anweisungen verursachen.

    from sqlalchemy.orm import deferred
    
    user_table = Table(
        "user",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("important_identifier", String),
    )
    
    
    class User(Base):
        __table__ = user_table
    
        important_identifier = column_property(
            user_table.c.important_identifier, active_history=True
        )

Siehe auch

Die Konstruktion column_property() ist auch wichtig für Fälle, in denen Klassen auf alternative FROM-Klauseln wie Joins und Selects gemappt werden. Weitere Hintergrundinformationen zu diesen Fällen finden Sie unter:

Für die deklarative Tabellenkonfiguration mit mapped_column() sind die meisten Optionen direkt verfügbar; siehe den Abschnitt Festlegen von Lade- und Persistenzoptionen für deklarativ gemappte Spalten für Beispiele.

Deklaratives Mapping mit reflektierten Tabellen

Es gibt verschiedene Muster, die die Erzeugung von gemappten Klassen für eine Reihe von Table-Objekten ermöglichen, die aus der Datenbank introspektiert wurden, unter Verwendung des Reflexionsprozesses, der unter Reflektieren von Datenbankobjekten beschrieben wird.

Ein einfacher Weg, eine Klasse auf eine aus der Datenbank reflektierte Tabelle abzubilden, ist die Verwendung einer deklarativen Hybrid-Abbildung, bei der der Parameter Table.autoload_with dem Konstruktor für Table übergeben wird.

from sqlalchemy import create_engine
from sqlalchemy import Table
from sqlalchemy.orm import DeclarativeBase

engine = create_engine("postgresql+psycopg2://user:pass@hostname/my_existing_database")


class Base(DeclarativeBase):
    pass


class MyClass(Base):
    __table__ = Table(
        "mytable",
        Base.metadata,
        autoload_with=engine,
    )

Eine Variante des obigen Musters, die für viele Tabellen skaliert, ist die Verwendung der Methode MetaData.reflect(), um einen vollständigen Satz von Table-Objekten auf einmal zu reflektieren und sie dann aus der MetaData zu referenzieren.

from sqlalchemy import create_engine
from sqlalchemy import Table
from sqlalchemy.orm import DeclarativeBase

engine = create_engine("postgresql+psycopg2://user:pass@hostname/my_existing_database")


class Base(DeclarativeBase):
    pass


Base.metadata.reflect(engine)


class MyClass(Base):
    __table__ = Base.metadata.tables["mytable"]

Ein Vorbehalt bei der Herangehensweise, __table__ zu verwenden, ist, dass die abgebildeten Klassen nicht deklariert werden können, bis die Tabellen reflektiert wurden, was die Datenbankverbindungsquelle während der Deklaration der Anwendungsklassen erfordert. Typischerweise werden Klassen bei der Importierung der Module einer Anwendung deklariert, aber die Datenbankverbindung ist erst verfügbar, wenn die Anwendung Code ausführt, um Konfigurationsinformationen zu verarbeiten und eine Engine zu erstellen. Derzeit gibt es zwei Ansätze, um dies zu umgehen, die in den nächsten beiden Abschnitten beschrieben werden.

Verwendung von DeferredReflection

Um den Anwendungsfall der Deklaration von abgebildeten Klassen zu unterstützen, bei denen die Reflexion von Tabellenmetadaten nachträglich erfolgen kann, steht eine einfache Erweiterung namens Mixin DeferredReflection zur Verfügung, die den deklarativen Abbildungsprozess so ändert, dass er verzögert wird, bis eine spezielle Klassenmethode DeferredReflection.prepare() aufgerufen wird. Diese Methode führt den Reflexionsprozess gegen eine Zieldatenbank durch und integriert die Ergebnisse in den deklarativen Tabellenabbildungsprozess, d. h. Klassen, die das Attribut __tablename__ verwenden.

from sqlalchemy.ext.declarative import DeferredReflection
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


class Reflected(DeferredReflection):
    __abstract__ = True


class Foo(Reflected, Base):
    __tablename__ = "foo"
    bars = relationship("Bar")


class Bar(Reflected, Base):
    __tablename__ = "bar"

    foo_id = mapped_column(Integer, ForeignKey("foo.id"))

Oben erstellen wir eine Mixin-Klasse Reflected, die als Basis für Klassen in unserer deklarativen Hierarchie dient, die abgebildet werden sollen, wenn die Methode Reflected.prepare aufgerufen wird. Die obige Abbildung ist nicht vollständig, bis wir dies tun, gegeben eine Engine.

engine = create_engine("postgresql+psycopg2://user:pass@hostname/my_existing_database")
Reflected.prepare(engine)

Der Zweck der Klasse Reflected ist die Definition des Umfangs, in dem Klassen reflexiv abgebildet werden sollen. Das Plugin durchsucht den Unterklassenbaum des Ziels, gegen das .prepare() aufgerufen wird, und reflektiert alle Tabellen, die von deklarierten Klassen benannt werden. Tabellen in der Zieldatenbank, die nicht Teil von Abbildungen sind und nicht über Fremdschlüsselbeziehungen mit den Zieltabellen verbunden sind, werden nicht reflektiert.

Verwendung von Automap

Eine stärker automatisierte Lösung für die Abbildung auf eine vorhandene Datenbank, bei der Tabellenreflexion verwendet werden soll, ist die Verwendung der Erweiterung Automap. Diese Erweiterung generiert ganze abgebildete Klassen aus einem Datenbankschema, einschließlich Beziehungen zwischen Klassen, basierend auf beobachteten Fremdschlüsselbeschränkungen. Obwohl sie Hooks zur Anpassung enthält, wie z. B. Hooks, die benutzerdefinierte Klassennamen und Beziehungsnamensschemata zulassen, ist Automap auf einen schnellen Null-Konfigurationsstil ausgerichtet. Wenn eine Anwendung ein vollständig explizites Modell wünscht, das Tabellenreflexion nutzt, ist die Klasse DeferredReflection aufgrund ihres weniger automatisierten Ansatzes möglicherweise vorzuziehen.

Siehe auch

Automap

Automatisierung von Spaltenbenennungsschemata aus reflektierten Tabellen

Bei der Verwendung einer der vorherigen Reflexionstechniken haben wir die Möglichkeit, das Benennungsschema zu ändern, nach dem Spalten abgebildet werden. Das Objekt Column enthält einen Parameter Column.key, der ein Zeichenkettenname ist und bestimmt, unter welchem Namen diese Column in der Table.c-Sammlung vorhanden sein wird, unabhängig vom SQL-Namen der Spalte. Dieser Schlüssel wird auch von Mapper als Attributname verwendet, unter dem die Column abgebildet wird, wenn sie nicht auf andere Weise geliefert wird, wie z. B. die unter Alternative Attributnamen für die Abbildung von Tabellenspalten gezeigte Methode.

Wenn wir mit Tabellenreflexion arbeiten, können wir die Parameter abfangen, die für Column verwendet werden, während sie empfangen werden, mithilfe des Ereignisses DDLEvents.column_reflect() und die erforderlichen Änderungen vornehmen, einschließlich des Attributs .key, aber auch Dinge wie Datentypen.

Der Event-Hook kann am einfachsten mit dem verwendeten MetaData-Objekt verknüpft werden, wie unten gezeigt.

from sqlalchemy import event
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


@event.listens_for(Base.metadata, "column_reflect")
def column_reflect(inspector, table, column_info):
    # set column.key = "attr_<lower_case_name>"
    column_info["key"] = "attr_%s" % column_info["name"].lower()

Mit dem obigen Ereignis wird die Reflexion von Column-Objekten mit unserem Ereignis abgefangen, das ein neues ".key"-Element hinzufügt, wie in einer Abbildung wie folgt.

class MyClass(Base):
    __table__ = Table("some_table", Base.metadata, autoload_with=some_engine)

Der Ansatz funktioniert sowohl mit der Basisklasse DeferredReflection als auch mit der Erweiterung Automap. Speziell für Automap siehe den Abschnitt Abfangen von Spaltendefinitionen für Hintergrundinformationen.

Abbildung auf einen expliziten Satz von Primärschlüsselspalten

Das Konstrukt Mapper erfordert, um eine Tabelle erfolgreich abzubilden, immer, dass mindestens eine Spalte als "Primärschlüssel" für diese auswählbare Spalte identifiziert wird. Dies geschieht, damit ein ORM-Objekt beim Laden oder Speichern im Identitäts-Map mit einem entsprechenden Identitäts-Schlüssel platziert werden kann.

In Fällen, in denen eine reflektierte Tabelle, die abgebildet werden soll, keine Primärschlüsselbeschränkung enthält, sowie im allgemeinen Fall für Abbildung auf beliebige auswählbare Elemente, bei denen keine Primärschlüsselspalten vorhanden sein mögen, wird der Parameter Mapper.primary_key bereitgestellt, damit jede Spaltengruppe als "Primärschlüssel" für die Tabelle konfiguriert werden kann, soweit es die ORM-Abbildung betrifft.

Gegeben das folgende Beispiel einer imperativen Tabellenabbildung auf ein vorhandenes Table-Objekt, bei dem die Tabelle keinen deklarierten Primärschlüssel hat (wie es in Reflexionsszenarien vorkommen kann), können wir eine solche Tabelle wie im folgenden Beispiel abbilden.

from sqlalchemy import Column
from sqlalchemy import MetaData
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy import UniqueConstraint
from sqlalchemy.orm import DeclarativeBase


metadata = MetaData()
group_users = Table(
    "group_users",
    metadata,
    Column("user_id", String(40), nullable=False),
    Column("group_id", String(40), nullable=False),
    UniqueConstraint("user_id", "group_id"),
)


class Base(DeclarativeBase):
    pass


class GroupUsers(Base):
    __table__ = group_users
    __mapper_args__ = {"primary_key": [group_users.c.user_id, group_users.c.group_id]}

Oben ist die Tabelle group_users eine Art Assoziationstabelle mit String-Spalten user_id und group_id, aber kein Primärschlüssel ist eingerichtet. Stattdessen gibt es nur eine UniqueConstraint, die festlegt, dass die beiden Spalten einen eindeutigen Schlüssel darstellen. Der Mapper inspiziert eindeutige Einschränkungen nicht automatisch auf Primärschlüssel. Stattdessen nutzen wir den Parameter Mapper.primary_key und übergeben eine Sammlung von [group_users.c.user_id, group_users.c.group_id], was angibt, dass diese beiden Spalten verwendet werden sollen, um den Identitätsschlüssel für Instanzen der Klasse GroupUsers zu konstruieren.

Abbildung einer Teilmenge von Tabellenspalten

Manchmal kann die Tabellenreflexion eine Table mit vielen Spalten liefern, die für unsere Bedürfnisse nicht wichtig sind und sicher ignoriert werden können. Für eine solche Tabelle mit vielen Spalten, die nicht in der Anwendung referenziert werden müssen, können die Parameter Mapper.include_properties oder Mapper.exclude_properties eine Teilmenge von abzubildenden Spalten angeben, wobei andere Spalten der Ziel- Table vom ORM in keiner Weise berücksichtigt werden. Beispiel.

class User(Base):
    __table__ = user_table
    __mapper_args__ = {"include_properties": ["user_id", "user_name"]}

Im obigen Beispiel wird die Klasse User auf die Tabelle user_table abgebildet und enthält nur die Spalten user_id und user_name - der Rest wird nicht referenziert.

Ebenso

class Address(Base):
    __table__ = address_table
    __mapper_args__ = {"exclude_properties": ["street", "city", "state", "zip"]}

bildet die Klasse Address auf die Tabelle address_table ab, einschließlich aller vorhandenen Spalten außer street, city, state und zip.

Wie in den beiden Beispielen angegeben, können Spalten entweder anhand ihres Zeichenkettennamens oder durch Bezugnahme auf das Column-Objekt direkt referenziert werden. Die direkte Bezugnahme auf das Objekt kann zur Deutlichkeit sowie zur Auflösung von Mehrdeutigkeiten bei der Abbildung auf Mehr-Tabellen-Konstrukte mit wiederholten Namen nützlich sein.

class User(Base):
    __table__ = user_table
    __mapper_args__ = {
        "include_properties": [user_table.c.user_id, user_table.c.user_name]
    }

Wenn Spalten nicht in eine Abbildung aufgenommen werden, werden diese Spalten in keiner SELECT-Anweisung, die beim Ausführen von select() oder Legacy- Query-Objekten ausgegeben wird, referenziert, noch gibt es ein abgebildetes Attribut in der abgebildeten Klasse, das die Spalte repräsentiert. Die Zuweisung eines Attributs dieses Namens hat keine Auswirkungen über die einer normalen Python-Attributzuweisung hinaus.

Es ist jedoch wichtig zu beachten, dass **Spaltendefaults auf Schemaebene weiterhin in Kraft bleiben** für diejenigen Column-Objekte, die sie enthalten, auch wenn sie von der ORM-Abbildung ausgeschlossen sein mögen.

Mit "Spaltendefaults auf Schemaebene" sind die unter Column INSERT/UPDATE Defaults beschriebenen Defaults gemeint, einschließlich derjenigen, die durch die Parameter Column.default, Column.onupdate, Column.server_default und Column.server_onupdate konfiguriert werden. Diese Konstrukte haben weiterhin normale Auswirkungen, da im Fall von Column.default und Column.onupdate das Column-Objekt immer noch auf der zugrunde liegenden Table vorhanden ist, wodurch die Standardfunktionen ausgeführt werden können, wenn das ORM ein INSERT oder UPDATE ausgibt, und im Fall von Column.server_default und Column.server_onupdate gibt die relationale Datenbank selbst diese Defaults als serverseitiges Verhalten aus.