This repository has been archived on 2026-05-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
GallerySubscriber/backend/app/models/download.py
T
2026-01-24 22:52:51 -05:00

94 lines
3.1 KiB
Python

"""Download model - individual download jobs."""
from datetime import datetime
from typing import Optional
from sqlalchemy import String, Integer, Text, ForeignKey, BigInteger
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.base import Base, TimestampMixin
class DownloadStatus:
"""Download status constants."""
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
SKIPPED = "skipped"
class ErrorType:
"""Error type constants matching legacy project."""
AUTH_ERROR = "auth_error"
RATE_LIMITED = "rate_limited"
NOT_FOUND = "not_found"
ACCESS_DENIED = "access_denied"
NETWORK_ERROR = "network_error"
NO_NEW_CONTENT = "no_new_content"
TIMEOUT = "timeout"
HTTP_ERROR = "http_error"
UNSUPPORTED_URL = "unsupported_url"
UNKNOWN_ERROR = "unknown_error"
class Download(Base, TimestampMixin):
"""A download represents a single download job for a source."""
__tablename__ = "downloads"
id: Mapped[int] = mapped_column(primary_key=True)
source_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("sources.id", ondelete="SET NULL"), nullable=True, index=True
)
url: Mapped[str] = mapped_column(Text, nullable=False)
# Status
status: Mapped[str] = mapped_column(
String(20), default=DownloadStatus.PENDING, index=True
)
error_type: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Results
file_count: Mapped[int] = mapped_column(Integer, default=0)
total_size: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True)
# Timing
started_at: Mapped[Optional[datetime]] = mapped_column(nullable=True)
completed_at: Mapped[Optional[datetime]] = mapped_column(nullable=True)
# Flexible metadata
metadata_: Mapped[dict] = mapped_column("metadata", JSONB, default=dict)
# Relationships
source: Mapped[Optional["Source"]] = relationship(back_populates="downloads")
content_items: Mapped[list["ContentItem"]] = relationship(back_populates="download")
def __repr__(self) -> str:
return f"<Download {self.id} ({self.status})>"
def to_dict(self) -> dict:
"""Convert to dictionary for API responses."""
return {
"id": self.id,
"source_id": self.source_id,
"url": self.url,
"status": self.status,
"error_type": self.error_type,
"error_message": self.error_message,
"file_count": self.file_count,
"total_size": self.total_size,
"started_at": self.started_at.isoformat() if self.started_at else None,
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
"metadata": self.metadata_,
"created_at": self.created_at.isoformat() if self.created_at else None,
}
# Import for type hints
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from app.models.source import Source
from app.models.content import ContentItem