65 lines
1.9 KiB
Python
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
|