deezer-python-gql 0.5.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,250 @@
1
+ Metadata-Version: 2.4
2
+ Name: deezer-python-gql
3
+ Version: 0.6.0
4
+ Summary: Async typed Python client for Deezer's Pipe GraphQL API.
5
+ Author-email: Julian Daberkow <jdaberkow@users.noreply.github.com>
6
+ License: Apache-2.0
7
+ Platform: any
8
+ Classifier: Environment :: Console
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Requires-Python: >=3.12
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: httpx>=0.27.0
15
+ Requires-Dist: pydantic>=2.0.0
16
+ Provides-Extra: test
17
+ Requires-Dist: codespell==2.4.2; extra == "test"
18
+ Requires-Dist: mypy==1.19.1; extra == "test"
19
+ Requires-Dist: pre-commit==4.5.1; extra == "test"
20
+ Requires-Dist: pre-commit-hooks==6.0.0; extra == "test"
21
+ Requires-Dist: pytest==9.0.2; extra == "test"
22
+ Requires-Dist: pytest-asyncio==1.3.0; extra == "test"
23
+ Requires-Dist: pytest-cov==7.1.0; extra == "test"
24
+ Requires-Dist: ruff==0.15.7; extra == "test"
25
+ Provides-Extra: dev
26
+ Requires-Dist: ariadne-codegen[subscriptions]; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # deezer-python-gql
30
+
31
+ Async typed Python client for Deezer's Pipe GraphQL API.
32
+
33
+ Built with [ariadne-codegen](https://github.com/mirumee/ariadne-codegen) — all
34
+ client methods and response models are generated from the GraphQL schema and
35
+ `.graphql` query files.
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ uv add deezer-python-gql
41
+ ```
42
+
43
+ ## Quick Start
44
+
45
+ ```python
46
+ import asyncio
47
+
48
+ from deezer_python_gql import DeezerGQLClient
49
+
50
+ async def main():
51
+ client = DeezerGQLClient(arl="YOUR_ARL_COOKIE")
52
+
53
+ # Current user
54
+ me = await client.get_me()
55
+ print(me)
56
+
57
+ # Track with media URLs, lyrics, and contributors
58
+ track = await client.get_track(track_id="3135556")
59
+ print(track.title, track.duration)
60
+
61
+ # Album with paginated track list
62
+ album = await client.get_album(album_id="302127")
63
+ print(album.display_title, album.tracks_count)
64
+
65
+ # Artist with top tracks and discography
66
+ artist = await client.get_artist(artist_id="27")
67
+ print(artist.name, artist.fans_count)
68
+
69
+ # Playlist with tracks
70
+ playlist = await client.get_playlist(playlist_id="53362031")
71
+ print(playlist.title, playlist.estimated_tracks_count)
72
+
73
+ # Unified search across all entity types
74
+ results = await client.search(query="Daft Punk")
75
+ print(len(results.tracks.edges), "tracks found")
76
+
77
+ asyncio.run(main())
78
+ ```
79
+
80
+ ## Available Queries
81
+
82
+ ### Content Retrieval
83
+
84
+ | Method | Description |
85
+ | --------------------------------------------- | --------------------------------------------------------------- |
86
+ | `get_me()` | Current authenticated user |
87
+ | `get_track(track_id)` | Full track details — ISRC, media tokens, lyrics, contributors |
88
+ | `get_album(album_id)` | Album with cover, label, paginated tracks, fallback |
89
+ | `get_artist(artist_id)` | Artist with bio, top tracks, albums (ordered by release date) |
90
+ | `get_playlist(playlist_id)` | Playlist with owner, picture, paginated tracks |
91
+ | `get_livestream(livestream_id)` | Livestream (radio station) with streaming URLs and codec info |
92
+ | `get_podcast(podcast_id)` | Podcast with paginated episodes and rights info |
93
+ | `get_podcast_episode(podcast_episode_id)` | Single episode with media URL, codec, and parent podcast ref |
94
+ | `get_audiobook(audiobook_id)` | Audiobook with paginated chapters, contributors, and fallback |
95
+ | `get_audiobook_chapter(audiobook_chapter_id)` | Chapter with media token, estimated sizes, and streaming rights |
96
+
97
+ ### Search & Discovery
98
+
99
+ | Method | Description |
100
+ | ------------------------------------------------ | ------------------------------------------------------------------------------- |
101
+ | `search(query, ...)` | Unified search across tracks, albums, artists, playlists, podcasts, livestreams |
102
+ | `search_flows(query)` | Discover all available Deezer flows via search |
103
+ | `get_similar_tracks(track_id, nb)` | Recommended tracks based on a given track |
104
+ | `get_artist_mix(artist_ids, limit)` | Track mix blended from given artists |
105
+ | `get_track_mix(track_ids, limit)` | Track mix blended around given tracks |
106
+ | `get_flow()` | User's default Flow with tracks |
107
+ | `get_flow_batch()` | 4 batches of Flow tracks in one request (via GraphQL aliases) |
108
+ | `get_flow_configs(moods_first, genres_first)` | Mood & genre flow config lists for discovery |
109
+ | `get_flow_config_tracks(flow_config_id)` | Tracks for a specific mood/genre flow config |
110
+ | `get_made_for_me(first)` | "Made For You" SmartTracklist & Flow items |
111
+ | `get_smart_tracklist(smart_tracklist_id, first)` | Smart tracklist with paginated tracks |
112
+ | `get_charts(country_code, ...)` | Country charts — tracks, albums, artists, playlists |
113
+ | `get_recommendations(playlists_first, ...)` | Personalized recommendations across categories |
114
+ | `get_recently_played(first)` | Recently played mixed content (albums, playlists, artists...) |
115
+ | `get_user_charts()` | Personal top tracks, artists, and albums |
116
+
117
+ ### Library & Favorites
118
+
119
+ | Method | Description |
120
+ | --------------------------------------------- | ----------------------------------------------------------- |
121
+ | `get_favorite_artists(first, after)` | Paginated favorite artists |
122
+ | `get_favorite_albums(first, after)` | Paginated favorite albums |
123
+ | `get_favorite_tracks(first, after)` | Paginated favorite tracks |
124
+ | `get_favorite_playlists(first, after)` | Paginated favorite playlists |
125
+ | `get_favorite_podcasts(first, after)` | Paginated favorite podcasts |
126
+ | `get_favorite_audiobooks()` | Favorite audiobook IDs with dates (via deprecated endpoint) |
127
+ | `get_podcast_episode_bookmarks(first, after)` | Bookmarked podcast episodes with playback position |
128
+ | `get_user_playlists(first, after)` | User's own playlists (not just favorites) |
129
+
130
+ ### Music Together (Collaborative Playlists)
131
+
132
+ | Method | Description |
133
+ | ------------------------------------------ | --------------------------------------------------------- |
134
+ | `get_music_together_groups(first)` | User's Music Together groups |
135
+ | `get_music_together_group(group_id, mood)` | Single group with members, suggested & curated tracklists |
136
+ | `get_music_together_affinity(group_id)` | Group member affinity scores and discovery content |
137
+
138
+ ## Available Mutations
139
+
140
+ ### Favorites Management
141
+
142
+ | Method | Description |
143
+ | ---------------------------------------------- | ----------------------------------------------------------- |
144
+ | `add_artist_to_favorite(artist_id)` | Add artist to favorites |
145
+ | `remove_artist_from_favorite(artist_id)` | Remove artist from favorites |
146
+ | `add_album_to_favorite(album_id)` | Add album to favorites |
147
+ | `remove_album_from_favorite(album_id)` | Remove album from favorites |
148
+ | `add_track_to_favorite(track_id)` | Add track to favorites |
149
+ | `remove_track_from_favorite(track_id)` | Remove track from favorites |
150
+ | `add_playlist_to_favorite(playlist_id)` | Add playlist to favorites |
151
+ | `remove_playlist_from_favorite(playlist_id)` | Remove playlist from favorites |
152
+ | `add_podcast_to_favorite(podcast_id)` | Add podcast to favorites |
153
+ | `remove_podcast_from_favorite(podcast_id)` | Remove podcast from favorites |
154
+ | `add_audiobook_to_favorite(audiobook_id)` | Add audiobook to favorites (deprecated but functional) |
155
+ | `remove_audiobook_from_favorite(audiobook_id)` | Remove audiobook from favorites (deprecated but functional) |
156
+
157
+ ### Playlist Management
158
+
159
+ | Method | Description |
160
+ | ------------------------------------------------ | ------------------------------------------------- |
161
+ | `create_playlist(title, ...)` | Create a new playlist |
162
+ | `update_playlist(playlist_id, ...)` | Update playlist title, description, or visibility |
163
+ | `delete_playlist(playlist_id)` | Delete a playlist |
164
+ | `add_tracks_to_playlist(playlist_id, track_ids)` | Add tracks to a playlist |
165
+ | `remove_tracks_from_playlist(playlist_id, ...)` | Remove tracks from a playlist |
166
+
167
+ ### Podcast Episode Management
168
+
169
+ | Method | Description |
170
+ | ------------------------------------------------ | ------------------------------------------------- |
171
+ | `bookmark_podcast_episode(episode_id, offset)` | Bookmark episode with playback position (seconds) |
172
+ | `unbookmark_podcast_episode(episode_id)` | Remove bookmark from episode |
173
+ | `mark_as_played_podcast_episode(episode_id)` | Mark episode as played |
174
+ | `mark_as_not_played_podcast_episode(episode_id)` | Mark episode as not played |
175
+
176
+ ### Music Together
177
+
178
+ | Method | Description |
179
+ | ------------------------------------------------- | ------------------------------------------- |
180
+ | `music_together_create_group(name, ...)` | Create a new Music Together group |
181
+ | `music_together_join_group(group_id)` | Join an existing group |
182
+ | `music_together_leave_group(group_id)` | Leave a group |
183
+ | `music_together_refresh_suggested_tracklist(...)` | Refresh the suggested tracklist for a group |
184
+ | `music_together_update_group_settings(...)` | Update group settings (name, family mode) |
185
+ | `music_together_generate_group_name()` | Generate a random group name |
186
+
187
+ All methods return fully-typed Pydantic models generated from the GraphQL schema.
188
+
189
+ ## Development
190
+
191
+ Requires **Python 3.12+** and [uv](https://docs.astral.sh/uv/).
192
+
193
+ ```bash
194
+ # Install all dependencies (including codegen tooling)
195
+ make setup
196
+
197
+ # Re-generate the typed client from schema + queries
198
+ make generate
199
+
200
+ # Run linters and type checks
201
+ make lint
202
+
203
+ # Run tests
204
+ make test
205
+ ```
206
+
207
+ ### Adding a new query
208
+
209
+ 1. Create a `.graphql` file in `queries/`.
210
+ 2. Run `make generate` to produce the typed client method and response models.
211
+ 3. Add tests in `tests/`.
212
+
213
+ ## Exploring the API
214
+
215
+ To run ad-hoc GraphQL queries against the live Pipe API during development:
216
+
217
+ 1. Create a `.env` file (already gitignored) with your ARL cookie:
218
+
219
+ ```bash
220
+ echo 'DEEZER_ARL=your_arl_cookie_value' > .env
221
+ ```
222
+
223
+ 2. Run queries:
224
+
225
+ ```bash
226
+ # Run a .graphql file
227
+ uv run python scripts/explore.py queries/get_me.graphql
228
+
229
+ # Run an inline query
230
+ uv run python scripts/explore.py -q '{ me { id } }'
231
+
232
+ # With variables
233
+ uv run python scripts/explore.py -q 'query($id: String!) { track(trackId: $id) { title } }' \
234
+ -v '{"id": "3135556"}'
235
+
236
+ # Via make
237
+ make explore Q=queries/get_me.graphql
238
+ ```
239
+
240
+ The script handles JWT auth automatically — no manual token management needed.
241
+
242
+ ## Authentication
243
+
244
+ The Pipe API uses short-lived JWTs obtained from an ARL cookie. The base client
245
+ handles token acquisition and refresh automatically — you only need to supply a
246
+ valid ARL value.
247
+
248
+ ## License
249
+
250
+ Apache-2.0
@@ -0,0 +1,222 @@
1
+ # deezer-python-gql
2
+
3
+ Async typed Python client for Deezer's Pipe GraphQL API.
4
+
5
+ Built with [ariadne-codegen](https://github.com/mirumee/ariadne-codegen) — all
6
+ client methods and response models are generated from the GraphQL schema and
7
+ `.graphql` query files.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ uv add deezer-python-gql
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```python
18
+ import asyncio
19
+
20
+ from deezer_python_gql import DeezerGQLClient
21
+
22
+ async def main():
23
+ client = DeezerGQLClient(arl="YOUR_ARL_COOKIE")
24
+
25
+ # Current user
26
+ me = await client.get_me()
27
+ print(me)
28
+
29
+ # Track with media URLs, lyrics, and contributors
30
+ track = await client.get_track(track_id="3135556")
31
+ print(track.title, track.duration)
32
+
33
+ # Album with paginated track list
34
+ album = await client.get_album(album_id="302127")
35
+ print(album.display_title, album.tracks_count)
36
+
37
+ # Artist with top tracks and discography
38
+ artist = await client.get_artist(artist_id="27")
39
+ print(artist.name, artist.fans_count)
40
+
41
+ # Playlist with tracks
42
+ playlist = await client.get_playlist(playlist_id="53362031")
43
+ print(playlist.title, playlist.estimated_tracks_count)
44
+
45
+ # Unified search across all entity types
46
+ results = await client.search(query="Daft Punk")
47
+ print(len(results.tracks.edges), "tracks found")
48
+
49
+ asyncio.run(main())
50
+ ```
51
+
52
+ ## Available Queries
53
+
54
+ ### Content Retrieval
55
+
56
+ | Method | Description |
57
+ | --------------------------------------------- | --------------------------------------------------------------- |
58
+ | `get_me()` | Current authenticated user |
59
+ | `get_track(track_id)` | Full track details — ISRC, media tokens, lyrics, contributors |
60
+ | `get_album(album_id)` | Album with cover, label, paginated tracks, fallback |
61
+ | `get_artist(artist_id)` | Artist with bio, top tracks, albums (ordered by release date) |
62
+ | `get_playlist(playlist_id)` | Playlist with owner, picture, paginated tracks |
63
+ | `get_livestream(livestream_id)` | Livestream (radio station) with streaming URLs and codec info |
64
+ | `get_podcast(podcast_id)` | Podcast with paginated episodes and rights info |
65
+ | `get_podcast_episode(podcast_episode_id)` | Single episode with media URL, codec, and parent podcast ref |
66
+ | `get_audiobook(audiobook_id)` | Audiobook with paginated chapters, contributors, and fallback |
67
+ | `get_audiobook_chapter(audiobook_chapter_id)` | Chapter with media token, estimated sizes, and streaming rights |
68
+
69
+ ### Search & Discovery
70
+
71
+ | Method | Description |
72
+ | ------------------------------------------------ | ------------------------------------------------------------------------------- |
73
+ | `search(query, ...)` | Unified search across tracks, albums, artists, playlists, podcasts, livestreams |
74
+ | `search_flows(query)` | Discover all available Deezer flows via search |
75
+ | `get_similar_tracks(track_id, nb)` | Recommended tracks based on a given track |
76
+ | `get_artist_mix(artist_ids, limit)` | Track mix blended from given artists |
77
+ | `get_track_mix(track_ids, limit)` | Track mix blended around given tracks |
78
+ | `get_flow()` | User's default Flow with tracks |
79
+ | `get_flow_batch()` | 4 batches of Flow tracks in one request (via GraphQL aliases) |
80
+ | `get_flow_configs(moods_first, genres_first)` | Mood & genre flow config lists for discovery |
81
+ | `get_flow_config_tracks(flow_config_id)` | Tracks for a specific mood/genre flow config |
82
+ | `get_made_for_me(first)` | "Made For You" SmartTracklist & Flow items |
83
+ | `get_smart_tracklist(smart_tracklist_id, first)` | Smart tracklist with paginated tracks |
84
+ | `get_charts(country_code, ...)` | Country charts — tracks, albums, artists, playlists |
85
+ | `get_recommendations(playlists_first, ...)` | Personalized recommendations across categories |
86
+ | `get_recently_played(first)` | Recently played mixed content (albums, playlists, artists...) |
87
+ | `get_user_charts()` | Personal top tracks, artists, and albums |
88
+
89
+ ### Library & Favorites
90
+
91
+ | Method | Description |
92
+ | --------------------------------------------- | ----------------------------------------------------------- |
93
+ | `get_favorite_artists(first, after)` | Paginated favorite artists |
94
+ | `get_favorite_albums(first, after)` | Paginated favorite albums |
95
+ | `get_favorite_tracks(first, after)` | Paginated favorite tracks |
96
+ | `get_favorite_playlists(first, after)` | Paginated favorite playlists |
97
+ | `get_favorite_podcasts(first, after)` | Paginated favorite podcasts |
98
+ | `get_favorite_audiobooks()` | Favorite audiobook IDs with dates (via deprecated endpoint) |
99
+ | `get_podcast_episode_bookmarks(first, after)` | Bookmarked podcast episodes with playback position |
100
+ | `get_user_playlists(first, after)` | User's own playlists (not just favorites) |
101
+
102
+ ### Music Together (Collaborative Playlists)
103
+
104
+ | Method | Description |
105
+ | ------------------------------------------ | --------------------------------------------------------- |
106
+ | `get_music_together_groups(first)` | User's Music Together groups |
107
+ | `get_music_together_group(group_id, mood)` | Single group with members, suggested & curated tracklists |
108
+ | `get_music_together_affinity(group_id)` | Group member affinity scores and discovery content |
109
+
110
+ ## Available Mutations
111
+
112
+ ### Favorites Management
113
+
114
+ | Method | Description |
115
+ | ---------------------------------------------- | ----------------------------------------------------------- |
116
+ | `add_artist_to_favorite(artist_id)` | Add artist to favorites |
117
+ | `remove_artist_from_favorite(artist_id)` | Remove artist from favorites |
118
+ | `add_album_to_favorite(album_id)` | Add album to favorites |
119
+ | `remove_album_from_favorite(album_id)` | Remove album from favorites |
120
+ | `add_track_to_favorite(track_id)` | Add track to favorites |
121
+ | `remove_track_from_favorite(track_id)` | Remove track from favorites |
122
+ | `add_playlist_to_favorite(playlist_id)` | Add playlist to favorites |
123
+ | `remove_playlist_from_favorite(playlist_id)` | Remove playlist from favorites |
124
+ | `add_podcast_to_favorite(podcast_id)` | Add podcast to favorites |
125
+ | `remove_podcast_from_favorite(podcast_id)` | Remove podcast from favorites |
126
+ | `add_audiobook_to_favorite(audiobook_id)` | Add audiobook to favorites (deprecated but functional) |
127
+ | `remove_audiobook_from_favorite(audiobook_id)` | Remove audiobook from favorites (deprecated but functional) |
128
+
129
+ ### Playlist Management
130
+
131
+ | Method | Description |
132
+ | ------------------------------------------------ | ------------------------------------------------- |
133
+ | `create_playlist(title, ...)` | Create a new playlist |
134
+ | `update_playlist(playlist_id, ...)` | Update playlist title, description, or visibility |
135
+ | `delete_playlist(playlist_id)` | Delete a playlist |
136
+ | `add_tracks_to_playlist(playlist_id, track_ids)` | Add tracks to a playlist |
137
+ | `remove_tracks_from_playlist(playlist_id, ...)` | Remove tracks from a playlist |
138
+
139
+ ### Podcast Episode Management
140
+
141
+ | Method | Description |
142
+ | ------------------------------------------------ | ------------------------------------------------- |
143
+ | `bookmark_podcast_episode(episode_id, offset)` | Bookmark episode with playback position (seconds) |
144
+ | `unbookmark_podcast_episode(episode_id)` | Remove bookmark from episode |
145
+ | `mark_as_played_podcast_episode(episode_id)` | Mark episode as played |
146
+ | `mark_as_not_played_podcast_episode(episode_id)` | Mark episode as not played |
147
+
148
+ ### Music Together
149
+
150
+ | Method | Description |
151
+ | ------------------------------------------------- | ------------------------------------------- |
152
+ | `music_together_create_group(name, ...)` | Create a new Music Together group |
153
+ | `music_together_join_group(group_id)` | Join an existing group |
154
+ | `music_together_leave_group(group_id)` | Leave a group |
155
+ | `music_together_refresh_suggested_tracklist(...)` | Refresh the suggested tracklist for a group |
156
+ | `music_together_update_group_settings(...)` | Update group settings (name, family mode) |
157
+ | `music_together_generate_group_name()` | Generate a random group name |
158
+
159
+ All methods return fully-typed Pydantic models generated from the GraphQL schema.
160
+
161
+ ## Development
162
+
163
+ Requires **Python 3.12+** and [uv](https://docs.astral.sh/uv/).
164
+
165
+ ```bash
166
+ # Install all dependencies (including codegen tooling)
167
+ make setup
168
+
169
+ # Re-generate the typed client from schema + queries
170
+ make generate
171
+
172
+ # Run linters and type checks
173
+ make lint
174
+
175
+ # Run tests
176
+ make test
177
+ ```
178
+
179
+ ### Adding a new query
180
+
181
+ 1. Create a `.graphql` file in `queries/`.
182
+ 2. Run `make generate` to produce the typed client method and response models.
183
+ 3. Add tests in `tests/`.
184
+
185
+ ## Exploring the API
186
+
187
+ To run ad-hoc GraphQL queries against the live Pipe API during development:
188
+
189
+ 1. Create a `.env` file (already gitignored) with your ARL cookie:
190
+
191
+ ```bash
192
+ echo 'DEEZER_ARL=your_arl_cookie_value' > .env
193
+ ```
194
+
195
+ 2. Run queries:
196
+
197
+ ```bash
198
+ # Run a .graphql file
199
+ uv run python scripts/explore.py queries/get_me.graphql
200
+
201
+ # Run an inline query
202
+ uv run python scripts/explore.py -q '{ me { id } }'
203
+
204
+ # With variables
205
+ uv run python scripts/explore.py -q 'query($id: String!) { track(trackId: $id) { title } }' \
206
+ -v '{"id": "3135556"}'
207
+
208
+ # Via make
209
+ make explore Q=queries/get_me.graphql
210
+ ```
211
+
212
+ The script handles JWT auth automatically — no manual token management needed.
213
+
214
+ ## Authentication
215
+
216
+ The Pipe API uses short-lived JWTs obtained from an ARL cookie. The base client
217
+ handles token acquisition and refresh automatically — you only need to supply a
218
+ valid ARL value.
219
+
220
+ ## License
221
+
222
+ Apache-2.0