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
フィールドは通常省略されます。これは、宣言型プロセスが構文に与えられた属性名を利用し、これを列の名前として割り当てるためです(上記の例では、これは id
、 name
、 fullname
、 nickname
という名前を参照しています)。別の 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
構文内に含まれています。存在する場合は、Integer
、String
、DateTime
、Uuid
などの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
パラメータが存在することによって、そのColumn
がNULL
または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の通常のデフォルトであるColumn
のNULL
が使用されます。以下の例では、
id
列とdata
列はNOT NULL
、additional_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
スーパークラスに関連付けられる可能性があります。
例えば、 int
に BIGINT
データ型を、 datetime.datetime
に TIMESTAMP
データ型と 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
で解決するときに、以下の例のように String
と Numeric
の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は、設定した VARCHAR
と NUMERIC
の異なるバリエーションを示し、次のようになります。:
>>> 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_TIMESTAMP
を UTC_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.Enum
と typing.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
を扱う場合には、特別なフォールバックロジックが存在します。
Enum
が enum.Enum オブジェクトにリンクされている場合、Enum.native_enum
パラメータはデフォルトでTrue
に設定され、列挙型の名前はenum.Enum
データ型の名前から取られます。PostgreSQLのバックエンドはこの名前のCREATE TYPE
を想定します。
Enum
がtyping.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.id
は user_id
という名前の列に解決され、 User.name
は user_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 のセクションで説明されています。
See also
Adding Relationships to Mapped Classes After Declaration - relationship()
にも例があります。
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
構成体( Join
や Subquery
など)とともに提供されることもあります。
これは”ハイブリッド宣言型”マッピングと呼ばれ、クラスはマッパーの設定を含むすべてのものに対して宣言型スタイルを使用してマッピングされますが、マッピングされた 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”形式は、 Join
や Subquery
オブジェクトのような 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 Tables と Mapping a Class against Arbitrary Subqueries を参照してください。
“imperative table” 形式は、クラス自体がPythonデータクラスのような別の形式の属性宣言を使用している場合に特に有用です。詳細については Applying ORM Mappings to an existing dataclass (legacy dataclass use) を参照してください。
See also
Describing Databases with MetaData
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
Automating Column Naming Schemes from Reflected Tables¶
これまでのリフレクションテクニックのいずれかを使用する場合、列がマップされる命名スキームを変更するオプションがあります。 Column
オブジェクトにはパラメータ Column.key
が含まれています。これは、列のSQL名とは無関係に、この Column
が Table.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 key で identity 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_id
と group_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"]}
は、 street
、 city
、 state
、 zip
以外のすべての列を含む 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.default
と Column.onupdate
の場合、 Column
オブジェクトはまだ下にある Table
に存在しているので、ORMがINSERTまたはUPDATEを発行したときにデフォルトの関数が実行されます。また、 Column.server_default
と Column.server_onupdate
の場合、リレーショナルデータベース自体がこれらのデフォルトをサーバ側の動作として発行するので、これらの構文は通常の効果を持ち続けます。