"""Shared value objects used across multiple domains.""" from dataclasses import dataclass from pathlib import Path from typing import Union import re from .exceptions import ValidationError @dataclass(frozen=True) class ImdbId: """ Value object representing an IMDb ID. IMDb IDs follow the format: tt followed by 7-8 digits (e.g., tt1375666) """ value: str def __post_init__(self): """Validate IMDb ID format.""" if not self.value: raise ValidationError("IMDb ID cannot be empty") if not isinstance(self.value, str): raise ValidationError(f"IMDb ID must be a string, got {type(self.value)}") # IMDb ID format: tt + 7-8 digits pattern = r'^tt\d{7,8}$' if not re.match(pattern, self.value): raise ValidationError( f"Invalid IMDb ID format: {self.value}. " "Expected format: tt followed by 7-8 digits (e.g., tt1375666)" ) def __str__(self) -> str: return self.value def __repr__(self) -> str: return f"ImdbId('{self.value}')" @dataclass(frozen=True) class FilePath: """ Value object representing a file path with validation. Ensures the path is valid and optionally checks existence. """ value: Path def __init__(self, path: Union[str, Path]): """ Initialize FilePath. Args: path: String or Path object representing the file path """ if isinstance(path, str): path_obj = Path(path) elif isinstance(path, Path): path_obj = path else: raise ValidationError(f"Path must be str or Path, got {type(path)}") # Use object.__setattr__ because dataclass is frozen object.__setattr__(self, 'value', path_obj) def exists(self) -> bool: """Check if the path exists.""" return self.value.exists() def is_file(self) -> bool: """Check if the path is a file.""" return self.value.is_file() def is_dir(self) -> bool: """Check if the path is a directory.""" return self.value.is_dir() def __str__(self) -> str: return str(self.value) def __repr__(self) -> str: return f"FilePath('{self.value}')" @dataclass(frozen=True) class FileSize: """ Value object representing a file size in bytes. Provides human-readable formatting. """ bytes: int def __post_init__(self): """Validate file size.""" if not isinstance(self.bytes, int): raise ValidationError(f"File size must be an integer, got {type(self.bytes)}") if self.bytes < 0: raise ValidationError(f"File size cannot be negative: {self.bytes}") def to_human_readable(self) -> str: """ Convert bytes to human-readable format. Returns: String like "1.5 GB", "500 MB", etc. """ units = ['B', 'KB', 'MB', 'GB', 'TB'] size = float(self.bytes) unit_index = 0 while size >= 1024 and unit_index < len(units) - 1: size /= 1024 unit_index += 1 if unit_index == 0: return f"{int(size)} {units[unit_index]}" else: return f"{size:.2f} {units[unit_index]}" def __str__(self) -> str: return self.to_human_readable() def __repr__(self) -> str: return f"FileSize({self.bytes})"