deezer-python-gql 0.19.0__tar.gz → 0.20.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.
- {deezer_python_gql-0.19.0/deezer_python_gql.egg-info → deezer_python_gql-0.20.0}/PKG-INFO +18 -2
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/README.md +16 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/base_client.py +15 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/__init__.py +16 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/base_client.py +15 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/client.py +33 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/fragments.py +12 -0
- deezer_python_gql-0.20.0/deezer_python_gql/generated/get_family.py +34 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0/deezer_python_gql.egg-info}/PKG-INFO +18 -2
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql.egg-info/SOURCES.txt +1 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql.egg-info/requires.txt +1 -1
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/pyproject.toml +7 -2
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/tests/test_client.py +53 -1
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/LICENSE +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/MANIFEST.in +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/__init__.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/add_album_to_favorite.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/add_artist_to_favorite.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/add_audiobook_to_favorite.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/add_playlist_to_favorite.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/add_podcast_to_favorite.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/add_track_to_favorite.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/add_tracks_to_playlist.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/ban_artist_from_recommendation.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/ban_track_from_recommendation.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/base_model.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/bookmark_podcast_episode.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/create_playlist.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/delete_playlist.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/enums.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_album.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_artist.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_artist_mix.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_audiobook.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_audiobook_chapter.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_banned_artists.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_banned_tracks.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_charts.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_favorite_albums.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_favorite_artists.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_favorite_audiobooks.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_favorite_playlists.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_favorite_podcasts.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_favorite_tracks.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_flow.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_flow_batch.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_flow_config_tracks.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_flow_configs.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_flow_tuner.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_infinite_track_mix.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_latest_podcast_episodes.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_livestream.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_made_for_me.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_me.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_music_together_affinity.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_music_together_group.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_music_together_groups.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_personal_tracks.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_playlist.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_podcast.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_podcast_episode.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_podcast_episode_bookmarks.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_podcast_episodes_by_ids.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_recently_played.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_recommendations.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_similar_artists.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_similar_tracks.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_smart_tracklist.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_track.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_track_mix.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_tracks_by_ids.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_user_charts.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_user_playlists.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/input_types.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/mark_as_not_played_podcast_episode.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/mark_as_played_podcast_episode.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/music_together_create_group.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/music_together_generate_group_name.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/music_together_join_group.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/music_together_leave_group.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/music_together_refresh_suggested_tracklist.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/music_together_update_group_settings.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/remove_album_from_favorite.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/remove_artist_from_favorite.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/remove_audiobook_from_favorite.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/remove_playlist_from_favorite.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/remove_podcast_from_favorite.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/remove_track_from_favorite.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/remove_tracks_from_playlist.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/search.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/search_flows.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/set_flow_discovery_tuner.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/unban_artist_from_recommendation.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/unban_track_from_recommendation.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/unbookmark_podcast_episode.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/update_playlist.py +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/py.typed +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql.egg-info/dependency_links.txt +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql.egg-info/not-zip-safe +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql.egg-info/top_level.txt +0 -0
- {deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deezer-python-gql
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.0
|
|
4
4
|
Summary: Async typed Python client for Deezer's Pipe GraphQL API.
|
|
5
5
|
Author-email: Julian Daberkow <jdaberkow@users.noreply.github.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -22,7 +22,7 @@ Requires-Dist: pre-commit-hooks==6.0.0; extra == "test"
|
|
|
22
22
|
Requires-Dist: pytest==9.1.1; extra == "test"
|
|
23
23
|
Requires-Dist: pytest-asyncio==1.4.0; extra == "test"
|
|
24
24
|
Requires-Dist: pytest-cov==7.1.0; extra == "test"
|
|
25
|
-
Requires-Dist: ruff==0.16.
|
|
25
|
+
Requires-Dist: ruff==0.16.9; extra == "test"
|
|
26
26
|
Provides-Extra: dev
|
|
27
27
|
Requires-Dist: ariadne-codegen[subscriptions]; extra == "dev"
|
|
28
28
|
Dynamic: license-file
|
|
@@ -136,6 +136,7 @@ asyncio.run(main())
|
|
|
136
136
|
| `get_banned_tracks(first, after)` | Tracks banned from recommendations, with ban date |
|
|
137
137
|
| `get_banned_artists(first, after)` | Artists banned from recommendations, with ban date |
|
|
138
138
|
| `get_flow_tuner()` | Flow tuner: `DEFAULT` or `DISCOVERY` |
|
|
139
|
+
| `get_family()` | Deezer Family members, with `isLoggableAs` for usable profiles |
|
|
139
140
|
|
|
140
141
|
### Music Together (Collaborative Playlists)
|
|
141
142
|
|
|
@@ -286,6 +287,21 @@ If the ARL is rejected (bad or expired), the client raises
|
|
|
286
287
|
`GraphQLClientAuthError` — catch it to prompt for a new ARL instead of retrying.
|
|
287
288
|
Transient server errors raise `GraphQLClientHttpError` instead.
|
|
288
289
|
|
|
290
|
+
### Deezer Family profiles
|
|
291
|
+
|
|
292
|
+
Profiles of a Deezer Family have no ARL of their own, they share the login of the
|
|
293
|
+
Family admin. Pass `account_id` to act as one of them:
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
family = (await client.get_family()).family
|
|
297
|
+
profile = next(m for m in family.linked if m.permissions.is_loggable_as)
|
|
298
|
+
profile_client = DeezerGQLClient(arl="ADMIN_ARL", account_id=profile.id)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Members with their own login (independent accounts) are not loggable, they need their
|
|
302
|
+
own ARL. If Deezer does not sign in as the profile, the client raises
|
|
303
|
+
`GraphQLClientAuthError`.
|
|
304
|
+
|
|
289
305
|
## License
|
|
290
306
|
|
|
291
307
|
Apache-2.0
|
|
@@ -107,6 +107,7 @@ asyncio.run(main())
|
|
|
107
107
|
| `get_banned_tracks(first, after)` | Tracks banned from recommendations, with ban date |
|
|
108
108
|
| `get_banned_artists(first, after)` | Artists banned from recommendations, with ban date |
|
|
109
109
|
| `get_flow_tuner()` | Flow tuner: `DEFAULT` or `DISCOVERY` |
|
|
110
|
+
| `get_family()` | Deezer Family members, with `isLoggableAs` for usable profiles |
|
|
110
111
|
|
|
111
112
|
### Music Together (Collaborative Playlists)
|
|
112
113
|
|
|
@@ -257,6 +258,21 @@ If the ARL is rejected (bad or expired), the client raises
|
|
|
257
258
|
`GraphQLClientAuthError` — catch it to prompt for a new ARL instead of retrying.
|
|
258
259
|
Transient server errors raise `GraphQLClientHttpError` instead.
|
|
259
260
|
|
|
261
|
+
### Deezer Family profiles
|
|
262
|
+
|
|
263
|
+
Profiles of a Deezer Family have no ARL of their own, they share the login of the
|
|
264
|
+
Family admin. Pass `account_id` to act as one of them:
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
family = (await client.get_family()).family
|
|
268
|
+
profile = next(m for m in family.linked if m.permissions.is_loggable_as)
|
|
269
|
+
profile_client = DeezerGQLClient(arl="ADMIN_ARL", account_id=profile.id)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Members with their own login (independent accounts) are not loggable, they need their
|
|
273
|
+
own ARL. If Deezer does not sign in as the profile, the client raises
|
|
274
|
+
`GraphQLClientAuthError`.
|
|
275
|
+
|
|
260
276
|
## License
|
|
261
277
|
|
|
262
278
|
Apache-2.0
|
|
@@ -123,6 +123,9 @@ class DeezerBaseClient:
|
|
|
123
123
|
:param url: GraphQL endpoint URL (defaults to Pipe API).
|
|
124
124
|
:param session: Optional pre-configured aiohttp.ClientSession.
|
|
125
125
|
If provided, the caller is responsible for closing it.
|
|
126
|
+
:param account_id: Optional Deezer Family profile to act as. A profile has no
|
|
127
|
+
ARL of its own; it signs in with the ARL of the Family admin. Only profiles
|
|
128
|
+
that ``get_family()`` marks as ``isLoggableAs`` can be used.
|
|
126
129
|
"""
|
|
127
130
|
|
|
128
131
|
PIPE_URL = "https://pipe.deezer.com/api"
|
|
@@ -136,9 +139,11 @@ class DeezerBaseClient:
|
|
|
136
139
|
arl: str,
|
|
137
140
|
url: str = PIPE_URL,
|
|
138
141
|
session: ClientSession | None = None,
|
|
142
|
+
account_id: str | None = None,
|
|
139
143
|
) -> None:
|
|
140
144
|
self.url = url
|
|
141
145
|
self._arl = arl
|
|
146
|
+
self._account_id = account_id
|
|
142
147
|
self._session = session
|
|
143
148
|
self._owns_session = session is None
|
|
144
149
|
# An external session may be shared by clients for different Deezer accounts.
|
|
@@ -347,12 +352,18 @@ class DeezerBaseClient:
|
|
|
347
352
|
|
|
348
353
|
logger.debug("JWT expired or missing, refreshing from ARL")
|
|
349
354
|
params = {"jo": "p", "rto": "c", "i": "c"}
|
|
355
|
+
body = None
|
|
356
|
+
if self._account_id:
|
|
357
|
+
# A Family profile signs in with the admin's ARL and its own id
|
|
358
|
+
params["i"] = "p"
|
|
359
|
+
body = {"arl": self._arl, "account_id": self._account_id}
|
|
350
360
|
|
|
351
361
|
session = self._get_session()
|
|
352
362
|
async with session.post(
|
|
353
363
|
self.AUTH_URL,
|
|
354
364
|
params=params,
|
|
355
365
|
cookies=self._request_cookies(self.AUTH_URL, include_arl=True),
|
|
366
|
+
json=body,
|
|
356
367
|
timeout=ClientTimeout(total=10),
|
|
357
368
|
) as resp:
|
|
358
369
|
self._store_cookies(resp)
|
|
@@ -383,6 +394,10 @@ class DeezerBaseClient:
|
|
|
383
394
|
msg = "Unexpected response from auth.deezer.com (invalid or missing JWT)"
|
|
384
395
|
raise GraphQLClientAuthError(msg) from exc
|
|
385
396
|
|
|
397
|
+
if self._account_id and str(payload.get("userId")) != self._account_id:
|
|
398
|
+
msg = f"auth.deezer.com did not sign in as Family profile {self._account_id}"
|
|
399
|
+
raise GraphQLClientAuthError(msg)
|
|
400
|
+
|
|
386
401
|
self._jwt = jwt
|
|
387
402
|
self._jwt_expires_at = expires_at
|
|
388
403
|
logger.debug("JWT acquired, expires at %s", self._jwt_expires_at)
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/__init__.py
RENAMED
|
@@ -86,6 +86,8 @@ from .fragments import (
|
|
|
86
86
|
AudiobookFieldsContributorsEdges,
|
|
87
87
|
AudiobookFieldsContributorsEdgesNodeArtist,
|
|
88
88
|
AudiobookFieldsCover,
|
|
89
|
+
FamilyProfileFields,
|
|
90
|
+
FamilyProfileFieldsPermissions,
|
|
89
91
|
LivestreamFields,
|
|
90
92
|
LivestreamFieldsCover,
|
|
91
93
|
LivestreamFieldsMedia,
|
|
@@ -205,6 +207,13 @@ from .get_charts import (
|
|
|
205
207
|
GetChartsChartsCountryTracksEdgesNode,
|
|
206
208
|
GetChartsChartsCountryTracksPageInfo,
|
|
207
209
|
)
|
|
210
|
+
from .get_family import (
|
|
211
|
+
GetFamily,
|
|
212
|
+
GetFamilyMe,
|
|
213
|
+
GetFamilyMeFamily,
|
|
214
|
+
GetFamilyMeFamilyLinked,
|
|
215
|
+
GetFamilyMeFamilyMain,
|
|
216
|
+
)
|
|
208
217
|
from .get_favorite_albums import (
|
|
209
218
|
GetFavoriteAlbums,
|
|
210
219
|
GetFavoriteAlbumsMe,
|
|
@@ -759,6 +768,8 @@ __all__ = [
|
|
|
759
768
|
"DeletePlaylistDeletePlaylist",
|
|
760
769
|
"DiscoveryTuner",
|
|
761
770
|
"DiscoveryTunerInput",
|
|
771
|
+
"FamilyProfileFields",
|
|
772
|
+
"FamilyProfileFieldsPermissions",
|
|
762
773
|
"GetAlbum",
|
|
763
774
|
"GetAlbumAlbum",
|
|
764
775
|
"GetAlbumAlbumFallback",
|
|
@@ -837,6 +848,11 @@ __all__ = [
|
|
|
837
848
|
"GetChartsChartsCountryTracksEdges",
|
|
838
849
|
"GetChartsChartsCountryTracksEdgesNode",
|
|
839
850
|
"GetChartsChartsCountryTracksPageInfo",
|
|
851
|
+
"GetFamily",
|
|
852
|
+
"GetFamilyMe",
|
|
853
|
+
"GetFamilyMeFamily",
|
|
854
|
+
"GetFamilyMeFamilyLinked",
|
|
855
|
+
"GetFamilyMeFamilyMain",
|
|
840
856
|
"GetFavoriteAlbums",
|
|
841
857
|
"GetFavoriteAlbumsMe",
|
|
842
858
|
"GetFavoriteAlbumsMeUserFavorites",
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/base_client.py
RENAMED
|
@@ -125,6 +125,9 @@ class DeezerBaseClient:
|
|
|
125
125
|
:param url: GraphQL endpoint URL (defaults to Pipe API).
|
|
126
126
|
:param session: Optional pre-configured aiohttp.ClientSession.
|
|
127
127
|
If provided, the caller is responsible for closing it.
|
|
128
|
+
:param account_id: Optional Deezer Family profile to act as. A profile has no
|
|
129
|
+
ARL of its own; it signs in with the ARL of the Family admin. Only profiles
|
|
130
|
+
that ``get_family()`` marks as ``isLoggableAs`` can be used.
|
|
128
131
|
"""
|
|
129
132
|
|
|
130
133
|
PIPE_URL = "https://pipe.deezer.com/api"
|
|
@@ -138,9 +141,11 @@ class DeezerBaseClient:
|
|
|
138
141
|
arl: str,
|
|
139
142
|
url: str = PIPE_URL,
|
|
140
143
|
session: ClientSession | None = None,
|
|
144
|
+
account_id: str | None = None,
|
|
141
145
|
) -> None:
|
|
142
146
|
self.url = url
|
|
143
147
|
self._arl = arl
|
|
148
|
+
self._account_id = account_id
|
|
144
149
|
self._session = session
|
|
145
150
|
self._owns_session = session is None
|
|
146
151
|
# An external session may be shared by clients for different Deezer accounts.
|
|
@@ -349,12 +354,18 @@ class DeezerBaseClient:
|
|
|
349
354
|
|
|
350
355
|
logger.debug("JWT expired or missing, refreshing from ARL")
|
|
351
356
|
params = {"jo": "p", "rto": "c", "i": "c"}
|
|
357
|
+
body = None
|
|
358
|
+
if self._account_id:
|
|
359
|
+
# A Family profile signs in with the admin's ARL and its own id
|
|
360
|
+
params["i"] = "p"
|
|
361
|
+
body = {"arl": self._arl, "account_id": self._account_id}
|
|
352
362
|
|
|
353
363
|
session = self._get_session()
|
|
354
364
|
async with session.post(
|
|
355
365
|
self.AUTH_URL,
|
|
356
366
|
params=params,
|
|
357
367
|
cookies=self._request_cookies(self.AUTH_URL, include_arl=True),
|
|
368
|
+
json=body,
|
|
358
369
|
timeout=ClientTimeout(total=10),
|
|
359
370
|
) as resp:
|
|
360
371
|
self._store_cookies(resp)
|
|
@@ -385,6 +396,10 @@ class DeezerBaseClient:
|
|
|
385
396
|
msg = "Unexpected response from auth.deezer.com (invalid or missing JWT)"
|
|
386
397
|
raise GraphQLClientAuthError(msg) from exc
|
|
387
398
|
|
|
399
|
+
if self._account_id and str(payload.get("userId")) != self._account_id:
|
|
400
|
+
msg = f"auth.deezer.com did not sign in as Family profile {self._account_id}"
|
|
401
|
+
raise GraphQLClientAuthError(msg)
|
|
402
|
+
|
|
388
403
|
self._jwt = jwt
|
|
389
404
|
self._jwt_expires_at = expires_at
|
|
390
405
|
logger.debug("JWT acquired, expires at %s", self._jwt_expires_at)
|
|
@@ -65,6 +65,7 @@ from .get_audiobook_chapter import (
|
|
|
65
65
|
from .get_banned_artists import GetBannedArtists, GetBannedArtistsMe
|
|
66
66
|
from .get_banned_tracks import GetBannedTracks, GetBannedTracksMe
|
|
67
67
|
from .get_charts import GetCharts, GetChartsCharts
|
|
68
|
+
from .get_family import GetFamily, GetFamilyMe
|
|
68
69
|
from .get_favorite_albums import GetFavoriteAlbums, GetFavoriteAlbumsMe
|
|
69
70
|
from .get_favorite_artists import GetFavoriteArtists, GetFavoriteArtistsMe
|
|
70
71
|
from .get_favorite_audiobooks import GetFavoriteAudiobooks, GetFavoriteAudiobooksMe
|
|
@@ -1475,6 +1476,38 @@ class DeezerGQLClient(DeezerBaseClient):
|
|
|
1475
1476
|
data = self.get_data(response)
|
|
1476
1477
|
return GetCharts.model_validate(data).charts
|
|
1477
1478
|
|
|
1479
|
+
async def get_family(self, **kwargs: Any) -> Optional[GetFamilyMe]:
|
|
1480
|
+
query = gql("""
|
|
1481
|
+
query GetFamily {
|
|
1482
|
+
me {
|
|
1483
|
+
id
|
|
1484
|
+
family {
|
|
1485
|
+
main {
|
|
1486
|
+
...FamilyProfileFields
|
|
1487
|
+
}
|
|
1488
|
+
linked {
|
|
1489
|
+
...FamilyProfileFields
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
fragment FamilyProfileFields on FamilyUser {
|
|
1496
|
+
id
|
|
1497
|
+
name
|
|
1498
|
+
caption
|
|
1499
|
+
permissions {
|
|
1500
|
+
isLoggableAs
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
""")
|
|
1504
|
+
variables: dict[str, object] = {}
|
|
1505
|
+
response = await self.execute(
|
|
1506
|
+
query=query, operation_name="GetFamily", variables=variables, **kwargs
|
|
1507
|
+
)
|
|
1508
|
+
data = self.get_data(response)
|
|
1509
|
+
return GetFamily.model_validate(data).me
|
|
1510
|
+
|
|
1478
1511
|
async def get_favorite_albums(
|
|
1479
1512
|
self,
|
|
1480
1513
|
first: Union[Optional[int], UnsetType] = UNSET,
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/fragments.py
RENAMED
|
@@ -132,6 +132,17 @@ class AudiobookFieldsContributorsEdgesNodeArtist(BaseModel):
|
|
|
132
132
|
name: str
|
|
133
133
|
|
|
134
134
|
|
|
135
|
+
class FamilyProfileFields(BaseModel):
|
|
136
|
+
id: str
|
|
137
|
+
name: str
|
|
138
|
+
caption: str
|
|
139
|
+
permissions: "FamilyProfileFieldsPermissions"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class FamilyProfileFieldsPermissions(BaseModel):
|
|
143
|
+
is_loggable_as: bool = Field(alias="isLoggableAs")
|
|
144
|
+
|
|
145
|
+
|
|
135
146
|
class LivestreamFields(BaseModel):
|
|
136
147
|
id: str
|
|
137
148
|
name: str
|
|
@@ -292,6 +303,7 @@ AlbumFields.model_rebuild()
|
|
|
292
303
|
ArtistFields.model_rebuild()
|
|
293
304
|
AudiobookChapterFields.model_rebuild()
|
|
294
305
|
AudiobookFields.model_rebuild()
|
|
306
|
+
FamilyProfileFields.model_rebuild()
|
|
295
307
|
LivestreamFields.model_rebuild()
|
|
296
308
|
PageInfoFields.model_rebuild()
|
|
297
309
|
PlaylistFields.model_rebuild()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Generated by ariadne-codegen
|
|
2
|
+
# Source: queries
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from .base_model import BaseModel
|
|
7
|
+
from .fragments import FamilyProfileFields
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GetFamily(BaseModel):
|
|
11
|
+
me: Optional["GetFamilyMe"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GetFamilyMe(BaseModel):
|
|
15
|
+
id: str
|
|
16
|
+
family: Optional["GetFamilyMeFamily"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class GetFamilyMeFamily(BaseModel):
|
|
20
|
+
main: "GetFamilyMeFamilyMain"
|
|
21
|
+
linked: list["GetFamilyMeFamilyLinked"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GetFamilyMeFamilyMain(FamilyProfileFields):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GetFamilyMeFamilyLinked(FamilyProfileFields):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
GetFamily.model_rebuild()
|
|
33
|
+
GetFamilyMe.model_rebuild()
|
|
34
|
+
GetFamilyMeFamily.model_rebuild()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deezer-python-gql
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.0
|
|
4
4
|
Summary: Async typed Python client for Deezer's Pipe GraphQL API.
|
|
5
5
|
Author-email: Julian Daberkow <jdaberkow@users.noreply.github.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -22,7 +22,7 @@ Requires-Dist: pre-commit-hooks==6.0.0; extra == "test"
|
|
|
22
22
|
Requires-Dist: pytest==9.1.1; extra == "test"
|
|
23
23
|
Requires-Dist: pytest-asyncio==1.4.0; extra == "test"
|
|
24
24
|
Requires-Dist: pytest-cov==7.1.0; extra == "test"
|
|
25
|
-
Requires-Dist: ruff==0.16.
|
|
25
|
+
Requires-Dist: ruff==0.16.9; extra == "test"
|
|
26
26
|
Provides-Extra: dev
|
|
27
27
|
Requires-Dist: ariadne-codegen[subscriptions]; extra == "dev"
|
|
28
28
|
Dynamic: license-file
|
|
@@ -136,6 +136,7 @@ asyncio.run(main())
|
|
|
136
136
|
| `get_banned_tracks(first, after)` | Tracks banned from recommendations, with ban date |
|
|
137
137
|
| `get_banned_artists(first, after)` | Artists banned from recommendations, with ban date |
|
|
138
138
|
| `get_flow_tuner()` | Flow tuner: `DEFAULT` or `DISCOVERY` |
|
|
139
|
+
| `get_family()` | Deezer Family members, with `isLoggableAs` for usable profiles |
|
|
139
140
|
|
|
140
141
|
### Music Together (Collaborative Playlists)
|
|
141
142
|
|
|
@@ -286,6 +287,21 @@ If the ARL is rejected (bad or expired), the client raises
|
|
|
286
287
|
`GraphQLClientAuthError` — catch it to prompt for a new ARL instead of retrying.
|
|
287
288
|
Transient server errors raise `GraphQLClientHttpError` instead.
|
|
288
289
|
|
|
290
|
+
### Deezer Family profiles
|
|
291
|
+
|
|
292
|
+
Profiles of a Deezer Family have no ARL of their own, they share the login of the
|
|
293
|
+
Family admin. Pass `account_id` to act as one of them:
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
family = (await client.get_family()).family
|
|
297
|
+
profile = next(m for m in family.linked if m.permissions.is_loggable_as)
|
|
298
|
+
profile_client = DeezerGQLClient(arl="ADMIN_ARL", account_id=profile.id)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Members with their own login (independent accounts) are not loggable, they need their
|
|
302
|
+
own ARL. If Deezer does not sign in as the profile, the client raises
|
|
303
|
+
`GraphQLClientAuthError`.
|
|
304
|
+
|
|
289
305
|
## License
|
|
290
306
|
|
|
291
307
|
Apache-2.0
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql.egg-info/SOURCES.txt
RENAMED
|
@@ -37,6 +37,7 @@ deezer_python_gql/generated/get_audiobook_chapter.py
|
|
|
37
37
|
deezer_python_gql/generated/get_banned_artists.py
|
|
38
38
|
deezer_python_gql/generated/get_banned_tracks.py
|
|
39
39
|
deezer_python_gql/generated/get_charts.py
|
|
40
|
+
deezer_python_gql/generated/get_family.py
|
|
40
41
|
deezer_python_gql/generated/get_favorite_albums.py
|
|
41
42
|
deezer_python_gql/generated/get_favorite_artists.py
|
|
42
43
|
deezer_python_gql/generated/get_favorite_audiobooks.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "deezer-python-gql"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.20.0"
|
|
4
4
|
description = "Async typed Python client for Deezer's Pipe GraphQL API."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -30,7 +30,7 @@ test = [
|
|
|
30
30
|
"pytest==9.1.1",
|
|
31
31
|
"pytest-asyncio==1.4.0",
|
|
32
32
|
"pytest-cov==7.1.0",
|
|
33
|
-
"ruff==0.16.
|
|
33
|
+
"ruff==0.16.9",
|
|
34
34
|
]
|
|
35
35
|
dev = [
|
|
36
36
|
"ariadne-codegen[subscriptions]",
|
|
@@ -179,3 +179,8 @@ warn_unused_ignores = true
|
|
|
179
179
|
[tool.pytest.ini_options]
|
|
180
180
|
addopts = "--cov deezer_python_gql"
|
|
181
181
|
asyncio_mode = "auto"
|
|
182
|
+
|
|
183
|
+
[tool.uv]
|
|
184
|
+
override-dependencies = [
|
|
185
|
+
"ruff==0.16.9",
|
|
186
|
+
]
|
|
@@ -57,6 +57,7 @@ from deezer_python_gql.generated.get_audiobook_chapter import GetAudiobookChapte
|
|
|
57
57
|
from deezer_python_gql.generated.get_banned_artists import GetBannedArtists
|
|
58
58
|
from deezer_python_gql.generated.get_banned_tracks import GetBannedTracks
|
|
59
59
|
from deezer_python_gql.generated.get_charts import GetCharts
|
|
60
|
+
from deezer_python_gql.generated.get_family import GetFamily
|
|
60
61
|
from deezer_python_gql.generated.get_favorite_albums import GetFavoriteAlbums
|
|
61
62
|
from deezer_python_gql.generated.get_favorite_artists import GetFavoriteArtists
|
|
62
63
|
from deezer_python_gql.generated.get_favorite_audiobooks import GetFavoriteAudiobooks
|
|
@@ -147,11 +148,14 @@ def _load_fixture(name: str) -> dict[str, Any]:
|
|
|
147
148
|
return result
|
|
148
149
|
|
|
149
150
|
|
|
150
|
-
def _make_jwt(
|
|
151
|
+
def _make_jwt(
|
|
152
|
+
exp: float | None = None, subject: str | None = None, user_id: str | None = None
|
|
153
|
+
) -> str:
|
|
151
154
|
"""
|
|
152
155
|
Build a fake JWT with a configurable expiration timestamp.
|
|
153
156
|
|
|
154
157
|
:param exp: Unix timestamp for JWT expiry. Defaults to 6 min from now.
|
|
158
|
+
:param user_id: Optional ``userId`` claim, which Deezer sets to the account id.
|
|
155
159
|
"""
|
|
156
160
|
if exp is None:
|
|
157
161
|
exp = time.time() + 360 # 6 min, matching Deezer's real TTL
|
|
@@ -159,6 +163,8 @@ def _make_jwt(exp: float | None = None, subject: str | None = None) -> str:
|
|
|
159
163
|
payload_data: dict[str, float | str] = {"exp": exp}
|
|
160
164
|
if subject is not None:
|
|
161
165
|
payload_data["sub"] = subject
|
|
166
|
+
if user_id is not None:
|
|
167
|
+
payload_data["userId"] = user_id
|
|
162
168
|
payload = base64.urlsafe_b64encode(json.dumps(payload_data).encode()).rstrip(b"=").decode()
|
|
163
169
|
return f"{header}.{payload}.fake_signature"
|
|
164
170
|
|
|
@@ -525,6 +531,39 @@ async def test_auth_sends_arl_cookie_to_correct_domain() -> None:
|
|
|
525
531
|
assert auth_call.kwargs["cookies"] == {"arl": "my_secret_arl"}
|
|
526
532
|
|
|
527
533
|
|
|
534
|
+
@pytest.mark.asyncio
|
|
535
|
+
async def test_auth_family_profile_signs_in_with_account_id() -> None:
|
|
536
|
+
"""Verify a Family profile sends the admin's ARL and its id in the payload."""
|
|
537
|
+
client = DeezerBaseClient(arl="admin_arl", account_id="456")
|
|
538
|
+
|
|
539
|
+
with patch("deezer_python_gql.base_client.ClientSession") as mock_cls:
|
|
540
|
+
mock_instance = MagicMock()
|
|
541
|
+
mock_instance.post = MagicMock(return_value=_mock_auth_cm(_make_jwt(user_id="456")))
|
|
542
|
+
mock_instance.close = AsyncMock()
|
|
543
|
+
mock_cls.return_value = mock_instance
|
|
544
|
+
|
|
545
|
+
await client._ensure_jwt() # noqa: SLF001
|
|
546
|
+
|
|
547
|
+
auth_call = mock_instance.post.call_args
|
|
548
|
+
assert auth_call.kwargs["params"]["i"] == "p"
|
|
549
|
+
assert auth_call.kwargs["json"] == {"arl": "admin_arl", "account_id": "456"}
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
@pytest.mark.asyncio
|
|
553
|
+
async def test_auth_family_profile_rejects_a_token_for_another_account() -> None:
|
|
554
|
+
"""Verify a token for another account is never used for a Family profile."""
|
|
555
|
+
client = DeezerBaseClient(arl="admin_arl", account_id="456")
|
|
556
|
+
|
|
557
|
+
with patch("deezer_python_gql.base_client.ClientSession") as mock_cls:
|
|
558
|
+
mock_instance = MagicMock()
|
|
559
|
+
mock_instance.post = MagicMock(return_value=_mock_auth_cm(_make_jwt(user_id="123")))
|
|
560
|
+
mock_instance.close = AsyncMock()
|
|
561
|
+
mock_cls.return_value = mock_instance
|
|
562
|
+
|
|
563
|
+
with pytest.raises(GraphQLClientAuthError, match="Family profile 456"):
|
|
564
|
+
await client._ensure_jwt() # noqa: SLF001
|
|
565
|
+
|
|
566
|
+
|
|
528
567
|
@pytest.mark.parametrize(("first_arl", "second_arl"), [("arl-a", "arl-b"), ("arl-b", "arl-a")])
|
|
529
568
|
@pytest.mark.asyncio
|
|
530
569
|
async def test_shared_session_keeps_account_cookies_isolated(
|
|
@@ -1969,3 +2008,16 @@ def test_smoke_get_infinite_track_mix() -> None:
|
|
|
1969
2008
|
data = _load_fixture("get_infinite_track_mix.json")
|
|
1970
2009
|
mix = GetInfiniteTrackMix.model_validate(data).raw_infinite_track_mix
|
|
1971
2010
|
assert [t.id for t in mix.tracks] == ["3135554", "3155977", "10284909"]
|
|
2011
|
+
|
|
2012
|
+
|
|
2013
|
+
def test_smoke_get_family() -> None:
|
|
2014
|
+
"""Verify GetFamily fixture parses the admin, a profile and an independent account."""
|
|
2015
|
+
data = _load_fixture("get_family.json")
|
|
2016
|
+
me = GetFamily.model_validate(data).me
|
|
2017
|
+
assert me is not None
|
|
2018
|
+
assert me.family is not None
|
|
2019
|
+
assert me.family.main.id == me.id
|
|
2020
|
+
assert [(m.id, m.permissions.is_loggable_as) for m in me.family.linked] == [
|
|
2021
|
+
("2002", True),
|
|
2022
|
+
("3003", False),
|
|
2023
|
+
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/base_model.py
RENAMED
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/create_playlist.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/delete_playlist.py
RENAMED
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_album.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_artist.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_artist_mix.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_audiobook.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_charts.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_flow.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_flow_batch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_flow_tuner.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_livestream.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_made_for_me.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_playlist.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_podcast.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_track.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_track_mix.py
RENAMED
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/get_user_charts.py
RENAMED
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/input_types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/search_flows.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql/generated/update_playlist.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{deezer_python_gql-0.19.0 → deezer_python_gql-0.20.0}/deezer_python_gql.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|