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

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

Следующие примеры предполагают декларативный базовый класс как:

from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass

Все последующие примеры иллюстрируют класс, наследующий от приведенного выше Base. Стиль декоратора, представленный в Декларативное отображение с использованием декоратора (без декларативной базы), полностью поддерживается во всех следующих примерах, как и унаследованные формы Declarative Base, включая базовые классы, порожденные declarative_base().

Декларативная таблица с mapped_column()

При использовании Declarative тело сопоставляемого класса в большинстве случаев включает атрибут __tablename__, указывающий на строковое имя Table, которое должно быть сгенерировано вместе с сопоставлением. Конструкция mapped_column(), которая обладает дополнительными возможностями настройки, специфичными для ORM, отсутствующими в обычном классе Column, затем используется в теле класса для указания столбцов таблицы. Приведенный ниже пример иллюстрирует базовое использование этой конструкции в декларативном отображении:

from sqlalchemy import Integer, String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import mapped_column


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "user"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50), nullable=False)
    fullname = mapped_column(String)
    nickname = mapped_column(String(30))

Выше, конструкции mapped_column() размещаются в строке определения класса как атрибуты уровня класса. В момент объявления класса процесс декларативного отображения сгенерирует новый объект Table на основе коллекции MetaData, связанной с декларативным Base; каждый экземпляр mapped_column() будет затем использоваться для генерации объекта Column во время этого процесса, который станет частью коллекции Table.columns этого объекта Table.

В приведенном выше примере Declarative построит конструкцию Table, которая эквивалентна следующей:

# equivalent Table object produced
user_table = Table(
    "user",
    Base.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("fullname", String()),
    Column("nickname", String(30)),
)

Когда вышеуказанный класс User отображен, к этому объекту Table можно получить доступ непосредственно через атрибут __table__; это описано далее в Доступ к таблице и метаданным.

Конструкция mapped_column() принимает все аргументы, которые принимаются конструкцией Column, а также дополнительные аргументы, специфичные для ORM. Поле mapped_column.__name, указывающее имя столбца базы данных, обычно опускается, поскольку процесс Declarative будет использовать имя атрибута, переданное конструкции, и назначит его в качестве имени столбца (в приведенном выше примере это относится к именам id, name, fullname, nickname). Присвоение альтернативного имени mapped_column.__name также допустимо, при этом результирующий Column будет использовать данное имя в SQL и DDL операторах, а сопоставленный класс User будет продолжать разрешать доступ к атрибуту, используя заданное имя атрибута, независимо от имени, заданного самому столбцу (подробнее об этом в Явное именование декларативных сопоставленных столбцов).

Совет

Конструкция mapped_column() действительна только в рамках декларативного отображения классов. При создании объекта Table с помощью Core, а также при использовании конфигурации imperative table, конструкция Column по-прежнему необходима для указания наличия колонки базы данных.

См.также

Сопоставление столбцов таблицы - содержит дополнительные указания по влиянию на то, как Mapper интерпретирует входящие объекты Column.

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

Конструкция mapped_column() способна получать информацию о конфигурации колонки из аннотаций типов PEP 484, связанных с атрибутом, объявленным в классе Declarative mapped. Эти аннотации типов, если они используются, должны присутствовать внутри специального типа SQLAlchemy, называемого Mapped, который является общим_типом, который затем указывает на конкретный тип Python внутри него.

Ниже показано отображение из предыдущего раздела с добавлением использования Mapped:

from typing import Optional

from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(50))
    fullname: Mapped[Optional[str]]
    nickname: Mapped[Optional[str]] = mapped_column(String(30))

Выше, когда Declarative обрабатывает каждый атрибут класса, каждый mapped_column() будет получать дополнительные аргументы из соответствующей аннотации типа Mapped в левой части, если она присутствует. Кроме того, Declarative будет неявно генерировать пустую директиву mapped_column() всякий раз, когда встречается аннотация типа Mapped, не имеющая значения, присвоенного атрибуту (эта форма вдохновлена аналогичным стилем, используемым в Python dataclasses); эта конструкция mapped_column() будет получать свою конфигурацию из присутствующей аннотации Mapped.

mapped_column() берет тип данных и нулевую возможность из аннотации Mapped

Два качества, которые mapped_column() вытекают из аннотации Mapped, это:

  • datatype - тип Python, заданный внутри Mapped и содержащийся в конструкции typing.Optional, если она присутствует, связан с подклассом TypeEngine, таким как Integer, String, DateTime или Uuid, и это только несколько распространенных типов.

    Тип данных определяется на основе словаря Python type to SQLAlchemy datatype. Этот словарь является полностью настраиваемым, как подробно описано в следующем разделе Настройка карты типов. Карта типов по умолчанию реализована так, как показано в примере кода ниже:

    from typing import Any
    from typing import Dict
    from typing import Type
    
    import datetime
    import decimal
    import uuid
    
    from sqlalchemy import types
    
    # default type mapping, deriving the type for mapped_column()
    # from a Mapped[] annotation
    type_map: Dict[Type[Any], TypeEngine[Any]] = {
        bool: types.Boolean(),
        bytes: types.LargeBinary(),
        datetime.date: types.Date(),
        datetime.datetime: types.DateTime(),
        datetime.time: types.Time(),
        datetime.timedelta: types.Interval(),
        decimal.Decimal: types.Numeric(),
        float: types.Float(),
        int: types.Integer(),
        str: types.String(),
        uuid.UUID: types.Uuid(),
    }

    Если конструкция mapped_column() указывает на явный тип, переданный в аргументе mapped_column.__type, то данный тип Python игнорируется.

  • nullability - Конструкция mapped_column() будет указывать на то, что она Column как NULL или NOT NULL в первую очередь по наличию параметра mapped_column.nullable, передаваемого либо как True, либо как False. Кроме того, если параметр mapped_column.primary_key присутствует и имеет значение True, это также означает, что столбец должен быть NOT NULL.

    При отсутствии обоих этих параметров для определения нулевого типа будет использоваться наличие typing.Optional[] в аннотации типа Mapped, где typing.Optional[] означает NULL, а отсутствие typing.Optional[] означает NOT NULL. Если аннотация Mapped[] вообще отсутствует, и нет ни mapped_column.nullable, ни mapped_column.primary_key параметров, то используется обычное значение по умолчанию для Column в SQLAlchemy - NULL.

    В примере ниже столбцы id и data будут NOT NULL, а столбец additional_info будет NULL:

    from typing import Optional
    
    from sqlalchemy.orm import DeclarativeBase
    from sqlalchemy.orm import Mapped
    from sqlalchemy.orm import mapped_column
    
    
    class Base(DeclarativeBase):
        pass
    
    
    class SomeClass(Base):
        __tablename__ = "some_table"
    
        # primary_key=True, therefore will be NOT NULL
        id: Mapped[int] = mapped_column(primary_key=True)
    
        # not Optional[], therefore will be NOT NULL
        data: Mapped[str]
    
        # Optional[], therefore will be NULL
        additional_info: Mapped[Optional[str]]

    Также вполне допустимо иметь mapped_column(), чья недействительность отличается от того, что подразумевается аннотацией. Например, сопоставленный атрибут ORM может быть аннотирован как разрешающий None в коде Python, который работает с объектом при его первом создании и заполнении, однако в конечном итоге значение будет записано в колонку базы данных, которая является NOT NULL. Параметр mapped_column.nullable, если он присутствует, всегда будет иметь приоритет:

    class SomeClass(Base):
        # ...
    
        # will be String() NOT NULL, but can be None in Python
        data: Mapped[Optional[str]] = mapped_column(nullable=False)

    Аналогично, атрибут non-None, записанный в колонку базы данных, которая по какой-либо причине должна быть NULL на уровне схемы, mapped_column.nullable может быть установлен в True:

    class SomeClass(Base):
        # ...
    
        # will be String() NULL, but type checker will not expect
        # the attribute to be None
        data: Mapped[str] = mapped_column(nullable=True)

Настройка карты типов

При отображении типов Python на типы SQLAlchemy TypeEngine, описанном в предыдущем разделе, по умолчанию используется жестко закодированный словарь, присутствующий в модуле sqlalchemy.sql.sqltypes. Однако объект registry, координирующий процесс декларативного отображения, сначала обратится к локальному, определенному пользователем словарю типов, который может быть передан в качестве параметра registry.type_annotation_map при построении registry, который может быть связан с суперклассом DeclarativeBase при первом использовании.

В качестве примера, если мы хотим использовать тип данных BIGINT для int, тип данных TIMESTAMP с timezone=True для datetime.datetime, а затем только на Microsoft SQL Server мы хотели бы использовать тип данных NVARCHAR при использовании Python str, реестр и декларативная база могут быть настроены следующим образом:

import datetime

from sqlalchemy import BIGINT, Integer, NVARCHAR, String, TIMESTAMP
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped, mapped_column, registry


class Base(DeclarativeBase):
    type_annotation_map = {
        int: BIGINT,
        datetime.datetime: TIMESTAMP(timezone=True),
        str: String().with_variant(NVARCHAR, "mssql"),
    }


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    date: Mapped[datetime.datetime]
    status: Mapped[str]

Ниже показан оператор CREATE TABLE, созданный для приведенного выше отображения, сначала на бэкенде Microsoft SQL Server, иллюстрирующий тип данных NVARCHAR:

>>> from sqlalchemy.schema import CreateTable
>>> from sqlalchemy.dialects import mssql, postgresql
>>> print(CreateTable(SomeClass.__table__).compile(dialect=mssql.dialect()))
{printsql}CREATE TABLE some_table (
  id BIGINT NOT NULL IDENTITY,
  date TIMESTAMP NOT NULL,
  status NVARCHAR(max) NOT NULL,
  PRIMARY KEY (id)
)

Затем на бэкенде PostgreSQL, иллюстрирующем TIMESTAMP WITH TIME ZONE:

>>> print(CreateTable(SomeClass.__table__).compile(dialect=postgresql.dialect()))
{printsql}CREATE TABLE some_table (
  id BIGSERIAL NOT NULL,
  date TIMESTAMP WITH TIME ZONE NOT NULL,
  status VARCHAR NOT NULL,
  PRIMARY KEY (id)
)

Используя такие методы, как TypeEngine.with_variant(), мы можем построить карту типов, которая будет соответствовать нашим потребностям для различных бэкендов, и при этом сохранять возможность использовать лаконичные конфигурации mapped_column() только для аннотаций. Помимо этого есть еще два уровня конфигурируемости типа Python, описанные в следующих двух разделах.

Сопоставление нескольких конфигураций типов с типами Python

Поскольку отдельные типы Python могут быть связаны с конфигурациями TypeEngine любого типа с помощью параметра registry.type_annotation_map, дополнительной возможностью является возможность связать один тип Python с различными вариантами типа SQL на основе дополнительных квалификаторов типа. Одним из типичных примеров этого является сопоставление типа данных Python str с типами SQL VARCHAR различной длины. Другой пример - сопоставление различных разновидностей decimal.Decimal с колонками NUMERIC разного размера.

Система типизации Python предоставляет отличный способ добавления дополнительных метаданных к типу Python, который заключается в использовании общего типа PEP 593 Annotated, позволяющего передавать дополнительную информацию вместе с типом Python. Конструкция mapped_column() будет правильно интерпретировать объект Annotated по тождеству при разрешении его в registry.type_annotation_map, как в примере ниже, где мы объявляем два варианта String и Numeric:

from decimal import Decimal

from typing_extensions import Annotated

from sqlalchemy import Numeric
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import registry

str_30 = Annotated[str, 30]
str_50 = Annotated[str, 50]
num_12_4 = Annotated[Decimal, 12]
num_6_2 = Annotated[Decimal, 6]


class Base(DeclarativeBase):
    registry = registry(
        type_annotation_map={
            str_30: String(30),
            str_50: String(50),
            num_12_4: Numeric(12, 4),
            num_6_2: Numeric(6, 2),
        }
    )

Тип Python, переданный в контейнер Annotated, в приведенном выше примере типы str и Decimal, важен только для пользы инструментов типизации; что касается конструкции mapped_column(), то ей достаточно выполнить поиск каждого объекта типа в словаре registry.type_annotation_map, не заглядывая внутрь объекта Annotated, по крайней мере, в данном конкретном контексте. Аналогично, аргументы, передаваемые в Annotated помимо самого базового типа Python, также не важны, важно только то, что хотя бы один аргумент должен присутствовать, чтобы конструкция Annotated была действительной. Затем мы можем использовать эти дополненные типы непосредственно в нашем отображении, где они будут сопоставлены с более конкретными конструкциями типов, как в следующем примере:

class SomeClass(Base):
    __tablename__ = "some_table"

    short_name: Mapped[str_30] = mapped_column(primary_key=True)
    long_name: Mapped[str_50]
    num_value: Mapped[num_12_4]
    short_num_value: Mapped[num_6_2]

CREATE TABLE для приведенного выше отображения будет иллюстрировать различные варианты VARCHAR и NUMERIC, которые мы настроили, и будет выглядеть следующим образом:

>>> from sqlalchemy.schema import CreateTable
>>> print(CreateTable(SomeClass.__table__))
{printsql}CREATE TABLE some_table (
  short_name VARCHAR(30) NOT NULL,
  long_name VARCHAR(50) NOT NULL,
  num_value NUMERIC(12, 4) NOT NULL,
  short_num_value NUMERIC(6, 2) NOT NULL,
  PRIMARY KEY (short_name)
)

Хотя разнообразие в связывании типов Annotated с различными типами SQL предоставляет нам широкую степень гибкости, следующий раздел иллюстрирует второй способ использования Annotated в Declarative, который является еще более открытым.

Сопоставление объявлений целых столбцов с типами Python

В предыдущем разделе было показано использование экземпляров типа PEP 593 Annotated в качестве ключей в словаре registry.type_annotation_map. В этой форме конструкция mapped_column() фактически не просматривает сам объект Annotated, а используется только в качестве ключа словаря. Однако Declarative также имеет возможность извлекать всю предварительно созданную конструкцию mapped_column() из объекта Annotated непосредственно. Используя эту форму, мы можем определить не только различные разновидности типов данных SQL, связанных с типами Python без использования словаря registry.type_annotation_map, мы также можем установить любое количество аргументов, таких как возможность нулевого значения, значения столбцов по умолчанию и ограничения в многократно используемой форме.

Набор моделей ORM обычно имеет некоторый стиль первичного ключа, который является общим для всех сопоставленных классов. Также могут быть общие конфигурации столбцов, такие как временные метки с настройками по умолчанию и другие поля заранее установленных размеров и конфигураций. Мы можем компоновать эти конфигурации в экземпляры mapped_column(), которые затем объединяем непосредственно в экземпляры Annotated, которые затем повторно используются в любом количестве объявлений классов. Declarative распакует объект Annotated при предоставлении его таким образом, пропуская любые другие директивы, которые не относятся к SQLAlchemy, и ища только ORM-конструкции SQLAlchemy.

Пример ниже иллюстрирует различные предварительно сконфигурированные типы полей, используемые таким образом, где мы определяем intpk, который представляет колонку первичного ключа Integer, timestamp, который представляет тип DateTime, который будет использовать CURRENT_TIMESTAMP в качестве столбца по умолчанию на уровне DDL, и required_name, который представляет собой String длиной 30, который NOT NULL:

import datetime

from typing_extensions import Annotated

from sqlalchemy import func
from sqlalchemy import String
from sqlalchemy.orm import mapped_column


intpk = Annotated[int, mapped_column(primary_key=True)]
timestamp = Annotated[
    datetime.datetime,
    mapped_column(nullable=False, server_default=func.CURRENT_TIMESTAMP()),
]
required_name = Annotated[str, mapped_column(String(30), nullable=False)]

Вышеуказанные объекты Annotated можно затем использовать непосредственно в Mapped, где предварительно настроенные конструкции mapped_column() будут извлечены и скопированы в новый экземпляр, который будет специфичен для каждого атрибута:

class Base(DeclarativeBase):
    pass


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[intpk]
    name: Mapped[required_name]
    created_at: Mapped[timestamp]

CREATE TABLE для нашего вышеприведенного отображения выглядит как:

>>> from sqlalchemy.schema import CreateTable
>>> print(CreateTable(SomeClass.__table__))
{printsql}CREATE TABLE some_table (
  id INTEGER NOT NULL,
  name VARCHAR(30) NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
  PRIMARY KEY (id)
)

При использовании типов Annotated таким образом, конфигурация типа также может быть изменена на основе каждого атрибута. Для типов в приведенном выше примере, которые характеризуются явным использованием mapped_column.nullable, мы можем применить общий модификатор Optional[] к любому из наших типов, чтобы поле было опциональным или нет на уровне Python, что будет независимо от настройки NULL / NOT NULL, которая происходит в базе данных:

from typing_extensions import Annotated

import datetime
from typing import Optional

from sqlalchemy.orm import DeclarativeBase

timestamp = Annotated[
    datetime.datetime,
    mapped_column(nullable=False),
]


class Base(DeclarativeBase):
    pass


class SomeClass(Base):
    # ...

    # pep-484 type will be Optional, but column will be
    # NOT NULL
    created_at: Mapped[Optional[timestamp]]

Конструкция mapped_column() также согласовывается с явно переданной конструкцией mapped_column(), аргументы которой будут иметь приоритет над аргументами конструкции Annotated. Ниже мы добавим ограничение ForeignKey к первичному ключу integer, а также используем альтернативное значение по умолчанию сервера для столбца created_at:

import datetime

from typing_extensions import Annotated

from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.schema import CreateTable

intpk = Annotated[int, mapped_column(primary_key=True)]
timestamp = Annotated[
    datetime.datetime,
    mapped_column(nullable=False, server_default=func.CURRENT_TIMESTAMP()),
]


class Base(DeclarativeBase):
    pass


class Parent(Base):
    __tablename__ = "parent"

    id: Mapped[intpk]


class SomeClass(Base):
    __tablename__ = "some_table"

    # add ForeignKey to mapped_column(Integer, primary_key=True)
    id: Mapped[intpk] = mapped_column(ForeignKey("parent.id"))

    # change server default from CURRENT_TIMESTAMP to UTC_TIMESTAMP
    created_at: Mapped[timestamp] = mapped_column(server_default=func.UTC_TIMESTAMP())

Оператор CREATE TABLE иллюстрирует эти настройки для каждого атрибута, добавляя ограничение FOREIGN KEY, а также заменяя UTC_TIMESTAMP на CURRENT_TIMESTAMP:

>>> from sqlalchemy.schema import CreateTable
>>> print(CreateTable(SomeClass.__table__))
{printsql}CREATE TABLE some_table (
  id INTEGER NOT NULL,
  created_at DATETIME DEFAULT UTC_TIMESTAMP() NOT NULL,
  PRIMARY KEY (id),
  FOREIGN KEY(id) REFERENCES parent (id)
)

Примечание

Только что описанная возможность mapped_column(), когда полностью построенный набор аргументов колонки может быть указан с помощью объектов PEP 593 Annotated, которые содержат «шаблонный» объект mapped_column(), копируемый в атрибут, в настоящее время не реализована для других конструкций ORM, таких как relationship() и composite(). Хотя теоретически такая функциональность возможна, на данный момент попытка использовать Annotated для указания дальнейших аргументов для relationship() и подобных им вызовет исключение NotImplementedError во время выполнения, но может быть реализована в будущих выпусках.

Использование типов Python Enum или pep-586 Literal в карте типов

Добавлено в версии 2.0.0b4: - Added Enum support

Добавлено в версии 2.0.1: - Added Literal support

Определяемые пользователем типы Python, которые происходят от встроенного enum.Enum, а также от класса typing.Literal, автоматически связываются с типом данных SQLAlchemy Enum при использовании в декларативном отображении ORM. В примере ниже используется пользовательский enum.Enum в конструкторе Mapped[]:

import enum

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column


class Base(DeclarativeBase):
    pass


class Status(enum.Enum):
    PENDING = "pending"
    RECEIVED = "received"
    COMPLETED = "completed"


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    status: Mapped[Status]

В приведенном выше примере сопоставленный атрибут SomeClass.status будет связан с атрибутом Column с типом данных Enum(Status). Мы можем увидеть это, например, в выводе CREATE TABLE для базы данных PostgreSQL:

CREATE TYPE status AS ENUM ('PENDING', 'RECEIVED', 'COMPLETED')

CREATE TABLE some_table (
  id SERIAL NOT NULL,
  status status NOT NULL,
  PRIMARY KEY (id)
)

Аналогичным образом вместо typing.Literal можно использовать typing.Literal, который состоит из всех строк:

from typing import Literal

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column


class Base(DeclarativeBase):
    pass


Status = Literal["pending", "received", "completed"]


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    status: Mapped[Status]

Записи, используемые в registry.type_annotation_map, связывают базовый тип enum.Enum Python, а также тип typing.Literal с типом SQLAlchemy Enum SQL, используя специальную форму, которая указывает типу данных Enum, что он должен автоматически сконфигурироваться с произвольным перечислимым типом. Эта конфигурация, которая по умолчанию является неявной, в явном виде будет обозначена как:

import enum
import typing

import sqlalchemy
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    type_annotation_map = {
        enum.Enum: sqlalchemy.Enum(enum.Enum),
        typing.Literal: sqlalchemy.Enum(enum.Enum),
    }

Логика разрешения в Declarative способна разрешать подклассы enum.Enum, а также экземпляры typing.Literal для соответствия записи enum.Enum или typing.Literal в словаре registry.type_annotation_map. Затем SQL-тип Enum знает, как создать сконфигурированную версию самого себя с соответствующими настройками, включая длину строки по умолчанию. Если передается typing.Literal, который не состоит только из строковых значений, то выдается информационная ошибка.

Родные энумы и именование

Параметр Enum.native_enum указывает, должен ли тип данных Enum создавать так называемое «родное» перечисление, которое в MySQL/MariaDB является типом данных ENUM, а в PostgreSQL - новым объектом TYPE, созданным CREATE TYPE, или «неродное» перечисление, что означает, что для создания типа данных будет использоваться VARCHAR. Для бэкендов, отличных от MySQL/MariaDB или PostgreSQL, VARCHAR используется во всех случаях (сторонние диалекты могут иметь свое собственное поведение).

Поскольку CREATE TYPE в PostgreSQL требует наличия явного имени для создаваемого типа, существует специальная логика отката при работе с неявно созданным Enum без указания явного типа данных Enum в связке:

  1. Если Enum связан с объектом enum.Enum, параметр Enum.native_enum по умолчанию имеет значение True, а имя перечисления будет взято из имени типа данных enum.Enum. Бэкэнд PostgreSQL будет считать CREATE TYPE с этим именем.

  2. Если Enum связан с объектом typing.Literal, параметр Enum.native_enum по умолчанию принимает значение False; имя не генерируется и предполагается VARCHAR.

Чтобы использовать typing.Literal с типом PostgreSQL CREATE TYPE, необходимо использовать явный Enum, либо внутри map:

import enum
import typing

import sqlalchemy
from sqlalchemy.orm import DeclarativeBase

Status = Literal["pending", "received", "completed"]


class Base(DeclarativeBase):
    type_annotation_map = {
        Status: sqlalchemy.Enum("pending", "received", "completed", name="status_enum"),
    }

Или альтернативно в пределах mapped_column():

import enum
import typing

import sqlalchemy
from sqlalchemy.orm import DeclarativeBase

Status = Literal["pending", "received", "completed"]


class Base(DeclarativeBase):
    pass


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    status: Mapped[Status] = mapped_column(
        sqlalchemy.Enum("pending", "received", "completed", name="status_enum")
    )
Изменение конфигурации параметра по умолчанию

Для того чтобы изменить фиксированную конфигурацию неявно генерируемого типа данных Enum, укажите новые записи в registry.type_annotation_map, указывающие на дополнительные аргументы. Например, чтобы безоговорочно использовать «неродные перечисления», параметр Enum.native_enum может быть установлен в False для всех типов:

import enum
import typing
import sqlalchemy
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    type_annotation_map = {
        enum.Enum: sqlalchemy.Enum(enum.Enum, native_enum=False),
        typing.Literal: sqlalchemy.Enum(enum.Enum, native_enum=False),
    }

Изменено в версии 2.0.1: Реализована поддержка переопределения параметров, таких как Enum.native_enum внутри типа данных Enum при создании registry.type_annotation_map. Ранее эта функциональность не работала.

Чтобы использовать определенную конфигурацию для конкретного подтипа enum.Enum, например, установить длину строки равной 50 при использовании примера Status типа данных:

import enum
import sqlalchemy
from sqlalchemy.orm import DeclarativeBase


class Status(enum.Enum):
    PENDING = "pending"
    RECEIVED = "received"
    COMPLETED = "completed"


class Base(DeclarativeBase):
    type_annotation_map = {
        Status: sqlalchemy.Enum(Status, length=50, native_enum=False)
    }
Связывание конкретных enum.Enum или typing.Literal с другими типами данных

В приведенных выше примерах используется Enum, который автоматически настраивается на аргументы / атрибуты, присутствующие на объекте типа enum.Enum или typing.Literal. Для случаев, когда конкретные типы enum.Enum или typing.Literal должны быть связаны с другими типами, эти конкретные типы также могут быть помещены в карту типов. В приведенном ниже примере запись для Literal[], содержащая нестроковые типы, связана с типом данных JSON:

from typing import Literal

from sqlalchemy import JSON
from sqlalchemy.orm import DeclarativeBase

my_literal = Literal[0, 1, True, False, "true", "false"]


class Base(DeclarativeBase):
    type_annotation_map = {my_literal: JSON}

В приведенной выше конфигурации тип данных my_literal будет разрешаться в экземпляр JSON. Другие варианты Literal будут продолжать разрешаться в типы данных Enum.

Функции класса данных в mapped_column()

Конструкция mapped_column() интегрируется с функцией SQLAlchemy «native dataclasses», обсуждаемой в Декларативное отображение классов данных. О дополнительных директивах, поддерживаемых конструкцией mapped_column(), читайте в этом разделе.

Доступ к таблице и метаданным

Декларативно отображенный класс всегда будет включать атрибут __table__; когда вышеуказанная конфигурация с использованием __tablename__ завершена, декларативный процесс делает Table доступным через атрибут __table__:

# access the Table
user_table = User.__table__

Приведенная выше таблица в конечном итоге соответствует атрибуту Mapper.local_table, который мы можем видеть через runtime inspection system:

from sqlalchemy import inspect

user_table = inspect(User).local_table

Коллекция MetaData, связанная как с декларативным registry, так и с базовым классом, часто необходима для выполнения операций DDL, таких как CREATE, а также для использования с инструментами миграции, такими как Alembic. Этот объект доступен через атрибут .metadata в registry, а также через декларативный базовый класс. Ниже, для небольшого сценария, мы, возможно, захотим создать CREATE для всех таблиц базы данных SQLite:

engine = create_engine("sqlite://")

Base.metadata.create_all(engine)

Декларативная конфигурация таблиц

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

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

class MyClass(Base):
    __tablename__ = "sometable"
    __table_args__ = {"mysql_engine": "InnoDB"}

Другой, кортеж, где каждый аргумент является позиционным (обычно ограничения):

class MyClass(Base):
    __tablename__ = "sometable"
    __table_args__ = (
        ForeignKeyConstraint(["id"], ["remote_table.id"]),
        UniqueConstraint("foo"),
    )

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

class MyClass(Base):
    __tablename__ = "sometable"
    __table_args__ = (
        ForeignKeyConstraint(["id"], ["remote_table.id"]),
        UniqueConstraint("foo"),
        {"autoload": True},
    )

Класс может также указать декларативный атрибут __table_args__, а также атрибут __tablename__ в динамическом стиле с помощью декоратора метода declared_attr(). Подробнее см. в разделе Составление сопоставленных иерархий с помощью миксинов.

Явное имя схемы с декларативной таблицей

Имя схемы для Table, документированное в Указание имени схемы, применяется к отдельной Table с помощью аргумента Table.schema. При использовании декларативных таблиц этот параметр, как и любой другой, передается в словарь __table_args__:

from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


class MyClass(Base):
    __tablename__ = "sometable"
    __table_args__ = {"schema": "some_schema"}

Имя схемы также может быть применено ко всем объектам Table глобально с помощью параметра MetaData.schema, документированного в Указание имени схемы по умолчанию с помощью метаданных. Объект MetaData может быть построен отдельно и связан с подклассом DeclarativeBase путем присвоения атрибуту metadata непосредственно:

from sqlalchemy import MetaData
from sqlalchemy.orm import DeclarativeBase

metadata_obj = MetaData(schema="some_schema")


class Base(DeclarativeBase):
    metadata = metadata_obj


class MyClass(Base):
    # will use "some_schema" by default
    __tablename__ = "sometable"

Настройка параметров загрузки и сохранения для декларативных сопоставленных столбцов

Конструкция mapped_column() принимает дополнительные аргументы, специфичные для ORM, которые влияют на способ отображения сгенерированного Column, влияя на его поведение при загрузке и сохранении. Обычно используются следующие параметры:

  • отложенная загрузка столбцов - Булево значение mapped_column.deferred устанавливает, что Column по умолчанию использует deferred column loading. В приведенном ниже примере столбец User.bio не будет загружаться по умолчанию, а только при обращении к нему:

    class User(Base):
        __tablename__ = "user"
    
        id: Mapped[int] = mapped_column(primary_key=True)
        name: Mapped[str]
        bio: Mapped[str] = mapped_column(Text, deferred=True)

    См.также

    Ограничение загрузки столбцов с помощью функции Column Deferral - полное описание отложенной загрузки колонн

  • активная история - mapped_column.active_history гарантирует, что при изменении значения атрибута предыдущее значение будет загружено и станет частью коллекции AttributeState.history при просмотре истории атрибута. Это может повлечь за собой дополнительные SQL-запросы:

    class User(Base):
        __tablename__ = "user"
    
        id: Mapped[int] = mapped_column(primary_key=True)
        important_identifier: Mapped[str] = mapped_column(active_history=True)

Список поддерживаемых параметров см. в docstring для mapped_column().

См.также

Применение опций загрузки, сохранения и отображения для столбцов императивной таблицы - описывает использование column_property() и deferred() для использования с конфигурацией Imperative Table

Явное именование декларативных сопоставленных столбцов

Все приведенные примеры содержат конструкцию mapped_column(), связанную с сопоставленным атрибутом ORM, где имя атрибута Python, указанное в mapped_column(), также является именем столбца, как мы видим в операторах CREATE TABLE, а также в запросах. Имя столбца, выраженное в SQL, может быть указано путем передачи строкового позиционного аргумента mapped_column.__name в качестве первого позиционного аргумента. В приведенном ниже примере класс User сопоставлен с альтернативными именами, присвоенными самим столбцам:

class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column("user_id", primary_key=True)
    name: Mapped[str] = mapped_column("user_name")

Где выше User.id разрешается в колонку с именем user_id, а User.name разрешается в колонку с именем user_name. Мы можем написать оператор select(), используя наши имена атрибутов Python, и увидим сгенерированные имена SQL:

>>> from sqlalchemy import select
>>> print(select(User.id, User.name).where(User.name == "x"))
{printsql}SELECT "user".user_id, "user".user_name
FROM "user"
WHERE "user".user_name = :user_name_1

См.также

Альтернативные имена атрибутов для столбцов таблицы сопоставления - применяется к императивной таблице

Добавление дополнительных столбцов к существующему декларативному сопоставленному классу

Декларативная конфигурация таблицы позволяет добавлять новые Column объекты в существующее отображение после того, как метаданные Table уже сгенерированы.

Для декларативного класса, который объявлен с использованием декларативного базового класса, базовый метакласс DeclarativeMeta включает метод __setattr__(), который будет перехватывать дополнительные объекты mapped_column() или Core Column и добавлять их как в Table с помощью Table.append_column(), так и в существующий Mapper с помощью Mapper.add_property():

MyClass.some_new_column = mapped_column(String)

Использование ядра Column:

MyClass.some_new_column = Column(String)

Поддерживаются все аргументы, включая альтернативное имя, например MyClass.some_new_column = mapped_column("some_name", String). Однако тип SQL должен быть передан объекту mapped_column() или Column явно, как в приведенных выше примерах, где передается тип String. Для аннотационного типа Mapped нет возможности принять участие в операции.

Дополнительные объекты Column также могут быть добавлены к отображению в особых обстоятельствах использования наследования одной таблицы, когда дополнительные столбцы присутствуют в отображаемых подклассах, не имеющих собственных Table. Это проиллюстрировано в разделе Наследование одной таблицы.

Примечание

Присвоение сопоставленных свойств уже сопоставленному классу будет работать правильно только в том случае, если используется «декларативный базовый» класс, то есть определяемый пользователем подкласс DeclarativeBase или динамически генерируемый класс, возвращаемый declarative_base() или registry.generate_base(). Этот «базовый» класс включает метакласс Python, реализующий специальный метод __setattr__(), который перехватывает эти операции.

Присвоение атрибутов класса сопоставленному классу не будет работать, если класс сопоставлен с использованием декораторов типа registry.mapped() или императивных функций типа registry.map_imperatively().

Декларатив с императивной таблицей (также известный как гибридный декларатив)

Декларативные отображения также могут быть обеспечены заранее существующим объектом Table, или иначе Table или другой произвольной конструкцией FromClause (такой как Join или Subquery), которая конструируется отдельно.

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

from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


# construct a Table directly.  The Base.metadata collection is
# usually a good choice for MetaData but any MetaData
# collection may be used.

user_table = Table(
    "user",
    Base.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String),
    Column("fullname", String),
    Column("nickname", String),
)


# construct the User class using this table.
class User(Base):
    __table__ = user_table

Выше, объект Table построен с использованием подхода, описанного в Описание баз данных с помощью метаданных. Затем он может быть применен непосредственно к классу, который декларативно отображен. Декларативные атрибуты класса __tablename__ и __table_args__ в этой форме не используются. Приведенная выше конфигурация часто более читабельна в виде встроенного определения:

class User(Base):
    __table__ = Table(
        "user",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String),
        Column("fullname", String),
        Column("nickname", String),
    )

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

class Person(Base):
    __table__ = Table(
        "person",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String(50)),
        Column("type", String(50)),
    )

    __mapper_args__ = {
        "polymorphic_on": __table__.c.type,
        "polymorhpic_identity": "person",
    }

Форма «императивная таблица» также используется, когда необходимо отобразить конструкцию, не являющуюся Table, например, объект Join или Subquery. Пример ниже:

from sqlalchemy import func, select

subq = (
    select(
        func.count(orders.c.id).label("order_count"),
        func.max(orders.c.price).label("highest_order"),
        orders.c.customer_id,
    )
    .group_by(orders.c.customer_id)
    .subquery()
)

customer_select = (
    select(customers, subq)
    .join_from(customers, subq, customers.c.id == subq.c.customer_id)
    .subquery()
)


class Customer(Base):
    __table__ = customer_select

Справочную информацию по отображению на не:class:_schema.Table конструкции см. в разделах Сопоставление класса с несколькими таблицами и Сопоставление класса с произвольными подзапросами.

Форма «императивной таблицы» особенно полезна, когда сам класс использует альтернативную форму объявления атрибутов, например, классы данных Python. Подробнее см. раздел Применение отображений ORM к существующему классу данных (использование унаследованного класса данных).

Альтернативные имена атрибутов для столбцов таблицы сопоставления

В разделе Явное именование декларативных сопоставленных столбцов было показано, как использовать mapped_column() для предоставления конкретного имени для сгенерированного объекта Column отдельно от имени атрибута, под которым он сопоставлен.

При использовании конфигурации Imperative Table у нас уже присутствуют объекты Column. Чтобы сопоставить их с альтернативными именами, мы можем присвоить Column непосредственно желаемым атрибутам:

user_table = Table(
    "user",
    Base.metadata,
    Column("user_id", Integer, primary_key=True),
    Column("user_name", String),
)


class User(Base):
    __table__ = user_table

    id = user_table.c.user_id
    name = user_table.c.user_name

Приведенное выше отображение User будет ссылаться на столбцы "user_id" и "user_name" через атрибуты User.id и User.name, так же, как это было продемонстрировано в Явное именование декларативных сопоставленных столбцов.

Одно из замечаний к приведенному выше отображению заключается в том, что прямая инлайн-ссылка на Column не будет набираться правильно при использовании инструментов набора PEP 484. Стратегия решения этой проблемы заключается в применении объектов Column внутри функции column_property(); хотя Mapper уже автоматически генерирует этот объект свойства для своего внутреннего использования, именуя его в объявлении класса, инструменты набора смогут сопоставить атрибут с аннотацией Mapped:

from sqlalchemy.orm import column_property
from sqlalchemy.orm import Mapped


class User(Base):
    __table__ = user_table

    id: Mapped[int] = column_property(user_table.c.user_id)
    name: Mapped[str] = column_property(user_table.c.user_name)

См.также

Явное именование декларативных сопоставленных столбцов - применяется к декларативной таблице

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

В разделе Настройка параметров загрузки и сохранения для декларативных сопоставленных столбцов было рассмотрено, как установить параметры загрузки и сохранения при использовании конструкции mapped_column() с конфигурацией Declarative Table. При использовании конфигурации Imperative Table у нас уже есть существующие объекты Column, которые отображаются. Чтобы отобразить эти объекты Column вместе с дополнительными параметрами, специфичными для отображения ORM, мы можем использовать конструкции column_property() и deferred(), чтобы связать дополнительные параметры со столбцом. Варианты включают:

  • отложенная загрузка столбцов - Функция deferred() является сокращением для вызова column_property() с параметром column_property.deferred, установленным в True; эта конструкция устанавливает Column, используя deferred column loading по умолчанию. В приведенном ниже примере столбец User.bio не будет загружен по умолчанию, а только при обращении к нему:

    from sqlalchemy.orm import deferred
    
    user_table = Table(
        "user",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String),
        Column("bio", Text),
    )
    
    
    class User(Base):
        __table__ = user_table
    
        bio = deferred(user_table.c.bio)

См.также

Ограничение загрузки столбцов с помощью функции Column Deferral - полное описание отложенной загрузки колонн

  • активная история - column_property.active_history гарантирует, что при изменении значения атрибута предыдущее значение будет загружено и станет частью коллекции AttributeState.history при просмотре истории атрибута. Это может повлечь за собой дополнительные SQL-запросы:

    from sqlalchemy.orm import deferred
    
    user_table = Table(
        "user",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("important_identifier", String),
    )
    
    
    class User(Base):
        __table__ = user_table
    
        important_identifier = column_property(
            user_table.c.important_identifier, active_history=True
        )

См.также

Конструкция column_property() также важна для случаев, когда классы отображаются на альтернативные предложения FROM, такие как объединения и выборки. Более подробную информацию об этих случаях можно найти здесь:

Для конфигурации декларативной таблицы с помощью mapped_column() большинство опций доступны напрямую; примеры см. в разделе Настройка параметров загрузки и сохранения для декларативных сопоставленных столбцов.

Декларативное отображение с помощью отраженных таблиц

Существует несколько шаблонов, позволяющих создавать отображенные классы на основе серии объектов Table, которые были проанализированы из базы данных, используя процесс отражения, описанный в Отражение объектов базы данных.

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

from sqlalchemy import create_engine
from sqlalchemy import Table
from sqlalchemy.orm import DeclarativeBase

engine = create_engine("postgresql+psycopg2://user:pass@hostname/my_existing_database")


class Base(DeclarativeBase):
    pass


class MyClass(Base):
    __table__ = Table(
        "mytable",
        Base.metadata,
        autoload_with=engine,
    )

Вариант вышеописанной схемы, который подходит для многих таблиц, заключается в использовании метода MetaData.reflect() для отражения сразу полного набора объектов Table, а затем обращения к ним из MetaData:

from sqlalchemy import create_engine
from sqlalchemy import Table
from sqlalchemy.orm import DeclarativeBase

engine = create_engine("postgresql+psycopg2://user:pass@hostname/my_existing_database")


class Base(DeclarativeBase):
    pass


Base.metadata.reflect(engine)


class MyClass(Base):
    __table__ = Base.metadata.tables["mytable"]

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

Использование DeferredReflection

Чтобы приспособиться к случаю объявления сопоставленных классов, где отражение метаданных таблицы может произойти после, доступно простое расширение под названием DeferredReflection mixin, которое изменяет процесс декларативного сопоставления, чтобы отложить его до вызова специального метода DeferredReflection.prepare() на уровне класса, который выполнит процесс отражения в целевой базе данных и интегрирует результаты с процессом декларативного сопоставления таблиц, то есть классов, которые используют __tablename__ атрибут:

from sqlalchemy.ext.declarative import DeferredReflection
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


class Reflected(DeferredReflection):
    __abstract__ = True


class Foo(Reflected, Base):
    __tablename__ = "foo"
    bars = relationship("Bar")


class Bar(Reflected, Base):
    __tablename__ = "bar"

    foo_id = mapped_column(Integer, ForeignKey("foo.id"))

Выше мы создали класс mixin Reflected, который будет служить базой для классов в нашей декларативной иерархии, которые должны стать отображаемыми при вызове метода Reflected.prepare. Приведенное выше отображение не будет завершено, пока мы не сделаем это, учитывая Engine:

engine = create_engine("postgresql+psycopg2://user:pass@hostname/my_existing_database")
Reflected.prepare(engine)

Целью класса Reflected является определение области, в которой классы должны быть отражены. Плагин будет искать в дереве подклассов цели, по отношению к которой вызывается .prepare(), и отражать все таблицы, которые названы объявленными классами; таблицы в целевой базе данных, которые не являются частью отображений и не связаны с целевыми таблицами через ограничение внешнего ключа, не будут отражены.

Использование Automap

Более автоматизированное решение для сопоставления с существующей базой данных, где будет использоваться отражение таблиц, заключается в использовании расширения Automap. Это расширение генерирует целые сопоставленные классы из схемы базы данных, включая отношения между классами, основанные на наблюдаемых ограничениях внешнего ключа. Хотя оно включает в себя крючки для настройки, например, крючки, позволяющие создавать собственные схемы именования классов и отношений, automap ориентирован на быстрый стиль работы с нулевой конфигурацией. Если приложение хочет иметь полностью явную модель, использующую отражение таблиц, класс DeferredReflection может быть предпочтительнее из-за его менее автоматизированного подхода.

См.также

Automap

Автоматизация схем именования столбцов из отраженных таблиц

При использовании любого из предыдущих методов отражения у нас есть возможность изменить схему именования, по которой отображаются столбцы. Объект Column включает параметр Column.key, который представляет собой строковое имя, определяющее, под каким именем данный Column будет присутствовать в коллекции Table.c, независимо от SQL-имени столбца. Этот ключ также используется Mapper в качестве имени атрибута, под которым будет отображаться Column, если он не задан другим способом, например, как показано в Альтернативные имена атрибутов для столбцов таблицы сопоставления.

При работе с отражением таблицы мы можем перехватывать параметры, которые будут использоваться для Column по мере их получения с помощью события DDLEvents.column_reflect() и применять любые необходимые нам изменения, включая атрибут .key, а также такие вещи, как типы данных.

Крючок события проще всего связать с используемым объектом MetaData, как показано ниже:

from sqlalchemy import event
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


@event.listens_for(Base.metadata, "column_reflect")
def column_reflect(inspector, table, column_info):
    # set column.key = "attr_<lower_case_name>"
    column_info["key"] = "attr_%s" % column_info["name"].lower()

С помощью вышеуказанного события отражение объектов Column будет перехвачено нашим событием, добавляющим новый элемент «.key», например, в отображении, как показано ниже:

class MyClass(Base):
    __table__ = Table("some_table", Base.metadata, autoload_with=some_engine)

Этот подход также работает как с базовым классом DeferredReflection, так и с расширением Automap. Что касается конкретно automap, смотрите раздел Определения перехватывающих колонок для справки.

Сопоставление с явным набором столбцов первичного ключа

Конструкция Mapper для успешного отображения таблицы всегда требует, чтобы по крайней мере один столбец был определен как «первичный ключ» для этой выбираемой таблицы. Это необходимо для того, чтобы при загрузке или сохранении объекта ORM его можно было поместить в identity map с соответствующим identity key.

В тех случаях, когда отраженная таблица, подлежащая отображению, не включает ограничение первичного ключа, а также в общем случае для mapping against arbitrary selectables, когда столбцы первичного ключа могут отсутствовать, параметр Mapper.primary_key предоставляется для того, чтобы любой набор столбцов мог быть настроен как «первичный ключ» для таблицы, насколько это касается отображения ORM.

Учитывая следующий пример отображения Imperative Table на существующий объект Table, где таблица не имеет объявленного первичного ключа (как это может произойти в сценариях отражения), мы можем отобразить такую таблицу, как в следующем примере:

from sqlalchemy import Column
from sqlalchemy import MetaData
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy import UniqueConstraint
from sqlalchemy.orm import DeclarativeBase


metadata = MetaData()
group_users = Table(
    "group_users",
    metadata,
    Column("user_id", String(40), nullable=False),
    Column("group_id", String(40), nullable=False),
    UniqueConstraint("user_id", "group_id"),
)


class Base(DeclarativeBase):
    pass


class GroupUsers(Base):
    __table__ = group_users
    __mapper_args__ = {"primary_key": [group_users.c.user_id, group_users.c.group_id]}

Выше, таблица group_users представляет собой некую ассоциативную таблицу со строковыми столбцами user_id и group_id, но первичный ключ не установлен; вместо этого есть только UniqueConstraint, устанавливающий, что эти два столбца представляют собой уникальный ключ. Mapper не проверяет автоматически уникальные ограничения для первичных ключей; вместо этого мы используем параметр Mapper.primary_key, передавая коллекцию [group_users.c.user_id, group_users.c.group_id], указывающую, что эти два столбца должны быть использованы для построения ключа идентификации для экземпляров класса GroupUsers.

Сопоставление подмножества столбцов таблицы

Иногда отражение таблицы может предоставить Table с большим количеством столбцов, которые не важны для наших нужд и могут быть безопасно проигнорированы. Для такой таблицы, имеющей множество столбцов, на которые не нужно ссылаться в приложении, параметры Mapper.include_properties или Mapper.exclude_properties могут указывать на подмножество столбцов, подлежащих отображению, где другие столбцы из целевой Table не будут рассматриваться ORM никаким образом. Пример:

class User(Base):
    __table__ = user_table
    __mapper_args__ = {"include_properties": ["user_id", "user_name"]}

В приведенном выше примере класс User будет отображен на таблицу user_table, включая только столбцы user_id и user_name - на остальные столбцы ссылки нет.

Аналогично:

class Address(Base):
    __table__ = address_table
    __mapper_args__ = {"exclude_properties": ["street", "city", "state", "zip"]}

отобразит класс Address в таблицу address_table, включая все присутствующие столбцы, кроме street, city, state и zip.

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

class User(Base):
    __table__ = user_table
    __mapper_args__ = {
        "include_properties": [user_table.c.user_id, user_table.c.user_name]
    }

Когда колонки не включены в отображение, на них не будет ссылок в операторах SELECT, выдаваемых при выполнении объектов select() или унаследованных Query, также не будет атрибута отображаемого класса, который представляет колонку; присвоение атрибута с таким именем не будет иметь никакого эффекта, кроме обычного присвоения атрибута в Python.

Однако важно отметить, что умолчания столбцов на уровне схемы все еще будут действовать для тех объектов Column, которые их включают, даже если они могут быть исключены из отображения ORM.

«Умолчания столбцов уровня схемы» относятся к умолчаниям, описанным в Колонки INSERT/UPDATE по умолчанию, включая те, которые конфигурируются параметрами Column.default, Column.onupdate, Column.server_default и Column.server_onupdate. Эти конструкции продолжают оказывать нормальное воздействие, поскольку в случае Column.default и Column.onupdate объект Column все еще присутствует на базовом Table, что позволяет функциям по умолчанию иметь место, когда ORM выдает INSERT или UPDATE, а в случае Column.server_default и Column.server_onupdate сама реляционная база данных выдает эти значения по умолчанию как поведение на стороне сервера.

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