From df798f55cca56748d98bc4ef9b76424cbd26c118 Mon Sep 17 00:00:00 2001 From: Francwa Date: Tue, 19 May 2026 15:15:43 +0200 Subject: [PATCH] refactor(subtitles): introduce SubtitleKnowledge Protocol port MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Domain services (SubtitleIdentifier, PatternDetector) used to import the concrete SubtitleKnowledgeBase class directly from infrastructure for their type hint. With this commit they depend on a structural Protocol in alfred/domain/subtitles/ports/knowledge.py declaring just the 7 read-only query methods the domain actually consumes. The concrete YAML-backed SubtitleKnowledgeBase in infrastructure remains the sole adapter — no rename, no shim. With this change alfred/domain/subtitles/ has zero imports from alfred/infrastructure/. Also extend the changelog entry covering the full domain-io-extraction branch. --- CHANGELOG.md | 11 ++++++ alfred/domain/subtitles/ports/__init__.py | 6 +++ alfred/domain/subtitles/ports/knowledge.py | 38 +++++++++++++++++++ .../domain/subtitles/services/identifier.py | 5 +-- .../subtitles/services/pattern_detector.py | 5 +-- 5 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 alfred/domain/subtitles/ports/__init__.py create mode 100644 alfred/domain/subtitles/ports/knowledge.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 96b7284..0e9c21e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,6 +143,17 @@ callers). an explicit `default_rules: SubtitleMatchingRules` parameter. The `ManageSubtitles` use case loads defaults from the KB once and passes them in. + - **`SubtitleKnowledge` Protocol port** at + `alfred/domain/subtitles/ports/knowledge.py` declares the read-only + query surface domain services consume (7 methods: + `known_extensions`, `format_for_extension`, `language_for_token`, + `is_known_lang_token`, `type_for_token`, `is_known_type_token`, + `patterns`). `SubtitleIdentifier` and `PatternDetector` depend on + this Protocol instead of the concrete `SubtitleKnowledgeBase` from + infrastructure — `domain/subtitles/` now has zero imports from + `infrastructure/`. The remaining domain → infra leak + (`domain/release/` loading separator YAML at import-time) is + documented in tech-debt and scheduled for its own branch. - **`to_dot_folder_name(title)` helper** in `alfred/domain/shared/value_objects.py` — extracts the `re.sub(r"[^\w\s\.\-]", "", title).replace(" ", ".")` pattern that was diff --git a/alfred/domain/subtitles/ports/__init__.py b/alfred/domain/subtitles/ports/__init__.py new file mode 100644 index 0000000..2e9a59f --- /dev/null +++ b/alfred/domain/subtitles/ports/__init__.py @@ -0,0 +1,6 @@ +"""Domain ports for the subtitles domain — Protocol-based abstractions +that decouple domain services from concrete infrastructure adapters.""" + +from .knowledge import SubtitleKnowledge + +__all__ = ["SubtitleKnowledge"] diff --git a/alfred/domain/subtitles/ports/knowledge.py b/alfred/domain/subtitles/ports/knowledge.py new file mode 100644 index 0000000..deada38 --- /dev/null +++ b/alfred/domain/subtitles/ports/knowledge.py @@ -0,0 +1,38 @@ +"""SubtitleKnowledge port — the query surface domain services need from the +subtitle knowledge base, expressed as a Protocol so the domain never imports +the infrastructure adapter that backs it. + +The concrete implementation lives in +``alfred/infrastructure/knowledge/subtitles/base.py`` (the YAML-backed +``SubtitleKnowledgeBase``). Tests can supply any object that satisfies this +structural contract. +""" + +from __future__ import annotations + +from typing import Protocol + +from ..value_objects import SubtitleFormat, SubtitleLanguage, SubtitlePattern, SubtitleType + + +class SubtitleKnowledge(Protocol): + """Read-only query surface for subtitle knowledge consumed by the domain. + + Only the methods that domain services actually call belong here — anything + else (defaults loading, reload, pattern groups, raw dicts) stays on the + concrete class and is reserved for the application layer. + """ + + def known_extensions(self) -> set[str]: ... + + def format_for_extension(self, ext: str) -> SubtitleFormat | None: ... + + def language_for_token(self, token: str) -> SubtitleLanguage | None: ... + + def is_known_lang_token(self, token: str) -> bool: ... + + def type_for_token(self, token: str) -> SubtitleType | None: ... + + def is_known_type_token(self, token: str) -> bool: ... + + def patterns(self) -> dict[str, SubtitlePattern]: ... diff --git a/alfred/domain/subtitles/services/identifier.py b/alfred/domain/subtitles/services/identifier.py index f5248c6..bc98ccb 100644 --- a/alfred/domain/subtitles/services/identifier.py +++ b/alfred/domain/subtitles/services/identifier.py @@ -4,9 +4,8 @@ import logging import re from pathlib import Path -from alfred.infrastructure.knowledge.subtitles.base import SubtitleKnowledgeBase - from ...shared.ports import FilesystemScanner, MediaProber +from ..ports import SubtitleKnowledge from ...shared.value_objects import ImdbId from ..entities import MediaSubtitleMetadata, SubtitleCandidate from ..value_objects import ScanStrategy, SubtitlePattern, SubtitleType @@ -59,7 +58,7 @@ class SubtitleIdentifier: def __init__( self, - kb: SubtitleKnowledgeBase, + kb: SubtitleKnowledge, prober: MediaProber, scanner: FilesystemScanner, ): diff --git a/alfred/domain/subtitles/services/pattern_detector.py b/alfred/domain/subtitles/services/pattern_detector.py index 774f38b..a3e2827 100644 --- a/alfred/domain/subtitles/services/pattern_detector.py +++ b/alfred/domain/subtitles/services/pattern_detector.py @@ -3,9 +3,8 @@ import logging from pathlib import Path -from alfred.infrastructure.knowledge.subtitles.base import SubtitleKnowledgeBase - from ...shared.ports import FilesystemScanner, MediaProber +from ..ports import SubtitleKnowledge from ..value_objects import ScanStrategy, SubtitlePattern logger = logging.getLogger(__name__) @@ -22,7 +21,7 @@ class PatternDetector: def __init__( self, - kb: SubtitleKnowledgeBase, + kb: SubtitleKnowledge, prober: MediaProber, scanner: FilesystemScanner, ):