Files
Lyra/worker/lyra_worker/adapters/fakes.py
T
Jonathan 4a768b7122 fix(discover): drop suggestions for non-artists (no core discography)
ListenBrainz surfaces producers/session players/band members (Max Martin,
Dominic Howard) that co-occur in listens but have no releases of their own —
they showed as bare rows with an empty page. _derive_albums now drops a
surfaced artist (deletes their pending suggestion) when their MB discography
has no core release-group (Album/Single/EP, no secondary type), catching both
empty discographies and soundtrack/production-only credits, while keeping real
singles/EP-only artists. On browse failure the suggestion is kept.

Test fake browser gains default_core so scoring tests keep surfacing candidates.
worker 249 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 00:38:56 +02:00

117 lines
3.7 KiB
Python

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 = {
"qobuz": Quality(fmt="FLAC", lossless=True, bit_depth=24, sample_rate=96000),
"soulseek": Quality(fmt="FLAC", lossless=True, bit_depth=16, sample_rate=44100),
"youtube": Quality(fmt="OPUS", lossless=False, bitrate_kbps=160),
}
class _BaseFake:
name = "fake"
tier = 99
def __init__(self, matches: bool = True):
self._matches = matches
def health(self) -> bool:
return True
def search(self, target: MBTarget) -> list[Candidate]:
if not self._matches:
return []
tracks = target.track_count or 10
return [
Candidate(
source=self.name,
source_ref=f"{self.name}:{target.artist}:{target.album}",
matched_artist=target.artist,
matched_album=target.album,
quality=_QUALITIES.get(self.name, _QUALITIES["youtube"]),
track_count=tracks,
source_tier=self.tier,
)
]
def download(
self, candidate: Candidate, dest: str, on_progress: Callable[[float], None]
) -> DownloadResult:
for pct in (0.25, 0.5, 0.75, 1.0):
on_progress(pct)
return DownloadResult(ok=True, path=dest, track_count=candidate.track_count)
class FakeQobuz(_BaseFake):
name = "qobuz"
tier = 0
class FakeSoulseek(_BaseFake):
name = "soulseek"
tier = 1
class FakeYouTube(_BaseFake):
name = "youtube"
tier = 2
class FailingAdapter(_BaseFake):
name = "qobuz" # pretends to be a real source that then fails to download
tier = 0
def download(
self, candidate: Candidate, dest: str, on_progress: Callable[[float], None]
) -> DownloadResult:
return DownloadResult(ok=False, error="simulated download failure")
class FakeMbBrowser:
"""In-memory MbBrowser for tests. `releases` maps artist mbid -> [ReleaseGroupInfo].
`default_core=True` returns a synthetic core Album for any artist not in `releases`, so
scoring tests don't trip the "no core discography → drop the suggestion" filter."""
def __init__(self, artists=None, releases=None, default_core=False):
self._artists = list(artists or [])
self._releases = dict(releases or {})
self._default_core = default_core
def search_artist(self, name: str) -> list[ArtistHit]:
return list(self._artists)
def browse_release_groups(self, artist_mbid: str) -> list[ReleaseGroupInfo]:
if artist_mbid in self._releases:
return list(self._releases[artist_mbid])
if self._default_core:
return [ReleaseGroupInfo(f"rg-{artist_mbid}", "Album", "Album", (), "2000-01-01")]
return []
class FakeAudioProbe:
"""In-memory AudioProbe for tests."""
def __init__(self, artist="", album="", fmt="FLAC", quality_class=3):
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, []))