92 lines
3.1 KiB
Python
92 lines
3.1 KiB
Python
"""Movie domain entities."""
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from ..shared.value_objects import ImdbId, TmdbId
|
|
from .value_objects import MovieTitle, ReleaseYear
|
|
|
|
|
|
@dataclass(frozen=True, eq=False)
|
|
class Movie:
|
|
"""
|
|
Movie aggregate root for the movies domain.
|
|
|
|
TMDB-only aggregate: carries identity (``tmdb_id`` + optional
|
|
``imdb_id``) plus the catalog facts that come from TMDB (``title``,
|
|
``release_year``). Filesystem-side concerns (file path, quality,
|
|
tracks, ``added_at``) live on :class:`alfred.domain.releases.entities.
|
|
MovieRelease`, the per-movie release aggregate persisted alongside.
|
|
|
|
Frozen: rebuild via ``dataclasses.replace`` to project metadata
|
|
updates (e.g. a TMDB refresh) onto a new instance.
|
|
|
|
Equality is identity-based on ``tmdb_id``: two ``Movie`` instances
|
|
are equal iff they share the same primary key. ``imdb_id`` is a
|
|
secondary anchor and not part of the identity.
|
|
"""
|
|
|
|
tmdb_id: TmdbId
|
|
title: MovieTitle
|
|
imdb_id: ImdbId | None = None
|
|
release_year: ReleaseYear | None = None
|
|
|
|
def __post_init__(self) -> None:
|
|
if not isinstance(self.tmdb_id, TmdbId):
|
|
raise ValueError(
|
|
f"tmdb_id must be TmdbId, got {type(self.tmdb_id)}"
|
|
)
|
|
if not isinstance(self.title, MovieTitle):
|
|
if isinstance(self.title, str):
|
|
object.__setattr__(self, "title", MovieTitle(self.title))
|
|
else:
|
|
raise ValueError(
|
|
f"title must be MovieTitle or str, got {type(self.title)}"
|
|
)
|
|
if self.imdb_id is not None and not isinstance(self.imdb_id, ImdbId):
|
|
raise ValueError(
|
|
f"imdb_id must be ImdbId or None, got {type(self.imdb_id)}"
|
|
)
|
|
|
|
def __eq__(self, other: object) -> bool:
|
|
if not isinstance(other, Movie):
|
|
return NotImplemented
|
|
return self.tmdb_id == other.tmdb_id
|
|
|
|
def __hash__(self) -> int:
|
|
return hash(self.tmdb_id)
|
|
|
|
# WRONG
|
|
def get_folder_name(self) -> str:
|
|
"""
|
|
Get the folder name for this movie.
|
|
|
|
Format: "Title (Year)"
|
|
Example: "Inception (2010)"
|
|
"""
|
|
if self.release_year:
|
|
return f"{self.title.value} ({self.release_year.value})"
|
|
return self.title.value
|
|
|
|
# WRONG
|
|
def get_filename(self) -> str:
|
|
"""
|
|
Get the suggested base filename (without extension) for this movie.
|
|
|
|
Format: ``Title.Year`` (quality lives on
|
|
:class:`alfred.domain.releases.entities.MovieRelease` now and is
|
|
appended by the release-aware caller — typically the rescan /
|
|
organize flow, after Phase 4).
|
|
|
|
Example: ``Inception.2010``.
|
|
"""
|
|
parts = [self.title.normalized()]
|
|
if self.release_year:
|
|
parts.append(str(self.release_year.value))
|
|
return ".".join(parts)
|
|
|
|
def __str__(self) -> str:
|
|
return f"{self.title.value} ({self.release_year.value if self.release_year else 'Unknown'})"
|
|
|
|
def __repr__(self) -> str:
|
|
return f"Movie(tmdb_id={self.tmdb_id}, title='{self.title.value}')"
|