CI / lint (push) Successful in 4s
Build images / sign-extension (push) Successful in 4s
CI / extension-version (push) Successful in 4s
Build images / build-agent (push) Successful in 8s
CI / frontend-build (push) Successful in 27s
CI / backend-lint-and-test (push) Successful in 33s
Build images / build-web (push) Successful in 43s
Build images / build-ml (push) Successful in 51s
CI / integration (push) Successful in 3m47s
ruff's isort runs with order-by-type, which sorts ALL_CAPS names ahead of CamelCase ones, so `JSON` belongs at the head of the list rather than between `Integer` and `String`. Two of these (backup_run.py, post.py) have been failing lint since5e1996e— I did not check the push CI after that commit, only the baseline workflow I had dispatched, so ci.yml has been red on dev across5e1996e,ed2b1adandd044e93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QHszn9H8VBvx5Ke8x1hvw
70 lines
2.9 KiB
Python
70 lines
2.9 KiB
Python
"""Source — a platform-specific URL owned by an Artist (e.g., a Patreon URL).
|
|
|
|
Multiple sources per artist support creators with cross-platform presence.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import (
|
|
JSON,
|
|
Boolean,
|
|
DateTime,
|
|
ForeignKey,
|
|
Integer,
|
|
String,
|
|
Text,
|
|
UniqueConstraint,
|
|
)
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from .base import Base
|
|
|
|
|
|
class Source(Base):
|
|
__tablename__ = "source"
|
|
|
|
__table_args__ = (
|
|
# alembic 0010. One row per (artist, platform, url): re-adding a source
|
|
# the artist already has is an update, not a second row. The model had
|
|
# never declared it (#3275), so autogenerate would have proposed
|
|
# DROPPING it — the guarantee existed only in the migration chain.
|
|
#
|
|
# Named explicitly because the naming convention would render this
|
|
# `uq_source_artist_id` (uq keys off column_0_name), which is both
|
|
# wrong about the shape and not what the database actually has.
|
|
UniqueConstraint(
|
|
"artist_id", "platform", "url", name="uq_source_artist_platform_url"
|
|
),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
artist_id: Mapped[int] = mapped_column(
|
|
ForeignKey("artist.id", ondelete="CASCADE"), nullable=False, index=True
|
|
)
|
|
platform: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
url: Mapped[str] = mapped_column(Text, nullable=False)
|
|
enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default="true")
|
|
|
|
config_overrides: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
last_checked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
last_error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
# alembic 0032: last ErrorType category (auth_error, rate_limited,
|
|
# not_found, ...). Lets FailingSourcesCard surface the taxonomy as
|
|
# a colored chip so operators can bulk-triage by error class. Set
|
|
# by _update_source_health alongside last_error; cleared on 'ok'.
|
|
error_type: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
|
|
check_interval_override: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
consecutive_failures: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
|
|
|
|
# alembic 0031: sticky deep-scan budget. When > 0, the next N download
|
|
# runs use gallery-dl's full-walk config (skip: True + 1800s timeout);
|
|
# when 0, runs use tick mode (skip: "exit:20" + 870s, exits early once
|
|
# 20 contiguous archived items are seen). Auto-decrements per run, with
|
|
# an auto-reset to 0 on clean exit + zero downloads (queue drained).
|
|
backfill_runs_remaining: Mapped[int] = mapped_column(
|
|
Integer, nullable=False, default=0, server_default="0",
|
|
)
|
|
|
|
artist = relationship("Artist", back_populates="sources")
|