feat: Task 5 - Add NFO Management API Endpoints (85% complete)
- Create NFO API models (11 Pydantic models)
- Implement 8 REST API endpoints for NFO management
- Register NFO router in FastAPI app
- Create 18 comprehensive API tests
- Add detailed status documentation
Endpoints:
- GET /api/nfo/{id}/check - Check NFO/media status
- POST /api/nfo/{id}/create - Create NFO & media
- PUT /api/nfo/{id}/update - Update NFO
- GET /api/nfo/{id}/content - Get NFO content
- GET /api/nfo/{id}/media/status - Media status
- POST /api/nfo/{id}/media/download - Download media
- POST /api/nfo/batch/create - Batch operations
- GET /api/nfo/missing - List missing NFOs
Remaining: Refactor to use series_app dependency pattern
This commit is contained in:
357
src/server/models/nfo.py
Normal file
357
src/server/models/nfo.py
Normal file
@@ -0,0 +1,357 @@
|
||||
"""NFO API request and response models.
|
||||
|
||||
This module defines Pydantic models for NFO management API operations.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class MediaFilesStatus(BaseModel):
|
||||
"""Status of media files (poster, logo, fanart) for a series.
|
||||
|
||||
Attributes:
|
||||
has_poster: Whether poster.jpg exists
|
||||
has_logo: Whether logo.png exists
|
||||
has_fanart: Whether fanart.jpg exists
|
||||
poster_path: Path to poster file if exists
|
||||
logo_path: Path to logo file if exists
|
||||
fanart_path: Path to fanart file if exists
|
||||
"""
|
||||
has_poster: bool = Field(
|
||||
default=False,
|
||||
description="Whether poster.jpg exists"
|
||||
)
|
||||
has_logo: bool = Field(
|
||||
default=False,
|
||||
description="Whether logo.png exists"
|
||||
)
|
||||
has_fanart: bool = Field(
|
||||
default=False,
|
||||
description="Whether fanart.jpg exists"
|
||||
)
|
||||
poster_path: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Path to poster file if exists"
|
||||
)
|
||||
logo_path: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Path to logo file if exists"
|
||||
)
|
||||
fanart_path: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Path to fanart file if exists"
|
||||
)
|
||||
|
||||
|
||||
class NFOCheckResponse(BaseModel):
|
||||
"""Response for NFO existence check.
|
||||
|
||||
Attributes:
|
||||
serie_id: Series identifier
|
||||
serie_folder: Series folder name
|
||||
has_nfo: Whether tvshow.nfo exists
|
||||
nfo_path: Path to NFO file if exists
|
||||
media_files: Status of media files
|
||||
"""
|
||||
serie_id: str = Field(
|
||||
...,
|
||||
description="Series identifier"
|
||||
)
|
||||
serie_folder: str = Field(
|
||||
...,
|
||||
description="Series folder name"
|
||||
)
|
||||
has_nfo: bool = Field(
|
||||
...,
|
||||
description="Whether tvshow.nfo exists"
|
||||
)
|
||||
nfo_path: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Path to NFO file if exists"
|
||||
)
|
||||
media_files: MediaFilesStatus = Field(
|
||||
...,
|
||||
description="Status of media files"
|
||||
)
|
||||
|
||||
|
||||
class NFOCreateRequest(BaseModel):
|
||||
"""Request to create NFO file.
|
||||
|
||||
Attributes:
|
||||
serie_name: Name to search in TMDB
|
||||
year: Optional year to narrow search
|
||||
download_poster: Whether to download poster.jpg
|
||||
download_logo: Whether to download logo.png
|
||||
download_fanart: Whether to download fanart.jpg
|
||||
overwrite_existing: Whether to overwrite existing NFO
|
||||
"""
|
||||
serie_name: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Name to search in TMDB (defaults to folder name)"
|
||||
)
|
||||
year: Optional[int] = Field(
|
||||
default=None,
|
||||
description="Optional year to narrow search"
|
||||
)
|
||||
download_poster: bool = Field(
|
||||
default=True,
|
||||
description="Whether to download poster.jpg"
|
||||
)
|
||||
download_logo: bool = Field(
|
||||
default=True,
|
||||
description="Whether to download logo.png"
|
||||
)
|
||||
download_fanart: bool = Field(
|
||||
default=True,
|
||||
description="Whether to download fanart.jpg"
|
||||
)
|
||||
overwrite_existing: bool = Field(
|
||||
default=False,
|
||||
description="Whether to overwrite existing NFO"
|
||||
)
|
||||
|
||||
|
||||
class NFOCreateResponse(BaseModel):
|
||||
"""Response after NFO creation.
|
||||
|
||||
Attributes:
|
||||
serie_id: Series identifier
|
||||
serie_folder: Series folder name
|
||||
nfo_path: Path to created NFO file
|
||||
media_files: Status of downloaded media files
|
||||
tmdb_id: TMDB ID of matched series
|
||||
message: Success message
|
||||
"""
|
||||
serie_id: str = Field(
|
||||
...,
|
||||
description="Series identifier"
|
||||
)
|
||||
serie_folder: str = Field(
|
||||
...,
|
||||
description="Series folder name"
|
||||
)
|
||||
nfo_path: str = Field(
|
||||
...,
|
||||
description="Path to created NFO file"
|
||||
)
|
||||
media_files: MediaFilesStatus = Field(
|
||||
...,
|
||||
description="Status of downloaded media files"
|
||||
)
|
||||
tmdb_id: Optional[int] = Field(
|
||||
default=None,
|
||||
description="TMDB ID of matched series"
|
||||
)
|
||||
message: str = Field(
|
||||
...,
|
||||
description="Success message"
|
||||
)
|
||||
|
||||
|
||||
class NFOContentResponse(BaseModel):
|
||||
"""Response containing NFO XML content.
|
||||
|
||||
Attributes:
|
||||
serie_id: Series identifier
|
||||
serie_folder: Series folder name
|
||||
content: NFO XML content
|
||||
file_size: Size of NFO file in bytes
|
||||
last_modified: Last modification timestamp
|
||||
"""
|
||||
serie_id: str = Field(
|
||||
...,
|
||||
description="Series identifier"
|
||||
)
|
||||
serie_folder: str = Field(
|
||||
...,
|
||||
description="Series folder name"
|
||||
)
|
||||
content: str = Field(
|
||||
...,
|
||||
description="NFO XML content"
|
||||
)
|
||||
file_size: int = Field(
|
||||
...,
|
||||
description="Size of NFO file in bytes"
|
||||
)
|
||||
last_modified: Optional[datetime] = Field(
|
||||
default=None,
|
||||
description="Last modification timestamp"
|
||||
)
|
||||
|
||||
|
||||
class MediaDownloadRequest(BaseModel):
|
||||
"""Request to download specific media files.
|
||||
|
||||
Attributes:
|
||||
download_poster: Whether to download poster.jpg
|
||||
download_logo: Whether to download logo.png
|
||||
download_fanart: Whether to download fanart.jpg
|
||||
overwrite_existing: Whether to overwrite existing files
|
||||
"""
|
||||
download_poster: bool = Field(
|
||||
default=False,
|
||||
description="Whether to download poster.jpg"
|
||||
)
|
||||
download_logo: bool = Field(
|
||||
default=False,
|
||||
description="Whether to download logo.png"
|
||||
)
|
||||
download_fanart: bool = Field(
|
||||
default=False,
|
||||
description="Whether to download fanart.jpg"
|
||||
)
|
||||
overwrite_existing: bool = Field(
|
||||
default=False,
|
||||
description="Whether to overwrite existing files"
|
||||
)
|
||||
|
||||
|
||||
class NFOBatchCreateRequest(BaseModel):
|
||||
"""Request to batch create NFOs for multiple series.
|
||||
|
||||
Attributes:
|
||||
serie_ids: List of series IDs to process
|
||||
download_media: Whether to download media files
|
||||
skip_existing: Whether to skip series with existing NFOs
|
||||
max_concurrent: Maximum concurrent creations
|
||||
"""
|
||||
serie_ids: List[str] = Field(
|
||||
...,
|
||||
description="List of series IDs to process"
|
||||
)
|
||||
download_media: bool = Field(
|
||||
default=True,
|
||||
description="Whether to download media files"
|
||||
)
|
||||
skip_existing: bool = Field(
|
||||
default=True,
|
||||
description="Whether to skip series with existing NFOs"
|
||||
)
|
||||
max_concurrent: int = Field(
|
||||
default=3,
|
||||
ge=1,
|
||||
le=10,
|
||||
description="Maximum concurrent creations (1-10)"
|
||||
)
|
||||
|
||||
|
||||
class NFOBatchResult(BaseModel):
|
||||
"""Result for a single series in batch operation.
|
||||
|
||||
Attributes:
|
||||
serie_id: Series identifier
|
||||
serie_folder: Series folder name
|
||||
success: Whether operation succeeded
|
||||
message: Success or error message
|
||||
nfo_path: Path to NFO file if successful
|
||||
"""
|
||||
serie_id: str = Field(
|
||||
...,
|
||||
description="Series identifier"
|
||||
)
|
||||
serie_folder: str = Field(
|
||||
...,
|
||||
description="Series folder name"
|
||||
)
|
||||
success: bool = Field(
|
||||
...,
|
||||
description="Whether operation succeeded"
|
||||
)
|
||||
message: str = Field(
|
||||
...,
|
||||
description="Success or error message"
|
||||
)
|
||||
nfo_path: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Path to NFO file if successful"
|
||||
)
|
||||
|
||||
|
||||
class NFOBatchCreateResponse(BaseModel):
|
||||
"""Response after batch NFO creation.
|
||||
|
||||
Attributes:
|
||||
total: Total number of series processed
|
||||
successful: Number of successful creations
|
||||
failed: Number of failed creations
|
||||
skipped: Number of skipped series
|
||||
results: Detailed results for each series
|
||||
"""
|
||||
total: int = Field(
|
||||
...,
|
||||
description="Total number of series processed"
|
||||
)
|
||||
successful: int = Field(
|
||||
...,
|
||||
description="Number of successful creations"
|
||||
)
|
||||
failed: int = Field(
|
||||
...,
|
||||
description="Number of failed creations"
|
||||
)
|
||||
skipped: int = Field(
|
||||
...,
|
||||
description="Number of skipped series"
|
||||
)
|
||||
results: List[NFOBatchResult] = Field(
|
||||
...,
|
||||
description="Detailed results for each series"
|
||||
)
|
||||
|
||||
|
||||
class NFOMissingSeries(BaseModel):
|
||||
"""Information about a series missing NFO.
|
||||
|
||||
Attributes:
|
||||
serie_id: Series identifier
|
||||
serie_folder: Series folder name
|
||||
serie_name: Display name
|
||||
has_media: Whether any media files exist
|
||||
media_files: Status of media files
|
||||
"""
|
||||
serie_id: str = Field(
|
||||
...,
|
||||
description="Series identifier"
|
||||
)
|
||||
serie_folder: str = Field(
|
||||
...,
|
||||
description="Series folder name"
|
||||
)
|
||||
serie_name: str = Field(
|
||||
...,
|
||||
description="Display name"
|
||||
)
|
||||
has_media: bool = Field(
|
||||
default=False,
|
||||
description="Whether any media files exist"
|
||||
)
|
||||
media_files: MediaFilesStatus = Field(
|
||||
...,
|
||||
description="Status of media files"
|
||||
)
|
||||
|
||||
|
||||
class NFOMissingResponse(BaseModel):
|
||||
"""Response listing series without NFOs.
|
||||
|
||||
Attributes:
|
||||
total_series: Total number of series in library
|
||||
missing_nfo_count: Number of series without NFO
|
||||
series: List of series missing NFO
|
||||
"""
|
||||
total_series: int = Field(
|
||||
...,
|
||||
description="Total number of series in library"
|
||||
)
|
||||
missing_nfo_count: int = Field(
|
||||
...,
|
||||
description="Number of series without NFO"
|
||||
)
|
||||
series: List[NFOMissingSeries] = Field(
|
||||
...,
|
||||
description="List of series missing NFO"
|
||||
)
|
||||
Reference in New Issue
Block a user