0.6.3: Parse Spotify's renamed /items response shape (new apps get item, not track)

Spotify's February 2026 changes did not just move /playlists/{id}/tracks to
/items: for apps on the new behavior the per-entry payload key was renamed
from "track" to "item" (tracks.tracks.track -> items.items.item). Extended
Quota Mode (grandfathered) apps keep the old key, which is why this never
reproduced locally. Every parser in alembic read only "track", so on a new
app each entry looked like a null/local track and was silently skipped: the
CSV came out header-only, sldl no-opped with exit 0, and the playlist page
showed an empty tracklist. Confirmed live on a new app against a playlist
the connected account owns.

All /items consumers (spotify_client.py, spotify-playlist-csv.py,
spotify-retag.py) now parse both key names, and the fields query filter is
gone: it selects by key name, so filtering on track(...) is itself what
returned empty pages on the renamed shape.

Also per the migration guide, new apps only receive playlist contents for
playlists the connected account owns or collaborates on; other playlists
return metadata with no items field at all (public is no longer enough).
That case now raises a pointed error (UI and CSV fetch) instead of reading
as an empty playlist, run-playlist.sh logs the fetched track count and warns
loudly when it is zero, and the README guidance is updated to match.

New tests pin get_playlist_tracks against both response shapes, the
metadata-only error, and pagination.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
andrew
2026-07-15 09:50:57 -06:00
parent 2e3210cc01
commit 8ecff44811
7 changed files with 150 additions and 30 deletions
+7 -3
View File
@@ -14,12 +14,16 @@ templates = Jinja2Templates(directory="app/templates")
def _friendly_status_error(exc: Exception) -> str:
"""Turn a raw Spotify API error into something a non-technical user can act
on. 403 here almost always means the Spotify app can't read the playlist:
either the credentials are wrong or the playlist isn't public."""
wrong credentials, or the connected account can't see it. (The other
can't-read case, contents hidden for playlists the account doesn't own,
arrives as a RuntimeError from spotify_client with its own message.)"""
if isinstance(exc, httpx.HTTPStatusError) and exc.response.status_code == 403:
return (
"Spotify denied access (403). Check your Spotify credentials under "
"Settings then Credentials, and make sure the playlist is set to public "
"on Spotify -- alembic can't read private playlists."
"Settings then Credentials, and make sure the account connected via "
"Connect Spotify can see this playlist. For newly created Spotify "
"apps the connected account must own the playlist or be a "
"collaborator on it."
)
return str(exc)