Конфигурация картографа с помощью декларативного метода¶
В разделе Сопоставленные существенные компоненты класса обсуждаются общие конфигурационные элементы конструкции Mapper
, которая представляет собой структуру, определяющую, как определенный пользовательский класс сопоставляется с таблицей базы данных или другой конструкцией SQL. В следующих разделах описываются конкретные детали того, как декларативная система работает над созданием конструкции Mapper
.
Определение сопоставленных свойств с помощью декларативного метода¶
Примеры, приведенные в Конфигурация таблицы с помощью декларативного, иллюстрируют отображение на столбцы, привязанные к таблице, с использованием конструкции mapped_column()
. Существует несколько других разновидностей отображаемых конструкций ORM, которые могут быть настроены помимо столбцов, связанных с таблицей, наиболее распространенной из которых является конструкция relationship()
. Другие виды свойств включают выражения SQL, которые определяются с помощью конструкции column_property()
, и отображения нескольких столбцов с помощью конструкции composite()
.
В то время как в imperative mapping используется словарь properties для определения всех атрибутов сопоставленного класса, в декларативном отображении все эти свойства указываются в определении класса, а в случае декларативного табличного отображения - в объектах Column
, которые будут использоваться для создания объекта Table
.
Работая с примером отображения 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")
Приведенное выше декларативное отображение таблиц включает две таблицы, каждая из которых имеет relationship()
, ссылающуюся на другую, а также простое выражение SQL, отображаемое column_property()
, и дополнительное mapped_column()
, которое указывает, что загрузка должна быть «отложенной», как определено ключевым словом mapped_column.deferred
. Дополнительную документацию по этим конкретным концепциям можно найти в Основные модели взаимоотношений, Использование свойства_столбца и Ограничение загрузки столбцов с помощью функции Column Deferral.
Свойства могут быть определены с помощью декларативного отображения, как описано выше, с использованием стиля «гибридной таблицы»; объекты 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")
Следует отметить следующее:
Адрес
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 = mapped_column(String(40))
group_id = mapped_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 = 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(),
}
См.также
Настройка счетчика версий - справочная информация о функции счетчика версий ORM
Наследование одной таблицы
В приведенном ниже примере показаны настройки на уровне декларации для параметров 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",
)
См.также
Наследование одной таблицы - справочная информация о функции отображения наследования одной таблицы 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 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
, включающие ключ/значение '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
metadata
¶
Коллекция MetaData
, обычно используемая для присвоения нового Table
, является атрибутом registry.metadata
, связанным с используемым объектом registry
. При использовании декларативного базового класса, например, создаваемого суперклассом DeclarativeBase
, а также унаследованных функций, таких как 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 = 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)
См.также
__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
для разных баз:
class Base(DeclarativeBase):
pass
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)
См.также
Построение более глубоких иерархий с помощью polymorphic_abstract - альтернативная форма «абстрактного» сопоставленного класса, которая подходит для иерархий наследования.
__table_cls__
¶
Позволяет настраивать callable / класс, используемый для генерации Table
. Это очень открытый хук, который может позволить специальные настройки для 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
.