Gateway: Apple Music API client (paginated, paced, 429-retry) #6

Closed
opened 2026-08-09 20:31:47 +00:00 by hermes · 1 comment
Owner

Context

Gateway service (see #18). The gateway is the only component that talks to
the Apple Music API.

Task

app/apple_api.py: port beets-appleplaylists/beetsplug/appleplaylists/ apple_api.py almost verbatim (it is already tested):

  • dataclasses AppleTrack(title, artist, album, isrc, track_number) and
    ApplePlaylist(id, name, tracks) — keep the field names identical so the
    plugin's parsing is unchanged
  • AppleAPIClient(dev_token, user_token, session=None) with headers
    Authorization: Bearer <dev token> and Music-User-Token: <user token>
  • fetch_playlists() -> list of ApplePlaylist; tracks via
    /v1/me/library/playlists/{id}/tracks; nested library-playlists
    entries spliced into their parent at the position where they appear
    (queue[0:0] expansion); library-music-videos/library-albums skipped
  • pagination: limit=100 + offset, pause 0.25s between pages
  • 429 handling: up to 3 retries with a 1s sleep; other non-200s raise a
    sanitized AppleAPIError (no token data in messages)

Acceptance criteria

  • Port the plugin's tests/test_apple_api.py (FakeSession/FakeResponse
    pattern) and keep it green: pagination offsets, nested splicing, video
    skipping, 429 retry, non-200 error.
## Context Gateway service (see #18). The gateway is the only component that talks to the Apple Music API. ## Task `app/apple_api.py`: port `beets-appleplaylists/beetsplug/appleplaylists/ apple_api.py` almost verbatim (it is already tested): - dataclasses `AppleTrack(title, artist, album, isrc, track_number)` and `ApplePlaylist(id, name, tracks)` — keep the field names identical so the plugin's parsing is unchanged - `AppleAPIClient(dev_token, user_token, session=None)` with headers `Authorization: Bearer <dev token>` and `Music-User-Token: <user token>` - `fetch_playlists()` -> list of `ApplePlaylist`; tracks via `/v1/me/library/playlists/{id}/tracks`; nested `library-playlists` entries spliced into their parent at the position where they appear (queue[0:0] expansion); `library-music-videos`/`library-albums` skipped - pagination: `limit=100` + `offset`, pause 0.25s between pages - 429 handling: up to 3 retries with a 1s sleep; other non-200s raise a sanitized `AppleAPIError` (no token data in messages) ## Acceptance criteria - Port the plugin's `tests/test_apple_api.py` (FakeSession/FakeResponse pattern) and keep it green: pagination offsets, nested splicing, video skipping, 429 retry, non-200 error.
Author
Owner

Done in coop/apple-music-gateway (commit 491b188): app/apple_api.py ported from the plugin — AppleTrack/ApplePlaylist dataclasses with identical field names, AppleAPIClient with Authorization: Bearer <dev> + Music-User-Token headers, paginated fetch (limit 100 + offset, 0.25s page pause), nested library-playlists splicing at the queue position, library-music-videos/library-albums skipped, 429 retried up to 3x with 1s sleep, other non-200s raise a sanitized AppleAPIError. Gateway additions: AppleAPIError.status_code (routes map Apple 401/403 to the re-login hint) and fetch_playlist_summaries()/fetch_playlist_tracks() so the /api/v1 endpoints fetch one playlist at a time. Ported tests/test_apple_api.py green: pagination offsets, nested splicing, video skipping, 429 retry, non-200 error (plus status_code and the two new methods). uv run pytest: 62 passed.

Done in `coop/apple-music-gateway` (commit `491b188`): `app/apple_api.py` ported from the plugin — `AppleTrack`/`ApplePlaylist` dataclasses with identical field names, `AppleAPIClient` with `Authorization: Bearer <dev>` + `Music-User-Token` headers, paginated fetch (limit 100 + offset, 0.25s page pause), nested `library-playlists` splicing at the queue position, `library-music-videos`/`library-albums` skipped, 429 retried up to 3x with 1s sleep, other non-200s raise a sanitized `AppleAPIError`. Gateway additions: `AppleAPIError.status_code` (routes map Apple 401/403 to the re-login hint) and `fetch_playlist_summaries()`/`fetch_playlist_tracks()` so the /api/v1 endpoints fetch one playlist at a time. Ported `tests/test_apple_api.py` green: pagination offsets, nested splicing, video skipping, 429 retry, non-200 error (plus status_code and the two new methods). `uv run pytest`: 62 passed.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coop/beets-appleplaylists#6
No description provided.