From 889e33355ffb28dc71414976b5a179d7f023b393 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 12 Jul 2026 00:03:45 +0200 Subject: [PATCH] feat(discovery): SimilaritySource protocol + FakeSimilaritySource Co-Authored-By: Claude Opus 4.8 (1M context) --- worker/lyra_worker/adapters/fakes.py | 17 +++++++++++++++++ worker/lyra_worker/similarity/__init__.py | 0 worker/lyra_worker/similarity/base.py | 22 ++++++++++++++++++++++ worker/tests/test_similarity_base.py | 14 ++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 worker/lyra_worker/similarity/__init__.py create mode 100644 worker/lyra_worker/similarity/base.py create mode 100644 worker/tests/test_similarity_base.py diff --git a/worker/lyra_worker/adapters/fakes.py b/worker/lyra_worker/adapters/fakes.py index 83ab6fe..a97072b 100644 --- a/worker/lyra_worker/adapters/fakes.py +++ b/worker/lyra_worker/adapters/fakes.py @@ -2,6 +2,7 @@ from typing import Callable from lyra_worker.browser import ArtistHit, ReleaseGroupInfo from lyra_worker.probe import ProbeResult +from lyra_worker.similarity.base import SimilarArtist from lyra_worker.types import Candidate, DownloadResult, MBTarget, Quality _QUALITIES = { @@ -90,3 +91,19 @@ class FakeAudioProbe: self._r = ProbeResult(artist=artist, album=album, fmt=fmt, quality_class=quality_class) def probe(self, path: str) -> ProbeResult: return self._r + + +class FakeSimilaritySource: + """In-memory SimilaritySource for tests. `similar` maps seed mbid -> [SimilarArtist].""" + + name = "fake" + + def __init__(self, similar=None, healthy=True): + self._similar = dict(similar or {}) + self._healthy = healthy + + def health(self) -> bool: + return self._healthy + + def similar_artists(self, mbid: str) -> list[SimilarArtist]: + return list(self._similar.get(mbid, [])) diff --git a/worker/lyra_worker/similarity/__init__.py b/worker/lyra_worker/similarity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/worker/lyra_worker/similarity/base.py b/worker/lyra_worker/similarity/base.py new file mode 100644 index 0000000..fef5675 --- /dev/null +++ b/worker/lyra_worker/similarity/base.py @@ -0,0 +1,22 @@ +from dataclasses import dataclass +from typing import Protocol, runtime_checkable + + +@dataclass(frozen=True) +class SimilarArtist: + mbid: str + name: str + score: float + + +@runtime_checkable +class SimilaritySource(Protocol): + name: str + + def health(self) -> bool: + """Is this source reachable right now?""" + ... + + def similar_artists(self, mbid: str) -> list[SimilarArtist]: + """Artists similar to the given artist MBID (MBID-native).""" + ... diff --git a/worker/tests/test_similarity_base.py b/worker/tests/test_similarity_base.py new file mode 100644 index 0000000..805d626 --- /dev/null +++ b/worker/tests/test_similarity_base.py @@ -0,0 +1,14 @@ +from lyra_worker.adapters.fakes import FakeSimilaritySource +from lyra_worker.similarity.base import SimilarArtist, SimilaritySource + + +def test_fake_conforms_to_protocol_and_returns_similar(): + src = FakeSimilaritySource(similar={"seed": [SimilarArtist("c1", "Cand", 0.9)]}) + assert isinstance(src, SimilaritySource) + assert src.health() is True + assert src.similar_artists("seed") == [SimilarArtist("c1", "Cand", 0.9)] + assert src.similar_artists("unknown") == [] + + +def test_fake_can_report_unhealthy(): + assert FakeSimilaritySource(healthy=False).health() is False