63 lines
2.4 KiB
Python
63 lines
2.4 KiB
Python
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
|
||
import sqlalchemy as sa
|
||
from sqlalchemy.dialects import postgresql
|
||
from sqlalchemy.orm import Mapped, mapped_column
|
||
|
||
from app.core.config import settings
|
||
|
||
# BIGINT в Postgres; в SQLite автоинкремент работает только у колонки ровно
|
||
# "INTEGER PRIMARY KEY" (алиас rowid), поэтому для локальной БД — Integer.
|
||
BigIntPK = sa.BigInteger().with_variant(sa.Integer(), "sqlite")
|
||
|
||
# JSONB в Postgres (индексируемый, компактный), обычный JSON в SQLite.
|
||
JsonType = sa.JSON().with_variant(postgresql.JSONB(), "postgresql")
|
||
|
||
# Деньги, цены, курсы — только Numeric. Точность с запасом: курсы ЦБ бывают
|
||
# с 8 знаками на единицу (VND), цены MOEX — до 6 знаков.
|
||
Price = sa.Numeric(18, 6)
|
||
Rate = sa.Numeric(18, 8)
|
||
Amount = sa.Numeric(20, 6)
|
||
Percent = sa.Numeric(12, 6)
|
||
Quantity = sa.Numeric(20, 4)
|
||
|
||
SYSTEM_USER = "system"
|
||
|
||
|
||
def qualified(table_column: str) -> str:
|
||
"""'currency.id' -> 'schema.currency.id', если задана DATABASE_SCHEMA.
|
||
|
||
Нужен для ForeignKey: при заданной схеме таблицы в metadata
|
||
регистрируются как 'schema.table', и FK без схемы не разрешится.
|
||
"""
|
||
if settings.DATABASE_SCHEMA:
|
||
return f"{settings.DATABASE_SCHEMA}.{table_column}"
|
||
return table_column
|
||
|
||
|
||
class IdMixin:
|
||
id: Mapped[int] = mapped_column(BigIntPK, sa.Identity(), primary_key=True)
|
||
|
||
|
||
class AuditMixin:
|
||
"""created_*/updated_* на всех таблицах.
|
||
|
||
Пользователь — email из профиля Keycloak, для джобов — SYSTEM_USER.
|
||
updated_at при upsert (INSERT ... ON CONFLICT) выставляется явно в
|
||
репозиториях: onupdate срабатывает только для ORM-UPDATE.
|
||
"""
|
||
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||
)
|
||
created_by: Mapped[str] = mapped_column(sa.String(255), nullable=False)
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
sa.DateTime(timezone=True),
|
||
server_default=sa.func.now(),
|
||
onupdate=sa.func.now(),
|
||
nullable=False,
|
||
)
|
||
updated_by: Mapped[str] = mapped_column(sa.String(255), nullable=False)
|