Spotify: Batch Spotify spotifysync API calls#6485
Spotify: Batch Spotify spotifysync API calls#6485arsaboo wants to merge 18 commits intobeetbox:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
PR make Spotify spotifysync do fewer HTTP call. It batch track metadata and audio-features fetch, so big library not hit rate limit so fast.
Changes:
- Add batch fetch for
/v1/tracks(50 ids per request) and/v1/audio-features(100 ids per request). - Deduplicate repeated Spotify track IDs within one run of
_fetch_info. - Add tests for chunking, endpoint usage, dedupe, and 403-disable behavior; add changelog entry.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
beetsplug/spotify.py |
Implement batch endpoints + ID chunking/dedupe and shared disable logic for audio features. |
test/plugins/test_spotify.py |
Add response mocks + assertions for batching/dedupe/403 behavior. |
docs/changelog.rst |
Add Unreleased changelog entry for Spotify batching change. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #6485 +/- ##
==========================================
+ Coverage 71.78% 71.95% +0.16%
==========================================
Files 156 156
Lines 20176 20236 +60
Branches 3214 3226 +12
==========================================
+ Hits 14484 14561 +77
+ Misses 5006 4970 -36
- Partials 686 705 +19
🚀 New features to boost your workflow:
|
|
|
||
| track_ids = [track_id for _, track_id in items_to_update] | ||
| unique_track_ids = list(dict.fromkeys(track_ids)) | ||
| track_info_by_id = self.track_info_batch(unique_track_ids) |
There was a problem hiding this comment.
| track_info_by_id = self.track_info_batch(unique_track_ids) | |
| audio_features_by_id = self.track_info_batch(unique_track_ids) |
There was a problem hiding this comment.
Renaming here would clash with audio_features_by_id already used for track_audio_features_batch. Kept as track_info_by_id for now.
There was a problem hiding this comment.
Makes sense. I renamed the typed dict to TrackDetails so you can have track_details_by_id here, to remove ambiguity regarding beets.hooks.info::TrackInfo.
| popularity, isrc, ean, upc = track_info_by_id.get( | ||
| spotify_track_id, (None, None, None, None) | ||
| ) |
There was a problem hiding this comment.
Actually, define AudioFeatures as a typed dict and return them from track_info_batch, then you can simplify this logic:
| popularity, isrc, ean, upc = track_info_by_id.get( | |
| spotify_track_id, (None, None, None, None) | |
| ) | |
| if audio_features := audio_features_by_id(spotify_track_id): | |
| item.update(**audio_features) |
There was a problem hiding this comment.
It doesn't work directly because the SpotifyTrackInfo field names (popularity, isrc, ean, upc) don't match the beets item field names (spotify_track_popularity, isrc, ean, upc), and for audio features the Spotify keys (danceability) also differ from beets fields (spotify_danceability).
|
|
||
| Unauthorized responses trigger one token refresh attempt before the | ||
| method gives up and falls back to an empty result set. | ||
|
|
There was a problem hiding this comment.
And undo all unrelated changes in the docstrings please.
|
|
||
| track_ids = [track_id for _, track_id in items_to_update] | ||
| unique_track_ids = list(dict.fromkeys(track_ids)) | ||
| track_info_by_id = self.track_info_batch(unique_track_ids) |
There was a problem hiding this comment.
Makes sense. I renamed the typed dict to TrackDetails so you can have track_details_by_id here, to remove ambiguity regarding beets.hooks.info::TrackInfo.
| "Processing {}/{} tracks - {} ", index, len(items), item | ||
| ) | ||
| # If we're not forcing re-downloading for all tracks, check | ||
| # whether the popularity data is already present |
There was a problem hiding this comment.
Configure your AI agent to not remove stuff like this
This changes the Spotify plugin to batch
spotifysynclookups instead of making per-track API calls. It now:The previous implementation made one metadata request and one audio-features request per track, which was inefficient for larger libraries and increased the chance of hitting rate limits. Batching reduces request volume substantially while keeping the stored fields and user-facing behavior the same.
docs/changelog.rstto the bottom of one of the lists near the top of the document.)