72 lines
3.0 KiB
Python
72 lines
3.0 KiB
Python
"""Durable jobs, their per-item progress, and their event history.
|
|
|
|
A job is a unit of long-running work whose lifecycle survives browser, API, and
|
|
worker interruption. Progress is derived from ``job_items`` (the durable source of
|
|
truth), never from browser memory. ``job_events`` is an append-only activity log.
|
|
Leases (owner + expiry + heartbeat) and a monotonic ``fencing_token`` let a
|
|
recovered worker be fenced out after a newer worker takes ownership.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import DateTime, ForeignKey, Integer, String, func
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from photo_pipeline.db import Base
|
|
|
|
|
|
class Job(Base):
|
|
__tablename__ = "jobs"
|
|
|
|
id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
job_type: Mapped[str] = mapped_column(String, nullable=False)
|
|
state: Mapped[str] = mapped_column(String, nullable=False)
|
|
lock_key: Mapped[str | None] = mapped_column(String)
|
|
idempotency_key: Mapped[str | None] = mapped_column(String, unique=True)
|
|
|
|
lease_owner: Mapped[str | None] = mapped_column(String)
|
|
lease_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
heartbeat_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
fencing_token: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
|
|
error_code: Mapped[str | None] = mapped_column(String)
|
|
error_message: Mapped[str | None] = mapped_column(String)
|
|
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, server_default=func.now()
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
|
)
|
|
started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
|
|
|
|
|
class JobItem(Base):
|
|
__tablename__ = "job_items"
|
|
|
|
job_id: Mapped[str] = mapped_column(ForeignKey("jobs.id"), primary_key=True)
|
|
item_key: Mapped[str] = mapped_column(String, primary_key=True)
|
|
state: Mapped[str] = mapped_column(String, nullable=False)
|
|
attempt_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
error_code: Mapped[str | None] = mapped_column(String)
|
|
error_message: Mapped[str | None] = mapped_column(String)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
|
)
|
|
|
|
|
|
class JobEvent(Base):
|
|
__tablename__ = "job_events"
|
|
|
|
id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
job_id: Mapped[str] = mapped_column(ForeignKey("jobs.id"), nullable=False)
|
|
event_type: Mapped[str] = mapped_column(String, nullable=False)
|
|
message: Mapped[str | None] = mapped_column(String)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, server_default=func.now()
|
|
)
|