deezer-python-gql 0.21.0__tar.gz → 0.22.2__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.21.0/deezer_python_gql.egg-info → deezer_python_gql-0.22.2}/PKG-INFO +5 -5
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/base_client.py +9 -4
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/__init__.py +26 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/base_client.py +9 -4
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/client.py +37 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/enums.py +14 -0
- deezer_python_gql-0.22.2/deezer_python_gql/generated/input_types.py +62 -0
- deezer_python_gql-0.22.2/deezer_python_gql/generated/push_queue_state.py +39 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2/deezer_python_gql.egg-info}/PKG-INFO +5 -5
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql.egg-info/SOURCES.txt +1 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql.egg-info/requires.txt +4 -4
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/pyproject.toml +6 -6
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/tests/test_client.py +98 -5
- deezer_python_gql-0.21.0/deezer_python_gql/generated/input_types.py +0 -3
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/LICENSE +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/MANIFEST.in +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/README.md +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/__init__.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/add_album_to_favorite.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/add_artist_to_favorite.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/add_audiobook_to_favorite.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/add_playlist_to_favorite.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/add_podcast_to_favorite.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/add_track_to_favorite.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/add_tracks_to_playlist.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/ban_artist_from_recommendation.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/ban_track_from_recommendation.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/base_model.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/bookmark_podcast_episode.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/create_playlist.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/delete_playlist.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/fragments.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_album.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_artist.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_artist_mix.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_audiobook.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_audiobook_chapter.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_banned_artists.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_banned_tracks.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_charts.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_family.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_favorite_albums.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_favorite_artists.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_favorite_audiobooks.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_favorite_playlists.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_favorite_podcasts.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_favorite_tracks.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_flow.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_flow_batch.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_flow_config_tracks.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_flow_configs.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_flow_tuner.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_infinite_track_mix.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_latest_podcast_episodes.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_livestream.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_made_for_me.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_me.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_music_together_affinity.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_music_together_group.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_music_together_groups.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_personal_tracks.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_playlist.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_podcast.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_podcast_episode.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_podcast_episode_bookmarks.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_podcast_episodes_by_ids.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_recently_played.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_recommendations.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_similar_artists.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_similar_tracks.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_smart_tracklist.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_track.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_track_mix.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_tracks_by_ids.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_user_charts.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_user_playlists.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/mark_as_not_played_podcast_episode.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/mark_as_played_podcast_episode.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/move_track_in_playlist.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/music_together_create_group.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/music_together_generate_group_name.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/music_together_join_group.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/music_together_leave_group.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/music_together_refresh_suggested_tracklist.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/music_together_update_group_settings.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/remove_album_from_favorite.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/remove_artist_from_favorite.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/remove_audiobook_from_favorite.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/remove_playlist_from_favorite.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/remove_podcast_from_favorite.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/remove_track_from_favorite.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/remove_tracks_from_playlist.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/search.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/search_flows.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/set_flow_discovery_tuner.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/unban_artist_from_recommendation.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/unban_track_from_recommendation.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/unbookmark_podcast_episode.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/update_playlist.py +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/py.typed +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql.egg-info/dependency_links.txt +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql.egg-info/not-zip-safe +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql.egg-info/top_level.txt +0 -0
- {deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/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.22.2
|
|
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
|
|
@@ -16,13 +16,13 @@ Requires-Dist: pydantic>=2.0.0
|
|
|
16
16
|
Requires-Dist: yarl>=1.22.0
|
|
17
17
|
Provides-Extra: test
|
|
18
18
|
Requires-Dist: codespell==2.4.3; extra == "test"
|
|
19
|
-
Requires-Dist: mypy==2.4.
|
|
19
|
+
Requires-Dist: mypy==2.4.1; extra == "test"
|
|
20
20
|
Requires-Dist: pre-commit==4.7.0; extra == "test"
|
|
21
21
|
Requires-Dist: pre-commit-hooks==6.0.0; extra == "test"
|
|
22
|
-
Requires-Dist: pytest==9.
|
|
23
|
-
Requires-Dist: pytest-asyncio==1.4.1; extra == "test"
|
|
22
|
+
Requires-Dist: pytest==9.2.0; extra == "test"
|
|
23
|
+
Requires-Dist: pytest-asyncio==1.4.1.post1; extra == "test"
|
|
24
24
|
Requires-Dist: pytest-cov==7.1.0; extra == "test"
|
|
25
|
-
Requires-Dist: ruff==0.17.
|
|
25
|
+
Requires-Dist: ruff==0.17.3; extra == "test"
|
|
26
26
|
Provides-Extra: dev
|
|
27
27
|
Requires-Dist: ariadne-codegen[subscriptions]; extra == "dev"
|
|
28
28
|
Dynamic: license-file
|
|
@@ -11,6 +11,7 @@ from dataclasses import dataclass
|
|
|
11
11
|
from typing import TYPE_CHECKING, Any, ClassVar, Self, cast
|
|
12
12
|
|
|
13
13
|
from aiohttp import ClientSession, ClientTimeout
|
|
14
|
+
from pydantic_core import to_jsonable_python
|
|
14
15
|
from yarl import URL
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
@@ -227,8 +228,11 @@ class DeezerBaseClient:
|
|
|
227
228
|
# Inline import avoids depending on generated code at module level.
|
|
228
229
|
from deezer_python_gql.generated.base_model import UnsetType # noqa: PLC0415
|
|
229
230
|
|
|
231
|
+
# input types are pydantic models, sent under their GraphQL field names
|
|
230
232
|
payload["variables"] = {
|
|
231
|
-
k:
|
|
233
|
+
k: to_jsonable_python(v, by_alias=True)
|
|
234
|
+
for k, v in variables.items()
|
|
235
|
+
if not isinstance(v, UnsetType)
|
|
232
236
|
}
|
|
233
237
|
|
|
234
238
|
session = self._get_session()
|
|
@@ -284,11 +288,11 @@ class DeezerBaseClient:
|
|
|
284
288
|
variables = response.variables
|
|
285
289
|
if data:
|
|
286
290
|
# Partial success — some items failed (e.g. deleted albums in favorites).
|
|
287
|
-
#
|
|
291
|
+
# That is expected, so log at debug level and return the valid data.
|
|
288
292
|
error_details = [
|
|
289
293
|
f"{e.get('message', 'Unknown error')} (path={e.get('path')})" for e in errors
|
|
290
294
|
]
|
|
291
|
-
logger.
|
|
295
|
+
logger.debug(
|
|
292
296
|
"GraphQL response for %s (variables=%s) contained %d error(s): %s",
|
|
293
297
|
op,
|
|
294
298
|
variables,
|
|
@@ -373,7 +377,8 @@ class DeezerBaseClient:
|
|
|
373
377
|
text = await resp.text()
|
|
374
378
|
|
|
375
379
|
if not is_success:
|
|
376
|
-
|
|
380
|
+
# 429 is rate limiting, not a rejected ARL
|
|
381
|
+
if 400 <= status < 500 and status != 429:
|
|
377
382
|
msg = f"ARL authentication rejected by auth.deezer.com (HTTP {status})"
|
|
378
383
|
raise GraphQLClientAuthError(msg)
|
|
379
384
|
raise GraphQLClientHttpError(
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/__init__.py
RENAMED
|
@@ -63,10 +63,12 @@ from .enums import (
|
|
|
63
63
|
AudiobookContributorRoles,
|
|
64
64
|
DiscoveryTuner,
|
|
65
65
|
DiscoveryTunerInput,
|
|
66
|
+
MediaTypeInput,
|
|
66
67
|
MusicTogetherRefreshSuggestedTracklistMoodInput,
|
|
67
68
|
MusicTogetherSuggestedTracklistMoodInput,
|
|
68
69
|
PodcastEpisodeOrder,
|
|
69
70
|
PodcastType,
|
|
71
|
+
RepeatModeInput,
|
|
70
72
|
TrackContributorRoles,
|
|
71
73
|
)
|
|
72
74
|
from .fragments import (
|
|
@@ -551,6 +553,12 @@ from .get_user_playlists import (
|
|
|
551
553
|
GetUserPlaylistsMePlaylistsEdgesNode,
|
|
552
554
|
GetUserPlaylistsMePlaylistsPageInfo,
|
|
553
555
|
)
|
|
556
|
+
from .input_types import (
|
|
557
|
+
PushQueueContextInput,
|
|
558
|
+
PushQueueMediaInput,
|
|
559
|
+
PushQueueStateClientDetailsInput,
|
|
560
|
+
PushQueueStateInput,
|
|
561
|
+
)
|
|
554
562
|
from .mark_as_not_played_podcast_episode import (
|
|
555
563
|
MarkAsNotPlayedPodcastEpisode,
|
|
556
564
|
MarkAsNotPlayedPodcastEpisodeMarkAsNotPlayedPodcastEpisode,
|
|
@@ -599,6 +607,13 @@ from .music_together_update_group_settings import (
|
|
|
599
607
|
MusicTogetherUpdateGroupSettingsMusicTogetherUpdateGroupSettingsMusicTogetherUpdateGroupSettingsOutput,
|
|
600
608
|
MusicTogetherUpdateGroupSettingsMusicTogetherUpdateGroupSettingsMusicTogetherUpdateGroupSettingsOutputGroup,
|
|
601
609
|
)
|
|
610
|
+
from .push_queue_state import (
|
|
611
|
+
PushQueueState,
|
|
612
|
+
PushQueueStatePushQueueStatePushQueueStateError,
|
|
613
|
+
PushQueueStatePushQueueStatePushQueueStateErrorConflict,
|
|
614
|
+
PushQueueStatePushQueueStatePushQueueStateErrorConflictCurrent,
|
|
615
|
+
PushQueueStatePushQueueStatePushQueueStateOutput,
|
|
616
|
+
)
|
|
602
617
|
from .remove_album_from_favorite import (
|
|
603
618
|
RemoveAlbumFromFavorite,
|
|
604
619
|
RemoveAlbumFromFavoriteRemoveAlbumFromFavorite,
|
|
@@ -1147,6 +1162,7 @@ __all__ = [
|
|
|
1147
1162
|
"MarkAsPlayedPodcastEpisode",
|
|
1148
1163
|
"MarkAsPlayedPodcastEpisodeMarkAsPlayedPodcastEpisode",
|
|
1149
1164
|
"MarkAsPlayedPodcastEpisodeMarkAsPlayedPodcastEpisodeBookmark",
|
|
1165
|
+
"MediaTypeInput",
|
|
1150
1166
|
"MoveTrackInPlaylist",
|
|
1151
1167
|
"MoveTrackInPlaylistMoveTrackInPlaylist",
|
|
1152
1168
|
"MusicTogetherCreateGroup",
|
|
@@ -1185,6 +1201,15 @@ __all__ = [
|
|
|
1185
1201
|
"PodcastFields",
|
|
1186
1202
|
"PodcastFieldsCover",
|
|
1187
1203
|
"PodcastType",
|
|
1204
|
+
"PushQueueContextInput",
|
|
1205
|
+
"PushQueueMediaInput",
|
|
1206
|
+
"PushQueueState",
|
|
1207
|
+
"PushQueueStateClientDetailsInput",
|
|
1208
|
+
"PushQueueStateInput",
|
|
1209
|
+
"PushQueueStatePushQueueStatePushQueueStateError",
|
|
1210
|
+
"PushQueueStatePushQueueStatePushQueueStateErrorConflict",
|
|
1211
|
+
"PushQueueStatePushQueueStatePushQueueStateErrorConflictCurrent",
|
|
1212
|
+
"PushQueueStatePushQueueStatePushQueueStateOutput",
|
|
1188
1213
|
"RemoveAlbumFromFavorite",
|
|
1189
1214
|
"RemoveAlbumFromFavoriteRemoveAlbumFromFavorite",
|
|
1190
1215
|
"RemoveAlbumFromFavoriteRemoveAlbumFromFavoriteAlbum",
|
|
@@ -1204,6 +1229,7 @@ __all__ = [
|
|
|
1204
1229
|
"RemoveTrackFromFavoriteRemoveTrackFromFavoriteTrack",
|
|
1205
1230
|
"RemoveTracksFromPlaylist",
|
|
1206
1231
|
"RemoveTracksFromPlaylistRemoveTracksFromPlaylist",
|
|
1232
|
+
"RepeatModeInput",
|
|
1207
1233
|
"Search",
|
|
1208
1234
|
"SearchFlows",
|
|
1209
1235
|
"SearchFlowsSearch",
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/base_client.py
RENAMED
|
@@ -13,6 +13,7 @@ from dataclasses import dataclass
|
|
|
13
13
|
from typing import TYPE_CHECKING, Any, ClassVar, Self, cast
|
|
14
14
|
|
|
15
15
|
from aiohttp import ClientSession, ClientTimeout
|
|
16
|
+
from pydantic_core import to_jsonable_python
|
|
16
17
|
from yarl import URL
|
|
17
18
|
|
|
18
19
|
if TYPE_CHECKING:
|
|
@@ -229,8 +230,11 @@ class DeezerBaseClient:
|
|
|
229
230
|
# Inline import avoids depending on generated code at module level.
|
|
230
231
|
from deezer_python_gql.generated.base_model import UnsetType # noqa: PLC0415
|
|
231
232
|
|
|
233
|
+
# input types are pydantic models, sent under their GraphQL field names
|
|
232
234
|
payload["variables"] = {
|
|
233
|
-
k:
|
|
235
|
+
k: to_jsonable_python(v, by_alias=True)
|
|
236
|
+
for k, v in variables.items()
|
|
237
|
+
if not isinstance(v, UnsetType)
|
|
234
238
|
}
|
|
235
239
|
|
|
236
240
|
session = self._get_session()
|
|
@@ -286,11 +290,11 @@ class DeezerBaseClient:
|
|
|
286
290
|
variables = response.variables
|
|
287
291
|
if data:
|
|
288
292
|
# Partial success — some items failed (e.g. deleted albums in favorites).
|
|
289
|
-
#
|
|
293
|
+
# That is expected, so log at debug level and return the valid data.
|
|
290
294
|
error_details = [
|
|
291
295
|
f"{e.get('message', 'Unknown error')} (path={e.get('path')})" for e in errors
|
|
292
296
|
]
|
|
293
|
-
logger.
|
|
297
|
+
logger.debug(
|
|
294
298
|
"GraphQL response for %s (variables=%s) contained %d error(s): %s",
|
|
295
299
|
op,
|
|
296
300
|
variables,
|
|
@@ -375,7 +379,8 @@ class DeezerBaseClient:
|
|
|
375
379
|
text = await resp.text()
|
|
376
380
|
|
|
377
381
|
if not is_success:
|
|
378
|
-
|
|
382
|
+
# 429 is rate limiting, not a rejected ARL
|
|
383
|
+
if 400 <= status < 500 and status != 429:
|
|
379
384
|
msg = f"ARL authentication rejected by auth.deezer.com (HTTP {status})"
|
|
380
385
|
raise GraphQLClientAuthError(msg)
|
|
381
386
|
raise GraphQLClientHttpError(
|
|
@@ -119,6 +119,7 @@ from .get_track_mix import GetTrackMix, GetTrackMixTrackMix
|
|
|
119
119
|
from .get_tracks_by_ids import GetTracksByIds, GetTracksByIdsTracksByIds
|
|
120
120
|
from .get_user_charts import GetUserCharts, GetUserChartsMe
|
|
121
121
|
from .get_user_playlists import GetUserPlaylists, GetUserPlaylistsMe
|
|
122
|
+
from .input_types import PushQueueStateInput
|
|
122
123
|
from .mark_as_not_played_podcast_episode import (
|
|
123
124
|
MarkAsNotPlayedPodcastEpisode,
|
|
124
125
|
MarkAsNotPlayedPodcastEpisodeMarkAsNotPlayedPodcastEpisode,
|
|
@@ -160,6 +161,11 @@ from .music_together_update_group_settings import (
|
|
|
160
161
|
MusicTogetherUpdateGroupSettingsMusicTogetherUpdateGroupSettingsMusicTogetherUpdateGroupSettingsError,
|
|
161
162
|
MusicTogetherUpdateGroupSettingsMusicTogetherUpdateGroupSettingsMusicTogetherUpdateGroupSettingsOutput,
|
|
162
163
|
)
|
|
164
|
+
from .push_queue_state import (
|
|
165
|
+
PushQueueState,
|
|
166
|
+
PushQueueStatePushQueueStatePushQueueStateError,
|
|
167
|
+
PushQueueStatePushQueueStatePushQueueStateOutput,
|
|
168
|
+
)
|
|
163
169
|
from .remove_album_from_favorite import (
|
|
164
170
|
RemoveAlbumFromFavorite,
|
|
165
171
|
RemoveAlbumFromFavoriteRemoveAlbumFromFavorite,
|
|
@@ -4469,6 +4475,37 @@ class DeezerGQLClient(DeezerBaseClient):
|
|
|
4469
4475
|
data = self.get_data(response)
|
|
4470
4476
|
return MoveTrackInPlaylist.model_validate(data).move_track_in_playlist
|
|
4471
4477
|
|
|
4478
|
+
async def push_queue_state(
|
|
4479
|
+
self, input: PushQueueStateInput, **kwargs: Any
|
|
4480
|
+
) -> Union[
|
|
4481
|
+
PushQueueStatePushQueueStatePushQueueStateOutput,
|
|
4482
|
+
PushQueueStatePushQueueStatePushQueueStateError,
|
|
4483
|
+
]:
|
|
4484
|
+
query = gql("""
|
|
4485
|
+
mutation PushQueueState($input: PushQueueStateInput!) {
|
|
4486
|
+
pushQueueState(input: $input) {
|
|
4487
|
+
__typename
|
|
4488
|
+
... on PushQueueStateOutput {
|
|
4489
|
+
version
|
|
4490
|
+
}
|
|
4491
|
+
... on PushQueueStateError {
|
|
4492
|
+
isBadRequest
|
|
4493
|
+
conflict {
|
|
4494
|
+
current {
|
|
4495
|
+
version
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
""")
|
|
4502
|
+
variables: dict[str, object] = {"input": input}
|
|
4503
|
+
response = await self.execute(
|
|
4504
|
+
query=query, operation_name="PushQueueState", variables=variables, **kwargs
|
|
4505
|
+
)
|
|
4506
|
+
data = self.get_data(response)
|
|
4507
|
+
return PushQueueState.model_validate(data).push_queue_state
|
|
4508
|
+
|
|
4472
4509
|
async def ban_track_from_recommendation(
|
|
4473
4510
|
self, track_id: str, **kwargs: Any
|
|
4474
4511
|
) -> BanTrackFromRecommendationBanTrackFromRecommendation:
|
|
@@ -46,6 +46,20 @@ class PodcastEpisodeOrder(str, Enum):
|
|
|
46
46
|
LATEST = "LATEST"
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
class MediaTypeInput(str, Enum):
|
|
50
|
+
TRACK = "TRACK"
|
|
51
|
+
EPISODE = "EPISODE"
|
|
52
|
+
CHAPTER = "CHAPTER"
|
|
53
|
+
LIVE = "LIVE"
|
|
54
|
+
RAW_URL = "RAW_URL"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RepeatModeInput(str, Enum):
|
|
58
|
+
NONE = "NONE"
|
|
59
|
+
ONE = "ONE"
|
|
60
|
+
ALL = "ALL"
|
|
61
|
+
|
|
62
|
+
|
|
49
63
|
class DiscoveryTuner(str, Enum):
|
|
50
64
|
DEFAULT = "DEFAULT"
|
|
51
65
|
DISCOVERY = "DISCOVERY"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Generated by ariadne-codegen
|
|
2
|
+
# Source: schema.graphql
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from .base_model import BaseModel
|
|
9
|
+
from .enums import MediaTypeInput, RepeatModeInput
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PushQueueStateInput(BaseModel):
|
|
13
|
+
client_details: "PushQueueStateClientDetailsInput" = Field(alias="clientDetails")
|
|
14
|
+
"Details about the client pushing the queue state"
|
|
15
|
+
media_list: list["PushQueueMediaInput"] = Field(alias="mediaList")
|
|
16
|
+
"List of all media in the queue in playing order"
|
|
17
|
+
position: int
|
|
18
|
+
"Zero based index of the current playback position in the queue"
|
|
19
|
+
progress_ms: Optional[int] = Field(alias="progressMS", default=None)
|
|
20
|
+
"Progress in the current media in ms"
|
|
21
|
+
previous_version: Optional[str] = Field(alias="previousVersion", default=None)
|
|
22
|
+
"Previous known queue state version\nNote: Except when there is no queue state available, this version MUST be provided otherwise you'll get a conflict error"
|
|
23
|
+
queue_configuration: str = Field(alias="queueConfiguration")
|
|
24
|
+
"Queue's configuration"
|
|
25
|
+
repeat_mode: RepeatModeInput = Field(alias="repeatMode")
|
|
26
|
+
"Queue repeat mode"
|
|
27
|
+
contexts: Optional[list["PushQueueContextInput"]] = None
|
|
28
|
+
"The list of unique contexts referenced by mediaList.contextId"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PushQueueMediaInput(BaseModel):
|
|
32
|
+
"""Details of media that belongs to the pushed queue"""
|
|
33
|
+
|
|
34
|
+
id: str
|
|
35
|
+
"The media id"
|
|
36
|
+
type_: MediaTypeInput = Field(alias="type")
|
|
37
|
+
"The media type"
|
|
38
|
+
context_id: Optional[str] = Field(alias="contextId", default=None)
|
|
39
|
+
'The context identifier, it should correspond to an entry in "PushQueueStateInput.contexts"\nEach media can have its own context or share multiple time the same context by using the same contextId'
|
|
40
|
+
local_queue_item_id: Optional[str] = Field(alias="localQueueItemId", default=None)
|
|
41
|
+
"Item identifier used on client side"
|
|
42
|
+
unshuffled_position: int = Field(alias="unshuffledPosition")
|
|
43
|
+
"The position within the unshuffled version of the queue"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class PushQueueContextInput(BaseModel):
|
|
47
|
+
id: str
|
|
48
|
+
"Context identifier"
|
|
49
|
+
content: str
|
|
50
|
+
"The context content itself"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class PushQueueStateClientDetailsInput(BaseModel):
|
|
54
|
+
"""Details about the client that wants to push the queue state"""
|
|
55
|
+
|
|
56
|
+
local_queue_id: str = Field(alias="localQueueID")
|
|
57
|
+
"Identifier of the client's local queue, doesn't necessarily match a real queue id"
|
|
58
|
+
locally_updated_at: str = Field(alias="locallyUpdatedAt")
|
|
59
|
+
"Date in ISO-8601 format including milliseconds when update was made on client"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
PushQueueStateInput.model_rebuild()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Generated by ariadne-codegen
|
|
2
|
+
# Source: queries
|
|
3
|
+
|
|
4
|
+
from typing import Literal, Optional, Union
|
|
5
|
+
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from .base_model import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PushQueueState(BaseModel):
|
|
12
|
+
push_queue_state: Union[
|
|
13
|
+
"PushQueueStatePushQueueStatePushQueueStateOutput",
|
|
14
|
+
"PushQueueStatePushQueueStatePushQueueStateError",
|
|
15
|
+
] = Field(alias="pushQueueState", discriminator="typename__")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PushQueueStatePushQueueStatePushQueueStateOutput(BaseModel):
|
|
19
|
+
typename__: Literal["PushQueueStateOutput"] = Field(alias="__typename")
|
|
20
|
+
version: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PushQueueStatePushQueueStatePushQueueStateError(BaseModel):
|
|
24
|
+
typename__: Literal["PushQueueStateError"] = Field(alias="__typename")
|
|
25
|
+
is_bad_request: bool = Field(alias="isBadRequest")
|
|
26
|
+
conflict: Optional["PushQueueStatePushQueueStatePushQueueStateErrorConflict"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PushQueueStatePushQueueStatePushQueueStateErrorConflict(BaseModel):
|
|
30
|
+
current: Optional["PushQueueStatePushQueueStatePushQueueStateErrorConflictCurrent"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PushQueueStatePushQueueStatePushQueueStateErrorConflictCurrent(BaseModel):
|
|
34
|
+
version: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
PushQueueState.model_rebuild()
|
|
38
|
+
PushQueueStatePushQueueStatePushQueueStateError.model_rebuild()
|
|
39
|
+
PushQueueStatePushQueueStatePushQueueStateErrorConflict.model_rebuild()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deezer-python-gql
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.22.2
|
|
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
|
|
@@ -16,13 +16,13 @@ Requires-Dist: pydantic>=2.0.0
|
|
|
16
16
|
Requires-Dist: yarl>=1.22.0
|
|
17
17
|
Provides-Extra: test
|
|
18
18
|
Requires-Dist: codespell==2.4.3; extra == "test"
|
|
19
|
-
Requires-Dist: mypy==2.4.
|
|
19
|
+
Requires-Dist: mypy==2.4.1; extra == "test"
|
|
20
20
|
Requires-Dist: pre-commit==4.7.0; extra == "test"
|
|
21
21
|
Requires-Dist: pre-commit-hooks==6.0.0; extra == "test"
|
|
22
|
-
Requires-Dist: pytest==9.
|
|
23
|
-
Requires-Dist: pytest-asyncio==1.4.1; extra == "test"
|
|
22
|
+
Requires-Dist: pytest==9.2.0; extra == "test"
|
|
23
|
+
Requires-Dist: pytest-asyncio==1.4.1.post1; extra == "test"
|
|
24
24
|
Requires-Dist: pytest-cov==7.1.0; extra == "test"
|
|
25
|
-
Requires-Dist: ruff==0.17.
|
|
25
|
+
Requires-Dist: ruff==0.17.3; extra == "test"
|
|
26
26
|
Provides-Extra: dev
|
|
27
27
|
Requires-Dist: ariadne-codegen[subscriptions]; extra == "dev"
|
|
28
28
|
Dynamic: license-file
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql.egg-info/SOURCES.txt
RENAMED
|
@@ -83,6 +83,7 @@ deezer_python_gql/generated/music_together_join_group.py
|
|
|
83
83
|
deezer_python_gql/generated/music_together_leave_group.py
|
|
84
84
|
deezer_python_gql/generated/music_together_refresh_suggested_tracklist.py
|
|
85
85
|
deezer_python_gql/generated/music_together_update_group_settings.py
|
|
86
|
+
deezer_python_gql/generated/push_queue_state.py
|
|
86
87
|
deezer_python_gql/generated/remove_album_from_favorite.py
|
|
87
88
|
deezer_python_gql/generated/remove_artist_from_favorite.py
|
|
88
89
|
deezer_python_gql/generated/remove_audiobook_from_favorite.py
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql.egg-info/requires.txt
RENAMED
|
@@ -7,10 +7,10 @@ ariadne-codegen[subscriptions]
|
|
|
7
7
|
|
|
8
8
|
[test]
|
|
9
9
|
codespell==2.4.3
|
|
10
|
-
mypy==2.4.
|
|
10
|
+
mypy==2.4.1
|
|
11
11
|
pre-commit==4.7.0
|
|
12
12
|
pre-commit-hooks==6.0.0
|
|
13
|
-
pytest==9.
|
|
14
|
-
pytest-asyncio==1.4.1
|
|
13
|
+
pytest==9.2.0
|
|
14
|
+
pytest-asyncio==1.4.1.post1
|
|
15
15
|
pytest-cov==7.1.0
|
|
16
|
-
ruff==0.17.
|
|
16
|
+
ruff==0.17.3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "deezer-python-gql"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.22.2"
|
|
4
4
|
description = "Async typed Python client for Deezer's Pipe GraphQL API."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -24,13 +24,13 @@ text = "Apache-2.0"
|
|
|
24
24
|
[project.optional-dependencies]
|
|
25
25
|
test = [
|
|
26
26
|
"codespell==2.4.3",
|
|
27
|
-
"mypy==2.4.
|
|
27
|
+
"mypy==2.4.1",
|
|
28
28
|
"pre-commit==4.7.0",
|
|
29
29
|
"pre-commit-hooks==6.0.0",
|
|
30
|
-
"pytest==9.
|
|
31
|
-
"pytest-asyncio==1.4.1",
|
|
30
|
+
"pytest==9.2.0",
|
|
31
|
+
"pytest-asyncio==1.4.1.post1",
|
|
32
32
|
"pytest-cov==7.1.0",
|
|
33
|
-
"ruff==0.17.
|
|
33
|
+
"ruff==0.17.3",
|
|
34
34
|
]
|
|
35
35
|
dev = [
|
|
36
36
|
"ariadne-codegen[subscriptions]",
|
|
@@ -182,5 +182,5 @@ asyncio_mode = "auto"
|
|
|
182
182
|
|
|
183
183
|
[tool.uv]
|
|
184
184
|
override-dependencies = [
|
|
185
|
-
"ruff==0.17.
|
|
185
|
+
"ruff==0.17.3",
|
|
186
186
|
]
|
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
import asyncio
|
|
13
13
|
import base64
|
|
14
14
|
import json
|
|
15
|
+
import logging
|
|
15
16
|
import time
|
|
16
17
|
from http.cookies import SimpleCookie
|
|
17
18
|
from pathlib import Path
|
|
@@ -48,7 +49,7 @@ from deezer_python_gql.generated.ban_track_from_recommendation import (
|
|
|
48
49
|
from deezer_python_gql.generated.bookmark_podcast_episode import BookmarkPodcastEpisode
|
|
49
50
|
from deezer_python_gql.generated.create_playlist import CreatePlaylist
|
|
50
51
|
from deezer_python_gql.generated.delete_playlist import DeletePlaylist
|
|
51
|
-
from deezer_python_gql.generated.enums import PodcastType
|
|
52
|
+
from deezer_python_gql.generated.enums import MediaTypeInput, PodcastType, RepeatModeInput
|
|
52
53
|
from deezer_python_gql.generated.get_album import GetAlbum
|
|
53
54
|
from deezer_python_gql.generated.get_artist import GetArtist
|
|
54
55
|
from deezer_python_gql.generated.get_artist_mix import GetArtistMix
|
|
@@ -99,6 +100,11 @@ from deezer_python_gql.generated.get_track_mix import GetTrackMix
|
|
|
99
100
|
from deezer_python_gql.generated.get_tracks_by_ids import GetTracksByIds
|
|
100
101
|
from deezer_python_gql.generated.get_user_charts import GetUserCharts
|
|
101
102
|
from deezer_python_gql.generated.get_user_playlists import GetUserPlaylists
|
|
103
|
+
from deezer_python_gql.generated.input_types import (
|
|
104
|
+
PushQueueMediaInput,
|
|
105
|
+
PushQueueStateClientDetailsInput,
|
|
106
|
+
PushQueueStateInput,
|
|
107
|
+
)
|
|
102
108
|
from deezer_python_gql.generated.mark_as_not_played_podcast_episode import (
|
|
103
109
|
MarkAsNotPlayedPodcastEpisode,
|
|
104
110
|
)
|
|
@@ -114,6 +120,11 @@ from deezer_python_gql.generated.music_together_generate_group_name import (
|
|
|
114
120
|
)
|
|
115
121
|
from deezer_python_gql.generated.music_together_join_group import MusicTogetherJoinGroup
|
|
116
122
|
from deezer_python_gql.generated.music_together_leave_group import MusicTogetherLeaveGroup
|
|
123
|
+
from deezer_python_gql.generated.push_queue_state import (
|
|
124
|
+
PushQueueState,
|
|
125
|
+
PushQueueStatePushQueueStatePushQueueStateError,
|
|
126
|
+
PushQueueStatePushQueueStatePushQueueStateOutput,
|
|
127
|
+
)
|
|
117
128
|
from deezer_python_gql.generated.remove_album_from_favorite import RemoveAlbumFromFavorite
|
|
118
129
|
from deezer_python_gql.generated.remove_artist_from_favorite import RemoveArtistFromFavorite
|
|
119
130
|
from deezer_python_gql.generated.remove_audiobook_from_favorite import (
|
|
@@ -378,6 +389,7 @@ def test_client_has_generated_methods() -> None:
|
|
|
378
389
|
"get_latest_podcast_episodes",
|
|
379
390
|
"get_tracks_by_ids",
|
|
380
391
|
"set_flow_discovery_tuner",
|
|
392
|
+
"push_queue_state",
|
|
381
393
|
]
|
|
382
394
|
for method in expected_methods:
|
|
383
395
|
assert hasattr(client, method), f"Missing method: {method}"
|
|
@@ -637,10 +649,11 @@ async def test_auth_rejected_arl_raises_auth_error() -> None:
|
|
|
637
649
|
|
|
638
650
|
|
|
639
651
|
@pytest.mark.asyncio
|
|
640
|
-
|
|
641
|
-
|
|
652
|
+
@pytest.mark.parametrize("status", [429, 503])
|
|
653
|
+
async def test_auth_server_error_raises_http_error(status: int) -> None:
|
|
654
|
+
"""Verify a 5xx or 429 from auth.deezer.com raises GraphQLClientHttpError, not AuthError."""
|
|
642
655
|
client = DeezerBaseClient(arl="test_arl")
|
|
643
|
-
auth_cm = _mock_post_context_manager(b"Service Unavailable", status=
|
|
656
|
+
auth_cm = _mock_post_context_manager(b"Service Unavailable", status=status)
|
|
644
657
|
|
|
645
658
|
with patch("deezer_python_gql.base_client.ClientSession") as mock_cls:
|
|
646
659
|
mock_instance = MagicMock()
|
|
@@ -650,7 +663,7 @@ async def test_auth_server_error_raises_http_error() -> None:
|
|
|
650
663
|
|
|
651
664
|
with pytest.raises(GraphQLClientHttpError) as exc_info:
|
|
652
665
|
await client._ensure_jwt() # noqa: SLF001
|
|
653
|
-
assert exc_info.value.status_code ==
|
|
666
|
+
assert exc_info.value.status_code == status
|
|
654
667
|
|
|
655
668
|
|
|
656
669
|
@pytest.mark.asyncio
|
|
@@ -773,6 +786,25 @@ def test_get_data_raises_on_missing_data_key() -> None:
|
|
|
773
786
|
client.get_data(response)
|
|
774
787
|
|
|
775
788
|
|
|
789
|
+
def test_get_data_logs_partial_errors_at_debug(caplog: pytest.LogCaptureFixture) -> None:
|
|
790
|
+
"""Verify items Deezer no longer has are logged at debug level, not as a warning."""
|
|
791
|
+
client = DeezerBaseClient(arl="test")
|
|
792
|
+
response = GQLResponse(
|
|
793
|
+
status=200,
|
|
794
|
+
data=json.dumps(
|
|
795
|
+
{
|
|
796
|
+
"data": {"me": {"id": "1"}},
|
|
797
|
+
"errors": [{"message": "PodcastEpisode does not exist", "path": ["me"]}],
|
|
798
|
+
}
|
|
799
|
+
).encode(),
|
|
800
|
+
is_success=True,
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
with caplog.at_level(logging.DEBUG, logger="deezer_python_gql"):
|
|
804
|
+
assert client.get_data(response) == {"me": {"id": "1"}}
|
|
805
|
+
assert [record.levelno for record in caplog.records] == [logging.DEBUG]
|
|
806
|
+
|
|
807
|
+
|
|
776
808
|
def test_get_data_raises_on_graphql_errors() -> None:
|
|
777
809
|
"""Verify get_data raises GraphQLClientGraphQLMultiError for GraphQL-level errors."""
|
|
778
810
|
client = DeezerBaseClient(arl="test")
|
|
@@ -2030,3 +2062,64 @@ def test_smoke_get_family() -> None:
|
|
|
2030
2062
|
("2002", True),
|
|
2031
2063
|
("3003", False),
|
|
2032
2064
|
]
|
|
2065
|
+
|
|
2066
|
+
|
|
2067
|
+
def test_smoke_push_queue_state() -> None:
|
|
2068
|
+
"""Verify PushQueueState fixture parses the new queue state version."""
|
|
2069
|
+
data = _load_fixture("push_queue_state.json")
|
|
2070
|
+
result = PushQueueState.model_validate(data).push_queue_state
|
|
2071
|
+
assert isinstance(result, PushQueueStatePushQueueStatePushQueueStateOutput)
|
|
2072
|
+
assert result.version == "01a1a613-c68c-731f-8677-15d9c535adbb"
|
|
2073
|
+
|
|
2074
|
+
|
|
2075
|
+
def test_smoke_push_queue_state_conflict() -> None:
|
|
2076
|
+
"""Verify a PushQueueState conflict parses the version it conflicts with."""
|
|
2077
|
+
data = _load_fixture("push_queue_state_conflict.json")
|
|
2078
|
+
result = PushQueueState.model_validate(data).push_queue_state
|
|
2079
|
+
assert isinstance(result, PushQueueStatePushQueueStatePushQueueStateError)
|
|
2080
|
+
assert result.is_bad_request is False
|
|
2081
|
+
assert result.conflict is not None
|
|
2082
|
+
assert result.conflict.current is not None
|
|
2083
|
+
assert result.conflict.current.version == "01a1a63d-1f5b-7d29-ae26-099eb9c20ab7"
|
|
2084
|
+
|
|
2085
|
+
|
|
2086
|
+
@pytest.mark.asyncio
|
|
2087
|
+
async def test_execute_sends_input_types_by_their_graphql_names() -> None:
|
|
2088
|
+
"""Verify an input type variable is sent as JSON with the API's field names."""
|
|
2089
|
+
client = DeezerGQLClient(arl="test_arl")
|
|
2090
|
+
client._jwt = _make_jwt(exp=time.time() + 600) # noqa: SLF001
|
|
2091
|
+
client._jwt_expires_at = time.time() + 600 # noqa: SLF001
|
|
2092
|
+
state = PushQueueStateInput(
|
|
2093
|
+
clientDetails=PushQueueStateClientDetailsInput(
|
|
2094
|
+
localQueueID="queue", locallyUpdatedAt="2026-11-04T10:21:16.223Z"
|
|
2095
|
+
),
|
|
2096
|
+
mediaList=[PushQueueMediaInput(id="1", type=MediaTypeInput.TRACK, unshuffledPosition=0)],
|
|
2097
|
+
position=0,
|
|
2098
|
+
queueConfiguration="{}",
|
|
2099
|
+
repeatMode=RepeatModeInput.NONE,
|
|
2100
|
+
)
|
|
2101
|
+
gql_cm = _mock_post_context_manager(
|
|
2102
|
+
json.dumps({"data": _load_fixture("push_queue_state.json")}).encode()
|
|
2103
|
+
)
|
|
2104
|
+
|
|
2105
|
+
# the generated client runs on the generated copy of the base client
|
|
2106
|
+
with patch("deezer_python_gql.generated.base_client.ClientSession") as mock_cls:
|
|
2107
|
+
mock_instance = MagicMock()
|
|
2108
|
+
mock_instance.post = MagicMock(return_value=gql_cm)
|
|
2109
|
+
mock_instance.close = AsyncMock()
|
|
2110
|
+
mock_cls.return_value = mock_instance
|
|
2111
|
+
|
|
2112
|
+
await client.push_queue_state(input=state)
|
|
2113
|
+
|
|
2114
|
+
sent = mock_instance.post.call_args.kwargs["json"]["variables"]["input"]
|
|
2115
|
+
assert sent["clientDetails"]["localQueueID"] == "queue"
|
|
2116
|
+
assert sent["mediaList"] == [
|
|
2117
|
+
{
|
|
2118
|
+
"id": "1",
|
|
2119
|
+
"type": "TRACK",
|
|
2120
|
+
"contextId": None,
|
|
2121
|
+
"localQueueItemId": None,
|
|
2122
|
+
"unshuffledPosition": 0,
|
|
2123
|
+
}
|
|
2124
|
+
]
|
|
2125
|
+
assert sent["repeatMode"] == "NONE"
|
|
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.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/base_model.py
RENAMED
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/create_playlist.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/delete_playlist.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/fragments.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_album.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_artist.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_artist_mix.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_audiobook.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_charts.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_family.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.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_flow.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_flow_batch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_flow_tuner.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_livestream.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/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.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_playlist.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/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.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_track.py
RENAMED
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_track_mix.py
RENAMED
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/get_user_charts.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
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/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.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql/generated/update_playlist.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{deezer_python_gql-0.21.0 → deezer_python_gql-0.22.2}/deezer_python_gql.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|