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

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

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

from sqlalchemy.orm import declarative_base

Base = declarative_base()

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

Декларативная таблица

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

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

Base = declarative_base()


class User(Base):
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String)
    fullname = Column(String)
    nickname = Column(String)

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

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

См.также

Сопоставление столбцов таблицы - содержит дополнительные указания по влиянию на то, как Mapper интерпретирует входящие объекты 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__:

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

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

from sqlalchemy import MetaData

metadata_obj = MetaData(schema="some_schema")

Base = declarative_base(metadata=metadata_obj)


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

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

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

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

MyClass.some_new_column = Column("data", Unicode)

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

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

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

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

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

Base = declarative_base()

# 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 к существующему классу данных.

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

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

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

from sqlalchemy import create_engine
from sqlalchemy import Table
from sqlalchemy.orm import declarative_base

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

Base = declarative_base()


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 declarative_base

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

Base = declarative_base()

Base.metadata.reflect(engine)


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

См.также

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

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

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

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

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

Base = declarative_base()


class Reflected(DeferredReflection):
    __abstract__ = True


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


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

    foo_id = 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

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