from __future__ import annotations import enum import uuid from datetime import datetime from typing import Any from sqlalchemy import DateTime, Enum, ForeignKey, Integer, String, Text from sqlalchemy.dialects.postgresql import JSON, UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import Base, TimestampMixin class ExecutionRunStatus(str, enum.Enum): QUEUED = "QUEUED" RUNNING = "RUNNING" SUCCEEDED = "SUCCEEDED" FAILED = "FAILED" PARTIAL_FAILED = "PARTIAL_FAILED" class ExecutionStepStatus(str, enum.Enum): PENDING = "PENDING" RUNNING = "RUNNING" SUCCEEDED = "SUCCEEDED" FAILED = "FAILED" SKIPPED = "SKIPPED" class ExecutionRun(TimestampMixin, Base): __tablename__ = "execution_runs" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, ) pipeline_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("pipelines.id", ondelete="CASCADE"), nullable=False, index=True, ) initiated_by: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True, ) status: Mapped[ExecutionRunStatus] = mapped_column( Enum(ExecutionRunStatus, name="execution_run_status"), nullable=False, default=ExecutionRunStatus.QUEUED, server_default=ExecutionRunStatus.QUEUED.value, index=True, ) inputs: Mapped[dict[str, Any]] = mapped_column( JSON, nullable=False, default=dict, server_default="{}", ) summary: Mapped[dict[str, Any] | None] = mapped_column( JSON, nullable=True, ) error: Mapped[str | None] = mapped_column( Text, nullable=True, ) started_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) finished_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) pipeline = relationship("Pipeline", lazy="select") step_runs = relationship( "ExecutionStepRun", back_populates="run", cascade="all, delete-orphan", lazy="selectin", ) class ExecutionStepRun(TimestampMixin, Base): __tablename__ = "execution_step_runs" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, ) run_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("execution_runs.id", ondelete="CASCADE"), nullable=False, index=True, ) step: Mapped[int] = mapped_column( Integer, nullable=False, index=True, ) name: Mapped[str | None] = mapped_column( String(512), nullable=True, ) capability_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), nullable=True, index=True, ) action_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), nullable=True, index=True, ) status: Mapped[ExecutionStepStatus] = mapped_column( Enum(ExecutionStepStatus, name="execution_step_status"), nullable=False, default=ExecutionStepStatus.PENDING, server_default=ExecutionStepStatus.PENDING.value, index=True, ) resolved_inputs: Mapped[dict[str, Any] | None] = mapped_column( JSON, nullable=True, ) request_snapshot: Mapped[dict[str, Any] | None] = mapped_column( JSON, nullable=True, ) response_snapshot: Mapped[dict[str, Any] | None] = mapped_column( JSON, nullable=True, ) error: Mapped[str | None] = mapped_column( Text, nullable=True, ) started_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) finished_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) duration_ms: Mapped[int | None] = mapped_column( Integer, nullable=True, ) run = relationship("ExecutionRun", back_populates="step_runs", lazy="select")