Files
test/app/model/base.py
T
2026-09-21 19:03:31 +08:00

65 lines
1.9 KiB
Python

"""ORM 基类与公共 Mixin。
约定:
* 主键统一 id 自增
* 所有表都带 created_at / updated_at
* 业务表一律逻辑删除:is_del = 0 表示正常,1 表示已删除
* 查询正常数据统一写 `Model.alive()`,禁止在 DAO 里手写 `is_del == 1`
(上一版项目就是把方向写反导致了"查不到刚建的数据")
"""
from __future__ import annotations
from datetime import datetime
from typing import Any
from sqlalchemy import DateTime, SmallInteger
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
DEL_FLAG_NORMAL: int = 0
DEL_FLAG_DELETED: int = 1
class Base(DeclarativeBase):
"""SQLAlchemy 2.0 DeclarativeBase。"""
def to_dict(self, exclude: set[str] | None = None) -> dict[str, Any]:
exclude = exclude or set()
return {
c.name: getattr(self, c.name)
for c in self.__table__.columns
if c.name not in exclude
}
def __repr__(self) -> str: # pragma: no cover - 调试用
pk = getattr(self, "id", None)
return f"<{self.__class__.__name__} id={pk}>"
class TimestampMixin:
created_at: Mapped[datetime] = mapped_column(
DateTime, default=datetime.now, nullable=False, comment="创建时间"
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, default=datetime.now, onupdate=datetime.now, nullable=False, comment="更新时间"
)
class SoftDeleteMixin:
is_del: Mapped[int] = mapped_column(
SmallInteger,
default=DEL_FLAG_NORMAL,
server_default="0",
nullable=False,
index=True,
comment="逻辑删除:0=正常 1=已删除",
)
@classmethod
def alive(cls):
"""正常数据的过滤条件,供 select().where() 使用。"""
return cls.is_del == DEL_FLAG_NORMAL
def soft_delete(self) -> None:
self.is_del = DEL_FLAG_DELETED