Composing Mapped Hierarchies with Mixins¶
Declarative スタイルを使用してクラスをマッピングする際の一般的なニーズは、特定の列、テーブルまたはマッパーのオプション、名前付けスキーム、またはその他のマッピングされたプロパティなどの共通の機能を多くのクラスで共有することです。宣言型マッピングを使用する場合、このイディオムは mixin classes を使用することによって、また宣言型基底クラス自体を拡張することによってサポートされます。
..tip:: mixinクラスに加えて、共通の列オプションは PEP 593 Annotated
型を使って多くのクラスで共有することもできます。これらのSQLAlchemy 2.0機能の背景については Mapping Multiple Type Configurations to Python Types と Mapping Whole Column Declarations to Python Types を参照してください。
一般的に混在するイディオムの例を以下に示します。:
from sqlalchemy import ForeignKey
from sqlalchemy.orm import declared_attr
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 CommonMixin:
"""define a series of common elements that may be applied to mapped
classes using this class as a mixin class."""
@declared_attr.directive
def __tablename__(cls) -> str:
return cls.__name__.lower()
__table_args__ = {"mysql_engine": "InnoDB"}
__mapper_args__ = {"eager_defaults": True}
id: Mapped[int] = mapped_column(primary_key=True)
class HasLogRecord:
"""mark classes that have a many-to-one relationship to the
``LogRecord`` class."""
log_record_id: Mapped[int] = mapped_column(ForeignKey("logrecord.id"))
@declared_attr
def log_record(self) -> Mapped["LogRecord"]:
return relationship("LogRecord")
class LogRecord(CommonMixin, Base):
log_info: Mapped[str]
class MyModel(CommonMixin, HasLogRecord, Base):
name: Mapped[str]
上の例は、ベースに2つのミックスイン CommonMixin
と HasLogRecord
を含むクラス MyModel
と、同じく CommonMixin
を含む補助クラス`LogRecord`を示しており、ミックスインとベースクラスでサポートされている以下のようなさまざまな構成を示しています。
mapped_column()
、Mapped
またはColumn
を使って宣言された列は、ミックスインまたは基本クラスから、マップされるターゲットクラスにコピーされます。上の例では、列属性CommonMixin.id
とHasLogRecord.log_record_id
を使って示されています。
__table_args__
や__mapper_args__
のような宣言的な指示子は、ミックスインやベースクラスに割り当てることができ、ミックスインやベースから継承するすべてのクラスに対して自動的に有効になります。上の例では、__table_args__
と__mapper_args__
属性を使ってこれを説明しています。
すべての宣言型ディレクティブは、
__tablename__
、__table__
、__table_args__
、__mapper_args__
を含め、declared_attr
デコレータ(具体的にはdeclared_attr.directive
サブメンバー、これについては後ほど詳しく説明します)で修飾されたユーザ定義のクラスメソッドを使って実装できます。上では、Table
名を動的に生成するdef__tablename__(cls)
クラスメソッドを使って説明しました。このメソッドがMyModel
クラスに適用されると、テーブル名はMyModel
として生成され、LogRecord
クラスに適用されると、テーブル名はLogRecord
として生成されます。
relationship()
のような他のORMプロパティは、同じくdeclared_attr
デコレータでデコレートされたユーザ定義のクラスメソッドを使って、マップされる対象のクラスで生成できます。上では、これをLogRecord
と呼ばれるマップされたオブジェクトに対して多対1のrelationship()
を生成することで説明しました。
上記の機能はすべて、 select()
の例を使って説明することができます。
>>> from sqlalchemy import select
>>> print(select(MyModel).join(MyModel.log_record))
SELECT mymodel.name, mymodel.id, mymodel.log_record_id
FROM mymodel JOIN logrecord ON logrecord.id = mymodel.log_record_id
Tip
declared_attr
の例は、それぞれのメソッドの例に対して正しい PEP 484 アノテーションを説明しようとします。 declared_attr
関数でのアノテーションの使用は 完全にオプション であり、Declarativeでは使用されません。しかし、これらのアノテーションはMypyの --strict
型チェックに合格するために必要です。
Additionally, the declared_attr.directive
sub-member illustrated above is optional as well, and is only significant for PEP 484 typing tools, as it adjusts for the expected return type when creating methods to override Declarative directives such as __tablename__
, __mapper_args__
and __table_args__
.
さらに、上で説明した declared_attr.directive
サブメンバーもオプションであり、 PEP 484 型定義ツールでのみ重要です。これは、 __tablename__
、 __mapper_args__
、 __table_args__
などの宣言型ディレクティブをオーバーライドするメソッドを作成するときに、期待される戻り値の型を調整するためです。
New in version 2.0: SQLAlchemy ORMの PEP 484 型定義サポートの一部として、 declared_attr
属性と宣言型設定属性を区別するために、 declared_attr.directive
が declared_attr
に追加されました。
ミックスインとベース・クラスの順序には、決まった規則はありません。通常のPythonメソッドの解決規則が適用され、上記の例は以下と同様に機能します。:
class MyModel(Base, HasLogRecord, CommonMixin):
name: Mapped[str] = mapped_column()
これがうまくいくのは、ここでは Base
が CommonMixin
や HasLogRecord
が定義する変数、つまり __tablename__
、 __table_args__
、 id
などを定義していないからです。 Base
が同じ名前の属性を定義している場合、継承リストの最初に置かれたクラスが、新しく定義されたクラスで使用される属性を決定します。
Tip
上記の例では、 Mapped
アノテーションクラスに基づいた Annotated Declarative Table 形式を使用していますが、mixinクラスは、 mapped_column()
の代わりに Column
を直接使用する場合のように、アノテーションのないレガシーな宣言形式でも完璧に動作します。
Changed in version 2.0: SQLAlchemyの1.4シリーズから来たユーザで、 mypy plugin を使っていた可能性のある人は、mypyプラグインがもう使われていなければ、宣言型ミックスインをマークするための declarative_mixin()
クラスデコレータはもう必要ありません。
Augmenting the Base¶
純粋なミックスインを使用することに加えて、このセクションのほとんどのテクニックは、特定のベースから派生したすべてのクラスに適用されるべきパターンのために、ベースクラスに直接適用することもできます。以下の例は、前のセクションの例のいくつかを Base
クラスの観点から示しています。:
from sqlalchemy import ForeignKey
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
"""define a series of common elements that may be applied to mapped
classes using this class as a base class."""
@declared_attr.directive
def __tablename__(cls) -> str:
return cls.__name__.lower()
__table_args__ = {"mysql_engine": "InnoDB"}
__mapper_args__ = {"eager_defaults": True}
id: Mapped[int] = mapped_column(primary_key=True)
class HasLogRecord:
"""mark classes that have a many-to-one relationship to the
``LogRecord`` class."""
log_record_id: Mapped[int] = mapped_column(ForeignKey("logrecord.id"))
@declared_attr
def log_record(self) -> Mapped["LogRecord"]:
return relationship("LogRecord")
class LogRecord(Base):
log_info: Mapped[str]
class MyModel(HasLogRecord, Base):
name: Mapped[str]
上記の場合、 MyModel
も LogRecord
も、 Base
から派生して、クラス名から派生したテーブル名、 id
という名前の主キー列、および Base.__table_args__
と Base.__mapper_args__
で定義された上記のテーブルとマッパー引数を持ちます。
レガシーの declarative_base()
または registry.generate_base()
を使用する場合、以下の注釈のない例に示すように、 declarative_base.cls
パラメータを次のように使用して同等の効果を生成することができます。:
# legacy declarative_base() use
from sqlalchemy import Integer, String
from sqlalchemy import ForeignKey
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base:
"""define a series of common elements that may be applied to mapped
classes using this class as a base class."""
@declared_attr.directive
def __tablename__(cls):
return cls.__name__.lower()
__table_args__ = {"mysql_engine": "InnoDB"}
__mapper_args__ = {"eager_defaults": True}
id = mapped_column(Integer, primary_key=True)
Base = declarative_base(cls=Base)
class HasLogRecord:
"""mark classes that have a many-to-one relationship to the
``LogRecord`` class."""
log_record_id = mapped_column(ForeignKey("logrecord.id"))
@declared_attr
def log_record(self):
return relationship("LogRecord")
class LogRecord(Base):
log_info = mapped_column(String)
class MyModel(HasLogRecord, Base):
name = mapped_column(String)
Mixing in Columns¶
Declarative table スタイルの設定が( imperative table 設定とは対照的に)使用されていることを前提に、mixinで列を指定することができます。これにより、mixinで宣言された列をコピーして、宣言プロセスが生成する Table
の一部にすることができます。 mapped_column()
、 Mapped
、 Column
の3つの構成体はすべて、宣言型mixinでインライン宣言することができます:
class TimestampMixin:
created_at: Mapped[datetime] = mapped_column(default=func.now())
updated_at: Mapped[datetime]
class MyModel(TimestampMixin, Base):
__tablename__ = "test"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
上記の場合、クラスベースに TimestampMixin
を含むすべての宣言型クラスは、すべての行挿入にタイムスタンプを適用する created_at
列と、例の目的のためにデフォルトを含まない updated_at
列を自動的に含みます。
(if it did, we would use the Column.onupdate
parameter
これは mapped_column()
で受け付けられます)。これらの列構成体は常に 元のミックスインまたは基底クラス からコピーされるので、同じミックスイン/基底クラスを、それぞれが独自の列構成体を持つ任意の数のターゲットクラスに適用できます。
mixinでは、次のようなすべての宣言型列フォームがサポートされています。:
注釈付き属性 -
mapped_column()
がある場合とない場合:class TimestampMixin: created_at: Mapped[datetime] = mapped_column(default=func.now()) updated_at: Mapped[datetime]
mapped_column-
Mapped
がある場合とない場合:class TimestampMixin: created_at = mapped_column(default=func.now()) updated_at: Mapped[datetime] = mapped_column()
列 - 従来の宣言形式:
class TimestampMixin: created_at = Column(DateTime, default=func.now()) updated_at = Column(DateTime)
上記の各フォームでは、Declarativeが構文の コピー を作成し、ターゲット・クラスに適用することによって、mixinクラスの列ベースの属性を処理します。
Changed in version 2.0: 宣言型APIは、 declared_attr()
を使わなくても、mixinを使うときに Column
オブジェクトや mapped_column()
構造体を扱うことができるようになりました。 ForeignKey
要素を持つ列をmixinで直接使うことを妨げていた以前の制限が取り除かれました。
Mixing in Relationships¶
relationship()
で作成された関係は、 declared_attr
アプローチを排他的に使用した宣言的なmixinクラスで提供され、関係とその列にバインドされた内容をコピーするときに発生する可能性のあるあいまいさを排除します。以下は外部キー列と関係を組み合わせた例で、2つのクラス Foo
と Bar
の両方が多対1で共通のターゲットクラスを参照するように設定できます。:
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class RefTargetMixin:
target_id: Mapped[int] = mapped_column(ForeignKey("target.id"))
@declared_attr
def target(cls) -> Mapped["Target"]:
return relationship("Target")
class Foo(RefTargetMixin, Base):
__tablename__ = "foo"
id: Mapped[int] = mapped_column(primary_key=True)
class Bar(RefTargetMixin, Base):
__tablename__ = "bar"
id: Mapped[int] = mapped_column(primary_key=True)
class Target(Base):
__tablename__ = "target"
id: Mapped[int] = mapped_column(primary_key=True)
上記のマッピングでは、 Foo
と Bar
のそれぞれに、 .target
属性に沿ってアクセスされる Target
との関係が含まれています。
>>> from sqlalchemy import select
>>> print(select(Foo).join(Foo.target))
SELECT foo.id, foo.target_id
FROM foo JOIN target ON target.id = foo.target_id
>>> print(select(Bar).join(Bar.target))
SELECT bar.id, bar.target_id
FROM bar JOIN target ON target.id = bar.target_id
relationship.primaryjoin
のような特別な引数も混合クラスのメソッド内で使うことができます。これはしばしばマップされているクラスを参照する必要があります。ローカルにマップされた列を参照する必要があるスキームの場合、通常これらの列はDeclarativeによってマップされたクラスの属性として利用可能になります。この属性はデコレートされたクラスのメソッドに cls
引数として渡されます。この機能を使うと、例えば RefTargetMixin.target
メソッドを、 Target
と cls
の両方で保留中のマップされた列を参照する明示的なプライマリ結合を使って書き直すことができます。:
class Target(Base):
__tablename__ = "target"
id: Mapped[int] = mapped_column(primary_key=True)
class RefTargetMixin:
target_id: Mapped[int] = mapped_column(ForeignKey("target.id"))
@declared_attr
def target(cls) -> Mapped["Target"]:
# illustrates explicit 'primaryjoin' argument
return relationship("Target", primaryjoin=Target.id == cls.target_id)
Mixing in column_property()
and other MapperProperty
classes¶
relationship()
と同様に、 column_property()
のような他の MapperProperty
サブクラスも、ミックスインで使用されるときに生成されたクラスローカルなコピーを持つ必要があるので、 declared_attr
で修飾された関数内でも宣言されます。この関数内では、 mapped_column()
、 Mapped
、または Column
で宣言された他の通常のマップされた列が、新しい属性を構成するために使用できるように、以下の例のように2つの列を一緒に追加するように、 cls
引数から利用できるようになります。:
from sqlalchemy.orm import column_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class SomethingMixin:
x: Mapped[int]
y: Mapped[int]
@declared_attr
def x_plus_y(cls) -> Mapped[int]:
return column_property(cls.x + cls.y)
class Something(SomethingMixin, Base):
__tablename__ = "something"
id: Mapped[int] = mapped_column(primary_key=True)
上記では、完全な式を生成する文で Something.x_plus_y
を使用することができます。
>>> from sqlalchemy import select
>>> print(select(Something.x_plus_y))
SELECT something.x + something.y AS anon_1
FROM something
Tip
declared_attr
デコレータは、デコレートされた呼び出し可能オブジェクトをクラスメソッドとまったく同じように動作させます。しかし、Pylance_のような型定義ツールはこれを認識できない可能性があり、関数の本体内の変数 cls
へのアクセスに関して警告を発することがあります。この問題が発生したときに解決するために、 @classmethod
デコレータを declared_attr
と次のように直接組み合わせることができます:
class SomethingMixin:
x: Mapped[int]
y: Mapped[int]
@declared_attr
@classmethod
def x_plus_y(cls) -> Mapped[int]:
return column_property(cls.x + cls.y)
New in version 2.0: - declared_attr
can accommodate a function decorated with @classmethod
to help with PEP 484 integration where needed.
New in version 2.0: - declared_attr
は必要に応じて PEP 484 との統合を支援するために、 @classmethod
で修飾された関数に対応できます。
Using Mixins and Base Classes with Mapped Inheritance Patterns¶
Mapping Class Inheritance Hierarchies で説明されているように、マッパーの継承パターンを扱う場合、 declared_attr
をミックスインクラスで使用したり、クラス階層内のマップされたスーパークラスとマップされていないスーパークラスの両方を拡張したりするときに、いくつかの追加機能があります。
declared_attr
で修飾された関数をミックスインや基本クラスで定義し、マップされた継承階層のサブクラスで解釈されるようにする場合、Declarativeで使用される特別な名前を生成する関数(例えば __tablename__
、 __mapper_args__
)と、通常のマップされた属性を生成する関数(例えば mapped_column()
、 relationship()
)との間には重要な違いがあります。 Declarative directive を定義する関数は**階層内の各サブクラスに対して 呼び出されます 。一方、 マップされた属性 を生成する関数は 階層内の最初にマップされたスーパークラスに対してのみ 呼び出されます。
この動作の違いの理論的根拠は、マップされたプロパティはすでにクラスによって継承可能であるという事実に基づいています。たとえば、スーパークラスのマップされたテーブルの特定の列は、サブクラスの列にも複製されるべきではありません。一方、特定のクラスまたはそのマップされたテーブルに固有の要素(ローカルにマップされたテーブルの名前など)は継承できません。
この2つのユースケースの動作の違いを、次の2つのセクションで説明します。
Using declared_attr()
with inheriting Table
and Mapper
arguments¶
ミックスインの一般的な方法は、マップされた Table
の名前を動的に生成する def__tablename__(cls)
関数を作成することです。
このレシピは、以下の例のように、継承するマッパー階層のテーブル名を生成するために使用できます。この例では、クラス名に基づいてすべてのクラスに単純なテーブル名を与えるミックスインを作成します。以下のレシピでは、マップされたクラス Person
と Person
のサブクラス Engineer
に対してテーブル名が生成されますが、 Person
のサブクラス Manager
に対しては生成されません。:
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class Tablename:
@declared_attr.directive
def __tablename__(cls) -> Optional[str]:
return cls.__name__.lower()
class Person(Tablename, Base):
id: Mapped[int] = mapped_column(primary_key=True)
discriminator: Mapped[str]
__mapper_args__ = {"polymorphic_on": "discriminator"}
class Engineer(Person):
id: Mapped[int] = mapped_column(ForeignKey("person.id"), primary_key=True)
primary_language: Mapped[str]
__mapper_args__ = {"polymorphic_identity": "engineer"}
class Manager(Person):
@declared_attr.directive
def __tablename__(cls) -> Optional[str]:
"""override __tablename__ so that Manager is single-inheritance to Person"""
return None
__mapper_args__ = {"polymorphic_identity": "manager"}
上の例では、 Person
基本クラスと、新しいテーブル名を生成する Tablename
mixinクラスのサブクラスである Engineer
クラスの両方が、生成された __tablename__
属性を持ちます。これは、Declarativeに対して、各クラスが生成された独自の Table
にマップされることを示します。 Engineer
サブクラスの場合、適用される継承のスタイルは joined table inheritance です。これは、ベースの person
テーブルに結合する engineer
テーブルにマッピングされるためです。 Person
から継承する他のサブクラスも、デフォルトでこのスタイルの継承が適用されます(この特定の例では、それぞれが主キー列を指定する必要があります。これについては次のセクションで詳しく説明します)。
対照的に、 Person
の Manager
サブクラスは、 None
を返すために __tablename__
クラスメソッドを上書きします。これはDeclarativeに対して、このクラスは生成された Table
を持つべきでは**なく**、代わりに Person
がマップされたベースの Table
だけを利用することを示します。 Manager
サブクラスに適用される継承のスタイルは single table inheritance です。
上の例は、それぞれのマップされたクラスがどの Table
にマップされるのか、あるいは継承するスーパークラス’ Table
に自身をマップするのかを記述する必要があるので、 __tablename__
のような宣言的な指示が、それぞれのサブクラスに 個別に 適用されることを示しています。
代わりに、上記のデフォルトのテーブルスキームを 反転 して、単一のテーブル継承をデフォルトとし、それを上書きするために __tablename__
ディレクティブが指定された場合にのみ結合されたテーブル継承を定義できるようにしたい場合は、最上位の __tablename__()
メソッド内で宣言型ヘルパーを使用できます。この場合、ヘルパーは has_inherited_table()
と呼ばれます。この関数は、スーパークラスがすでに Table
にマップされている場合は True
を返します。このヘルパーを最も基本的な __tablename__()
クラスメソッド内で使用すると、テーブルがすでに存在している場合に、テーブル名に対して None
を 条件付きで 返すことができ、デフォルトでサブクラスを継承するための単一テーブル継承を示すことができます。:
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import has_inherited_table
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class Tablename:
@declared_attr.directive
def __tablename__(cls):
if has_inherited_table(cls):
return None
return cls.__name__.lower()
class Person(Tablename, Base):
id: Mapped[int] = mapped_column(primary_key=True)
discriminator: Mapped[str]
__mapper_args__ = {"polymorphic_on": "discriminator"}
class Engineer(Person):
@declared_attr.directive
def __tablename__(cls):
"""override __tablename__ so that Engineer is joined-inheritance to Person"""
return cls.__name__.lower()
id: Mapped[int] = mapped_column(ForeignKey("person.id"), primary_key=True)
primary_language: Mapped[str]
__mapper_args__ = {"polymorphic_identity": "engineer"}
class Manager(Person):
__mapper_args__ = {"polymorphic_identity": "manager"}
Using declared_attr()
to generate table-specific inheriting columns¶
declared_attr
と一緒に使用された場合、 `__tablename__`
やその他の特殊な名前がどのように処理されるかとは対照的に、列とプロパティ(例えば、関係、列のプロパティなど)が混在する場合、 declared_attr
ディレクティブが declared_attr.cascading
サブディレクティブと一緒に使用されない限り、関数は階層内の 基底クラスのみ に対して呼び出されます。以下では、 Person
クラスのみが id
という列を受け取ります。主キーが与えられていない Engineer
ではマッピングが失敗します。:
class HasId:
id: Mapped[int] = mapped_column(primary_key=True)
class Person(HasId, Base):
__tablename__ = "person"
discriminator: Mapped[str]
__mapper_args__ = {"polymorphic_on": "discriminator"}
# this mapping will fail, as there's no primary key
class Engineer(Person):
__tablename__ = "engineer"
primary_language: Mapped[str]
__mapper_args__ = {"polymorphic_identity": "engineer"}
通常、結合テーブルの継承では、各サブクラスに明確な名前の列が必要になります。ただし、この場合は、すべてのテーブルに id
列を作成し、外部キーを介して互いを参照させることができます。これは、 declared_attr.cascading
修飾子を使用してミックスインとして実現できます。これは、関数が **階層内の各クラス**に対して 、 いつも 呼び出されることを示します。
(以下の警告を参照してください) これは __tablename__
の場合と同じです:
class HasIdMixin:
@declared_attr.cascading
def id(cls) -> Mapped[int]:
if has_inherited_table(cls):
return mapped_column(ForeignKey("person.id"), primary_key=True)
else:
return mapped_column(Integer, primary_key=True)
class Person(HasIdMixin, Base):
__tablename__ = "person"
discriminator: Mapped[str]
__mapper_args__ = {"polymorphic_on": "discriminator"}
class Engineer(Person):
__tablename__ = "engineer"
primary_language: Mapped[str]
__mapper_args__ = {"polymorphic_identity": "engineer"}
Warning
declared_attr.cascading
機能は現在、サブクラスが異なる関数や値で属性をオーバーライドすることを 許可していません 。これは現在のところ、 @declared_attr
の解決方法の制限であり、この状態が検出された場合に警告が表示されます。この制限は、ORMマップされた列、関係、およびその他の :class`.MapperProperty` スタイルの属性にのみ適用されます。 declared_attr.cascading
とは内部的に異なる方法で解決される __tablename__
、 __mapper_args__
などの宣言型ディレクティブには 適用されません 。
Combining Table/Mapper Arguments from Multiple Mixins¶
宣言的なmixinで指定された __table_args__
や __mapper_args__
の場合、いくつかのmixinのいくつかのパラメータを、そのクラス自体で定義したいパラメータと組み合わせることができます。 declared_attr
デコレータをここで使用して、複数のコレクションからプルするユーザ定義の照合ルーチンを作成できます:
from sqlalchemy.orm import declarative_mixin, declared_attr
class MySQLSettings:
__table_args__ = {"mysql_engine": "InnoDB"}
class MyOtherMixin:
__table_args__ = {"info": "foo"}
class MyModel(MySQLSettings, MyOtherMixin, Base):
__tablename__ = "my_model"
@declared_attr.directive
def __table_args__(cls):
args = dict()
args.update(MySQLSettings.__table_args__)
args.update(MyOtherMixin.__table_args__)
return args
id = mapped_column(Integer, primary_key=True)
Creating Indexes and Constraints with Naming Conventions on Mixins¶
Index
、 UniqueConstraint
、 CheckConstraint
などの名前付き制約を使用する場合、各オブジェクトはミックスインから派生した特定のテーブルに対して一意であるため、実際にマップされたクラスごとに各オブジェクトの個々のインスタンスを作成する必要があります。
簡単な例として、ミックスインから派生したすべてのテーブルに適用される、名前付きで、複数列の可能性がある Index
を定義するには、 Index
の”インライン”形式を使用して、それを __table_args__
の一部として確立します。 declared_attr
を使用して、各サブクラスに対して呼び出されるクラスメソッドとして __table_args__()
を確立します。:
class MyMixin:
a = mapped_column(Integer)
b = mapped_column(Integer)
@declared_attr.directive
def __table_args__(cls):
return (Index(f"test_idx_{cls.__tablename__}", "a", "b"),)
class MyModelA(MyMixin, Base):
__tablename__ = "table_a"
id = mapped_column(Integer, primary_key=True)
class MyModelB(MyMixin, Base):
__tablename__ = "table_b"
id = mapped_column(Integer, primary_key=True)
上の例では、2つのテーブル table_a
と table_b
が、インデックス test_idx_table_a
と test_idx_table_b
を使って生成されます。
通常、最近のSQLAlchemyでは、 Configuring Constraint Naming Conventions で説明されているような命名規則を使用します。命名規則は、新しい Constraint
オブジェクトが作成されるときに MetaData.naming_convention
を使用して自動的に行われますが、この規則は特定の Constraint
の親 Table
に基づいてオブジェクト構築時に適用されるため、独自の Table
を持つ継承サブクラスごとに個別の Constraint
オブジェクトを作成する必要があります。この場合も、 declared_attr
を __table_args__()`
とともに使用します。以下では、抽象マップベースを使用して説明します。:
from uuid import UUID
from sqlalchemy import CheckConstraint
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import UniqueConstraint
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
constraint_naming_conventions = {
"ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s",
}
class Base(DeclarativeBase):
metadata = MetaData(naming_convention=constraint_naming_conventions)
class MyAbstractBase(Base):
__abstract__ = True
@declared_attr.directive
def __table_args__(cls):
return (
UniqueConstraint("uuid"),
CheckConstraint("x > 0 OR y < 100", name="xy_chk"),
)
id: Mapped[int] = mapped_column(primary_key=True)
uuid: Mapped[UUID]
x: Mapped[int]
y: Mapped[int]
class ModelAlpha(MyAbstractBase):
__tablename__ = "alpha"
class ModelBeta(MyAbstractBase):
__tablename__ = "beta"
上のマッピングでは、プライマリ・キー、CHECK制約、一意性制約を含むすべての制約のテーブル固有の名前を含むDDLが生成されます。:
CREATE TABLE alpha (
id INTEGER NOT NULL,
uuid CHAR(32) NOT NULL,
x INTEGER NOT NULL,
y INTEGER NOT NULL,
CONSTRAINT pk_alpha PRIMARY KEY (id),
CONSTRAINT uq_alpha_uuid UNIQUE (uuid),
CONSTRAINT ck_alpha_xy_chk CHECK (x > 0 OR y < 100)
)
CREATE TABLE beta (
id INTEGER NOT NULL,
uuid CHAR(32) NOT NULL,
x INTEGER NOT NULL,
y INTEGER NOT NULL,
CONSTRAINT pk_beta PRIMARY KEY (id),
CONSTRAINT uq_beta_uuid UNIQUE (uuid),
CONSTRAINT ck_beta_xy_chk CHECK (x > 0 OR y < 100)
)