ORM Mapped Class Overview

ORMクラスマッピング設定の概要。

SQLAlchemy ORMに慣れていない読者や、Python全般に慣れていない読者には、 ORM Quick Start をブラウズし、できれば SQLAlchemy Unified Tutorial で作業することをお勧めします。ORM設定は Using ORM Declarative Forms to Define Table Metadata で最初に紹介されています。

ORM Mapping Styles

SQLAlchemyは、2つの異なるスタイルのマッパー構成を特徴とし、その後、それらをどのように設定するかについてのさらなるサブオプションを特徴とする。マッパースタイルの可変性は、開発者の好みのさまざまなリストに適合するように存在します。これには、ユーザー定義クラスの抽象化の程度、リレーショナル・スキーマのテーブルや列へのマッピング方法、使用されているクラス階層の種類(カスタム・メタクラス・スキームが存在するかどうかを含む)、そして最後に、Python dataclasses が同時に使用されている場合など、他のクラス・インストルメンテーション・アプローチが存在するかどうかが含まれます。

現代のSQLAlchemyでは、これらのスタイルの違いはほとんど表面的なものです。特定のSQLAlchemy設定スタイルがクラスをマップする意図を表現するために使用される場合、クラスをマップする内部プロセスはそれぞれに対してほとんど同じ方法で進行します。最終的な結果は、通常 Table オブジェクトによって表される選択可能なユニットに対して設定された Mapper を持つユーザ定義クラスになります。クラス自体は、クラスのレベルとそのクラスのインスタンスの両方でリレーショナル操作にリンクされた動作を含めるために instrumented になっています。プロセスは基本的にすべての場合で同じであるため、異なるスタイルからマップされたクラスは常に相互に完全に相互運用可能です。mypyのような型チェッカーを使用する場合、プロトコル MappedClassProtocol を使用してマップされたクラスを示すことができます。

元のマッピングAPIは一般に”クラシカル”スタイルと呼ばれていますが、より自動化されたマッピングスタイルは”宣言型”スタイルと呼ばれています。SQLAlchemyでは、これら2つのマッピングスタイルを 命令型マッピング および 宣言型マッピング と 呼んでいます。

使用されるマッピングのスタイルにかかわらず、SQLAlchemy 1.4のすべてのORMマッピングは、マップされたクラスのレジストリである registry として知られる単一のオブジェクトから生成されます。このレジストリを使用すると、マッパー構成のセットをグループとして完成させることができ、特定のレジストリ内のクラスは、構成プロセス内で名前によって互いを参照できます。

Changed in version 1.4: 宣言的および古典的なマッピングは、”宣言的”および”命令的”マッピングと呼ばれるようになり、内部的に統一されています。これらはすべて、関連するマッピングの集合を表す registry 構成体に由来します。

Declarative Mapping

宣言型マッピング は、現代のSQLAlchemyでマッピングを構築する典型的な方法です。最も一般的なパターンは、最初に DeclarativeBase スーパークラスを使って基底クラスを構築することです。生成された基底クラスは、サブクラス化されると、デフォルトで新しい基底に対してローカルな特定の registry に関連して、基底クラスから派生するすべてのサブクラスに宣言型マッピングプロセスを適用します。次の例は、宣言型テーブルマッピングで使用される宣言型基底の使用方法を示しています。:

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

# declarative base class
class Base(DeclarativeBase):
    pass

# an example mapping using the base
class User(Base):
    __tablename__ = "user"

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

上では、 DeclarativeBase クラスが新しい基底クラスを生成するために使われています(SQLAlchemyのドキュメントでは、これは一般的に Base と呼ばれていますが、任意の名前にすることができます)。上では新しいマップされたクラス User が構築されているので、この基底クラスから新しいマップされるクラスが継承されます。

Changed in version 2.0: DeclarativeBase スーパークラスは、 declarative_base() 関数と registry.generate_base() メソッドの使用に取って代わります。スーパークラスのアプローチは、プラグインを使用せずに PEP 484 ツールと統合されます。移行の注意については ORM Declarative Models を参照してください。

基本クラスは、関連するマップされたクラスのコレクションを保持する registry オブジェクトと、クラスがマップされる Table オブジェクトのコレクションを保持する MetaData オブジェクトを参照します。

主要な宣言型マッピング・スタイルについては、次の項で詳しく説明します。

  • Declarative Table with mapped_column() - テーブル列は、 mapped_column() ディレクティブを使用して、マップされたクラス内でインラインで宣言されます(または、レガシー形式では Column オブジェクトを直接使用します)。 mapped_column() ディレクティブは、マップされた列に関する詳細を直接提供できる Mapped クラスを使用して、オプションで型アノテーションと組み合わせることもできます。列ディレクティブは、 __tablename__ およびオプションの __table_args__ クラスレベルディレクティブと組み合わせることで、宣言型マッピングプロセスがマップされる Table オブジェクトを構築できるようにします。

  • Declarative with Imperative Table (a.k.a. Hybrid Declarative) - テーブル名と属性を別々に指定する代わりに、明示的に構築された Table オブジェクトは、宣言的にマップされるクラスに関連付けられます。このスタイルのマッピングは”宣言的”マッピングと”命令的”マッピングのハイブリッドであり、クラスを reflectedTable オブジェクトにマッピングしたり、クラスを結合やサブクエリなどの既存のCore構成体にマッピングするなどのテクニックに適用されます。

宣言型マッピングのドキュメントは Mapping Classes with Declarative に続きます。

Imperative Mapping

命令型 または 古典的 マッピングとは、 registry.map_mandatory() メソッドを使用してマッピングされたクラスの構成を指します。この場合、ターゲットクラスには宣言型のクラス属性は含まれません。

Changed in version 2.0: registry.map_mandatory() メソッドが古典的なマッピングの作成に使用されるようになりました。スタンドアロン関数の sqlalchemy.orm.mapper() は事実上削除されました。

“古典的な”形式では、テーブルメタデータは Table 構文で別々に作成され、 registry インスタンスを確立した後、 registry.map_mandatory() メソッドを介して User クラスに関連付けられます。通常、 registry の単一のインスタンスは、相互に関連するすべてのマップされたクラスに対して共有されます:

from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import registry

mapper_registry = registry()

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

class User:
    pass

mapper_registry.map_imperatively(User, user_table)

他のクラスとの関係など、マップされた属性に関する情報は、 properties 辞書を介して提供されます。以下の例は、2番目の Table オブジェクトを示しています。このオブジェクトは Address というクラスにマップされ、 relationship() を介して User にリンクされています:

address = Table(
    "address",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("user_id", Integer, ForeignKey("user.id")),
    Column("email_address", String(50)),
)

mapper_registry.map_imperatively(
    User,
    user,
    properties={
        "addresses": relationship(Address, backref="user", order_by=address.c.id)
    },
)

mapper_registry.map_imperatively(Address, address)

命令型アプローチでマッピングされたクラスは、宣言型アプローチでマッピングされたクラスと 完全に交換可能 であることに注意してください。どちらのシステムも最終的には同じ構成を作成し、 Table というユーザー定義クラスで構成され、 Mapper オブジェクトとリンクされます。” Mapper の動作”について説明する場合、これには宣言型システムを使用する場合も含まれます。

Mapped Class Essential Components

すべてのマッピング形式で、クラスのマッピングは、コンストラクタを介して最終的に Mapper オブジェクトの一部となる構築引数を渡すことによって、さまざまな方法で設定できます。 Mapper に渡されるパラメータは、指定されたマッピング形式から発生します。これには、命令型マッピングの場合は registry.map_mandatory() に渡されるパラメータが含まれます。宣言型システムを使用している場合は、 __mapper_args__ などの属性とともにマップされるテーブル列、SQL式、および関係の組み合わせから発生します。

Mapper クラスが検索する設定情報には4つの一般的なクラスがあります。:

The class to be mapped

これは私たちのアプリケーションで構築するクラスです。通常、このクラスの構造に制限はありません。 [1] Pythonクラスがマップされる場合、そのクラスには 1つの Mapper オブジェクトしか存在できません。 [2]

imperative スタイルでマッピングする場合、クラスは map_imperative.class_ 引数として直接渡されます。

The table, or other from clause object

ほとんどの場合、これは Table のインスタンスです。より高度なユースケースでは、任意の種類の FromClause オブジェクトを参照することもできます。最も一般的な代替オブジェクトは Subquery および Join オブジェクトです。

declarative マッピングスタイルでマッピングする場合、サブジェクトテーブルは、提示された Column オブジェクトと __tablename__ 属性に基づいて宣言システムによって生成されるか、あるいは、 __table__ 属性を介して確立されます。これら2つの設定スタイルは、 Declarative Table with mapped_column()Declarative with Imperative Table (a.k.a. Hybrid Declarative) に示されています。

imperative スタイルでマッピングする場合、サブジェクトテーブルは map_imperative.local_table 引数として位置的に渡されます。

マップされたクラスの「クラスごとに1つのマッパー」要件とは対照的に、マッピングの対象である Table またはその他の FromClause オブジェクトは、任意の数のマッピングに関連付けることができます。 Mapper はユーザー定義のクラスに直接変更を適用しますが、指定された Table またはその他の FromClause は一切変更しません。

The properties dictionary

これは、マップされたクラスに関連付けられるすべての属性の辞書です。デフォルトでは、 Mapper は与えられた Table から、それぞれがマップされたテーブルの個々の Column を参照する ColumnProperty オブジェクトの形で、この辞書のエントリを生成します。プロパティ辞書には、設定される他のすべての種類の MapperProperty オブジェクトも含まれます。最も一般的には、 relationship() 構文によって生成されるインスタンスです。

imperative スタイルでマッピングする場合、プロパティ辞書は``properties``パラメータとして直接 registry.map_imperative() に渡され、 Mapper.properties パラメータに渡されます。

Other mapper configuration parameters

declarative マッピングスタイルでマッピングする場合、追加のマッパー設定引数は、クラス属性 __mapper_args__ を介して設定されます。使用例は Mapper Configuration Options with Declarative にあります。

imperative スタイルでマッピングする場合、キーワード引数はto registry.map_imperative() メソッドに渡され、 Mapper クラスに渡されます。

受け付けられるパラメータの全範囲については Mapper で説明されています。

Mapped Class Behavior

registry オブジェクトを使用するマッピングのすべてのスタイルで、次の動作が一般的です。

Default Constructor

registry は、明示的にそれ自身の __init__ メソッドを持たない全てのマップされたクラスに対して、デフォルトのコンストラクタ、つまり __init__ メソッドを適用します。このメソッドの振る舞いは、指定された全ての属性をオプションのキーワード引数として受け付ける便利なキーワードコンストラクタを提供します。例えば:

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]
    fullname: Mapped[str]

上記の User 型のオブジェクトは、 User オブジェクトを以下のように作成できるコンストラクタを持ちます。

u1 = User(name=”some name”, fullname=”some fullname”)

Tip

Declarative Dataclass Mapping 機能は、Pythonデータクラスを使用してデフォルトの __init__() メソッドを生成する代替手段を提供し、高度に設定可能なコンストラクタ形式を可能にします。

Warning

このクラスの __init__() メソッドは、オブジェクトがPythonコードで構築されたときにのみ呼び出され、 オブジェクトがデータベースからロードまたはリフレッシュされたときには呼び出されません 。オブジェクトがロードされたときに特殊なロジックを呼び出す方法については、次のセクション Maintaining Non-Mapped State Across Loads を参照してください。

明示的な __init__() メソッドを含むクラスはそのメソッドを維持し、デフォルトのコンストラクタは適用されません。

使用されるデフォルトのコンストラクタを変更するには、デフォルトのコンストラクタとして使用される registry.constructor パラメータに、ユーザ定義のPython呼び出し可能オブジェクトを指定します。

このコンストラクタは、命令型マッピングにも適用されます。:

from sqlalchemy.orm import registry

mapper_registry = registry()

user_table = Table(
    "user",
    mapper_registry.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
)

class User:
    pass

mapper_registry.map_imperatively(User, user_table)

上記のクラスは、 Imperative Mapping で説明されているように絶対的にマップされますが、 registry に関連付けられたデフォルトのコンストラクタも備えています。

New in version 1.4: クラシカルなマッピングが、 registry.map_mandatory() メソッドを使ってマップされるときに、標準の設定レベルのコンストラクタをサポートするようになりました。

Maintaining Non-Mapped State Across Loads

マップされたクラスの __init__() メソッドは、オブジェクトがPythonコードで直接構築された時に呼び出されます。:

u1 = User(name="some name", fullname="some fullname")

しかし、オブジェクトがORM Session を使ってロードされた場合、 __init__() メソッドは**呼び出されません**:

u1 = session.scalars(select(User).where(User.name == "some name")).first()

その理由は、データベースからロードされると、オブジェクトを構築するために使用される操作、上の例では User 、 は、初期構築というよりも、アンピクルなどの デシリアライゼーション に似ているからです。オブジェクトの重要な状態の大部分は、最初にアセンブルされることではなく、データベースの行から再ロードされることです。

したがって、データベースに格納されたデータの一部ではないオブジェクト内の状態を維持するために、オブジェクトがロードされたときおよび構築されたときにこの状態が存在するように、以下に詳述する2つの一般的なアプローチがあります。

  1. 必要に応じて属性を動的に計算するには、stateではなく @property のようなPython記述子を使用します。単純な属性の場合、これは最も簡単な方法であり、最もエラーが発生しにくい方法です。たとえば、 Point.xPoint.y を持つオブジェクト Point が、これらの属性の合計を持つ属性を必要とする場合:

    class Point(Base):
        __tablename__ = "point"
        id: Mapped[int] = mapped_column(primary_key=True)
        x: Mapped[int]
        y: Mapped[int]
    
        @property
        def x_plus_y(self):
            return self.x + self.y

    動的記述子を使用する利点は、値が毎回計算されることです。つまり、基礎となる属性(この場合は xy )が変更されても、正しい値が維持されます。

  1. InstanceEvents.load() およびオプションの補足メソッド InstanceEvents.refresh() および InstanceEvents.refresh_flush() を使用して、ロード時に状態を確立します。これらは、オブジェクトがデータベースからロードされるたびに、またはオブジェクトが期限切れになった後にリフレッシュされるときに呼び出されるイベント・フックです。マッピングされていないローカル・オブジェクトの状態は期限切れ操作の影響を受けないため、通常は InstanceEvents.load() のみが必要です。上記の Point の例を修正するには、次のようにします。:

    from sqlalchemy import event
    
    class Point(Base):
        __tablename__ = "point"
        id: Mapped[int] = mapped_column(primary_key=True)
        x: Mapped[int]
        y: Mapped[int]
    
        def __init__(self, x, y, **kw):
            super().__init__(x=x, y=y, **kw)
            self.x_plus_y = x + y
    
    @event.listens_for(Point, "load")
    def receive_load(target, context):
        target.x_plus_y = target.x + target.y

    更新イベントも使用する場合は、必要に応じて、次のように1つの呼び出し可能オブジェクトの上にイベントフックをスタックできます。:

    @event.listens_for(Point, "load")
    @event.listens_for(Point, "refresh")
    @event.listens_for(Point, "refresh_flush")
    def receive_load(target, context, attrs=None):
        target.x_plus_y = target.x + target.y

    上記では、 attrs 属性が refresh イベントと refresh_flush イベントに存在し、更新される属性名のリストを示します。

Runtime Introspection of Mapped classes, Instances and Mappers

registry を使ってマップされたクラスには、すべてのマッピングに共通するいくつかの属性もあります。

  • __mapper__ 属性は、クラスに関連付けられた Mapper を参照します:

    mapper = User.__mapper__

    この Mapper は、マップされたクラスに対して inspect() 関数を使った時に返されるものでもあります:

    from sqlalchemy import inspect
    
    mapper = inspect(User)
  • __table__ 属性は、クラスがマップされる Table またはより一般的には FromClause オブジェクトを参照します:

    table = User.__table__

    この FromClause は、 MapperMapper.local_table 属性を使用したときに返されるものでもあります:

    table = inspect(User).local_table

    クラスがそれ自身のテーブルを持たないサブクラスである、単一テーブルの継承マッピングの場合、 Mapper.local_table 属性と .__table__ 属性は None になります。このクラスのクエリ中に実際に選択された”選択可能”を取得するには、 Mapper.selectable 属性を使用します:

    table = inspect(User).selectable

Inspection of Mapper objects

前のセクションで説明したように、 Mapper オブジェクトは、 Runtime Inspection API システムを使用して、メソッドに関係なく、任意のマップされたクラスから利用できます。 inspect() 関数を使用すると、マップされたクラスから Mapper を取得できます:

>>> from sqlalchemy import inspect
>>> insp = inspect(User)

Mapper.columns を含む詳細な情報が利用可能です:

>>> insp.columns
<sqlalchemy.util._collections.OrderedProperties object at 0x102f407f8>

これは、リスト形式または個別の名前で表示できる名前空間です:

>>> list(insp.columns)
[Column('id', Integer(), table=<user>, primary_key=True, nullable=False), Column('name', String(length=50), table=<user>), Column('fullname', String(length=50), table=<user>), Column('nickname', String(length=50), table=<user>)]
>>> insp.columns.name
Column('name', String(length=50), table=<user>)

その他の名前空間には Mapper.all_orm_descriptors があります。これにはすべてのマップされた属性と、ハイブリッド、関連プロキシが含まれます:

>>> insp.all_orm_descriptors
<sqlalchemy.util._collections.ImmutableProperties object at 0x1040e2c68>
>>> insp.all_orm_descriptors.keys()
['fullname', 'nickname', 'name', 'id']

Mapper.column_attrs と同様に:

>>> list(insp.column_attrs)
[<ColumnProperty at 0x10403fde0; id>, <ColumnProperty at 0x10403fce8; name>, <ColumnProperty at 0x1040e9050; fullname>, <ColumnProperty at 0x1040e9148; nickname>]
>>> insp.column_attrs.name
<ColumnProperty at 0x10403fce8; name>
>>> insp.column_attrs.name.expression
Column('name', String(length=50), table=<user>)

See also

Mapper

Inspection of Mapped Instances

inspect() 関数は、マップされたクラスのインスタンスについての情報も提供します。クラスそのものではなく、マップされたクラスのインスタンスに適用された場合、返されるオブジェクトは InstanceState と呼ばれます。これは、クラスで使用されている Mapper へのリンクだけでなく、インスタンス内の個々の属性の状態に関する情報、現在の値や、データベースにロードされた値との関係など、を提供する詳細なインターフェイスへのリンクも提供します。

データベースからロードされた User クラスのインスタンスを指定します。:

>>> u1 = session.scalars(select(User)).first()

inspect() 関数は InstanceState オブジェクトを返します:

>>> insp = inspect(u1)
>>> insp
<sqlalchemy.orm.state.InstanceState object at 0x7f07e5fec2e0>

このオブジェクトを使うと、 Mapper のような要素を見ることができます:

>>> insp.mapper
<Mapper at 0x7f07e614ef50; User>

もしあれば、オブジェクトが attached である Session

>>> insp.session
<sqlalchemy.orm.session.Session object at 0x7f07e614f160>

オブジェクトの現在の persistence state に関する情報:

>>> insp.persistent
True
>>> insp.pending
False

ロードされていない属性や lazy loaded などの属性状態情報( addresses が関連クラスにマップされたクラスの relationship() を参照していると仮定します):

>>> insp.unloaded
{'addresses'}

最後のフラッシュ以降に変更されていないアトリビュートなど、アトリビュートの現在のin-Pythonステータスに関する情報:

>>> insp.unmodified
{'nickname', 'name', 'fullname', 'id'}

また、最後のフラッシュ以降の属性の変更に関する特定の履歴:

>>> insp.attrs.nickname.value
'nickname'
>>> u1.nickname = "new nickname"
>>> insp.attrs.nickname.history
History(added=['new nickname'], unchanged=(), deleted=['nickname'])