Конфигурация таблицы с помощью декларативного¶
Как было представлено в Декларативное отображение, декларативный стиль включает возможность одновременно генерировать сопоставленный объект 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
в связке:
Если
Enum
связан с объектомenum.Enum
, параметрEnum.native_enum
по умолчанию имеет значениеTrue
, а имя перечисления будет взято из имени типа данныхenum.Enum
. Бэкэнд PostgreSQL будет считатьCREATE TYPE
с этим именем.Если
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 может быть предпочтительнее из-за его менее автоматизированного подхода.
См.также
Автоматизация схем именования столбцов из отраженных таблиц¶
При использовании любого из предыдущих методов отражения у нас есть возможность изменить схему именования, по которой отображаются столбцы. Объект 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
сама реляционная база данных выдает эти значения по умолчанию как поведение на стороне сервера.