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 mapping は properties 辞書を利用して、マップされたすべてのクラス属性を確立しますが、宣言型マッピングでは、これらのプロパティはすべてクラス定義のインラインで指定されます。宣言型テーブルマッピングの場合、クラス定義は Table
オブジェクトの生成に使用される Column
オブジェクトのインラインです。
User
と Address
のマッピング例を使って、 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__
を指定すると、宣言型はそのクラスのテーブルやマッパーの生成を完全にスキップします。クラスは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
テーブルのメンバとして追加されます。