Модели, зарегистрированные во время работы¶
Класс модели, построенный во время работы приложения — например, по таблице, прочитанной из базы, —
регистрируется после Hare.init() и снимается с регистрации без перезапуска.
from hare.inspectdb import DatabaseCatalog, ModelFactory
table_info = await DatabaseCatalog.inspect_table(connection, "loans")
Loan = ModelFactory.from_table_info(table_info, connection.dialect.name, app_label="live")
Hare.register_live_models([Loan], app_label="live", connection_alias="default", managed=False)
...
Hare.unregister_live_models([Loan])
Регистрация: register_live_models()¶
class Hare:
@classmethod
def register_live_models(
cls,
models: Iterable[type[Model]],
app_label: str,
connection_alias: str = "default",
*,
managed: bool | None = None,
) -> None: ...
Регистрирует подклассы Model после того, как Hare.init() уже выполнен, — для класса, который
появляется только во время работы (например, построен по схеме живой базы), а не объявлен заранее в
apps настроек. Принимает коллекцию классов моделей — одна модель передаётся списком из одного элемента.
Нужен активный HareContext (иначе ConfigurationError) и connection_alias, который действительно
настроен (иначе ConfigurationError с этим именем); ConfigurationError будет и если имя таблицы модели
совпадает с уже зарегистрированной. app_label может быть любой непустой строкой без точки — не
обязательно одним из настроенных приложений; пустая строка или строка с точкой (метка модели —
<app>.<Model>) даёт ConfigurationError.
Регистрация атомарна: если она не удалась (цель связи не найдена, имя обратной связи совпадает с полем
связанной модели и т. п.), модель не остаётся в реестре, ни одна связанная модель не сохраняет
добавленную ею обратную связь, а её managed и подключение восстанавливаются — исправьте класс и
зарегистрируйте его снова. Регистрация другого класса под именем, уже занятым в app_label, даёт
ConfigurationError — сначала вызовите unregister_live_models() для старого; повторная регистрация
того же класса ничего не делает.
Несколько моделей, зарегистрированных одним вызовом, — это один шаг; он нужен моделям, связи которых указывают друг на друга (A → B и B → A): по одной их зарегистрировать нельзя (цели первой ещё нет). Регистрация атомарна для всей группы: если не удалась хотя бы одна модель, не остаётся ни одной.
У живой модели почти всегда managed=False — иначе её попытаются вести и makemigrations/ hare drift,
споря с тем, кто на самом деле управляет её таблицей. managed записывается в
Meta.managed до настройки связей; None (по умолчанию) оставляет
собственный Meta.managed модели.
Снятие с регистрации: unregister_live_models()¶
class Hare:
@classmethod
def unregister_live_models(cls, models: Iterable[type[Model] | str]) -> None: ...
Модель задаётся классом или меткой "app.Model" (Hare.unregister_live_models(["content.Notice"])).
Отменяет регистрацию: модель уходит из реестра, обратные связи (обратные ссылки внешних ключей и
«один-к-одному», созданные поля «многие-ко-многим»), которые она добавила моделям, на которые указывают
её связи, удаляются, и все кэши, построенные для неё или для этих моделей, сбрасываются. Колонки её
внешних ключей тоже сбрасываются, поэтому новый класс с тем же именем — или тот же класс — потом можно
зарегистрировать снова. Класс, регистрация которого отменена, не может выполнять запросы
(ConfigurationError).
Отмена регистрации модели, на которую ещё указывает другая зарегистрированная модель (внешний ключ,
«один-к-одному», «многие-ко-многим»), даёт ConfigurationError с именами ссылающихся полей и ничего не
меняет — сначала отмените регистрацию ссылающихся моделей или снимите их вместе: связи между моделями,
снимаемыми одним вызовом, не мешают, как и связь модели с самой собой.
Чтение таблицы: DatabaseCatalog¶
class DatabaseCatalog:
@staticmethod
async def inspect_table(
connection: DatabaseClient, table: str, schema: str | None = None, *, verify_exists: bool = True
) -> TableInfo: ...
@staticmethod
async def inspect_tables(
connection: DatabaseClient, tables: list[str], schema: str | None = None, *, verify_exists: bool = True
) -> list[TableInfo]: ...
Читает колонки, ключи, индексы и ограничения таблицы как TableInfo; inspect_tables() возвращает по
одному TableInfo на каждое имя в том же порядке — в PostgreSQL за постоянное число запросов к
системному каталогу, сколько бы таблиц ни было. schema при None — схема подключения по умолчанию;
база без схем её не учитывает. Несуществующая таблица даёт TableNotFoundError, а диалект без чтения
схемы — UnsupportedDialectError. verify_exists=False пропускает запрос списка таблиц — для
вызывающего, который только что сам сверил имена с таким списком.
Модель по прочитанной схеме таблицы¶
class ModelFactory:
@staticmethod
def from_table_info(
table_info: TableInfo,
dialect: str,
*,
app_label: str,
foreign_key_target_overrides: dict[str, str] | None = None,
skip_many_to_many_through_tables: bool = True,
) -> type[Model]: ...
Строит класс модели для одной таблицы прямо через type() — это тот же класс, что получился бы при
выполнении кода, который создаёт для неё hare inspectdb: оба пути
используют одно состояние модели (hare.inspectdb.InspectedModelBuilder превращает таблицу в тот же
ModelState, что есть у объявленной модели), поэтому поля, составные первичные ключи, индексы и
ограничения не могут разойтись. Класс не регистрируется — передайте его в register_live_models().
app_label— метка, которой уточняются цели внешних ключей, в том числе составных ("<app_label>.<TargetClass>"); обычно та же метка, под которой вы регистрируете модель. Модели целевых таблиц должны быть зарегистрированы раньше.foreign_key_target_overrides—{target_table: "app.Model"}, полностью заменяет ссылку для этой таблицы; нужно, чтобы направить внешний ключ на уже зарегистрированную модель, имя класса которой не следует правилам именованияinspectdb.skip_many_to_many_through_tables— таблица только из колонок внешних ключей похожа на промежуточную таблицуManyToManyField; по умолчанию она отклоняется сManyToManyThroughTableSkippedError(там, где генератор кода вставил бы комментарий# NOTE).Falseстроит для неё обычную модель.
Таблица без первичного ключа становится моделью с Meta.primary_key = None (см.
Модели без первичного ключа).