from datetime import datetime from sqlalchemy import ( BigInteger, DateTime, ForeignKey, Integer, String, Text, func, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base class Run(Base): """单次备份执行记录。""" __tablename__ = "runs" id: Mapped[int] = mapped_column(Integer, primary_key=True) job_id: Mapped[int] = mapped_column( Integer, ForeignKey("jobs.id", ondelete="CASCADE"), nullable=False, index=True ) status: Mapped[str] = mapped_column(String(16), nullable=False, default="pending") trigger: Mapped[str] = mapped_column(String(16), nullable=False, default="manual") started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) finished_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) duration_seconds: Mapped[int | None] = mapped_column(Integer, nullable=True) artifact_path: Mapped[str | None] = mapped_column(String(512), nullable=True) artifact_size: Mapped[int | None] = mapped_column(BigInteger, nullable=True) artifact_checksum: Mapped[str | None] = mapped_column(String(64), nullable=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) log_excerpt: Mapped[str | None] = mapped_column(Text, nullable=True) job: Mapped["Job"] = relationship(lazy="joined") # noqa: F821 logs: Mapped[list["RunLog"]] = relationship( # noqa: F821 back_populates="run", cascade="all, delete-orphan", lazy="select" ) created_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), nullable=False ) class RunLog(Base): """完整日志(可独立表以避免 runs 表臃肿)。""" __tablename__ = "run_logs" id: Mapped[int] = mapped_column(Integer, primary_key=True) run_id: Mapped[int] = mapped_column( Integer, ForeignKey("runs.id", ondelete="CASCADE"), nullable=False, index=True ) content: Mapped[str] = mapped_column(Text, nullable=False) created_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), nullable=False ) run: Mapped["Run"] = relationship(back_populates="logs")