fix: look tags up by artist name rather than by MusicBrainz id
Build and publish container / build (pull_request) Successful in 2m53s
Build and publish container / build (pull_request) Successful in 2m53s
Tag lookups asked by MusicBrainz id whenever Lidarr had one, which is always. The reasoning was that an id cannot be ambiguous the way a name can. In practice Last.fm's mbid index is stale and partial, and it answered "the artist you supplied could not be found" for Devo, Escape the Fate, Blasterjaxx, Frank Carter & the Rattlesnakes and a long tail of others -- artists whose Last.fm pages plainly exist and carry precisely the tags the moods are built from. Devo is tagged new wave, post-punk and 80s; Escape the Fate is tagged post-hardcore, screamo and emocore. Both were skipped. Ask by name first, which is the index Last.fm's own site runs on, and keep the id only as a fallback for a name Lidarr spells differently. Failures were also being dropped without recording the attempt, so every one of those artists was re-queried on every subsequent pass, indefinitely. They are now distinguished: an artist that neither key resolves is recorded as fetched with no tags and not asked about again, while a genuine failure -- a rate limit, a bad key -- is deliberately left unrecorded so the next pass retries it. Telling the two apart needed the service's own error number, so LastfmError now carries it.
This commit is contained in:
@@ -1050,8 +1050,8 @@ def test_a_vibe_selects_by_tag(tmp_path):
|
||||
store, source, mirror = tagged_store(
|
||||
tmp_path,
|
||||
{
|
||||
"artist-mbid-1": [{"name": "screamo", "count": 100}],
|
||||
"artist-mbid-2": [{"name": "classic rock", "count": 100}],
|
||||
"Played Band": [{"name": "screamo", "count": 100}],
|
||||
"Silent Band": [{"name": "classic rock", "count": 100}],
|
||||
},
|
||||
)
|
||||
vibes = [{"name": "screamo", "tags": ["screamo"]}]
|
||||
@@ -1066,7 +1066,7 @@ def test_a_vibe_selects_by_tag(tmp_path):
|
||||
def test_a_weakly_tagged_artist_is_below_the_threshold(tmp_path):
|
||||
"""A single low-weight tag is not a genre, it is somebody's stray opinion."""
|
||||
store, source, mirror = tagged_store(
|
||||
tmp_path, {"artist-mbid-1": [{"name": "screamo", "count": 3}]}
|
||||
tmp_path, {"Played Band": [{"name": "screamo", "count": 3}]}
|
||||
)
|
||||
vibes = [{"name": "screamo", "tags": ["screamo"]}]
|
||||
|
||||
@@ -1079,7 +1079,7 @@ def test_a_vibe_can_be_restricted_by_release_year(tmp_path):
|
||||
"""What separates eighties synth records from everything else a synthpop
|
||||
tag drags in."""
|
||||
store, source, mirror = tagged_store(
|
||||
tmp_path, {"artist-mbid-1": [{"name": "synthpop", "count": 100}]}
|
||||
tmp_path, {"Played Band": [{"name": "synthpop", "count": 100}]}
|
||||
)
|
||||
inside = [{"name": "eighties", "tags": ["synthpop"], "years": [1975, 1992]}]
|
||||
outside = [{"name": "nineties", "tags": ["synthpop"], "years": [1993, 1999]}]
|
||||
@@ -1127,3 +1127,74 @@ def test_a_bad_vibes_file_is_refused_up_front(tmp_path, content):
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
music_curator.load_vibes(str(path))
|
||||
|
||||
|
||||
def test_tags_are_looked_up_by_name_not_by_mbid(tmp_path):
|
||||
"""Last.fm's mbid index is stale: it cannot find Devo or Escape the Fate by
|
||||
one, though their pages plainly exist. Its name index can."""
|
||||
api, source, mirror = playlist_library(tmp_path)
|
||||
store = store_at(tmp_path)
|
||||
music_curator.index_library(
|
||||
music_curator.Lidarr("http://lidarr", "key", transport=api), store
|
||||
)
|
||||
lastfm = FakeLastfm(tags={"Played Band": [{"name": "screamo", "count": 90}]})
|
||||
|
||||
music_curator.sync_tags(client_for(lastfm), store, NOW)
|
||||
|
||||
asked = [call for call in lastfm.calls if call.get("method") == "artist.getTopTags"]
|
||||
# The name is always tried first; the mbid only appears as a fallback for
|
||||
# the artist that the name could not resolve.
|
||||
assert "artist" in asked[0]
|
||||
assert [call for call in asked if call.get("artist") == "Played Band"]
|
||||
assert not [call for call in asked if call.get("mbid") == "artist-mbid-1"]
|
||||
assert store.scalar("SELECT COUNT(*) FROM artist_tag WHERE tag = 'screamo'") == 1
|
||||
|
||||
|
||||
def test_the_mbid_is_tried_when_the_name_is_not_found(tmp_path):
|
||||
"""Kept only for a name Lidarr spells differently to Last.fm."""
|
||||
api, source, mirror = playlist_library(tmp_path)
|
||||
store = store_at(tmp_path)
|
||||
music_curator.index_library(
|
||||
music_curator.Lidarr("http://lidarr", "key", transport=api), store
|
||||
)
|
||||
lastfm = FakeLastfm(tags={"artist-mbid-1": [{"name": "dnb", "count": 80}]})
|
||||
|
||||
music_curator.sync_tags(client_for(lastfm), store, NOW)
|
||||
|
||||
assert store.scalar("SELECT COUNT(*) FROM artist_tag WHERE tag = 'dnb'") == 1
|
||||
asked = [call for call in lastfm.calls if call.get("method") == "artist.getTopTags"]
|
||||
assert any("mbid" in call for call in asked)
|
||||
|
||||
|
||||
def test_an_artist_neither_key_resolves_is_recorded_and_not_retried(tmp_path):
|
||||
"""Otherwise every pass spends a request on it again, for ever."""
|
||||
api, source, mirror = playlist_library(tmp_path)
|
||||
store = store_at(tmp_path)
|
||||
music_curator.index_library(
|
||||
music_curator.Lidarr("http://lidarr", "key", transport=api), store
|
||||
)
|
||||
lastfm = FakeLastfm(tags={})
|
||||
|
||||
assert music_curator.sync_tags(client_for(lastfm), store, NOW) == 2
|
||||
spent = len(lastfm.calls)
|
||||
|
||||
assert music_curator.sync_tags(client_for(lastfm), store, NOW) == 0
|
||||
assert len(lastfm.calls) == spent
|
||||
|
||||
|
||||
def test_a_real_failure_is_not_recorded_so_the_next_pass_retries(tmp_path):
|
||||
"""A rate limit is not the same as an artist not existing."""
|
||||
api, source, mirror = playlist_library(tmp_path)
|
||||
store = store_at(tmp_path)
|
||||
music_curator.index_library(
|
||||
music_curator.Lidarr("http://lidarr", "key", transport=api), store
|
||||
)
|
||||
lastfm = FakeLastfm(
|
||||
tags={"Played Band": [], "Silent Band": []},
|
||||
outcomes=[{"error": 10, "message": "Invalid API key"}],
|
||||
)
|
||||
|
||||
music_curator.sync_tags(client_for(lastfm), store, NOW)
|
||||
|
||||
# One artist failed hard and must still be pending.
|
||||
assert store.scalar("SELECT COUNT(*) FROM artist_tag_fetched") == 1
|
||||
|
||||
Reference in New Issue
Block a user