Конфигурация картографа с помощью декларативного метода

В разделе Сопоставленные существенные компоненты класса обсуждаются общие конфигурационные элементы конструкции Mapper, которая представляет собой структуру, определяющую, как определенный пользовательский класс сопоставляется с таблицей базы данных или другой конструкцией SQL. В следующих разделах описываются конкретные детали того, как декларативная система работает над созданием конструкции Mapper.

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

Примеры, приведенные в Конфигурация таблицы с помощью декларативного, иллюстрируют сопоставление со столбцами, связанными с таблицей; сопоставление отдельного столбца с атрибутом класса ORM внутренне представлено конструкцией ColumnProperty. Существует множество других разновидностей свойств отображения, наиболее распространенной из которых является конструкция relationship(). Другие виды свойств включают синонимы к столбцам, которые определяются с помощью конструкции synonym(), SQL-выражения, которые определяются с помощью конструкции column_property(), и отложенные столбцы и SQL-выражения, которые загружаются только при обращении к ним, определяемые с помощью конструкции deferred().

В то время как imperative mapping использует словарь properties для определения всех атрибутов сопоставленного класса, в декларативном отображении все эти свойства указываются в определении класса, а в случае декларативного табличного отображения - в объектах Column, которые будут использоваться для создания объекта Table.

Работая с примером отображения User и Address, мы можем проиллюстрировать декларативное отображение таблиц, которое включает не только объекты Column, но и отношения и выражения SQL:

# mapping attributes using declarative with declarative table
# i.e. __tablename__

from sqlalchemy import Column, ForeignKey, Integer, String, Text
from sqlalchemy.orm import (
    column_property,
    declarative_base,
    deferred,
    relationship,
)

Base = declarative_base()


class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)
    firstname = Column(String(50))
    lastname = Column(String(50))

    fullname = column_property(firstname + " " + lastname)

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


class Address(Base):
    __tablename__ = "address"

    id = Column(Integer, primary_key=True)
    user_id = Column(ForeignKey("user.id"))
    email_address = Column(String)
    address_statistics = deferred(Column(Text))

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

Приведенное выше декларативное отображение таблиц включает две таблицы, каждая из которых имеет relationship(), ссылающуюся на другую, а также простое выражение SQL, отображаемое column_property(), и дополнительное Column, которое будет загружено на «отложенной» основе, как определено конструкцией deferred(). Дополнительную документацию по этим конкретным концепциям можно найти в Основные модели взаимоотношений, Использование свойства_столбца и Отложенная загрузка колонн.

Свойства могут быть определены с помощью декларативного отображения, как описано выше, с использованием стиля «гибридной таблицы»; объекты 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,
    declarative_base,
    deferred,
    relationship,
)

Base = declarative_base()


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")

На что следует обратить внимание:

  • Адрес Table содержит столбец с именем address_statistics, однако мы переназначаем этот столбец под тем же именем атрибута, чтобы он находился под управлением конструкции deferred().

  • При использовании декларативных и гибридных сопоставлений таблиц, когда мы определяем конструкцию ForeignKey, мы всегда называем целевую таблицу, используя имя таблицы, а не имя сопоставленного класса.

  • Когда мы определяем конструкции relationship(), поскольку эти конструкции создают связь между двумя сопоставленными классами, где один обязательно определяется раньше другого, мы можем ссылаться на удаленный класс, используя его строковое имя. Эта функциональность также распространяется на область других аргументов, указанных в relationship(), таких как аргументы «primary join» и «order by». Подробнее об этом см. в разделе Поздняя оценка аргументов о взаимоотношениях.

Опции конфигурации картографа с помощью декларативного метода

При всех формах отображения отображение класса настраивается через параметры, которые становятся частью объекта Mapper. Функция, которая в конечном итоге получает эти аргументы, является функцией mapper(), и они поступают к ней от одной из функций отображения, определенных на объекте registry.

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

Колонки первичного ключа, специфичные для карты

Пример ниже иллюстрирует настройки декларативного уровня для параметра mapper.primary_key, который устанавливает определенные столбцы как часть того, что ORM должен считать первичным ключом для класса, независимо от ограничений первичного ключа на уровне схемы:

class GroupUsers(Base):
    __tablename__ = "group_users"

    user_id = Column(String(40))
    group_id = Column(String(40))

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

См.также

Сопоставление с явным набором столбцов первичного ключа - дополнительная информация об отображении явных столбцов в ORM в качестве столбцов первичного ключа

Колонка ID версии

В примере ниже показаны настройки декларативного уровня для параметров mapper.version_id_col и mapper.version_id_generator, которые конфигурируют счетчик версий, поддерживаемый ORM, который обновляется и проверяется в процессе unit of work flush:

from datetime import datetime


class Widget(Base):
    __tablename__ = "widgets"

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

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

См.также

Настройка счетчика версий - информация о функции счетчика версий ORM

Наследование одной таблицы

В приведенном ниже примере показаны настройки на уровне декларации для параметров mapper.polymorphic_on и mapper.polymorphic_identity, которые используются при настройке отображения наследования одной таблицы:

class Person(Base):
    __tablename__ = "person"

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

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


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

См.также

Наследование одной таблицы - справочная информация о функции отображения наследования одной таблицы ORM.

Динамическое построение аргументов картографа

Словарь __mapper_args__ может быть сгенерирован из метода дескриптора, связанного с классом, а не из фиксированного словаря с помощью конструкции declared_attr(). Это полезно для создания аргументов для отображателей, которые программно выводятся из конфигурации таблицы или других аспектов отображаемого класса. Динамический атрибут __mapper_args__ обычно полезен при использовании декларативного миксина или абстрактного базового класса.

Например, чтобы исключить из отображения любые колонки, имеющие специальное значение Column.info, миксин может использовать метод __mapper_args__, который сканирует эти колонки из атрибута cls.__table__ и передает их в коллекцию mapper.exclude_properties:

from sqlalchemy import Column
from sqlalchemy import Integer
from sqlalchemy import select
from sqlalchemy import String
from sqlalchemy.orm import declarative_base
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)
            ]
        }


Base = declarative_base()


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

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

Выше, миксин ExcludeColsWFlag предоставляет хук для каждого класса __mapper_args__, который будет сканировать объекты Column, включающие ключ/значение 'exclude': True, переданные в параметре Column.info, а затем добавит их строковое имя «key» в коллекцию mapper.exclude_properties, что не позволит результирующему Mapper учитывать эти столбцы для любых операций SQL.

Другие директивы декларативного отображения

__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

Добавлено в версии 0.9.3.

metadata

Коллекция MetaData, обычно используемая для присвоения нового Table, является атрибутом registry.metadata, связанным с используемым объектом registry. При использовании декларативного базового класса, например, созданного declarative_base(), а также registry.generate_base(), этот MetaData также обычно присутствует в виде атрибута .metadata, который находится непосредственно на базовом классе, а значит, и на отображаемом классе через наследование. Declarative использует этот атрибут, если он присутствует, для определения целевой коллекции MetaData, а если отсутствует, использует MetaData, связанный непосредственно с registry.

Этот атрибут также может быть назначен по направлению, чтобы повлиять на коллекцию MetaData, которая будет использоваться на основе иерархии на каждом отображении для одной базы и/или registry. Это действует независимо от того, используется ли декларативный базовый класс или декоратор registry.mapped() напрямую, что позволяет использовать такие шаблоны, как пример метаданных для абстрактной базы в следующем разделе, __abstract__. Аналогичный паттерн можно проиллюстрировать с помощью registry.mapped() следующим образом:

reg = registry()


class BaseOne:
    metadata = MetaData()


class BaseTwo:
    metadata = MetaData()


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

    id = Column(Integer, primary_key=True)


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

    id = Column(Integer, primary_key=True)


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

    id = Column(Integer, primary_key=True)

Изменено в версии 1.4.3: Декоратор registry.mapped() будет использовать атрибут .metadata на классе в качестве альтернативной коллекции MetaData, которая будет использоваться вместо MetaData, находящейся на самом registry. Это соответствует поведению базового класса, возвращаемого методом/функцией registry.generate_base() и sqlalchemy.orm.declarative_base(). Обратите внимание, что эта возможность была нарушена из-за регрессии в 1.4.0, 1.4.1 и 1.4.2, даже при использовании declarative_base(); 1.4.3 необходима для восстановления этого поведения.

См.также

__abstract__

__abstract__

__abstract__ заставляет declarative полностью пропустить создание таблицы или маппера для класса. Класс может быть добавлен в иерархию так же, как и mixin (см. Миксины и пользовательские базовые классы), позволяя подклассам расширяться только от специального класса:

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__ является использование разных MetaData для разных баз:

Base = declarative_base()


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


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

Выше, классы, которые наследуются от DefaultBase, будут использовать один MetaData в качестве реестра таблиц, а те, которые наследуются от OtherBase, будут использовать другой. Сами таблицы могут быть созданы, возможно, в разных базах данных:

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

__table_cls__

Позволяет настраивать вызываемый объект/класс, используемый для генерации Table. Это очень открытый хук, который может позволить специальные настройки для Table, которые генерируются здесь:

class MyMixin(object):
    @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(object):
    @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 = Column(Integer, primary_key=True)


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

Приведенный выше класс Employee будет отображен как однотабличное наследование на Person; столбец employee_name будет добавлен как член таблицы Person.

Вернуться на верх