Files
Serien-Checker/serien_checker/database/models.py
2025-12-21 14:35:08 +01:00

233 lines
7.8 KiB
Python

"""
Data models and enums for Serien-Checker database
"""
from enum import Enum
from typing import Optional, Union
from dataclasses import dataclass
from datetime import datetime
def _safe_parse_datetime(value: Union[str, datetime, None]) -> Optional[datetime]:
"""
Safely parse a datetime value that might be a string, datetime object, or None
Args:
value: String ISO format, datetime object, or None
Returns:
datetime object or None
"""
if value is None:
return None
if isinstance(value, datetime):
return value
if isinstance(value, str):
return datetime.fromisoformat(value)
return None
class SeasonType(Enum):
"""Types of seasons"""
NORMAL = "normal"
SPECIALS = "specials"
EXTRAS = "extras"
BEST_OF = "best_of"
YEAR_BASED = "year_based"
class DatePreference(Enum):
"""Preferred date types for episode releases"""
DE_FIRST = "de_first" # Deutsche Erstausstrahlung (frühestes deutsches Datum)
DE_TV = "de_tv" # Deutsche TV-Premiere
DE_STREAMING = "de_streaming" # Deutsche Streaming-Premiere
DE_HOME_MEDIA = "de_home_media" # Deutsche Home-Media-Premiere
DE_SYNC = "de_sync" # Deutsche Synchronfassung
ORIGINAL = "original" # Erstausstrahlung (Original)
@dataclass
class Series:
"""Represents a TV series"""
id: Optional[int]
title: str
url: str
date_preference: DatePreference
last_updated: Optional[datetime] = None
@classmethod
def from_row(cls, row: tuple) -> 'Series':
"""Create Series from database row"""
return cls(
id=row[0],
title=row[1],
url=row[2],
date_preference=DatePreference(row[3]),
last_updated=datetime.fromisoformat(row[4]) if row[4] else None
)
@dataclass
class Season:
"""Represents a season of a series"""
id: Optional[int]
series_id: int
name: str
season_type: SeasonType
sort_order: int
@classmethod
def from_row(cls, row: tuple) -> 'Season':
"""Create Season from database row"""
return cls(
id=row[0],
series_id=row[1],
name=row[2],
season_type=SeasonType(row[3]),
sort_order=row[4]
)
@dataclass
class Episode:
"""Represents an episode"""
id: Optional[int]
season_id: int
episode_code: str # e.g., "01", "01a", "01b"
title: str
# Episode number (overall series number from fernsehserien.de)
episode_number: Optional[int] = None
# Episode ID from fernsehserien.de (e.g., "1828679")
episode_id: Optional[str] = None
# All available air dates
date_de_tv: Optional[datetime] = None
date_de_streaming: Optional[datetime] = None
date_de_home_media: Optional[datetime] = None
date_de_sync: Optional[datetime] = None
date_original: Optional[datetime] = None
# Comparison date (based on series preference)
comparison_date: Optional[datetime] = None
@classmethod
def from_row(cls, row: tuple) -> 'Episode':
"""Create Episode from database row"""
# Handle old (9/10/11 fields) and new (12 fields) schema
if len(row) >= 12:
# New schema with episode_number, episode_id, and date_de_home_media
return cls(
id=row[0],
season_id=row[1],
episode_number=row[2],
episode_code=row[3],
title=row[4],
episode_id=row[5],
date_de_tv=_safe_parse_datetime(row[6]),
date_de_streaming=_safe_parse_datetime(row[7]),
date_de_home_media=_safe_parse_datetime(row[8]),
date_de_sync=_safe_parse_datetime(row[9]),
date_original=_safe_parse_datetime(row[10]),
comparison_date=_safe_parse_datetime(row[11])
)
elif len(row) >= 11:
# Schema with episode_number and date_de_home_media but no episode_id
return cls(
id=row[0],
season_id=row[1],
episode_number=row[2],
episode_code=row[3],
title=row[4],
episode_id=None,
date_de_tv=_safe_parse_datetime(row[5]),
date_de_streaming=_safe_parse_datetime(row[6]),
date_de_home_media=_safe_parse_datetime(row[7]),
date_de_sync=_safe_parse_datetime(row[8]),
date_original=_safe_parse_datetime(row[9]),
comparison_date=_safe_parse_datetime(row[10])
)
elif len(row) >= 10:
# Schema with episode_number but without date_de_home_media and episode_id
return cls(
id=row[0],
season_id=row[1],
episode_number=row[2],
episode_code=row[3],
title=row[4],
episode_id=None,
date_de_tv=_safe_parse_datetime(row[5]),
date_de_streaming=_safe_parse_datetime(row[6]),
date_de_home_media=None,
date_de_sync=_safe_parse_datetime(row[7]),
date_original=_safe_parse_datetime(row[8]),
comparison_date=_safe_parse_datetime(row[9])
)
else:
# Old schema without episode_number, episode_id, and date_de_home_media
return cls(
id=row[0],
season_id=row[1],
episode_number=None,
episode_code=row[2],
title=row[3],
episode_id=None,
date_de_tv=_safe_parse_datetime(row[4]),
date_de_streaming=_safe_parse_datetime(row[5]),
date_de_home_media=None,
date_de_sync=_safe_parse_datetime(row[6]),
date_original=_safe_parse_datetime(row[7]),
comparison_date=_safe_parse_datetime(row[8])
)
def calculate_comparison_date(self, preference: DatePreference) -> Optional[datetime]:
"""
Calculate the comparison date based on preference with fallback logic
"""
# Special case: DE_FIRST = earliest German date
if preference == DatePreference.DE_FIRST:
german_dates = [
d for d in [self.date_de_tv, self.date_de_streaming, self.date_de_home_media, self.date_de_sync]
if d is not None
]
if german_dates:
return min(german_dates)
# No fallback - return None if no German date available
return None
# Priority order based on preference
# For German preferences: only use German dates, no fallback to original
# For ORIGINAL preference: only use original date
# Priority for German dates: TV → Streaming → Home-Media → Sync
priority_map = {
DatePreference.DE_TV: [self.date_de_tv, self.date_de_streaming, self.date_de_home_media, self.date_de_sync],
DatePreference.DE_STREAMING: [self.date_de_streaming, self.date_de_tv, self.date_de_home_media, self.date_de_sync],
DatePreference.DE_HOME_MEDIA: [self.date_de_home_media, self.date_de_streaming, self.date_de_tv, self.date_de_sync],
DatePreference.DE_SYNC: [self.date_de_sync, self.date_de_home_media, self.date_de_streaming, self.date_de_tv],
DatePreference.ORIGINAL: [self.date_original]
}
dates = priority_map.get(preference, [])
for date in dates:
if date is not None:
return date
return None
@dataclass
class Settings:
"""Application settings"""
id: Optional[int]
key: str
value: str
@classmethod
def from_row(cls, row: tuple) -> 'Settings':
"""Create Settings from database row"""
return cls(
id=row[0],
key=row[1],
value=row[2]
)