Table Configuration with Declarative

Declarative Mapping で紹介されているように、Declarativeスタイルには、マップされた Table オブジェクトを同時に生成する機能や、 Table や他の FromClause オブジェクトを直接収容する機能が含まれています。

次の例では、宣言ベースクラスを次のように想定しています。:

from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

以下のすべての例は、上記の Base から継承するクラスを示しています。 Declarative Mapping using a Decorator (no declarative base) で導入されたデコレータスタイルは、以下のすべての例でも完全にサポートされています。また、 declarative_base() で生成された基底クラスを含むDeclarative Baseのレガシー形式も同様です。

Declarative Table with mapped_column()

Declarativeを使用する場合、ほとんどの場合、マッピングされるクラスの本体には、マッピングとともに生成される Table の文字列名を示す属性 __tablename__ が含まれます。 mapped_column() 構造体は、通常の Column クラスにはない追加のORM固有の設定機能を備えており、クラス本体内でテーブル内の列を示すために使用されます。次の例は、宣言型マッピングにおけるこの構造体の最も基本的な使用方法を示しています。:

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))

上記では、 mapped_column() 構文がクラスレベルの属性としてクラス定義内にインラインで配置されています。クラスが宣言された時点で、宣言型マッピングプロセスは、宣言型 Base に関連付けられた MetaData コレクションに対して新しい Table オブジェクトを生成します。このプロセスでは、 mapped_column() の各インスタンスが Column オブジェクトの生成に使用され、この Table オブジェクトの Table.columns コレクションの一部になります。

上の例では、Declarativeは以下と等価な Table 構成体を構築します。:

# 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)),
)

上記の User クラスがマップされている場合、この Table オブジェクトは __table__ 属性から直接アクセスできます。これについては Accessing Table and Metadata で詳しく説明されています。

mapped_column() 構文は、 Column 構文が受け付けるすべての引数と、追加のORM固有の引数を受け付けます。データベース列の名前を示す mapped_column.__name フィールドは通常省略されます。これは、宣言型プロセスが構文に与えられた属性名を利用し、これを列の名前として割り当てるためです(上記の例では、これは idnamefullnamenickname という名前を参照しています)。別の mapped_column.__name を割り当てることも有効です。この場合、結果の Column はSQL文とDDL文で与えられた名前を使用しますが、 User マップクラスは、列自体に与えられた名前とは無関係に、与えられた属性名を使用して属性にアクセスできます(詳細は Naming Declarative Mapped Columns Explicitly を参照)。

Tip

mapped_column() 構文は Declarativeクラスマッピング内でのみ有効です 。Coreを使用して Table オブジェクトを構築する場合、および imperative table 構成を使用する場合でも、データベース列の存在を示すために Column 構文が必要です。

See also

Mapping Table Columns - Mapper がどのように入ってくる Column オブジェクトを解釈するかに影響する追加の注意事項があります。

Using Annotated Declarative Table (Type Annotated Forms for mapped_column())

mapped_column() 構文は、Declarative mappedクラスで宣言された属性に関連付けられた PEP 484 型アノテーションから列設定情報を得ることができます。これらの型アノテーションが使用される場合、 Mapped と呼ばれる特別なSQLAlchemy型の中に 存在しなければなりません 。これはgeneric_typeで、その中の特定のPython型を示します。

以下は、前のセクションのマッピングに Mapped の使用を追加したものです:

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))

上記では、Declarativeがそれぞれのクラス属性を処理するとき、それぞれの mapped_column() は、左側に対応する Mapped 型アノテーションがあれば、そこから追加の引数を派生させます。さらに、Declarativeは、属性に値が割り当てられていない Mapped 型アノテーションに遭遇するたびに、暗黙的に空の mapped_column() ディレクティブを生成します(この形式は、Python dataclasses_で使用されている同様のスタイルにヒントを得たものです)。この mapped_column() 構造体は、存在する Mapped アノテーションから構成を派生させます。

mapped_column() derives the datatype and nullability from the Mapped annotation

mapped_column()Mapped アノテーションから派生した2つの性質は以下のとおりです:

  • datatype - Mapped 内で指定されたPythonの型で、 typing.Optional 構文内に含まれています。存在する場合は、 IntegerStringDateTimeUuid などの TypeEngine サブクラスに関連付けられます。

    データ型は、SQLAlchemyデータ型に対するPython型の辞書に基づいて決定されます。この辞書は完全にカスタマイズ可能で、次のセクションの Customizing the Type Map で詳しく説明します。デフォルトの型マップは、以下のコード例のように実装されます:

    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(),
    }

    mapped_column() 構文が mapped_column.__type 引数に渡された明示的な型を示している場合、与えられたPythonの型は無視されます。

  • nullability - mapped_column() 構文は、何よりもまず、 mapped_column.nullable パラメータが存在することによって、その ColumnNULL または NOT NULL であることを示し、 True または False のいずれかとして渡されます。さらに、 mapped_column.primary_key パラメータが存在し、 True に設定されている場合、その列は NOT NULL である必要があることも意味します。

    これらのパラメータの 両方 が存在しない場合、 Mapped 型アノテーション内の typing.Optional[] の存在がnull許容を決定するために使用されます。ここで、 typing.Optional[]NULL を意味し、 typing.Optional[] が存在しない場合は NOT NULL を意味します。 Mapped[] アノテーションが存在せず、 mapped_column.nullable または mapped_column.primary_key パラメータが存在しない場合、SQLAlchemyの通常のデフォルトである ColumnNULL が使用されます。

    以下の例では、 id 列と data 列は NOT NULLadditional_info 列は NULL になります。:

    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]]

    また、 mapped_column() のNULL許容範囲が、アノテーションによって暗黙的に示されるものと 異なる 場合も、完全に有効です。例えば、ORMにマッピングされた属性は、オブジェクトが最初に作成されて値が設定されるときに、そのオブジェクトを扱うPythonコード内で None を許可するようにアノテーションを付けることができますが、その値は最終的には NOT NULL であるデータベース列に書き込まれます。 mapped_column.nullable パラメータが存在する場合は、常に優先されます:

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

    同様に、何らかの理由でスキーマレベルでNULLにする必要があるデータベース列に書き込まれたNone以外の属性 mapped_column.nullable は、 True に設定できます:

    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)

Customizing the Type Map

前のセクションで説明したSQLAlchemy TypeEngine 型へのPython型のマッピングは、デフォルトでは SQLAlchemy.sql.sqltypes モジュールにハードコードされた辞書が存在します。しかし、宣言型マッピングプロセスを調整する registry オブジェクトは、最初にローカルのユーザ定義型辞書を参照します。これは、 registry を構築するときに registry.type_annotation_map パラメータとして渡される可能性があります。これは、最初に使用されたときに DeclarativeBase スーパークラスに関連付けられる可能性があります。

例えば、 intBIGINT データ型を、 datetime.datetimeTIMESTAMP データ型と timezone=True を使用し、Microsoft SQL Serverでのみ NVARCHAR データ型を使用し、Pythonの str を使用する場合、レジストリと宣言ベースは次のように設定できます。:

import datetime

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

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]

次の図は、上記のマッピングのために生成されたCREATE TABLE文を示しています。最初はMicrosoft SQL Serverバックエンドで、 NVARCHAR データ型を示しています。:

>>> 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) )

次にPostgreSQLのバックエンドで、 TIMESTAMP WITH TIME ZONE の例を示します。:

>>> 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) )

TypeEngine.with_variant() などのメソッドを利用することで、簡潔な注釈のみの mapped_column() 設定を使用しながら、さまざまなバックエンドに必要なものにカスタマイズされた型マップを構築することができます。これ以外にも、次の2つのセクションで説明する2つのレベルのPython型設定が利用できます。

Mapping Multiple Type Configurations to Python Types

個々のPython型は、 registry.type_annotation_map パラメータを使って、あらゆる種類の TypeEngine 設定に関連付けることができますので、追加の機能として、追加の型修飾子に基づいて、単一のPython型をSQL型の異なるバリアントに関連付けることができます。この典型的な例の1つは、Pythonの str データ型を異なる長さの VARCHAR SQL型にマッピングすることです。もう1つは、さまざまな種類の decimal.Decimal を異なるサイズの NUMERIC 列にマッピングすることです。

Pythonの型付けシステムは、Python型に追加のメタデータを追加するための優れた方法を提供します。これは、 PEP 593 Annotated ジェネリック型を使用することで、追加の情報をPython型と一緒にバンドルすることができます。 mapped_column() 構文は、 registry.type_annotation_map で解決するときに、以下の例のように StringNumeric の2つのバリアントを宣言する場合のように、IDによって Annotated オブジェクトを正しく解釈します:

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),
        }
    )

Annotated コンテナに渡されたPythonの型、上の例では str 型と Decimal 型は、型定義ツールの利点のためだけに重要です。 mapped_column() 構文に関しては、少なくともこの特定のコンテキストでは、 Annotated オブジェクトの内部を実際に見ることなく、 registry.type_annotation_map 辞書内の各型オブジェクトの検索を実行するだけで済みます。同様に、基礎となるPythonの型自体を超えて Annotated に渡された引数も重要ではありません。 Annotated 構文が有効になるためには、少なくとも1つの引数が存在しなければならないだけです。次の例のように、これらの拡張された型をマッピングで直接使用して、より具体的な型定義と一致させることができます。:

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]

上記のマッピング用のCREATE TABLEは、設定した VARCHARNUMERIC の異なるバリエーションを示し、次のようになります。:

>>> 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) )

Annotated 型をさまざまなSQL型にリンクすることで、さまざまな程度の柔軟性が得られますが、次のセクションでは、 Annotated 型を宣言型とともに使用する2番目の方法について説明します。これは、さらにオープンな方法です。

Mapping Whole Column Declarations to Python Types

前のセクションでは、 PEP 593 Annotated 型インスタンスを registry.type_annotation_map ディクショナリ内のキーとして使用する例を示しました。この形式では、 mapped_column() 構文は実際には`Annotated`オブジェクト自体の内部を見るのではなく、ディクショナリキーとしてのみ使用されます。しかし、Declarativeには、事前に確立された mapped_column() 構文全体を Annotated オブジェクトから直接抽出する機能もあります。この形式を使用すると、 registry.type_annotation_map ディクショナリを使用せずに、Python型にリンクされたさまざまな種類のSQLデータ型を定義できるだけでなく、nullability、カラムのデフォルト、制約などの任意の数の引数を再利用可能な方法で設定することもできます。

ORMモデルのセットは、通常、すべてのマップされたクラスに共通する何らかの種類の主キースタイルを持ちます。また、デフォルトのタイムスタンプや、事前に設定されたサイズや設定の他のフィールドなど、共通の列設定がある場合もあります。これらの設定を mapped_column() インスタンスに構成し、それを直接 Annotated のインスタンスにバンドルして、任意の数のクラス宣言で再利用することができます。Declarativeは、この方法で提供された場合、 Annotated オブジェクトを展開し、SQLAlchemyに適用されない他のディレクティブをスキップし、SQLAlchemy ORM構成体のみを検索します。

以下の例は、この方法で使用されるさまざまな事前設定されたフィールド型を示しています。ここでは、 Integer 主キー列を表す intpk 、DDLレベル列のデフォルトとして CURRENT_TIMESTAMP を使用する DateTime 型を表す timestamp 、そして長さ30で NOT NULL である String である required_name を定義します。:

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)]

上記の Annotated オブジェクトは Mapped 内で直接使うことができます。ここでは事前に設定された mapped_column() 構文が抽出され、各属性に固有の新しいインスタンスにコピーされます:

class Base(DeclarativeBase):
    pass

class SomeClass(Base):
    __tablename__ = "some_table"

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

上記のマッピングのための CREATE TABLE は以下のようになります:

>>> 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) )

この方法で Annotated 型を使用すると、型の設定も属性ごとに影響を受ける可能性があります。 mapped_column.nullable の明示的な使用を特徴とする上記の例の型では、任意の型に Optional[] 汎用修飾子を適用して、データベースで行われる NULL / NOT NULL 設定とは無関係に、Pythonレベルでフィールドがオプションであるか否かを指定できます。:

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]]

mapped_column() 構文は、明示的に渡された mapped_column() 構文とも調整されます。その引数は Annotated 構文の引数よりも優先されます。以下では、整数の主キーに ForeignKey 制約を追加し、また別のサーバのデフォルトを 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())

CREATE TABLE文は、これらの属性ごとの設定を示し、 FOREIGN KEY 制約を追加し、 CURRENT_TIMESTAMPUTC_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) )

Note

ここで説明した mapped_column() の機能は、完全に構築された列引数のセットが、属性にコピーされる”テンプレート” mapped_column() オブジェクトを含む PEP 593 Annotated オブジェクトを使って示される可能性がありますが、現在のところ、 relationship()composite() のような他のORM構成体には実装されていません。この機能は理論的には可能ですが、今のところ、 relationship() や同様のものの引数をさらに示すために Annotated を使おうとすると、実行時に NotImplementedError 例外が発生しますが、将来のリリースで実装される可能性があります。

Using Python Enum or pep-586 Literal types in the type map

New in version 2.0.0b4: - Enum サポート追加

New in version 2.0.1: - Literal サポート追加

Python組み込みの enum.Enum および typing.Literal クラスから派生したユーザ定義のPython型は、ORM宣言マッピングで使用されると、自動的にSQLAlchemy Enum データ型にリンクされます。以下の例では、 Mapped[] コンストラクタ内でカスタムの enum.Enum を使用しています。:

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]

上の例では、マップされた属性 SomeClass.status は、データ型が Enum(Status)Column にリンクされます。これは、例えばPostgreSQLデータベースのCREATE TABLE出力で見ることができます。:

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

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

同様に、代わりに typing.Literal を使用して、すべての文字列で構成される typing.Literal を使用することもできます。:

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]

registry.type_annotation_map で使用されるエントリは、Pythonの基本型である enum.Enumtyping.Literal 型をSQLAlchemy Enum SQL型にリンクします。これは Enum データ型に対して、任意の列挙型に対して自動的に設定されるべきであることを示す特別な形式を使用します。この設定はデフォルトでは暗黙的ですが、次のように明示的に示されます:

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),
    }

Declarative内の解決ロジックは、 enum.Enum のサブクラスと typing.Literal のインスタンスを解決して、 registry.type_annotation_map 辞書の enum.Enum または typing.Literal エントリに一致させることができます。 Enum SQL型は、デフォルトの文字列長を含む適切な設定で、自身の構成バージョンを生成する方法を知っています。文字列値だけで構成されていない typing.Literal が渡された場合、情報エラーが発生します。

Native Enums and Naming

Enum.native_enum パラメータは、 Enum データ型がいわゆる”native”列挙型を生成するかどうかを参照します。”native”列挙型はMySQL/MariaDBで ENUM データ型で、PostgreSQLでは CREATE TYPE によって生成された新しい TYPE オブジェクト、または”non-native”列挙型で、これはデータ型の生成に VARCHAR が使用されることを意味します。MySQL/MariaDBやPostgreSQL以外のバックエンドでは、すべての場合に”VARCHAR”が使用されます(サードパーティのダイアレクトには独自の動作があります)。

PostgreSQLの CREATE TYPE は生成される型に対して明示的な名前を必要とするため、マッピング内で明示的な Enum データ型を指定せずに暗黙的に生成された Enum を扱う場合には、特別なフォールバックロジックが存在します。

  1. Enumenum.Enum オブジェクトにリンクされている場合、 Enum.native_enum パラメータはデフォルトで True に設定され、列挙型の名前は enum.Enum データ型の名前から取られます。PostgreSQLのバックエンドはこの名前の CREATE TYPE を想定します。

  1. Enumtyping.Literal オブジェクトにリンクされている場合、 Enum.native_enum パラメータはデフォルトで False になります。名前は生成されず、 VARCHAR が想定されます。

PostgreSQLの CREATE TYPE 型で typing.Literal を使用するには、明示的な Enum を型マップ内で使用する必要があります。:

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"),
    }

あるいは、 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")
    )
Altering the Configuration of the Default Enum

暗黙的に生成される Enum データ型の固定設定を変更するには、 registry.type_annotation_map に新しいエントリを指定し、追加の引数を示します。例えば、”非ネイティブ列挙”を無条件に使用するには、 Enum.native_enum パラメータをすべての型に対してFalseに設定します:

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),
    }

Changed in version 2.0.1: registry.type_annotation_map を確立する際に、 Enum データ型内の Enum.native_enum などのオーバーライドパラメータのサポートを実装しました。以前は、この機能は動作していませんでした。

特定の enum.Enum サブタイプに特定の設定を使用します。例えば、 Status データ型の例を使用する場合、文字列の長さを50に設定します。:

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)
    }

デフォルトでは、自動的に生成される Enum は、 Base によって使用される MetaData インスタンスに関連付けられません。したがって、メタデータがスキーマを定義する場合、自動的にenumに関連付けられることはありません。enumをメタデータまたはテーブル内のスキーマに自動的に関連付けるには、それらが Enum.inherit_schema に属するように設定できます:

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)}
Linking Specific enum.Enum or typing.Literal to other datatypes

上記の例では、 Enum を使用しています。 Enum は、 enum.Enum または typing.Literal 型オブジェクトに存在する引数/属性に自動的に設定されます。特定の種類の enum.Enum または typing.Literal が他の型にリンクされる必要があるユースケースでは、これらの特定の型も型マップに配置できます。以下の例では、文字列型以外の型を含む Literal[] のエントリが JSON データ型にリンクされています。:

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}

上記の設定では、 my_literal データ型は JSON インスタンスに解決されます。他の Literal 型は引き続き Enum データ型に解決されます。

Dataclass features in mapped_column()

mapped_column() 構文は、 Declarative Dataclass Mapping で説明されているSQLAlchemyの”native dataclasses”機能と統合されています。 mapped_column() でサポートされている追加のディレクティブに関する現在の背景については、そのセクションを参照してください。

Accessing Table and Metadata

宣言的にマップされたクラスには、必ず __table__ という属性が含まれます。 __tablename__ を使った上記の設定が完了すると、宣言的なプロセスによって Table__table__ 属性を介して利用できるようになります。:

# access the Table
user_table = User.__table__

上の表は究極的には Mapper.local_table 属性に対応するものと同じで、 runtime inspection system を通して見ることができます:

from sqlalchemy import inspect

user_table = inspect(User).local_table

宣言的な registry と基底クラスの両方に関連付けられた MetaData コレクションは、CREATEのようなDDL操作を実行するために、またAlembicのようなマイグレーションツールで使用するために、しばしば必要になります。このオブジェクトは、宣言的な基底クラスと同様に、 registry.metaData 属性を介して利用できます。以下では、小さなスクリプトとして、SQLiteデータベースに対してすべてのテーブルにCREATEを発行したい場合があります:

engine = create_engine("sqlite://")

Base.metadata.create_all(engine)

Declarative Table Configuration

宣言的なクラス属性 __tablename__ を持つ宣言的なテーブル構成を使用する場合、 Table コンストラクタに追加の引数を指定するには、宣言的なクラス属性 __table_args__ を使用する必要があります。

この属性は、通常 Table コンストラクタに送られる位置引数とキーワード引数の両方に対応します。この属性は2つの形式のどちらかで指定できます。1つは辞書として指定します:

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

もう1つはタプルで、各引数はポジショナルです。

(通常は制約):

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

キーワード引数は、最後の引数を辞書として指定することで、上記の形式で指定できます。:

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

クラスは、 declared_attr() メソッドデコレータを使って、宣言型の属性 __table_args__ や宣言型の属性 __tablename__ を動的なスタイルで指定することもできます。背景については Composing Mapped Hierarchies with Mixins を参照してください。

Explicit Schema Name with Declarative Table

Specifying the Schema Name で説明されているように、 Table のスキーマ名は、 Table.schema 引数を使用して個々の Table に適用されます。宣言テーブルを使用する場合、このオプションは他のものと同じように __table_args__ 辞書に渡されます:

from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

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

スキーマ名は、 Specifying a Default Schema Name with MetaData に記載されている MetaData.schema パラメータを使用して、すべての Table オブジェクトにグローバルに適用することもできます。 MetaData オブジェクトは、 metaData 属性に直接割り当てることによって、別々に構築し、 DeclarativeBase サブクラスに関連付けることができます。:

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"

See also

Specifying the Schema Name - Describing Databases with MetaData を参照してください。

Setting Load and Persistence Options for Declarative Mapped Columns

mapped_column() 構文は、生成された Column のマップ方法に影響する追加のORM固有の引数を受け入れ、ロード時と永続化時の動作に影響します。一般的に使用されるオプションは次のとおりです。:

  • deferred column loading - mapped_column.deferred ブール値は、デフォルトで deferred column loading を使用して bio` 列はロードされませんが、アクセスされた場合にのみロードされます:

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

    See also

    Limiting which Columns Load with Column Deferral - 遅延列読み込みの完全な説明

  • active history - mapped_column.active_history は、属性の値が変更されたときに、属性の履歴を検査するときに、以前の値がロードされ、 AttributeState.history コレクションの一部になることを保証します。これにより、追加のSQL文が発生する可能性があります:

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

サポートされているパラメータのリストについては、 mapped_column() のdocstringを参照してください。

See also

Applying Load, Persistence and Mapping Options for Imperative Table Columns - column_property()deferred() をImperative Table設定で使用する方法を説明しています

Naming Declarative Mapped Columns Explicitly

これまでの例はすべて、ORMマップ属性にリンクされた mapped_column() 構文を特徴としています。ここで、 mapped_column() に与えられたPython属性名は、CREATE TABLE文やクエリで見られるように、列の属性名でもあります。SQLで表現された列の名前は、最初の位置引数として文字列位置引数 mapped_column.__name を渡すことで示すことができます。下の例では、 User クラスは列自体に与えられた代替名でマップされています:

class User(Base):
    __tablename__ = "user"

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

上記の場合、 User.iduser_id という名前の列に解決され、 User.nameuser_name という名前の列に解決されます。Pythonの属性名を使って select() 文を書くと、生成されたSQL名を見ることができます。:

>>> 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

See also

Alternate Attribute Names for Mapping Table Columns - Imperative Tableへの適用

Appending additional columns to an existing Declarative mapped class

宣言的なテーブル設定では、 Table メタデータがすでに生成された後に、新しい Column オブジェクトを既存のマッピングに追加できます。

宣言的な基底クラスを使用して宣言された宣言的なクラスの場合、基礎となるメタクラス DeclarativeMeta には、追加の mapped_column() またはCore Column オブジェクトをインターセプトし、それらを Table.append_column() を使用して Table に追加するとともに、 Mapper.add_property() を使用して既存の Mapper に追加する __setattr__() メソッドが含まれています:

MyClass.some_new_column = mapped_column(String)

core Column の使用:

MyClass.some_new_column = Column(String)

全ての引数は、 MyClass.some_new_column = mapped_column("some_name", String) のような別の名前を含めてサポートされています。しかし、SQL型は mapped_column() または Column オブジェクトに明示的に渡さなければなりません。上の例では String 型が渡されています。 Mapped アノテーション型が操作に関与する機能はありません。

追加の Column オブジェクトは、単一テーブルの継承を使用する特定の状況でマッピングに追加することもできます。この場合、追加の列は、独自の Table を持たないマップされたサブクラスに存在します。これは Single Table Inheritance のセクションで説明されています。

Note

すでにマップされているクラスへのマップされたプロパティの割り当ては、”宣言ベース”クラス、つまり DeclarativeBase のユーザ定義のサブクラス、または declarative_base()registry.generate_base() によって返される動的に生成されたクラスが使用されている場合にのみ正しく機能します。この”ベース”クラスには、これらの操作をインターセプトする特別な __setattr__() メソッドを実装するPythonのメタクラスが含まれています。

クラスが registry.mapped() のようなデコレータや registry.map_mandatory() のような命令関数を使ってマップされている場合、クラスにマップされた属性のマップされたクラスへの実行時の割り当ては 機能しません

Declarative with Imperative Table (a.k.a. Hybrid Declarative)

宣言的なマッピングは、既存の Table オブジェクト、または別に構築された Table やその他の任意の FromClause 構成体( JoinSubquery など)とともに提供されることもあります。

これは”ハイブリッド宣言型”マッピングと呼ばれ、クラスはマッパーの設定を含むすべてのものに対して宣言型スタイルを使用してマッピングされますが、マッピングされた Table オブジェクトは個別に生成され、宣言型プロセスに直接渡されます:

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

上の例では、 Table オブジェクトは Describing Databases with MetaData で説明されているアプローチを使って構築されています。そして、宣言的にマップされたクラスに直接適用することができます。宣言的なクラス属性である __tablename____table_args__ はこの形式では使用されません。上記の設定はインライン定義として読みやすいことがよくあります:

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

上記のスタイルの自然な効果は、 __table__ 属性自体がクラス定義ブロック内で定義されることです。そのため、次の例のように、後続の属性内で直接参照することができます。この例では、ポリモーフィックマッパー設定で type 列を参照しています。:

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,
        "polymorhpic_identity": "person",
    }

“imperative table”形式は、 JoinSubquery オブジェクトのような Table 以外の構造体がマップされる場合にも使われます。以下に例を示します:

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

Table 以外の構造体へのマッピングの背景については、 Mapping a Class against Multiple TablesMapping a Class against Arbitrary Subqueries を参照してください。

“imperative table” 形式は、クラス自体がPythonデータクラスのような別の形式の属性宣言を使用している場合に特に有用です。詳細については Applying ORM Mappings to an existing dataclass (legacy dataclass use) を参照してください。

Alternate Attribute Names for Mapping Table Columns

セクション Naming Declarative Mapped Columns Explicitly では、 mapped_column() を使用して、生成された Column オブジェクトに、マップされる属性名とは別に特定の名前を付ける方法を説明しました。

Imperative Table設定を使用する場合、すでに Column オブジェクトが存在します。これらを別名にマップするために、 Column を必要な属性に直接割り当てることができます:

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

上記の User マッピングは、 Naming Declarative Mapped Columns Explicitly で示したのと同じ方法で、 User.id 属性と User.name 属性を介して、 user_id 列と user_name 列を参照します。

上記のマッピングで注意すべき点の1つは、 PEP 484 型定義ツールを使用すると、 Column への直接のインラインリンクが正しく型定義されないことです。これを解決するための戦略は、 column_property() 関数内に Column オブジェクトを適用することです。 Mapper はすでに内部使用のためにこのプロパティオブジェクトを自動的に生成していますが、クラス宣言で名前を付けることによって、型定義ツールは属性を Mapped アノテーションに一致させることができます:

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)

See also

Naming Declarative Mapped Columns Explicitly - 宣言テーブルへの適用

Applying Load, Persistence and Mapping Options for Imperative Table Columns

セクション Setting Load and Persistence Options for Declarative Mapped Columns では、宣言テーブル設定で mapped_column() 構文を使用する場合のロードオプションとパーシステンスオプションの設定方法をレビューしました。命令テーブル設定を使用する場合、すでにマッピングされている既存の Column オブジェクトがあります。これらの Column オブジェクトをORMマッピングに固有の追加パラメータとともにマッピングするために、 column_property() および deferred() 構文を使用して追加パラメータを列に関連付けることができます。オプションには次のものがあります:

  • deferred column loading - deferred() 関数は、 column_property.deferred パラメータを True に設定して bio`() 列はデフォルトではロードされませんが、アクセスされた場合にのみロードされます:

    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)

See also

Limiting which Columns Load with Column Deferral - 遅延列読み込みの完全な説明

  • active history - column_property.active_history は、属性の値が変更されると、属性の履歴を検査するときに、以前の値がロードされ、 AttributeState.history コレクションの一部になることを保証します。これにより、追加のSQL文が発生する可能性があります:

    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
        )

See also

column_property() 構文は、クラスが結合や選択などの代替FROM句にマップされる場合にも重要です。これらの場合の背景については、以下を参照してください。

mapped_column() を使った宣言テーブルの設定では、ほとんどのオプションが直接利用できます。例については Setting Load and Persistence Options for Declarative Mapped Columns を参照してください。

Mapping Declaratively with Reflected Tables

Reflecting Database Objects で説明されているリフレクションプロセスを使用して、データベースからイントロスペクションされた一連の Table オブジェクトに対してマップされたクラスを生成するためのいくつかのパターンがあります。

データベースから反映されたテーブルにクラスをマップする簡単な方法は、宣言的なハイブリッドマッピングを使用し、 Table.autoload_with パラメータを Table のコンストラクタに渡すことです:

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,
    )

多くのテーブルに対応する上記のパターンの変形として、 MetaData.reflect() メソッドを使用して Table オブジェクトの完全なセットを一度に反映し、それらを MetaData から参照する方法があります:

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"]

__table__ を使用するアプローチの注意点の1つは、テーブルが反映されるまでマップされたクラスを宣言できないことです。これには、アプリケーションクラスが宣言されている間、データベース接続ソースが存在している必要があります。一般的に、クラスはアプリケーションのモジュールがインポートされるときに宣言されますが、データベース接続は、アプリケーションが構成情報を消費してエンジンを作成できるようにコードの実行を開始するまで使用できません。現在、これを回避するための2つのアプローチがあり、次の2つのセクションで説明します。

Using DeferredReflection

テーブルメタデータの反映が後で発生する可能性のある、マップされたクラスを宣言するユースケースに対応するために、 DeferredReflection ミックスインと呼ばれる簡単な拡張が利用できます。これは、特別なクラスレベルの DeferredReflection.prepare() メソッドが呼び出されるまで、宣言的なマッピングプロセスを遅延させるように変更します。このメソッドは、ターゲットデータベースに対して反映プロセスを実行し、その結果を宣言的なテーブルマッピングプロセス、つまり __tablename__ 属性を使用するクラスと統合します。:

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"))

上記では、 Reflected.prepare メソッドが呼び出されたときにマップされるべき宣言的な階層内のクラスのベースとして機能するミックスインクラス Reflected を作成します。上記のマッピングは、 Engine が与えられた場合、そうするまで完了しません:

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

Reflected クラスの目的は、クラスがリフレクションによってマップされるスコープを定義することです。プラグインは、 .prepare() が呼び出されたターゲットのサブクラスツリーを検索し、宣言されたクラスによって名前が付けられたすべてのテーブルを反映します。マッピングの一部ではなく、外部キー制約によってターゲットテーブルに関連付けられていないターゲットデータベース内のテーブルは反映されません。

Using Automap

テーブルリフレクションが使用される既存のデータベースに対してマッピングするためのより自動化されたソリューションは、 Automap 拡張を使用することです。この拡張は、観測された外部キー制約に基づくクラス間の関係を含めて、データベーススキーマからマッピングされたクラス全体を生成します。これには、カスタムクラスの名前付けや関係の名前付けスキームを可能にするフックなど、カスタマイズのためのフックが含まれていますが、automapは便宜的なゼロ構成スタイルの作業を指向しています。アプリケーションがテーブルリフレクションを利用する完全に明示的なモデルを望む場合、 DeferredReflection クラスは、あまり自動化されていないアプローチに適しているかもしれません。

See also

Automap

Automating Column Naming Schemes from Reflected Tables

これまでのリフレクションテクニックのいずれかを使用する場合、列がマップされる命名スキームを変更するオプションがあります。 Column オブジェクトにはパラメータ Column.key が含まれています。これは、列のSQL名とは無関係に、この ColumnTable.c コレクションに存在する名前を決定する文字列名です。このキーは、 Alternate Attribute Names for Mapping Table Columns に示されているような他の方法で提供されない場合、 Mapper によって Column がマップされる属性名としても使用されます。

テーブルリフレクションを使用する場合、 Column で使用されるパラメータを DDLEvents.column_reflect() イベントを使用して受け取り、必要な変更を適用することができます。これには、 .key 属性だけでなく、データ型のようなものも含まれます。

イベントフックは、以下に示すように、使用中の MetaData オブジェクトに最も簡単に関連付けられます。:

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()

上記のイベントでは、 Column オブジェクトの反映は、以下のようなマッピングのように、新しい”.key”要素を追加するイベントでインターセプトされます:

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

このアプローチは、 DeferredReflection 基底クラスと Automap 拡張の両方で動作します。特にオートマップについては、背景について Intercepting Column Definitions を参照してください。

Mapping to an Explicit Set of Primary Key Columns

Mapper 構文でテーブルを正しくマップするためには、少なくとも1つの列がその選択対象の”主キー”として識別されている必要があります。これは、ORMオブジェクトがロードまたは永続化されるときに、適切な identity keyidentity map に配置できるようにするためです。

マップされるべき反映されたテーブルが主キー制約を含まない場合や、プライマリキー列が存在しないかもしれない mapping against arbitrary selectables の一般的なケースでは、 Mapper.primary_key パラメータが提供され、ORMマッピングに関する限り、任意の列のセットをテーブルの”プライマリキー”として設定できます。

テーブルが宣言された主キーを持たない(リフレクションのシナリオで発生する可能性がある)既存の Table オブジェクトに対してImperative Tableをマッピングする次の例では、そのようなテーブルを次の例のようにマッピングすることができます。:

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]}

上の例では、 group_users テーブルは文字列列 user_idgroup_id を持つ何らかの関連付けテーブルですが、主キーは設定されていません。代わりに、2つの列が一意のキーを表すことを確立する UniqueConstraint だけがあります。 Mapper は主キーの一意性制約を自動的に検査しません。代わりに、 Mapper.primary_key パラメータを使用して、 [group_users.c.user_id, group_users.c.group_id] のコレクションを渡します。これは、 GroupUsers クラスのインスタンスのIDキーを構築するために、これら2つの列を使用する必要があることを示しています。

Mapping a Subset of Table Columns

時には、テーブルリフレクションは、我々の要求に対して重要ではなく、安全に無視される可能性のある多くの列を持つ Table を提供することがあります。アプリケーションで参照する必要のない多くの列を持つそのようなテーブルの場合、 Mapper.include_properties または Mapper.exclude_properties パラメータは、マップされる列のサブセットを示すことができます。ここで、ターゲット Table の他の列は、ORMによってまったく考慮されません。例:

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

上の例では、 User クラスは user_table テーブルにマップされ、 user_id 列と user_name 列のみが含まれ、残りは参照されません。同様に:

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

は、 streetcitystatezip 以外のすべての列を含む address_table テーブルに Address クラスをマップします。

2つの例で示したように、列は文字列名で参照することも、 Column オブジェクトを直接参照することもできます。オブジェクトを直接参照することは、明示性のためだけでなく、名前が繰り返される可能性のある複数のテーブル構造にマッピングする際のあいまいさを解決するためにも役立ちます:

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

カラムがマッピングに含まれていない場合、これらのカラムは select() や従来の Query オブジェクトの実行時に発行されるSELECT文では参照されません。また、カラムを表すmappedクラスにmapped属性が存在することもありません。その名前の属性を割り当てても、通常のPythonの属性割り当て以上の効果はありません。

ただし、 スキーマレベルの列のデフォルトは、それを含む :class:`_schema.Column` オブジェクトに対しては、たとえORMマッピングから除外されても 有効であることに注意してください。

“Schema level column defaults”とは、 Column INSERT/UPDATE Defaults に記述されているデフォルトのことで、 Column.default,:paramref:_schema.Column.onupdate,:paramref:_schema.Column.server_default,:paramref:_schema.Column.server_onupdate パラメータで設定されているものも含まれます。 Column.defaultColumn.onupdate の場合、 Column オブジェクトはまだ下にある Table に存在しているので、ORMがINSERTまたはUPDATEを発行したときにデフォルトの関数が実行されます。また、 Column.server_defaultColumn.server_onupdate の場合、リレーショナルデータベース自体がこれらのデフォルトをサーバ側の動作として発行するので、これらの構文は通常の効果を持ち続けます。