New archi: domain driven development
Working but need to check out code
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
"""TV Show domain entities."""
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
|
||||
from ..shared.value_objects import ImdbId, FilePath, FileSize
|
||||
from .value_objects import ShowStatus, SeasonNumber, EpisodeNumber
|
||||
|
||||
|
||||
@dataclass
|
||||
class TVShow:
|
||||
"""
|
||||
TV Show entity representing a TV show in the media library.
|
||||
|
||||
This is the main aggregate root for the TV shows domain.
|
||||
Migrated from agent/models/tv_show.py
|
||||
"""
|
||||
imdb_id: ImdbId
|
||||
title: str
|
||||
seasons_count: int
|
||||
status: ShowStatus
|
||||
tmdb_id: Optional[int] = None
|
||||
overview: Optional[str] = None
|
||||
poster_path: Optional[str] = None
|
||||
first_air_date: Optional[str] = None
|
||||
vote_average: Optional[float] = None
|
||||
added_at: datetime = field(default_factory=datetime.now)
|
||||
|
||||
def __post_init__(self):
|
||||
"""Validate TV show entity."""
|
||||
# Ensure ImdbId is actually an ImdbId instance
|
||||
if not isinstance(self.imdb_id, ImdbId):
|
||||
if isinstance(self.imdb_id, str):
|
||||
object.__setattr__(self, 'imdb_id', ImdbId(self.imdb_id))
|
||||
else:
|
||||
raise ValueError(f"imdb_id must be ImdbId or str, got {type(self.imdb_id)}")
|
||||
|
||||
# Ensure ShowStatus is actually a ShowStatus instance
|
||||
if not isinstance(self.status, ShowStatus):
|
||||
if isinstance(self.status, str):
|
||||
object.__setattr__(self, 'status', ShowStatus.from_string(self.status))
|
||||
else:
|
||||
raise ValueError(f"status must be ShowStatus or str, got {type(self.status)}")
|
||||
|
||||
# Validate seasons_count
|
||||
if not isinstance(self.seasons_count, int) or self.seasons_count < 0:
|
||||
raise ValueError(f"seasons_count must be a non-negative integer, got {self.seasons_count}")
|
||||
|
||||
def is_ongoing(self) -> bool:
|
||||
"""Check if the show is still ongoing."""
|
||||
return self.status == ShowStatus.ONGOING
|
||||
|
||||
def is_ended(self) -> bool:
|
||||
"""Check if the show has ended."""
|
||||
return self.status == ShowStatus.ENDED
|
||||
|
||||
def get_folder_name(self) -> str:
|
||||
"""
|
||||
Get the folder name for this TV show.
|
||||
|
||||
Format: "Title"
|
||||
Example: "Breaking.Bad"
|
||||
"""
|
||||
import re
|
||||
# Remove special characters and replace spaces with dots
|
||||
cleaned = re.sub(r'[^\w\s\.\-]', '', self.title)
|
||||
return cleaned.replace(' ', '.')
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.title} ({self.status.value}, {self.seasons_count} seasons)"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"TVShow(imdb_id={self.imdb_id}, title='{self.title}')"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Season:
|
||||
"""
|
||||
Season entity representing a season of a TV show.
|
||||
"""
|
||||
show_imdb_id: ImdbId
|
||||
season_number: SeasonNumber
|
||||
episode_count: int
|
||||
name: Optional[str] = None
|
||||
overview: Optional[str] = None
|
||||
air_date: Optional[str] = None
|
||||
poster_path: Optional[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
"""Validate season entity."""
|
||||
# Ensure ImdbId is actually an ImdbId instance
|
||||
if not isinstance(self.show_imdb_id, ImdbId):
|
||||
if isinstance(self.show_imdb_id, str):
|
||||
object.__setattr__(self, 'show_imdb_id', ImdbId(self.show_imdb_id))
|
||||
|
||||
# Ensure SeasonNumber is actually a SeasonNumber instance
|
||||
if not isinstance(self.season_number, SeasonNumber):
|
||||
if isinstance(self.season_number, int):
|
||||
object.__setattr__(self, 'season_number', SeasonNumber(self.season_number))
|
||||
|
||||
# Validate episode_count
|
||||
if not isinstance(self.episode_count, int) or self.episode_count < 0:
|
||||
raise ValueError(f"episode_count must be a non-negative integer, got {self.episode_count}")
|
||||
|
||||
def is_special(self) -> bool:
|
||||
"""Check if this is the specials season."""
|
||||
return self.season_number.is_special()
|
||||
|
||||
def get_folder_name(self) -> str:
|
||||
"""
|
||||
Get the folder name for this season.
|
||||
|
||||
Format: "Season 01" or "Specials" for season 0
|
||||
"""
|
||||
if self.is_special():
|
||||
return "Specials"
|
||||
return f"Season {self.season_number.value:02d}"
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.name:
|
||||
return f"Season {self.season_number.value}: {self.name}"
|
||||
return f"Season {self.season_number.value}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Season(show={self.show_imdb_id}, number={self.season_number.value})"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Episode:
|
||||
"""
|
||||
Episode entity representing an episode of a TV show.
|
||||
"""
|
||||
show_imdb_id: ImdbId
|
||||
season_number: SeasonNumber
|
||||
episode_number: EpisodeNumber
|
||||
title: str
|
||||
file_path: Optional[FilePath] = None
|
||||
file_size: Optional[FileSize] = None
|
||||
overview: Optional[str] = None
|
||||
air_date: Optional[str] = None
|
||||
still_path: Optional[str] = None
|
||||
vote_average: Optional[float] = None
|
||||
runtime: Optional[int] = None # in minutes
|
||||
|
||||
def __post_init__(self):
|
||||
"""Validate episode entity."""
|
||||
# Ensure ImdbId is actually an ImdbId instance
|
||||
if not isinstance(self.show_imdb_id, ImdbId):
|
||||
if isinstance(self.show_imdb_id, str):
|
||||
object.__setattr__(self, 'show_imdb_id', ImdbId(self.show_imdb_id))
|
||||
|
||||
# Ensure SeasonNumber is actually a SeasonNumber instance
|
||||
if not isinstance(self.season_number, SeasonNumber):
|
||||
if isinstance(self.season_number, int):
|
||||
object.__setattr__(self, 'season_number', SeasonNumber(self.season_number))
|
||||
|
||||
# Ensure EpisodeNumber is actually an EpisodeNumber instance
|
||||
if not isinstance(self.episode_number, EpisodeNumber):
|
||||
if isinstance(self.episode_number, int):
|
||||
object.__setattr__(self, 'episode_number', EpisodeNumber(self.episode_number))
|
||||
|
||||
def has_file(self) -> bool:
|
||||
"""Check if the episode has an associated file."""
|
||||
return self.file_path is not None and self.file_path.exists()
|
||||
|
||||
def is_downloaded(self) -> bool:
|
||||
"""Check if the episode is downloaded."""
|
||||
return self.has_file()
|
||||
|
||||
def get_filename(self) -> str:
|
||||
"""
|
||||
Get the suggested filename for this episode.
|
||||
|
||||
Format: "S01E01 - Episode Title.ext"
|
||||
Example: "S01E05 - Pilot.mkv"
|
||||
"""
|
||||
season_str = f"S{self.season_number.value:02d}"
|
||||
episode_str = f"E{self.episode_number.value:02d}"
|
||||
|
||||
# Clean title for filename
|
||||
import re
|
||||
clean_title = re.sub(r'[^\w\s\-]', '', self.title)
|
||||
clean_title = clean_title.replace(' ', '.')
|
||||
|
||||
return f"{season_str}{episode_str}.{clean_title}"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"S{self.season_number.value:02d}E{self.episode_number.value:02d} - {self.title}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Episode(show={self.show_imdb_id}, S{self.season_number.value:02d}E{self.episode_number.value:02d})"
|
||||
Reference in New Issue
Block a user