DM-модели#
Модуль: restalchemy.dm.models
Этот модуль содержит базовые классы и mixin-классы для моделей данных (DM) в RESTAlchemy.
MetaModel и Model#
MetaModel#
MetaModel — метакласс, используемый всеми DM-моделями. Он:
- Собирает определения полей, созданных через
properties.property()иproperties.container(). - Объединяет свойства базовых классов.
- Отслеживает ID-свойства в
id_properties. - Присваивает операционное хранилище
__operational_storage__для вспомогательных данных на уровне класса модели.
Обычно MetaModel не используется напрямую; вы наследуетесь от Model или её наследников.
Model#
Model — фундаментальный базовый класс для DM-моделей:
from restalchemy.dm import models, properties, types
class Foo(models.Model):
foo_id = properties.property(types.Integer(), id_property=True, required=True)
name = properties.property(types.String(max_length=255), default="")
Основное поведение:
- Конструктор принимает именованные аргументы и передаёт их в
pour(). pour()строитPropertyManagerиз коллекцииpropertiesи выполняет валидацию.- Доступ к атрибутам проксируется в свойства:
model.fieldчитаетproperties[field].value.model.field = valueустанавливает значение с валидацией.as_plain_dict()возвращает словарь с "плоским" представлением значений.- Модель ведёт себя как отображение по своим свойствам (
__getitem__,__iter__,__len__).
Обработка ошибок:
- При установке значения неверного типа выбрасывается
ModelTypeError. - При отсутствии обязательного поля выбрасывается
PropertyRequired. - При попытке изменить read-only или ID-свойство выбрасывается
ReadOnlyProperty.
Переопределение валидации:
class PositiveFoo(models.Model):
value = properties.property(types.Integer(), required=True)
def validate(self):
if self.value <= 0:
raise ValueError("value must be positive")
validate() вызывается из pour() после создания свойств.
Работа с ID#
ModelWithID#
ModelWithID расширяет Model для моделей с ровно одним ID-свойством:
get_id()возвращает текущее значение ID-свойства.- Операции сравнения и хеширования основаны на
get_id().
Если у модели нет ID-свойств или их несколько, get_id_property() выбрасывает TypeError, и логику ID нужно реализовать самостоятельно.
ModelWithUUID и ModelWithRequiredUUID#
ModelWithUUID определяет UUID как первичный ключ:
class ModelWithUUID(ModelWithID):
uuid = properties.property(
types.UUID(),
read_only=True,
id_property=True,
default=lambda: uuid.uuid4(),
)
Пример использования:
class Foo(models.ModelWithUUID):
value = properties.property(types.Integer(), required=True)
foo = Foo(value=10)
print(foo.uuid) # авто-сгенерированный UUID
print(foo.get_id()) # то же самое, что foo.uuid
ModelWithRequiredUUID похож, но UUID должен быть задан явно, без значения по умолчанию.
Операционное хранилище#
DmOperationalStorage#
Небольшой вспомогательный класс, используемый MetaModel как __operational_storage__ для каждой модели:
store(name, data)— сохраняет произвольные данные по имени.get(name)— возвращает данные или выбрасываетNotFoundOperationalStorageError, если запрошенного ключа нет.
Пример:
from restalchemy.dm import models
class Foo(models.ModelWithUUID):
pass
Foo.__operational_storage__.store("table_name", "foos")
assert Foo.__operational_storage__.get("table_name") == "foos"
Обычно используется внутренними механизмами и расширениями.
Распространённые mixin-классы#
ModelWithTimestamp#
Добавляет поля created_at и updated_at с UTC-временем:
- Оба поля обязательные, read-only и используют
types.UTCDateTimeZ(). update()автоматически обновляетupdated_at, если модель "грязная" (или приforce=True).
class TimestampedFoo(models.ModelWithUUID, models.ModelWithTimestamp):
value = properties.property(types.Integer(), required=True)
ModelWithProject#
Добавляет обязательное, read-only поле project_id типа types.UUID():
class ProjectResource(models.ModelWithUUID, models.ModelWithProject):
name = properties.property(types.String(max_length=255), required=True)
ModelWithTags#
Добавляет поле tags — список строк, по умолчанию пустой — чтобы помечать
строки значениями, по которым потом можно искать:
class TaggedResource(models.ModelWithUUID, models.ModelWithTags):
name = properties.property(types.String(max_length=255), required=True)
Колонка — массив PostgreSQL, и для поиска по ней нужен GIN-индекс; миграция, создающая таблицу, объявляет и то и другое:
Искать можно из Python — ContainsAll / ContainsAny —
или выражением-фильтром по HTTP (?q=tags:"env:prod"), см.
Фильтрация коллекций по HTTP.
ModelWithNameDesc и ModelWithRequiredNameDesc#
Общие поля name и description:
ModelWithNameDesc:name: строка до 255 символов, по умолчанию"".description: строка до 255 символов, по умолчанию"".ModelWithRequiredNameDesc:nameявляется обязательным полем.
Полезно для большого количества сущностей с именем и описанием.
Дополнительные свойства и simple view#
CustomPropertiesMixin#
Позволяет определять дополнительные "кастомные" свойства с отдельными типами:
__custom_properties__: словарь имя → тип (types.BaseType).get_custom_properties()возвращает пары(name, type).get_custom_property_type(name)возвращает тип для заданного свойства._check_custom_property_value()валидирует и может проверять фиксированные значения.
Обычно используется совместно с mixin-классами simple view.
DumpToSimpleViewMixin#
Метод dump_to_simple_view() конвертирует модель в структуру из простых типов Python (для JSON, OpenAPI, storage):
result = model.dump_to_simple_view(
skip=["internal_field"],
save_uuid=True,
custom_properties=False,
)
Поведение:
- Обходит
self.propertiesи конвертирует каждое значение черезto_simple_type()типа поля. - При
save_uuid=TrueUUID-поля (включаяAllowNone(UUID)) сериализуются как строки. - При
custom_properties=True(или наличии__custom_properties__) конвертируются и кастомные свойства.
RestoreFromSimpleViewMixin#
Метод restore_from_simple_view() создаёт модель из "плоской" структуры:
user = User.restore_from_simple_view(
skip_unknown_fields=True,
name="Alice",
created_at="2006-01-02T15:04:05.000576Z",
)
Поведение:
- Нормализует имена полей (заменяет
-на_). - Опционально пропускает неизвестные поля.
- Использует
from_simple_type()/from_unicode()типа свойства для конвертации.
SimpleViewMixin#
Удобный mixin, совмещающий оба поведения:
class User(models.ModelWithUUID, models.SimpleViewMixin):
name = properties.property(types.String(max_length=255), required=True)
Пример round-trip через simple view:
Резюме#
- Используйте
Model(или её наследников) как основу для всех DM-моделей. - Применяйте mixin-классы
ModelWithUUID,ModelWithTimestamp,ModelWithProject,ModelWithNameDescдля избежания дублирования. - Используйте simple view mixin-ы для конвертации моделей в простые структуры и обратно при интеграции с API, OpenAPI и внешними хранилищами.