refactor(application): inject kb/prober into resolve_destination use cases

Remove the module-level _KB / _PROBER singletons from
alfred/application/filesystem/resolve_destination.py. The four
resolve_{season,episode,movie,series}_destination use cases now take
kb: ReleaseKnowledge and prober: MediaProber as required arguments,
matching the shape of inspect_release.

The singletons now live at the agent-tools frontier
(alfred/agent/tools/filesystem.py), where the LLM-facing wrappers
instantiate YamlReleaseKnowledge / FfprobeMediaProber once and thread
them through. The wrappers' Python signatures are unchanged — the
inspect-based JSON-schema generator in agent/registry.py still sees the
same LLM-passable params.

analyze_release drops the dirty 'from ... import _KB' indirection.

Tests inject their own stubs by keyword (prober=_StubProber(...)) via
thin convenience wrappers, replacing the prior
monkeypatch.setattr(rd, '_PROBER', ...) pattern.

testing/debug_release.py: instantiate YamlReleaseKnowledge() /
FfprobeMediaProber() inline at the two call sites.

Suite: 1077 passed.
This commit is contained in:
2026-05-21 07:46:13 +02:00
parent 5e0ed11672
commit 9f1ce94690
5 changed files with 144 additions and 53 deletions
+60 -22
View File
@@ -31,13 +31,53 @@ from alfred.application.filesystem.resolve_destination import (
_Clarification,
_find_existing_tvshow_folders,
_resolve_series_folder,
resolve_episode_destination,
resolve_movie_destination,
resolve_season_destination,
resolve_series_destination,
resolve_episode_destination as _resolve_episode_destination,
resolve_movie_destination as _resolve_movie_destination,
resolve_season_destination as _resolve_season_destination,
resolve_series_destination as _resolve_series_destination,
)
from alfred.infrastructure.knowledge.release_kb import YamlReleaseKnowledge
from alfred.infrastructure.persistence import Memory, set_memory
_KB = YamlReleaseKnowledge()
class _NullProber:
"""Default prober stub — never returns probe data."""
def list_subtitle_streams(self, video): # pragma: no cover
return []
def probe(self, video):
return None
_DEFAULT_PROBER = _NullProber()
def resolve_season_destination(*args, prober=None, **kwargs):
return _resolve_season_destination(
*args, kb=_KB, prober=prober or _DEFAULT_PROBER, **kwargs
)
def resolve_episode_destination(*args, prober=None, **kwargs):
return _resolve_episode_destination(
*args, kb=_KB, prober=prober or _DEFAULT_PROBER, **kwargs
)
def resolve_movie_destination(*args, prober=None, **kwargs):
return _resolve_movie_destination(
*args, kb=_KB, prober=prober or _DEFAULT_PROBER, **kwargs
)
def resolve_series_destination(*args, prober=None, **kwargs):
return _resolve_series_destination(
*args, kb=_KB, prober=prober or _DEFAULT_PROBER, **kwargs
)
REL_EPISODE = "Oz.S01E01.1080p.WEBRip.x265-KONTRAST"
REL_SEASON = "Oz.S03.1080p.WEBRip.x265-KONTRAST"
REL_MOVIE = "Inception.2010.1080p.BluRay.x265-GROUP"
@@ -365,46 +405,40 @@ class TestProbeEnrichmentWiring:
should pick up ffprobe data via inspect_release and let the enriched
tech_string land in the destination name."""
def test_movie_picks_up_probe_quality(
self, cfg_memory, tmp_path, monkeypatch
):
from alfred.application.filesystem import resolve_destination as rd
monkeypatch.setattr(rd, "_PROBER", _StubProber(_stereo_movie_info()))
def test_movie_picks_up_probe_quality(self, cfg_memory, tmp_path):
# Release name parses to "movie" but is missing the quality token;
# probe must supply 1080p and refresh tech_string.
bare_name = "Inception.2010.BluRay.x264-GROUP"
video = tmp_path / "movie.mkv"
video.write_bytes(b"")
out = resolve_movie_destination(bare_name, str(video), "Inception", 2010)
out = resolve_movie_destination(
bare_name,
str(video),
"Inception",
2010,
prober=_StubProber(_stereo_movie_info()),
)
assert out.status == "ok"
# tech_string -> "1080p.BluRay.x264" -> "1080p" shows up in names.
assert "1080p" in out.movie_folder_name
assert "1080p" in out.filename
def test_movie_skips_probe_when_path_missing(self, cfg_memory, monkeypatch):
def test_movie_skips_probe_when_path_missing(self, cfg_memory):
# If the file doesn't exist, no probe runs (the stub would have
# injected 1080p — its absence proves the skip).
from alfred.application.filesystem import resolve_destination as rd
monkeypatch.setattr(rd, "_PROBER", _StubProber(_stereo_movie_info()))
out = resolve_movie_destination(
"Inception.2010.BluRay.x264-GROUP",
"/nowhere/m.mkv",
"Inception",
2010,
prober=_StubProber(_stereo_movie_info()),
)
assert out.status == "ok"
assert "1080p" not in out.movie_folder_name
def test_season_picks_up_probe_via_source_path(
self, cfg_memory, tmp_path, monkeypatch
):
from alfred.application.filesystem import resolve_destination as rd
monkeypatch.setattr(rd, "_PROBER", _StubProber(_stereo_movie_info()))
def test_season_picks_up_probe_via_source_path(self, cfg_memory, tmp_path):
# Season pack name missing quality token; probe must add it.
bare_name = "Oz.S03.BluRay.x265-KONTRAST"
release_dir = tmp_path / bare_name
@@ -412,7 +446,11 @@ class TestProbeEnrichmentWiring:
(release_dir / "episode.mkv").write_bytes(b"")
out = resolve_season_destination(
bare_name, "Oz", 1997, source_path=str(release_dir)
bare_name,
"Oz",
1997,
source_path=str(release_dir),
prober=_StubProber(_stereo_movie_info()),
)
assert out.status == "ok"