Skip to content

Add include/extend/fields scoped query params to all catalog methods#26

Open
ParkJeongseop wants to merge 1 commit into
mpalazzolo:masterfrom
ParkJeongseop:feat/scoped-include-extend-fields
Open

Add include/extend/fields scoped query params to all catalog methods#26
ParkJeongseop wants to merge 1 commit into
mpalazzolo:masterfrom
ParkJeongseop:feat/scoped-include-extend-fields

Conversation

@ParkJeongseop
Copy link
Copy Markdown

Add include / extend / fields scoped query params to all catalog methods

Summary

Apple Music's catalog API accepts scoped query parameters such as
include[songs]=albums, extend[songs]=extendedAssetUrls, and
fields[artists]=name,url on most endpoints — including /search — to embed
relationships, request additional attributes, or trim response payloads. The
wrapper currently exposes only include (as a plain string) on a subset of
methods, and not at all on search(), charts(), or any of the
*_relationship / *_relationship_view methods. As a result, callers can't
ask for relationship data in search results, can't use extend or fields
anywhere, and have no way to send the scoped include[<resource>]=... form
that the search endpoint requires.

This PR adds include, extend, and fields to every catalog-fetching method
in a single, consistent shape, with full backward compatibility.

What changed

  • _build_scoped_params helper — Static method that converts the three
    scoped inputs into the correct query-parameter shape:

    • include={'songs': ['albums', 'artists']}include[songs]=albums,artists
    • include={'songs': 'albums,artists'}include[songs]=albums,artists
    • include=['albums', 'artists']include=albums,artists
    • include='albums'include=albums
    • None / empty → omitted entirely
  • Automatic translation in _get — When any caller passes include,
    extend, or fields as a kwarg to _get, those values are routed through
    _build_scoped_params before being sent to requests. This means new and
    existing methods alike get dict-form support without each one re-implementing
    the conversion.

  • Method signatures extendedinclude, extend, and fields keyword
    arguments added to every catalog method that accepts query parameters:

    • by-ID / by-IDs / by-ISRC: album, albums, song, songs,
      songs_by_isrc, artist, artists, playlist, playlists,
      music_video, music_videos, music_videos_by_isrc, station,
      stations, curator, curators, activity, activities,
      apple_curator, apple_curators
    • relationships: album_relationship, album_relationship_view,
      music_video_relationship, music_video_relationship_view,
      playlist_relationship, playlist_relationship_view, song_relationship,
      artist_relationship, artist_relationship_view, curator_relationship,
      activity_relationship, apple_curator_relationship
    • other: search, charts

    genre* and storefront* are unchanged because the Apple Music API does
    not expose relationships on those resources.

Backward compatibility

Fully backward compatible. All three new parameters default to None, in
which case nothing is sent. The existing string form (include='albums') is
still accepted on every method that previously accepted it and produces the
exact same query string as before:

# Pre-PR, still works identically post-PR:
am.song('1651192350', storefront='kr', l='ko', include='albums')
# → ?include=albums

The new dict form unlocks scoped params on search() and similar methods that
need include[<resource>] rather than the bare include:

am.search('love', storefront='kr', l='ko', limit=5, types=['songs'],
          include={'songs': ['albums']})
# → ?term=love&types=songs&limit=5&l=ko&include[songs]=albums

No method signatures had positional parameters removed or reordered. The new
keyword arguments are appended at the end.

Verification

Live calls against the Apple Music catalog API (storefront='kr').

search() with scoped include — previously impossible

import applemusicpy
am = applemusicpy.AppleMusic(secret_key, key_id, team_id)

resp = am.search('love', storefront='kr', l='ko', limit=1, types=['songs'],
                 include={'songs': ['albums']})
song = resp['results']['songs']['data'][0]
print('relationships:', list(song.get('relationships', {}).keys()))
print('album id:', song['relationships']['albums']['data'][0]['id'])

Before: relationships key not present in response — the search endpoint
silently ignored any include= because the wrapper had no way to send
include[songs]=.... To find the album id you had to make a second song()
request per result.

After:

relationships: ['albums']
album id: 1440667549

songs_by_isrc() with include — pre-existing form, unchanged

resp = am.songs_by_isrc(['KRC531900007'], storefront='kr', l='ko',
                        include='albums')
print(resp['data'][0]['relationships']['albums']['data'][0]['id'])
1651192298

Same behavior and same query string as before this PR.

Unit-level shape check on _build_scoped_params

_build_scoped_params(include={'songs': ['albums']})
  -> {'include[songs]': 'albums'}
_build_scoped_params(include={'songs': 'albums,artists'})
  -> {'include[songs]': 'albums,artists'}
_build_scoped_params(include='albums')
  -> {'include': 'albums'}
_build_scoped_params(include=['albums', 'artists'])
  -> {'include': 'albums,artists'}
_build_scoped_params(extend={'songs': ['extendedAssetUrls']},
                     fields={'artists': ['name']})
  -> {'extend[songs]': 'extendedAssetUrls', 'fields[artists]': 'name'}
_build_scoped_params()
  -> {}

Test plan

  • _build_scoped_params produces the expected query-parameter dict for
    dict, str, list, mixed, and empty inputs
  • Every public method previously listed gains include, extend, and
    fields keyword arguments (inspect.signature check across all 34
    methods)
  • am.song(track_id, include='albums') (existing string form) still
    embeds relationships.albums against the live API — no regression
  • am.search(..., include={'songs': ['albums']}) (new scoped form)
    embeds relationships.albums in search results against the live API
  • am.songs_by_isrc([isrc], include='albums') continues to embed
    relationships against the live API
  • requests correctly percent-encodes the bracketed parameter names
    (include[songs]include%5Bsongs%5D); Apple Music accepts the
    encoded form

Apple Music API accepts scoped parameters (include[songs]=albums,
extend[songs]=..., fields[artists]=name) on most catalog endpoints,
including /search, but the wrapper had no way to send them.

- Add _build_scoped_params helper that turns dict/str/list inputs into
  the appropriate query parameter shape (include[type]=... vs include=...).
- Wire automatic scoping into _get so any method passing include / extend
  / fields kwargs gets dict-form support transparently.
- Extend signatures of every catalog fetch / relationship / by-isrc /
  search / charts method with include, extend, fields keyword arguments.

Backward compatible: passing a plain string still produces the unscoped
include=... form, matching previous behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant