6e252d1e81
aggregates.py used to call SubtitleKnowledgeBase().default_rules() via a DEFAULT_RULES() helper, which silently pulled the infrastructure layer (YAML loader) into the domain on every resolve. Make the dependency explicit: resolve() now takes the default rules as a parameter, and the caller (the ManageSubtitles use case) loads them from the KB once and passes them in. Domain stays I/O-free. - Drop DEFAULT_RULES helper and the SubtitleKnowledgeBase import from alfred/domain/subtitles/aggregates.py - SubtitleRuleSet.resolve(default_rules: SubtitleMatchingRules) - manage_subtitles use case passes kb.default_rules() at the call site - Tests use a local SubtitleMatchingRules stand-in instead of relying on KB defaults
96 lines
3.6 KiB
Python
96 lines
3.6 KiB
Python
"""Subtitle domain aggregates."""
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
from ..shared.value_objects import ImdbId
|
|
from .value_objects import RuleScope, SubtitleMatchingRules
|
|
|
|
|
|
@dataclass
|
|
class SubtitleRuleSet:
|
|
"""
|
|
Rules for subtitle selection at a given scope level, with inheritance.
|
|
|
|
Only delta fields are stored — None means "inherit from parent".
|
|
Resolution order: global → release_group → show/movie → season → episode.
|
|
|
|
A RuleSet can also be pinned to a specific media item (imdb_id),
|
|
bypassing the scope hierarchy for that item.
|
|
"""
|
|
|
|
scope: RuleScope
|
|
parent: SubtitleRuleSet | None = None
|
|
pinned_to: ImdbId | None = None
|
|
|
|
# Deltas — None = inherit
|
|
_languages: list[str] | None = field(default=None, repr=False)
|
|
_formats: list[str] | None = field(default=None, repr=False)
|
|
_types: list[str] | None = field(default=None, repr=False)
|
|
_format_priority: list[str] | None = field(default=None, repr=False)
|
|
_min_confidence: float | None = field(default=None, repr=False)
|
|
|
|
def resolve(self, default_rules: SubtitleMatchingRules) -> SubtitleMatchingRules:
|
|
"""
|
|
Walk the parent chain and merge deltas into effective rules.
|
|
|
|
``default_rules`` seeds the top of the chain — it is the caller's
|
|
responsibility to load these from the knowledge base (infrastructure).
|
|
Keeping the default rules as a parameter keeps the domain free of
|
|
any I/O dependency.
|
|
"""
|
|
base = (
|
|
self.parent.resolve(default_rules) if self.parent else default_rules
|
|
)
|
|
return SubtitleMatchingRules(
|
|
preferred_languages=self._languages or base.preferred_languages,
|
|
preferred_formats=self._formats or base.preferred_formats,
|
|
allowed_types=self._types or base.allowed_types,
|
|
format_priority=self._format_priority or base.format_priority,
|
|
min_confidence=self._min_confidence
|
|
if self._min_confidence is not None
|
|
else base.min_confidence,
|
|
)
|
|
|
|
def override(
|
|
self,
|
|
languages: list[str] | None = None,
|
|
formats: list[str] | None = None,
|
|
types: list[str] | None = None,
|
|
format_priority: list[str] | None = None,
|
|
min_confidence: float | None = None,
|
|
) -> None:
|
|
"""Set delta overrides at this scope level."""
|
|
if languages is not None:
|
|
self._languages = languages
|
|
if formats is not None:
|
|
self._formats = formats
|
|
if types is not None:
|
|
self._types = types
|
|
if format_priority is not None:
|
|
self._format_priority = format_priority
|
|
if min_confidence is not None:
|
|
self._min_confidence = min_confidence
|
|
|
|
def to_dict(self) -> dict:
|
|
"""Serialize deltas only (for persistence in rules.yaml)."""
|
|
delta: dict[str, Any] = {}
|
|
if self._languages is not None:
|
|
delta["languages"] = self._languages
|
|
if self._formats is not None:
|
|
delta["formats"] = self._formats
|
|
if self._types is not None:
|
|
delta["types"] = self._types
|
|
if self._format_priority is not None:
|
|
delta["format_priority"] = self._format_priority
|
|
if self._min_confidence is not None:
|
|
delta["min_confidence"] = self._min_confidence
|
|
return {
|
|
"scope": {"level": self.scope.level, "identifier": self.scope.identifier},
|
|
"override": delta,
|
|
}
|
|
|
|
@classmethod
|
|
def global_default(cls) -> SubtitleRuleSet:
|
|
return cls(scope=RuleScope(level="global"))
|