Интегрирован wesp в сайт
CI / quality (push) Canceled after 0s

This commit is contained in:
влад
2026-07-17 12:57:18 +03:00
parent 5dfa06ddbe
commit 355c0ef9f1
883 changed files with 194576 additions and 177 deletions
@@ -0,0 +1,188 @@
"""
Добавление в SQLite недостающих колонок по ORM-моделям (легаси → рефактор).
Вызывается из единой миграции wesp_2_0 на каждом bind отдельно (recipes / reports).
Не создаёт таблицы и не меняет типы существующих колонок.
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect as sa_inspect
from sqlalchemy.sql import sqltypes
def _bool_server_default(col: sa.Column) -> sa.TextClause:
if col.default is None:
return sa.text("0")
arg = getattr(col.default, "arg", False)
if callable(arg):
return sa.text("0")
return sa.text("1" if arg else "0")
def _int_server_default(col: sa.Column) -> str:
if col.default is None:
return "0"
arg = getattr(col.default, "arg", None)
if arg is None or callable(arg):
return "0"
try:
return str(int(arg))
except (TypeError, ValueError):
return "0"
def _float_server_default(col: sa.Column) -> str:
if col.default is None:
return "0"
arg = getattr(col.default, "arg", None)
if arg is None or callable(arg):
return "0"
try:
return str(float(arg))
except (TypeError, ValueError):
return "0"
def _is_bool_type(t: sa.types.TypeEngine) -> bool:
return isinstance(t, sqltypes.Boolean)
def _is_int_type(t: sa.types.TypeEngine) -> bool:
return isinstance(t, sqltypes.Integer)
def _is_float_type(t: sa.types.TypeEngine) -> bool:
return isinstance(t, (sqltypes.Float, sqltypes.REAL))
def _is_stringy_type(t: sa.types.TypeEngine) -> bool:
return isinstance(t, (sqltypes.String, sqltypes.Text, sqltypes.Unicode, sqltypes.UnicodeText))
def _is_datetime_type(t: sa.types.TypeEngine) -> bool:
return isinstance(t, sqltypes.DateTime)
def _add_column_sqlite_safe(op, table: str, col: sa.Column) -> None:
typ = col.type
name = col.name
# В SQLAlchemy по умолчанию nullable=None/True; только явный False — NOT NULL в DDL.
if col.nullable is not False:
op.add_column(table, sa.Column(name, typ, nullable=True))
return
if col.server_default is not None:
op.add_column(
table,
sa.Column(name, typ, nullable=False, server_default=col.server_default),
)
return
if _is_bool_type(typ):
op.add_column(
table,
sa.Column(name, typ, nullable=False, server_default=_bool_server_default(col)),
)
return
if _is_stringy_type(typ):
op.add_column(
table,
sa.Column(name, typ, nullable=False, server_default=""),
)
return
if _is_int_type(typ):
op.add_column(
table,
sa.Column(
name,
typ,
nullable=False,
server_default=_int_server_default(col),
),
)
return
if _is_float_type(typ):
op.add_column(
table,
sa.Column(
name,
typ,
nullable=False,
server_default=_float_server_default(col),
),
)
return
if _is_datetime_type(typ):
op.add_column(table, sa.Column(name, typ, nullable=True))
bind = op.get_bind()
insp = sa_inspect(bind)
cols_now = {c["name"] for c in insp.get_columns(table)}
parts: list[str] = []
for key in ("created_at", "updated_at"):
if key in cols_now and key != name:
parts.append(f'"{key}"')
if parts:
expr = "COALESCE(" + ", ".join(parts) + ", CURRENT_TIMESTAMP)"
else:
expr = "CURRENT_TIMESTAMP"
op.execute(
sa.text(f'UPDATE "{table}" SET "{name}" = {expr} WHERE "{name}" IS NULL')
)
with op.batch_alter_table(table) as batch:
batch.alter_column(name, existing_type=typ, nullable=False)
return
op.add_column(table, sa.Column(name, typ, nullable=True))
def apply_legacy_orm_gaps(op, kw: dict) -> None:
tag = kw.get("tag")
reports_only = tag == "reports"
from app import db # noqa: WPS433 — внутри миграции, после загрузки приложения в env
import app.models # noqa: F401, WPS433
bind = op.get_bind()
insp = sa_inspect(bind)
for mapper in db.Model.registry.mappers:
cls = mapper.class_
bk = getattr(cls, "__bind_key__", None)
if reports_only:
if bk != "reports":
continue
else:
if bk == "reports":
continue
# db.Model (Flask-SQLAlchemy) задаёт __abstract__ = True; наследники остаются конкретными.
if cls.__dict__.get("__abstract__", False):
continue
tab = getattr(cls, "__tablename__", None)
if not tab:
continue
if not insp.has_table(tab):
continue
existing = {c["name"] for c in insp.get_columns(tab)}
for col in cls.__table__.columns:
if col.primary_key:
continue
if col.key in existing:
continue
_add_column_sqlite_safe(op, tab, col)
existing.add(col.key)
from app.services.migration_pace import migration_pause_step
migration_pause_step(f"column:{tab}.{col.key}")