feat: implement download queue Pydantic models
- Add comprehensive download queue models in src/server/models/download.py - DownloadStatus and DownloadPriority enums for type safety - EpisodeIdentifier for episode references - DownloadProgress for real-time progress tracking - DownloadItem for queue item representation with timestamps and error handling - QueueStatus for overall queue state management - QueueStats for aggregated queue statistics - DownloadRequest/DownloadResponse for API contracts - QueueOperationRequest and QueueReorderRequest for queue management - QueueStatusResponse for complete status endpoint responses - Add comprehensive unit tests (47 tests, all passing) - Test validation constraints (positive numbers, ranges, etc.) - Test default values and optional fields - Test serialization/deserialization - Test model relationships and nested structures - Update documentation - Add download models section to infrastructure.md - Remove completed task from instructions.md - Update models package __init__.py All models follow PEP 8 style guide with proper type hints and validation.
This commit is contained in:
207
src/server/models/download.py
Normal file
207
src/server/models/download.py
Normal file
@@ -0,0 +1,207 @@
|
||||
"""Download queue Pydantic models for the Aniworld web application.
|
||||
|
||||
This module defines request/response models used by the download queue API
|
||||
and the download service. Models are intentionally lightweight and focused
|
||||
on serialization, validation, and OpenAPI documentation.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, HttpUrl
|
||||
|
||||
|
||||
class DownloadStatus(str, Enum):
|
||||
"""Status of a download item in the queue."""
|
||||
|
||||
PENDING = "pending"
|
||||
DOWNLOADING = "downloading"
|
||||
PAUSED = "paused"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
CANCELLED = "cancelled"
|
||||
|
||||
|
||||
class DownloadPriority(str, Enum):
|
||||
"""Priority level for download queue items."""
|
||||
|
||||
LOW = "low"
|
||||
NORMAL = "normal"
|
||||
HIGH = "high"
|
||||
|
||||
|
||||
class EpisodeIdentifier(BaseModel):
|
||||
"""Episode identification information for a download item."""
|
||||
|
||||
season: int = Field(..., ge=1, description="Season number (1-based)")
|
||||
episode: int = Field(
|
||||
..., ge=1, description="Episode number within season (1-based)"
|
||||
)
|
||||
title: Optional[str] = Field(None, description="Episode title if known")
|
||||
|
||||
|
||||
class DownloadProgress(BaseModel):
|
||||
"""Real-time progress information for an active download."""
|
||||
|
||||
percent: float = Field(
|
||||
0.0, ge=0.0, le=100.0, description="Download progress percentage"
|
||||
)
|
||||
downloaded_mb: float = Field(
|
||||
0.0, ge=0.0, description="Downloaded size in megabytes"
|
||||
)
|
||||
total_mb: Optional[float] = Field(
|
||||
None, ge=0.0, description="Total size in megabytes if known"
|
||||
)
|
||||
speed_mbps: Optional[float] = Field(
|
||||
None, ge=0.0, description="Download speed in MB/s"
|
||||
)
|
||||
eta_seconds: Optional[int] = Field(
|
||||
None, ge=0, description="Estimated time remaining in seconds"
|
||||
)
|
||||
|
||||
|
||||
class DownloadItem(BaseModel):
|
||||
"""Represents a single download item in the queue."""
|
||||
|
||||
id: str = Field(..., description="Unique download item identifier")
|
||||
serie_id: str = Field(..., description="Series identifier")
|
||||
serie_name: str = Field(..., min_length=1, description="Series name")
|
||||
episode: EpisodeIdentifier = Field(
|
||||
..., description="Episode identification"
|
||||
)
|
||||
status: DownloadStatus = Field(
|
||||
DownloadStatus.PENDING, description="Current download status"
|
||||
)
|
||||
priority: DownloadPriority = Field(
|
||||
DownloadPriority.NORMAL, description="Queue priority"
|
||||
)
|
||||
|
||||
# Timestamps
|
||||
added_at: datetime = Field(
|
||||
default_factory=datetime.utcnow,
|
||||
description="When item was added to queue"
|
||||
)
|
||||
started_at: Optional[datetime] = Field(
|
||||
None, description="When download started"
|
||||
)
|
||||
completed_at: Optional[datetime] = Field(
|
||||
None, description="When download completed/failed"
|
||||
)
|
||||
|
||||
# Progress tracking
|
||||
progress: Optional[DownloadProgress] = Field(
|
||||
None, description="Current progress if downloading"
|
||||
)
|
||||
|
||||
# Error handling
|
||||
error: Optional[str] = Field(None, description="Error message if failed")
|
||||
retry_count: int = Field(0, ge=0, description="Number of retry attempts")
|
||||
|
||||
# Download source
|
||||
source_url: Optional[HttpUrl] = Field(
|
||||
None, description="Source URL for download"
|
||||
)
|
||||
|
||||
|
||||
class QueueStatus(BaseModel):
|
||||
"""Overall status of the download queue system."""
|
||||
|
||||
is_running: bool = Field(
|
||||
False, description="Whether the queue processor is running"
|
||||
)
|
||||
is_paused: bool = Field(False, description="Whether downloads are paused")
|
||||
active_downloads: List[DownloadItem] = Field(
|
||||
default_factory=list, description="Currently downloading items"
|
||||
)
|
||||
pending_queue: List[DownloadItem] = Field(
|
||||
default_factory=list, description="Items waiting to be downloaded"
|
||||
)
|
||||
completed_downloads: List[DownloadItem] = Field(
|
||||
default_factory=list, description="Recently completed downloads"
|
||||
)
|
||||
failed_downloads: List[DownloadItem] = Field(
|
||||
default_factory=list, description="Failed download items"
|
||||
)
|
||||
|
||||
|
||||
class QueueStats(BaseModel):
|
||||
"""Statistics about the download queue."""
|
||||
|
||||
total_items: int = Field(
|
||||
0, ge=0, description="Total number of items in all queues"
|
||||
)
|
||||
pending_count: int = Field(0, ge=0, description="Number of pending items")
|
||||
active_count: int = Field(
|
||||
0, ge=0, description="Number of active downloads"
|
||||
)
|
||||
completed_count: int = Field(
|
||||
0, ge=0, description="Number of completed downloads"
|
||||
)
|
||||
failed_count: int = Field(
|
||||
0, ge=0, description="Number of failed downloads"
|
||||
)
|
||||
|
||||
total_downloaded_mb: float = Field(
|
||||
0.0, ge=0.0, description="Total megabytes downloaded"
|
||||
)
|
||||
average_speed_mbps: Optional[float] = Field(
|
||||
None, ge=0.0, description="Average download speed in MB/s"
|
||||
)
|
||||
estimated_time_remaining: Optional[int] = Field(
|
||||
None, ge=0, description="Estimated time to complete queue in seconds"
|
||||
)
|
||||
|
||||
|
||||
class DownloadRequest(BaseModel):
|
||||
"""Request to add episode(s) to the download queue."""
|
||||
|
||||
serie_id: str = Field(..., description="Series identifier")
|
||||
serie_name: str = Field(
|
||||
..., min_length=1, description="Series name for display"
|
||||
)
|
||||
episodes: List[EpisodeIdentifier] = Field(
|
||||
..., min_length=1, description="List of episodes to download"
|
||||
)
|
||||
priority: DownloadPriority = Field(
|
||||
DownloadPriority.NORMAL, description="Priority level for queue items"
|
||||
)
|
||||
|
||||
|
||||
class DownloadResponse(BaseModel):
|
||||
"""Response after adding items to the download queue."""
|
||||
|
||||
status: str = Field(..., description="Status of the request")
|
||||
message: str = Field(..., description="Human-readable status message")
|
||||
added_items: List[str] = Field(
|
||||
default_factory=list,
|
||||
description="IDs of successfully added download items"
|
||||
)
|
||||
failed_items: List[str] = Field(
|
||||
default_factory=list, description="Episodes that failed to be added"
|
||||
)
|
||||
|
||||
|
||||
class QueueOperationRequest(BaseModel):
|
||||
"""Request to perform operations on queue items."""
|
||||
|
||||
item_ids: List[str] = Field(
|
||||
..., min_length=1, description="List of download item IDs"
|
||||
)
|
||||
|
||||
|
||||
class QueueReorderRequest(BaseModel):
|
||||
"""Request to reorder items in the pending queue."""
|
||||
|
||||
item_id: str = Field(..., description="Download item ID to move")
|
||||
new_position: int = Field(
|
||||
..., ge=0, description="New position in queue (0-based)"
|
||||
)
|
||||
|
||||
|
||||
class QueueStatusResponse(BaseModel):
|
||||
"""Complete response for queue status endpoint."""
|
||||
|
||||
status: QueueStatus = Field(..., description="Current queue status")
|
||||
statistics: QueueStats = Field(..., description="Queue statistics")
|
||||
Reference in New Issue
Block a user