Mapper Configuration with Declarative

セクション Mapped Class Essential Components では、 Mapper 構造体の一般的な設定要素について説明しています。これは、特定のユーザ定義クラスをデータベーステーブルや他のSQL構造体にマップする方法を定義する構造体です。以下のセクションでは、宣言型システムがどのように Mapper を構築するかについて詳しく説明します。

Defining Mapped Properties with Declarative

Table Configuration with Declarative の例では、 mapped_column() 構文を使用して、テーブルにバインドされた列に対するマッピングを示しています。テーブルにバインドされた列以外にも、設定可能なORMにマップされた構文はいくつかあります。最も一般的なのは relationship() 構文です。その他の種類のプロパティには、 column_property() 構文を使用して定義されたSQL式と、 composite() 構文を使用した複数列のマッピングがあります。

imperative mappingproperties 辞書を利用して、マップされたすべてのクラス属性を確立しますが、宣言型マッピングでは、これらのプロパティはすべてクラス定義のインラインで指定されます。宣言型テーブルマッピングの場合、クラス定義は Table オブジェクトの生成に使用される Column オブジェクトのインラインです。

UserAddress のマッピング例を使って、 mapped_column() オブジェクトだけでなく、関係やSQL式も含む宣言的なテーブルマッピングを示すことができます。:

from typing import List
from typing import Optional

from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import String
from sqlalchemy import Text
from sqlalchemy.orm import column_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    firstname: Mapped[str] = mapped_column(String(50))
    lastname: Mapped[str] = mapped_column(String(50))
    fullname: Mapped[str] = column_property(firstname + " " + lastname)

    addresses: Mapped[List["Address"]] = relationship(back_populates="user")

class Address(Base):
    __tablename__ = "address"

    id: Mapped[int] = mapped_column(primary_key=True)
    user_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
    email_address: Mapped[str]
    address_statistics: Mapped[Optional[str]] = mapped_column(Text, deferred=True)

    user: Mapped["User"] = relationship(back_populates="addresses")

プロパティは、上記のように”ハイブリッドテーブル”スタイルを使用して宣言的なマッピングで指定することもできます。テーブルの直接の一部である Column オブジェクトは Table 定義に移動しますが、合成されたSQL式を含む他のすべては、クラス定義と一致します。 Column を直接参照する必要がある構文は、 Table オブジェクトの観点から参照します。ハイブリッドテーブルスタイルを使用して上記のマッピングを説明するには:

# mapping attributes using declarative with imperative table
# i.e. __table__

from sqlalchemy import Column, ForeignKey, Integer, String, Table, Text
from sqlalchemy.orm import column_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import deferred
from sqlalchemy.orm import relationship

class Base(DeclarativeBase):
    pass

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

    fullname = column_property(__table__.c.firstname + " " + __table__.c.lastname)

    addresses = relationship("Address", back_populates="user")

class Address(Base):
    __table__ = Table(
        "address",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("user_id", ForeignKey("user.id")),
        Column("email_address", String),
        Column("address_statistics", Text),
    )

    address_statistics = deferred(__table__.c.address_statistics)

    user = relationship("User", back_populates="addresses")

上記の注意事項:

  • address:class:_schema.Table`には ``address_statistics` という名前の列がありますが、この列を同じ属性名で再マップして、 deferred() 構文の制御下に置きます。

  • 宣言テーブルとハイブリッドテーブルの両方のマッピングでは、 ForeignKey 構文を定義するとき、マップされたクラス名ではなく、常に テーブル名 を使用してターゲットテーブルに名前を付けます。

  • relationship() 構文を定義すると、これらの構文は2つのマップされたクラスの間にリンクを作成し、一方が他方よりも前に定義される必要があるため、リモートクラスをその文字列名を使用して参照できます。この機能は、 relationship() で指定された他の引数(“primary join”や”order by”引数など)の領域にも拡張されます。詳細については: ref:orm_declarative_relationship_eval を参照してください。

Mapper Configuration Options with Declarative

すべてのマッピング形式では、クラスのマッピングは Mapper オブジェクトの一部となるパラメータを通して設定されます。最終的にこれらの引数を受け取る関数は Mapper 関数で、 registry オブジェクトで定義された前面のマッピング関数の1つから渡されます。

宣言型のマッピングでは、mapperの引数は、 Mapper クラス変数にキーワード引数として渡されるディクショナリである、宣言型の __mapper_args__ を使って指定されます。以下に例を示します。:

Map Specific Primary Key Columns

以下の例は、 Mapper.primary_key パラメータの宣言レベルの設定を示しています。これは、スキーマレベルのプライマリキー制約とは無関係に、ORMがクラスのプライマリキーと見なすべきものの一部として特定の列を設定します。:

class GroupUsers(Base):
    __tablename__ = "group_users"

    user_id = mapped_column(String(40))
    group_id = mapped_column(String(40))

    __mapper_args__ = {"primary_key": [user_id, group_id]}

See also

Mapping to an Explicit Set of Primary Key Columns-主キー列としての明示的な列のORMマッピングのさらなる背景

Version ID Column

以下の例は、 Mapper.version_id_col および Mapper.version_id_generator パラメータの宣言レベルの設定を示しています。これは、 unit of work フラッシュプロセス内で更新されチェックされる、ORMが管理するバージョンカウンタを設定します。:

from datetime import datetime

class Widget(Base):
    __tablename__ = "widgets"

    id = mapped_column(Integer, primary_key=True)
    timestamp = mapped_column(DateTime, nullable=False)

    __mapper_args__ = {
        "version_id_col": timestamp,
        "version_id_generator": lambda v: datetime.now(),
    }

See also

Configuring a Version Counter - ORMバージョンカウンタ機能の背景

Single Table Inheritance

以下の例は、 Mapper.polymorphic_on および Mapper.polymorphic_identity パラメータの宣言レベルの設定を示しています。これらは、単一テーブルの継承マッピングを設定する際に使用されます。:

class Person(Base):
    __tablename__ = "person"

    person_id = mapped_column(Integer, primary_key=True)
    type = mapped_column(String, nullable=False)

    __mapper_args__ = dict(
        polymorphic_on=type,
        polymorphic_identity="person",
    )

class Employee(Person):
    __mapper_args__ = dict(
        polymorphic_identity="employee",
    )

See also

Single Table Inheritance - ORM単一テーブル継承マッピング機能の背景。

Constructing mapper arguments dynamically

declared_attr() 構文を利用することで、固定された辞書からではなく、クラスにバインドされた記述子メソッドから __mapper_args__ 辞書を生成することができます。これは、テーブルの設定やマップされたクラスの他の側面からプログラム的に派生したマッパーの引数を作成するのに便利です。動的な __mapper_args__ 属性は、一般的に宣言型ミックスインや抽象基底クラスを使用するときに便利です。

例えば、特別な Column.info 値を持つ列をマッピングから省略するために、mixinはこれらの列を cls.__table__ 属性からスキャンして Mapper.exclude_properties コレクションに渡す __mapper_args__ メソッドを使うことができます:

from sqlalchemy import Column
from sqlalchemy import Integer
from sqlalchemy import select
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr

class ExcludeColsWFlag:
    @declared_attr
    def __mapper_args__(cls):
        return {
            "exclude_properties": [
                column.key
                for column in cls.__table__.c
                if column.info.get("exclude", False)
            ]
        }

class Base(DeclarativeBase):
    pass

class SomeClass(ExcludeColsWFlag, Base):
    __tablename__ = "some_table"

    id = mapped_column(Integer, primary_key=True)
    data = mapped_column(String)
    not_needed = mapped_column(String, info={"exclude": True})

上の例では、 ExcludeColsWFlag ミックスインは、クラスごとの __mapper_args__ フックを提供します。このフックは、 Column.info パラメータに渡されたキー/値 'exclude' :True を含む Column オブジェクトをスキャンし、それらの文字列「key」名を Mapper.exclude_properties コレクションに追加します。これにより、結果として得られる Mapper は、これらの列をSQL操作で考慮しなくなります。

Other Declarative Mapping Directives

__declare_last__()

__declare_last__() フックを使うと、クラスレベルの関数を定義できます。この関数は MapperEvents.after_configured() イベントによって自動的に呼び出されます。このイベントは、マッピングが完了し、’configure’ステップが終了した後に発生します:

class MyClass(Base):
    @classmethod
    def __declare_last__(cls):
        """ """
        # do something with mappings

__declare_first__()

__declare_last__() に似ていますが、マッパーの設定の最初に MapperEvents.before_configured() イベントを介して呼び出されます。:

class MyClass(Base):
    @classmethod
    def __declare_first__(cls):
        """ """
        # do something before mappings are configured

metadata

通常、新しい Table を割り当てるために使用される MetaData コレクションは、使用中の registry オブジェクトに関連付けられた registry.MetaData 属性です。 DeclarativeBase スーパークラスによって生成されるような宣言的な基底クラスや、 declarative_base()registry.generate_base() のようなレガシー関数を使用する場合、この MetaData は通常、基底クラスに直接存在する、つまり継承によってマップされたクラスにも存在する .metadata という名前の属性としても存在します。宣言的は、この属性が存在する場合、ターゲットの MetaData コレクションを決定するために使用します。存在しない場合は、 registry に直接関連付けられた MetaData を使用します。

この属性は、単一のベースおよび/または registry に対してマップされた階層ごとに使用される MetaData コレクションに影響を与えるために割り当てられることもあります。これは、宣言的なベースクラスが使用されているか、 registry.mapped() デコレータが直接使用されているかにかかわらず有効になります。したがって、次のセクションの __abstract__ のMetaData-per-abstract baseの例のようなパターンが可能になります。同様のパターンは、次のように registry.mapped() を使用して説明できます:

reg = registry()

class BaseOne:
    metadata = MetaData()

class BaseTwo:
    metadata = MetaData()

@reg.mapped
class ClassOne:
    __tablename__ = "t1"  # will use reg.metadata

    id = mapped_column(Integer, primary_key=True)

@reg.mapped
class ClassTwo(BaseOne):
    __tablename__ = "t1"  # will use BaseOne.metadata

    id = mapped_column(Integer, primary_key=True)

@reg.mapped
class ClassThree(BaseTwo):
    __tablename__ = "t1"  # will use BaseTwo.metadata

    id = mapped_column(Integer, primary_key=True)

See also

__abstract__

__abstract__

_abstract__ を指定すると、宣言型はそのクラスのテーブルやマッパーの生成を完全にスキップします。クラスはmixinと同じ方法で階層内に追加でき( Mixin and Custom Base Classes を参照)、サブクラスは特別なクラスから拡張することができます:

class SomeAbstractBase(Base):
    __abstract__ = True

    def some_helpful_method(self):
        """ """

    @declared_attr
    def __mapper_args__(cls):
        return {"helpful mapper arguments": True}

class MyMappedClass(SomeAbstractBase):
    pass

__abstract__ の使用法の1つとして、異なるベースに対して別々の MetaData を使用することが考えられます。:

class Base(DeclarativeBase):
    pass

class DefaultBase(Base):
    __abstract__ = True
    metadata = MetaData()

class OtherBase(Base):
    __abstract__ = True
    metadata = MetaData()

上記では、 DefaultBase から継承するクラスはテーブルのレジストリとして1つの MetaData を使用し、 OtherBase から継承するクラスは別の MetaData を使用します。その後、テーブル自体を別のデータベース内に作成することができます。:

DefaultBase.metadata.create_all(some_engine)
OtherBase.metadata.create_all(some_other_engine)

See also

Building Deeper Hierarchies with polymorphic_abstract - 継承階層に適した”抽象”マップクラスの代替形式です。

__table_cls__

Table の生成に使用されるcallable/classをカスタマイズできるようにします。これは非常に自由なフックで、ここで生成される Table を特別にカスタマイズすることができます:

class MyMixin:
    @classmethod
    def __table_cls__(cls, name, metadata_obj, *arg, **kw):
        return Table(f"my_{name}", metadata_obj, *arg, **kw)

上記のミックスインでは、すべての Table オブジェクトが生成され、プレフィックス "my_" と、通常は __tablename__ 属性を使って指定される名前が続きます。

また、 __table_cls__None を返す場合もサポートしています。この場合、クラスはそのサブクラスに対して単一テーブルの継承とみなされます。これは、例えば主キーが存在しない場合に単一テーブルの継承と定義するなど、テーブル自体の引数に基づいて単一テーブルの継承を行う必要があることを決定するために、いくつかのカスタマイズスキームで役立つ場合があります。:

class AutoTable:
    @declared_attr
    def __tablename__(cls):
        return cls.__name__

    @classmethod
    def __table_cls__(cls, *arg, **kw):
        for obj in arg[1:]:
            if (isinstance(obj, Column) and obj.primary_key) or isinstance(
                obj, PrimaryKeyConstraint
            ):
                return Table(*arg, **kw)

        return None

class Person(AutoTable, Base):
    id = mapped_column(Integer, primary_key=True)

class Employee(Person):
    employee_name = mapped_column(String)

上記の Employee クラスは Person に対して単一テーブルの継承としてマップされ、 employee_name 列は Person テーブルのメンバとして追加されます。