From f0aaf50c979d1727fc0be3d9bd65256886774552 Mon Sep 17 00:00:00 2001 From: Francwa Date: Wed, 20 May 2026 23:46:22 +0200 Subject: [PATCH] =?UTF-8?q?refactor(subtitles):=20RuleScope.level=20?= =?UTF-8?q?=E2=86=92=20RuleScopeLevel=20enum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- alfred/domain/subtitles/__init__.py | 2 ++ alfred/domain/subtitles/aggregates.py | 9 ++++++--- alfred/domain/subtitles/value_objects.py | 13 ++++++++++++- alfred/infrastructure/subtitle/rule_repository.py | 8 +++++--- tests/domain/test_subtitle_utils.py | 7 ++++--- 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/alfred/domain/subtitles/__init__.py b/alfred/domain/subtitles/__init__.py index 8dcc29d..048cdd8 100644 --- a/alfred/domain/subtitles/__init__.py +++ b/alfred/domain/subtitles/__init__.py @@ -6,6 +6,7 @@ from .exceptions import SubtitleNotFound from .services import PatternDetector, SubtitleIdentifier, SubtitleMatcher from .value_objects import ( RuleScope, + RuleScopeLevel, ScanStrategy, SubtitleFormat, SubtitleLanguage, @@ -30,5 +31,6 @@ __all__ = [ "TypeDetectionMethod", "SubtitleMatchingRules", "RuleScope", + "RuleScopeLevel", "SubtitleNotFound", ] diff --git a/alfred/domain/subtitles/aggregates.py b/alfred/domain/subtitles/aggregates.py index 18c2f71..11d5283 100644 --- a/alfred/domain/subtitles/aggregates.py +++ b/alfred/domain/subtitles/aggregates.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, field from typing import Any from ..shared.value_objects import ImdbId -from .value_objects import RuleScope, SubtitleMatchingRules +from .value_objects import RuleScope, RuleScopeLevel, SubtitleMatchingRules @dataclass @@ -86,10 +86,13 @@ class SubtitleRuleSet: if self._min_confidence is not None: delta["min_confidence"] = self._min_confidence return { - "scope": {"level": self.scope.level, "identifier": self.scope.identifier}, + "scope": { + "level": self.scope.level.value, + "identifier": self.scope.identifier, + }, "override": delta, } @classmethod def global_default(cls) -> SubtitleRuleSet: - return cls(scope=RuleScope(level="global")) + return cls(scope=RuleScope(level=RuleScopeLevel.GLOBAL)) diff --git a/alfred/domain/subtitles/value_objects.py b/alfred/domain/subtitles/value_objects.py index 1d9f466..1edcc3e 100644 --- a/alfred/domain/subtitles/value_objects.py +++ b/alfred/domain/subtitles/value_objects.py @@ -83,9 +83,20 @@ class SubtitleMatchingRules: 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) class RuleScope: """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"… diff --git a/alfred/infrastructure/subtitle/rule_repository.py b/alfred/infrastructure/subtitle/rule_repository.py index cbec79a..8e8d3ec 100644 --- a/alfred/infrastructure/subtitle/rule_repository.py +++ b/alfred/infrastructure/subtitle/rule_repository.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING import yaml 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: from alfred.infrastructure.persistence.memory.ltm.components.subtitle_preferences import ( @@ -72,7 +72,9 @@ class RuleSetRepository: rg_data = _load_yaml(rg_path).get("override", {}) if rg_data: rg_ruleset = SubtitleRuleSet( - scope=RuleScope(level="release_group", identifier=release_group), + scope=RuleScope( + level=RuleScopeLevel.RELEASE_GROUP, identifier=release_group + ), parent=current, ) rg_ruleset.override(**_filter_override(rg_data)) @@ -85,7 +87,7 @@ class RuleSetRepository: local_data = _load_yaml(self._alfred_dir / "rules.yaml").get("override", {}) if local_data: local_ruleset = SubtitleRuleSet( - scope=RuleScope(level="show"), + scope=RuleScope(level=RuleScopeLevel.SHOW), parent=current, ) local_ruleset.override(**_filter_override(local_data)) diff --git a/tests/domain/test_subtitle_utils.py b/tests/domain/test_subtitle_utils.py index 192918a..f07fff1 100644 --- a/tests/domain/test_subtitle_utils.py +++ b/tests/domain/test_subtitle_utils.py @@ -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.value_objects import ( RuleScope, + RuleScopeLevel, SubtitleFormat, SubtitleLanguage, SubtitleMatchingRules, @@ -257,7 +258,7 @@ class TestSubtitleRuleSet: def test_override_partial_keeps_parent_for_unset_fields(self): parent = SubtitleRuleSet.global_default() child = SubtitleRuleSet( - scope=RuleScope(level="show", identifier="tt1"), + scope=RuleScope(level=RuleScopeLevel.SHOW, identifier="tt1"), parent=parent, ) child.override(languages=["jpn"]) @@ -267,14 +268,14 @@ class TestSubtitleRuleSet: assert rules.min_confidence == parent.resolve(_DEFAULT_RULES).min_confidence 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"]) out = rs.to_dict() assert out["scope"] == {"level": "show", "identifier": "tt1"} assert out["override"] == {"languages": ["fra"]} def test_to_dict_full_override(self): - rs = SubtitleRuleSet(scope=RuleScope(level="global")) + rs = SubtitleRuleSet(scope=RuleScope(level=RuleScopeLevel.GLOBAL)) rs.override( languages=["fra"], formats=["srt"],