Составление сопоставленных иерархий с помощью миксинов

При отображении классов с использованием стиля Declarative часто возникает необходимость разделить общую функциональность, такую как определенные колонки, параметры таблицы или отображающего устройства, схемы именования или другие отображаемые свойства, между многими классами. При использовании декларативных отображений эта идиома поддерживается с помощью mixin classes, а также путем дополнения самого декларативного базового класса.

Совет

В дополнение к классам-миксинам, общие параметры столбцов могут также совместно использоваться многими классами с помощью типов PEP 593 Annotated; смотрите Сопоставление нескольких конфигураций типов с типами Python и Сопоставление объявлений целых столбцов с типами Python для получения информации об этих возможностях SQLAlchemy 2.0.

Ниже приведен пример некоторых часто смешиваемых идиом:

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]

Приведенный выше пример иллюстрирует класс MyModel, который включает в свою базу два миксина CommonMixin и HasLogRecord, а также дополнительный класс LogRecord, который также включает CommonMixin, демонстрируя разнообразие конструкций, поддерживаемых миксинами и базовыми классами, включая:

  • колонки, объявленные с помощью mapped_column(), Mapped или Column, копируются из миксинов или базовых классов в целевой класс для отображения; выше это показано с помощью атрибутов колонок CommonMixin.id и HasLogRecord.log_record_id.

  • Декларативные директивы, такие как __table_args__ и __mapper_args__, могут быть присвоены миксину или базовому классу, где они будут автоматически действовать для всех классов, которые наследуются от миксина или базы. Приведенный выше пример иллюстрирует это с помощью атрибутов __table_args__ и __mapper_args__.

  • Все директивы Declarative, включая все директивы __tablename__, __table__, __table_args__ и __mapper_args__, могут быть реализованы с помощью определяемых пользователем методов класса, которые украшаются декоратором declared_attr (в частности, подчленом declared_attr.directive, подробнее об этом чуть позже). Выше это показано на примере метода класса def __tablename__(cls), который динамически генерирует имя Table; при применении к классу MyModel имя таблицы будет сгенерировано как "mymodel", а при применении к классу LogRecord имя таблицы будет сгенерировано как "logrecord".

  • Другие свойства ORM, такие как relationship(), могут быть сгенерированы на целевом классе для отображения с помощью пользовательских методов класса, также украшенных декоратором declared_attr. Выше это было проиллюстрировано на примере генерации свойства многие-к-одному relationship() для сопоставленного объекта под названием LogRecord.

Все описанные выше возможности можно продемонстрировать на примере select():

>>> from sqlalchemy import select
>>> print(select(MyModel).join(MyModel.log_record))
{printsql}SELECT mymodel.name, mymodel.id, mymodel.log_record_id
FROM mymodel JOIN logrecord ON logrecord.id = mymodel.log_record_id

Совет

В примерах declared_attr будет сделана попытка проиллюстрировать правильные аннотации PEP 484 для каждого примера метода. Использование аннотаций в функциях declared_attr является совершенно необязательным и не используется в Declarative; однако эти аннотации необходимы для прохождения проверки типов Mypy --strict.

Кроме того, подчлен declared_attr.directive, проиллюстрированный выше, также является необязательным и имеет значение только для инструментов типизации PEP 484, поскольку он корректирует ожидаемый тип возврата при создании методов для переопределения декларативных директив, таких как __tablename__, __mapper_args__ и __table_args__.

Добавлено в версии 2.0: В рамках поддержки типизации PEP 484 для SQLAlchemy ORM, добавили declared_attr.directive к declared_attr для различения между Mapped атрибутами и декларативными конфигурационными атрибутами

Не существует фиксированного соглашения о порядке следования миксинов и базовых классов. Применяются обычные правила разрешения методов Python, и приведенный выше пример будет работать так же хорошо с:

class MyModel(Base, HasLogRecord, CommonMixin):
    name: Mapped[str] = mapped_column()

Это работает потому, что Base здесь не определяет ни одну из переменных, которые определяют CommonMixin или HasLogRecord, т.е. __tablename__, __table_args__, id и т.д. Если бы Base определял одноименный атрибут, то класс, стоящий первым в списке наследований, определял бы, какой атрибут используется во вновь определенном классе.

Совет

Хотя в приведенном выше примере используется форма Annotated Declarative Table, основанная на аннотационном классе Mapped, классы mixin также прекрасно работают с неаннотированными и унаследованными декларативными формами, например, при использовании Column непосредственно вместо mapped_column().

Изменено в версии 2.0: Для пользователей SQLAlchemy версии 1.4, которые, возможно, использовали mypy plugin, декоратор класса declarative_mixin() больше не нужен для маркировки декларативных миксинов, предполагая, что плагин mypy больше не используется.

Расширение базы

Помимо использования чистого миксина, большинство приемов, описанных в этом разделе, можно также применять непосредственно к базовому классу для получения шаблонов, которые должны применяться ко всем классам, производным от конкретной базы. Пример ниже иллюстрирует некоторые из примеров предыдущего раздела в терминах класса 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)

Смешивание в колоннах

Колонки могут быть указаны в миксинах при условии использования стиля конфигурации Declarative table (в отличие от конфигурации imperative table), так что колонки, объявленные в миксине, могут быть затем скопированы, чтобы стать частью Table, который генерирует процесс Declarative. Все три конструкции mapped_column(), Mapped и Column могут быть объявлены inline в декларативном миксине:

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, которая не включает значение по умолчанию для целей примера (если бы она включала, мы бы использовали параметр Column.onupdate, который принимается mapped_column()). Эти конструкции столбцов всегда копируются из исходного миксина или базового класса, так что один и тот же миксин/базовый класс может быть применен к любому количеству целевых классов, каждый из которых будет иметь свои собственные конструкции столбцов.

Все декларативные формы колонок поддерживаются миксинами, включая:

  • Аннотированные атрибуты - с присутствием или без присутствия 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 путем создания копии конструкции, которая затем применяется к целевому классу.

Изменено в версии 2.0: Декларативный API теперь может принимать объекты Column, а также конструкции mapped_column() любой формы при использовании миксинов без необходимости использования declared_attr(). Предыдущие ограничения, которые не позволяли использовать колонки с элементами ForeignKey непосредственно в миксинах, были устранены.

Смешение в отношениях

Отношения, созданные с помощью relationship(), снабжаются декларативными классами-миксинами исключительно с использованием подхода declared_attr, что устраняет любую двусмысленность, которая может возникнуть при копировании отношения и его, возможно, связанного с колонками содержимого. Ниже приведен пример, в котором столбец внешнего ключа и отношение сочетаются таким образом, что два класса Foo и Bar могут быть настроены так, чтобы ссылаться на общий целевой класс по принципу «многие-к-одному:»:

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))
{printsql}SELECT foo.id, foo.target_id
FROM foo JOIN target ON target.id = foo.target_id{stop}
>>> print(select(Bar).join(Bar.target))
{printsql}SELECT bar.id, bar.target_id
FROM bar JOIN target ON target.id = bar.target_id{stop}

Специальные аргументы, такие как relationship.primaryjoin, могут также использоваться в смешанных методах класса, которые часто должны ссылаться на сопоставляемый класс. Для схем, которым необходимо ссылаться на локально отображаемые столбцы, в обычных случаях эти столбцы предоставляются Declarative как атрибуты отображаемого класса, который передается в качестве аргумента cls в декорированный метод класса. Используя эту возможность, мы можем, например, переписать метод RefTargetMixin.target с помощью явного primaryjoin, который ссылается на ожидающие отображения столбцы в 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)

Смешивание column_property() и других MapperProperty классов

Как и relationship(), другие подклассы MapperProperty, такие как column_property(), также нуждаются в создании локальных для класса копий, когда используются миксинами, поэтому они также объявляются внутри функций, оформленных declared_attr. Внутри функции другие обычные отображаемые колонки, которые были объявлены с помощью mapped_column(), Mapped или Column, будут доступны из аргумента 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))
{printsql}SELECT something.x + something.y AS anon_1
FROM something

Совет

Декоратор 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)

Добавлено в версии 2.0: - declared_attr can accommodate a function decorated with @classmethod to help with PEP 484 integration where needed.

Использование миксинов и базовых классов с шаблонами сопоставленного наследования

При работе с шаблонами наследования mapper, документированными в Отображение иерархий наследования классов, некоторые дополнительные возможности появляются при использовании declared_attr либо с классами-миксинами, либо при дополнении как сопоставленных, так и несопоставленных суперклассов в иерархии классов.

При определении функций, украшенных declared_attr на миксинах или базовых классах для интерпретации подклассами в иерархии наследования с отображением, необходимо проводить важное различие между функциями, которые генерируют специальные имена, используемые Declarative, такие как __tablename__, __mapper_args__, и теми, которые могут генерировать обычные отображаемые атрибуты, такие как mapped_column() и relationship(). Функции, определяющие директивы Declarative, вызываются для каждого подкласса в иерархии, тогда как функции, генерирующие сопоставленные атрибуты, вызываются только для первого сопоставленного суперкласса в иерархии.

Обоснование такого различия в поведении основано на том, что сопоставленные свойства уже наследуются классами, например, определенный столбец в сопоставленной таблице суперкласса не должен дублироваться и в подклассе, тогда как элементы, специфичные для конкретного класса или его сопоставленной таблицы, не наследуются, например, имя локально сопоставленной таблицы.

Разница в поведении между этими двумя вариантами использования демонстрируется в следующих двух разделах.

Использование declared_attr() с наследуемыми аргументами Table и Mapper

Обычный рецепт использования микшинов заключается в создании функции def __tablename__(cls), которая динамически генерирует имя для сопоставленного Table.

Этот рецепт можно использовать для генерации имен таблиц для наследуемой иерархии маппера, как в примере ниже, который создает миксин, дающий каждому классу простое имя таблицы на основе имени класса. Рецепт показан ниже, где имя таблицы генерируется для сопоставленного класса Person и подкласса Engineer от Person, но не для подкласса Manager от Person:

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, так и класс Engineer, являющиеся подклассами класса Tablename mixin, который генерирует новые имена таблиц, будут иметь атрибут generated __tablename__, что в Declarative указывает на то, что каждый класс должен иметь свой собственный генерируемый Table, с которым он будет сопоставлен. Для подкласса Engineer применяется стиль наследования joined table inheritance, так как он будет отображен на таблицу engineer, которая присоединяется к базовой таблице person. Любые другие подклассы, наследующие от Person, также будут иметь этот стиль наследования по умолчанию (и в данном конкретном примере для каждого из них потребуется указать столбец первичного ключа; подробнее об этом в следующем разделе).

Напротив, подкласс Manager класса Person переопределяет метод класса __tablename__, чтобы вернуть None. Это указывает Declarative, что данный класс **не должен иметь порожденного Table, и вместо этого будет использовать исключительно базовый Table, к которому привязан Person. Для подкласса Manager применяется стиль наследования single table inheritance.

Приведенный пример показывает, что декларативные директивы типа __tablename__ обязательно применяются к каждому подклассу индивидуально, поскольку каждый сопоставленный класс должен указать, к какому Table он будет сопоставлен, или будет ли он сам сопоставлен с Table наследующего суперкласса.

Если мы хотим обратно изменить схему таблиц по умолчанию, показанную выше, чтобы наследование одной таблицы было по умолчанию, а наследование объединенных таблиц могло быть определено только при наличии директивы __tablename__ для ее переопределения, мы можем использовать декларативные помощники в самом верхнем методе __tablename__(), в данном случае помощник под названием has_inherited_table(). Эта функция вернет True, если суперкласс уже сопоставлен с Table. Мы можем использовать этот помощник внутри самого базового метода класса __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"}

Использование declared_attr() для создания столбцов наследования для конкретной таблицы

В отличие от того, как обрабатываются __tablename__ и другие специальные имена при использовании declared_attr, когда мы смешиваем столбцы и свойства (например, отношения, свойства столбцов и т.д.), функция вызывается только для базового класса в иерархии, если только директива 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"}

Предупреждение

Функция declared_attr.cascading в настоящее время не позволяет подклассу переопределить атрибут с другой функцией или значением. Это текущее ограничение в механике разрешения @declared_attr, и при обнаружении этого условия выдается предупреждение. Это ограничение относится только к колонкам, отображаемым ORM, отношениям и другим стилям атрибутов MapperProperty. Оно не применимо к декларативным директивам, таким как __tablename__, __mapper_args__ и т.д., которые разрешаются внутренним способом, отличным от declared_attr.cascading.

Объединение аргументов таблицы/маппера из нескольких миксинов

В случае __table_args__ или __mapper_args__, заданных с помощью декларативных миксинов, вы можете захотеть объединить некоторые параметры из нескольких миксинов с теми, которые вы хотите определить в самом классе. Декоратор 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
    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)

Создание индексов с помощью миксинов

Чтобы определить именованный, потенциально многоколоночный Index, который применяется ко всем таблицам, полученным из миксина, используйте «inline» форму Index и установите его как часть __table_args__:

class MyMixin:
    a = mapped_column(Integer)
    b = mapped_column(Integer)

    @declared_attr
    def __table_args__(cls):
        return (Index(f"test_idx_{cls.__tablename__}", "a", "b"),)


class MyModel(MyMixin, Base):
    __tablename__ = "atable"
    c = mapped_column(Integer, primary_key=True)
Вернуться на верх