refactor(subtitles): RuleScope.level → RuleScopeLevel enum

Six niveaux possibles (global, release_group, movie, show, season,
episode) étaient passés en str libre, le commentaire docstring servant
de seule documentation. Introduit RuleScopeLevel(str, Enum) — toujours
sérialisable en YAML, mais le set fixe est désormais imposé par le
typage. to_dict() sort explicitement .value pour rester safe côté
écrivains YAML.
This commit is contained in:
2026-05-20 23:46:22 +02:00
parent a09262b33f
commit f0aaf50c97
5 changed files with 29 additions and 10 deletions
+2
View File
@@ -6,6 +6,7 @@ from .exceptions import SubtitleNotFound
from .services import PatternDetector, SubtitleIdentifier, SubtitleMatcher from .services import PatternDetector, SubtitleIdentifier, SubtitleMatcher
from .value_objects import ( from .value_objects import (
RuleScope, RuleScope,
RuleScopeLevel,
ScanStrategy, ScanStrategy,
SubtitleFormat, SubtitleFormat,
SubtitleLanguage, SubtitleLanguage,
@@ -30,5 +31,6 @@ __all__ = [
"TypeDetectionMethod", "TypeDetectionMethod",
"SubtitleMatchingRules", "SubtitleMatchingRules",
"RuleScope", "RuleScope",
"RuleScopeLevel",
"SubtitleNotFound", "SubtitleNotFound",
] ]
+6 -3
View File
@@ -4,7 +4,7 @@ from dataclasses import dataclass, field
from typing import Any from typing import Any
from ..shared.value_objects import ImdbId from ..shared.value_objects import ImdbId
from .value_objects import RuleScope, SubtitleMatchingRules from .value_objects import RuleScope, RuleScopeLevel, SubtitleMatchingRules
@dataclass @dataclass
@@ -86,10 +86,13 @@ class SubtitleRuleSet:
if self._min_confidence is not None: if self._min_confidence is not None:
delta["min_confidence"] = self._min_confidence delta["min_confidence"] = self._min_confidence
return { return {
"scope": {"level": self.scope.level, "identifier": self.scope.identifier}, "scope": {
"level": self.scope.level.value,
"identifier": self.scope.identifier,
},
"override": delta, "override": delta,
} }
@classmethod @classmethod
def global_default(cls) -> SubtitleRuleSet: def global_default(cls) -> SubtitleRuleSet:
return cls(scope=RuleScope(level="global")) return cls(scope=RuleScope(level=RuleScopeLevel.GLOBAL))
+12 -1
View File
@@ -83,9 +83,20 @@ class SubtitleMatchingRules:
min_confidence: float = 0.7 min_confidence: float = 0.7
class RuleScopeLevel(str, Enum):
"""At which level a subtitle rule set applies."""
GLOBAL = "global"
RELEASE_GROUP = "release_group"
MOVIE = "movie"
SHOW = "show"
SEASON = "season"
EPISODE = "episode"
@dataclass(frozen=True) @dataclass(frozen=True)
class RuleScope: class RuleScope:
"""At which level a rule set applies.""" """At which level a rule set applies."""
level: str # "global" | "release_group" | "movie" | "show" | "season" | "episode" level: RuleScopeLevel
identifier: str | None = None # imdb_id, group name, "S01", "S01E03"… identifier: str | None = None # imdb_id, group name, "S01", "S01E03"…
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
import yaml import yaml
from alfred.domain.subtitles.aggregates import SubtitleRuleSet from alfred.domain.subtitles.aggregates import SubtitleRuleSet
from alfred.domain.subtitles.value_objects import RuleScope from alfred.domain.subtitles.value_objects import RuleScope, RuleScopeLevel
if TYPE_CHECKING: if TYPE_CHECKING:
from alfred.infrastructure.persistence.memory.ltm.components.subtitle_preferences import ( from alfred.infrastructure.persistence.memory.ltm.components.subtitle_preferences import (
@@ -72,7 +72,9 @@ class RuleSetRepository:
rg_data = _load_yaml(rg_path).get("override", {}) rg_data = _load_yaml(rg_path).get("override", {})
if rg_data: if rg_data:
rg_ruleset = SubtitleRuleSet( rg_ruleset = SubtitleRuleSet(
scope=RuleScope(level="release_group", identifier=release_group), scope=RuleScope(
level=RuleScopeLevel.RELEASE_GROUP, identifier=release_group
),
parent=current, parent=current,
) )
rg_ruleset.override(**_filter_override(rg_data)) rg_ruleset.override(**_filter_override(rg_data))
@@ -85,7 +87,7 @@ class RuleSetRepository:
local_data = _load_yaml(self._alfred_dir / "rules.yaml").get("override", {}) local_data = _load_yaml(self._alfred_dir / "rules.yaml").get("override", {})
if local_data: if local_data:
local_ruleset = SubtitleRuleSet( local_ruleset = SubtitleRuleSet(
scope=RuleScope(level="show"), scope=RuleScope(level=RuleScopeLevel.SHOW),
parent=current, parent=current,
) )
local_ruleset.override(**_filter_override(local_data)) local_ruleset.override(**_filter_override(local_data))
+4 -3
View File
@@ -28,6 +28,7 @@ from alfred.domain.subtitles.entities import MediaSubtitleMetadata, SubtitleCand
from alfred.domain.subtitles.services.utils import available_subtitles from alfred.domain.subtitles.services.utils import available_subtitles
from alfred.domain.subtitles.value_objects import ( from alfred.domain.subtitles.value_objects import (
RuleScope, RuleScope,
RuleScopeLevel,
SubtitleFormat, SubtitleFormat,
SubtitleLanguage, SubtitleLanguage,
SubtitleMatchingRules, SubtitleMatchingRules,
@@ -257,7 +258,7 @@ class TestSubtitleRuleSet:
def test_override_partial_keeps_parent_for_unset_fields(self): def test_override_partial_keeps_parent_for_unset_fields(self):
parent = SubtitleRuleSet.global_default() parent = SubtitleRuleSet.global_default()
child = SubtitleRuleSet( child = SubtitleRuleSet(
scope=RuleScope(level="show", identifier="tt1"), scope=RuleScope(level=RuleScopeLevel.SHOW, identifier="tt1"),
parent=parent, parent=parent,
) )
child.override(languages=["jpn"]) child.override(languages=["jpn"])
@@ -267,14 +268,14 @@ class TestSubtitleRuleSet:
assert rules.min_confidence == parent.resolve(_DEFAULT_RULES).min_confidence assert rules.min_confidence == parent.resolve(_DEFAULT_RULES).min_confidence
def test_to_dict_only_emits_set_deltas(self): def test_to_dict_only_emits_set_deltas(self):
rs = SubtitleRuleSet(scope=RuleScope(level="show", identifier="tt1")) rs = SubtitleRuleSet(scope=RuleScope(level=RuleScopeLevel.SHOW, identifier="tt1"))
rs.override(languages=["fra"]) rs.override(languages=["fra"])
out = rs.to_dict() out = rs.to_dict()
assert out["scope"] == {"level": "show", "identifier": "tt1"} assert out["scope"] == {"level": "show", "identifier": "tt1"}
assert out["override"] == {"languages": ["fra"]} assert out["override"] == {"languages": ["fra"]}
def test_to_dict_full_override(self): def test_to_dict_full_override(self):
rs = SubtitleRuleSet(scope=RuleScope(level="global")) rs = SubtitleRuleSet(scope=RuleScope(level=RuleScopeLevel.GLOBAL))
rs.override( rs.override(
languages=["fra"], languages=["fra"],
formats=["srt"], formats=["srt"],