utp_service/backend/app/models/task.py

78 lines
3.5 KiB
Python

from datetime import datetime
from typing import Any, Optional
from uuid import uuid4
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.types import Uuid
from .base import Base
TASK_STATUSES = ("pending", "running", "success", "partial", "failed", "stale", "cancelled")
class Task(Base):
__tablename__ = "tasks"
id: Mapped[str] = mapped_column(
Uuid(as_uuid=False), primary_key=True, default=lambda: str(uuid4())
)
type: Mapped[str] = mapped_column(String(100), nullable=False)
status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending")
target_scope: Mapped[dict] = mapped_column(JSONB, nullable=False)
params: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
result: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True)
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_by: Mapped[Optional[int]] = mapped_column(
Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True
)
cancel_requested: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
started_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
finished_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
heartbeat_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
events: Mapped[list["TaskEvent"]] = relationship(
"TaskEvent", back_populates="task", cascade="all, delete-orphan"
)
device_results: Mapped[list["TaskResult"]] = relationship(
"TaskResult", back_populates="task", cascade="all, delete-orphan"
)
class TaskEvent(Base):
__tablename__ = "task_events"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
task_id: Mapped[str] = mapped_column(
Uuid(as_uuid=False), ForeignKey("tasks.id", ondelete="CASCADE"), nullable=False, index=True
)
message: Mapped[str] = mapped_column(Text, nullable=False)
level: Mapped[str] = mapped_column(String(10), nullable=False, default="info")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
task: Mapped["Task"] = relationship("Task", back_populates="events")
class TaskResult(Base):
__tablename__ = "task_results"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
task_id: Mapped[str] = mapped_column(
Uuid(as_uuid=False), ForeignKey("tasks.id", ondelete="CASCADE"), nullable=False, index=True
)
device_id: Mapped[int] = mapped_column(
Integer, ForeignKey("devices.id", ondelete="CASCADE"), nullable=False, index=True
)
status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending")
stdout: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
stderr: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
exit_code: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
data: Mapped[Optional[dict[str, Any]]] = mapped_column(JSONB, nullable=True)
duration_ms: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
executed_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
task: Mapped["Task"] = relationship("Task", back_populates="device_results")