deezer-python 6.2.0__py3-none-any.whl → 7.0.0__py3-none-any.whl

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/__init__.py CHANGED
@@ -15,7 +15,7 @@ from deezer.resources import (
15
15
  User,
16
16
  )
17
17
 
18
- __version__ = "6.2.0"
18
+ __version__ = "7.0.0"
19
19
  __all__ = [
20
20
  "Album",
21
21
  "Artist",
deezer/auth.py ADDED
@@ -0,0 +1,27 @@
1
+ import typing
2
+
3
+ import httpx
4
+
5
+
6
+ class DeezerQueryAuth(httpx.Auth):
7
+ """
8
+ Deezer Auth for httpx.
9
+
10
+ Attach given access token to the request,
11
+ by passing it as query parameter.
12
+
13
+ :param access_token: Your Deezer access token.
14
+ """
15
+
16
+ def __init__(self, access_token: str):
17
+ self.access_token = access_token
18
+
19
+ def auth_flow(self, request: httpx.Request) -> typing.Generator[httpx.Request, None, None]:
20
+ """
21
+ Add authentication query parameter to the request.
22
+
23
+ :param request: httpx.Request
24
+ :return: httpx.Request
25
+ """
26
+ request.url = request.url.copy_merge_params({"access_token": self.access_token})
27
+ yield request
deezer/client.py CHANGED
@@ -1,10 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
- import warnings
4
3
  from typing import Any, ClassVar
5
4
 
6
- import requests
5
+ import httpx
6
+ from httpx._types import HeaderTypes
7
7
 
8
+ from deezer.auth import DeezerQueryAuth
8
9
  from deezer.exceptions import (
9
10
  DeezerErrorResponse,
10
11
  DeezerHTTPError,
@@ -26,10 +27,8 @@ from deezer.resources import (
26
27
  User,
27
28
  )
28
29
 
29
- _UNSET = object()
30
30
 
31
-
32
- class Client:
31
+ class Client(httpx.Client):
33
32
  """
34
33
  A client to retrieve some basic infos about Deezer resources.
35
34
 
@@ -50,15 +49,6 @@ class Client:
50
49
 
51
50
  :param access_token: user access token.
52
51
  :param headers: a dictionary of headers to be used.
53
-
54
- .. deprecated:: 6.2.0
55
-
56
- The following parameters will be removed in the next major version:
57
-
58
- * **app_id**
59
- * **app_secret**
60
-
61
- They were never actively used by the package.
62
52
  """
63
53
 
64
54
  objects_types: ClassVar[dict[str, type[Resource] | None]] = {
@@ -76,37 +66,21 @@ class Client:
76
66
  "track": Track,
77
67
  "user": User,
78
68
  }
79
- base_url = "https://api.deezer.com"
80
69
 
81
70
  def __init__(
82
71
  self,
83
- app_id=_UNSET,
84
- app_secret=_UNSET,
85
- access_token=None,
86
- headers=None,
87
- **kwargs,
72
+ access_token: str | None = None,
73
+ headers: HeaderTypes | None = None,
88
74
  ):
89
- if app_id is not _UNSET:
90
- warnings.warn(
91
- "The 'app_id' parameter is not actually used and is deprecated. "
92
- "It will be removed in the next major release.",
93
- DeprecationWarning,
94
- stacklevel=2,
95
- )
96
- self.app_id = app_id
97
- if app_secret is not _UNSET:
98
- warnings.warn(
99
- "The 'app_secret' parameter is not actually used and is deprecated. "
100
- "It will be removed in the next major release.",
101
- DeprecationWarning,
102
- stacklevel=2,
103
- )
104
- self.app_secret = app_secret
105
- self.access_token = access_token
106
- self.session = requests.Session()
107
-
108
- headers = headers or {}
109
- self.session.headers.update(headers)
75
+ if access_token:
76
+ deezer_auth = DeezerQueryAuth(access_token=access_token)
77
+ else:
78
+ deezer_auth = None
79
+ super().__init__(
80
+ base_url="https://api.deezer.com",
81
+ auth=deezer_auth,
82
+ headers=headers,
83
+ )
110
84
 
111
85
  def _process_json(
112
86
  self,
@@ -127,9 +101,7 @@ class Client:
127
101
  :returns: instance of :class:`~deezer.Resource`
128
102
  """
129
103
  if "data" in item:
130
- parsed_data = [
131
- self._process_json(i, parent, paginate_list=False) for i in item["data"]
132
- ]
104
+ parsed_data = [self._process_json(i, parent, paginate_list=False) for i in item["data"]]
133
105
  if not paginate_list:
134
106
  return parsed_data
135
107
  item["data"] = parsed_data
@@ -168,7 +140,7 @@ class Client:
168
140
  resource_type: type[Resource] | None = None,
169
141
  resource_id: int | None = None,
170
142
  paginate_list=False,
171
- **params,
143
+ **kwargs,
172
144
  ):
173
145
  """
174
146
  Make a request to the API and parse the response.
@@ -179,18 +151,15 @@ class Client:
179
151
  :param resource_type: The resource class to use as top level.
180
152
  :param resource_id: The resource id to use as top level.
181
153
  :param paginate_list: Whether to wrap list into a pagination object.
182
- :param params: Query parameters to add to the request
183
154
  """
184
- if self.access_token is not None:
185
- params["access_token"] = str(self.access_token)
186
- response = self.session.request(
155
+ response = super().request(
187
156
  method,
188
- f"{self.base_url}/{path}",
189
- params=params,
157
+ path,
158
+ **kwargs,
190
159
  )
191
160
  try:
192
161
  response.raise_for_status()
193
- except requests.HTTPError as exc:
162
+ except httpx.HTTPStatusError as exc:
194
163
  raise DeezerHTTPError.from_http_error(exc) from exc
195
164
  json_data = response.json()
196
165
  if not isinstance(json_data, dict):
@@ -205,8 +174,8 @@ class Client:
205
174
  paginate_list=paginate_list,
206
175
  )
207
176
 
208
- def _get_paginated_list(self, path, **params):
209
- return PaginatedList(client=self, base_path=path, **params)
177
+ def _get_paginated_list(self, path: str, params: dict | None = None):
178
+ return PaginatedList(client=self, base_path=path, params=params)
210
179
 
211
180
  def get_album(self, album_id: int) -> Album:
212
181
  """
@@ -233,9 +202,7 @@ class Client:
233
202
  :param genre_id: the genre ID, default to `All` genre (genre_id = 0).
234
203
  :returns: a :class:`~deezer.Chart` instance.
235
204
  """
236
- return self.request(
237
- "GET", f"chart/{genre_id}", resource_type=Chart, resource_id=genre_id
238
- )
205
+ return self.request("GET", f"chart/{genre_id}", resource_type=Chart, resource_id=genre_id)
239
206
 
240
207
  def get_tracks_chart(self, genre_id: int = 0) -> list[Track]:
241
208
  """
@@ -388,9 +355,7 @@ class Client:
388
355
  :returns: a :class:`PaginatedList <deezer.PaginatedList>`
389
356
  of :class:`Track <deezer.Track>` instances
390
357
  """
391
- return PaginatedList(
392
- client=self, base_path="user/me/recommendations/tracks", **kwargs
393
- )
358
+ return PaginatedList(client=self, base_path="user/me/recommendations/tracks", **kwargs)
394
359
 
395
360
  def get_user_recommended_albums(self, **kwargs) -> PaginatedList[Album]:
396
361
  """
@@ -399,9 +364,7 @@ class Client:
399
364
  :returns: a :class:`PaginatedList <deezer.PaginatedList>`
400
365
  of :class:`Track <deezer.Album>` instances
401
366
  """
402
- return PaginatedList(
403
- client=self, base_path="user/me/recommendations/albums", **kwargs
404
- )
367
+ return PaginatedList(client=self, base_path="user/me/recommendations/albums", **kwargs)
405
368
 
406
369
  def get_user_recommended_artists(self, **kwargs) -> PaginatedList[Artist]:
407
370
  """
@@ -410,9 +373,7 @@ class Client:
410
373
  :returns: a :class:`PaginatedList <deezer.PaginatedList>`
411
374
  of :class:`Track <deezer.Artist>` instances
412
375
  """
413
- return PaginatedList(
414
- client=self, base_path="user/me/recommendations/artists", **kwargs
415
- )
376
+ return PaginatedList(client=self, base_path="user/me/recommendations/artists", **kwargs)
416
377
 
417
378
  def get_user_recommended_playlists(self, **kwargs) -> PaginatedList[Playlist]:
418
379
  """
@@ -421,9 +382,7 @@ class Client:
421
382
  :returns: a :class:`PaginatedList <deezer.PaginatedList>`
422
383
  of :class:`Track <deezer.Playlist>` instances
423
384
  """
424
- return PaginatedList(
425
- client=self, base_path="user/me/recommendations/playlists", **kwargs
426
- )
385
+ return PaginatedList(client=self, base_path="user/me/recommendations/playlists", **kwargs)
427
386
 
428
387
  def get_user_flow(self, **kwargs) -> PaginatedList[Track]:
429
388
  """
@@ -452,7 +411,7 @@ class Client:
452
411
  :param album_id: the ID of the album to add.
453
412
  :return: boolean whether the operation succeeded.
454
413
  """
455
- return self.request("POST", "user/me/albums", album_id=album_id)
414
+ return self.request("POST", "user/me/albums", params={"album_id": album_id})
456
415
 
457
416
  def remove_user_album(self, album_id: int) -> bool:
458
417
  """
@@ -461,7 +420,7 @@ class Client:
461
420
  :param album_id: the ID of the album to remove.
462
421
  :return: boolean whether the operation succeeded.
463
422
  """
464
- return self.request("DELETE", "user/me/albums", album_id=album_id)
423
+ return self.request("DELETE", "user/me/albums", params={"album_id": album_id})
465
424
 
466
425
  def get_user_artists(self, user_id: int | None = None) -> PaginatedList[Artist]:
467
426
  """
@@ -481,7 +440,7 @@ class Client:
481
440
  :param artist_id: the ID of the artist to add.
482
441
  :return: boolean whether the operation succeeded.
483
442
  """
484
- return self.request("POST", "user/me/artists", artist_id=artist_id)
443
+ return self.request("POST", "user/me/artists", params={"artist_id": artist_id})
485
444
 
486
445
  def remove_user_artist(self, artist_id: int) -> bool:
487
446
  """
@@ -490,7 +449,11 @@ class Client:
490
449
  :param artist_id: the ID of the artist to remove.
491
450
  :return: boolean whether the operation succeeded.
492
451
  """
493
- return self.request("DELETE", "user/me/artists", artist_id=artist_id)
452
+ return self.request(
453
+ "DELETE",
454
+ "user/me/artists",
455
+ params={"artist_id": artist_id},
456
+ )
494
457
 
495
458
  def get_user_followers(self, user_id: int | None = None) -> PaginatedList[User]:
496
459
  """
@@ -521,7 +484,7 @@ class Client:
521
484
  :param user_id: the ID of the user to follow.
522
485
  :return: boolean whether the operation succeeded.
523
486
  """
524
- return self.request("POST", "user/me/followings", user_id=user_id)
487
+ return self.request("POST", "user/me/followings", params={"user_id": user_id})
525
488
 
526
489
  def remove_user_following(self, user_id: int) -> bool:
527
490
  """
@@ -530,7 +493,7 @@ class Client:
530
493
  :param user_id: the ID of the user to stop following.
531
494
  :return: boolean whether the operation succeeded.
532
495
  """
533
- return self.request("DELETE", "user/me/followings", user_id=user_id)
496
+ return self.request("DELETE", "user/me/followings", params={"user_id": user_id})
534
497
 
535
498
  def get_user_history(self) -> PaginatedList[Track]:
536
499
  """
@@ -559,7 +522,7 @@ class Client:
559
522
  :param track_id: the ID of the track to add.
560
523
  :return: boolean whether the operation succeeded.
561
524
  """
562
- return self.request("POST", "user/me/tracks", track_id=track_id)
525
+ return self.request("POST", "user/me/tracks", params={"track_id": track_id})
563
526
 
564
527
  def remove_user_track(self, track_id: int) -> bool:
565
528
  """
@@ -568,7 +531,7 @@ class Client:
568
531
  :param track_id: the ID of the track to remove.
569
532
  :return: boolean whether the operation succeeded.
570
533
  """
571
- return self.request("DELETE", "user/me/tracks", track_id=track_id)
534
+ return self.request("DELETE", "user/me/tracks", params={"track_id": track_id})
572
535
 
573
536
  def remove_user_playlist(self, playlist_id: int) -> bool:
574
537
  """
@@ -577,7 +540,7 @@ class Client:
577
540
  :param playlist_id: the ID of the playlist to remove.
578
541
  :return: boolean whether the operation succeeded.
579
542
  """
580
- return self.request("DELETE", "user/me/playlists", playlist_id=playlist_id)
543
+ return self.request("DELETE", "user/me/playlists", params={"playlist_id": playlist_id})
581
544
 
582
545
  def add_user_playlist(self, playlist_id: int) -> bool:
583
546
  """
@@ -586,7 +549,7 @@ class Client:
586
549
  :param playlist_id: the ID of the playlist to add.
587
550
  :return: boolean whether the operation succeeded.
588
551
  """
589
- return self.request("POST", "user/me/playlists", playlist_id=playlist_id)
552
+ return self.request("POST", "user/me/playlists", params={"playlist_id": playlist_id})
590
553
 
591
554
  def create_playlist(self, playlist_name) -> int:
592
555
  """
@@ -595,7 +558,7 @@ class Client:
595
558
  :param playlist_name: the name of the playlist.
596
559
  :return: the ID of the playlist that was created
597
560
  """
598
- result = self.request("POST", "user/me/playlists", title=playlist_name)
561
+ result = self.request("POST", "user/me/playlists", params={"title": playlist_name})
599
562
  # Note: the REST API call returns a dict with just the "id" key in it,
600
563
  # so we return that instead of the full Playlist object
601
564
  return result.id
@@ -626,15 +589,15 @@ class Client:
626
589
  if query:
627
590
  query_parts.append(query)
628
591
  query_parts.extend(
629
- f'{param_name}:"{param_value}"'
630
- for param_name, param_value in advanced_params.items()
631
- if param_value
592
+ f'{param_name}:"{param_value}"' for param_name, param_value in advanced_params.items() if param_value
632
593
  )
633
594
 
634
595
  return self._get_paginated_list(
635
596
  path=f"search/{path}" if path else "search",
636
- q=" ".join(query_parts),
637
- **optional_params,
597
+ params={
598
+ "q": " ".join(query_parts),
599
+ **optional_params,
600
+ },
638
601
  )
639
602
 
640
603
  def search(
deezer/exceptions.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- import requests
3
+ import httpx
4
4
 
5
5
 
6
6
  class DeezerAPIException(Exception):
@@ -12,9 +12,9 @@ class DeezerRetryableException(DeezerAPIException):
12
12
 
13
13
 
14
14
  class DeezerHTTPError(DeezerAPIException):
15
- """Specialisation wrapping HTTPError from the requests library."""
15
+ """Specialization wrapping HTTPError from the httpx library."""
16
16
 
17
- def __init__(self, http_exception: requests.HTTPError, *args: object) -> None:
17
+ def __init__(self, http_exception: httpx.HTTPStatusError, *args: object) -> None:
18
18
  if http_exception.response is not None and http_exception.response.text:
19
19
  url = http_exception.response.request.url
20
20
  status_code = http_exception.response.status_code
@@ -24,8 +24,8 @@ class DeezerHTTPError(DeezerAPIException):
24
24
  super().__init__(http_exception, *args)
25
25
 
26
26
  @classmethod
27
- def from_http_error(cls, exc: requests.HTTPError) -> DeezerHTTPError:
28
- """Initialise the appropriate internal exception from a HTTPError."""
27
+ def from_http_error(cls, exc: httpx.HTTPStatusError) -> DeezerHTTPError:
28
+ """Initialize the appropriate internal exception from a HTTPError."""
29
29
  if exc.response is not None:
30
30
  if exc.response.status_code in {502, 503, 504}:
31
31
  return DeezerRetryableHTTPError(exc)
@@ -37,7 +37,7 @@ class DeezerHTTPError(DeezerAPIException):
37
37
 
38
38
 
39
39
  class DeezerRetryableHTTPError(DeezerRetryableException, DeezerHTTPError):
40
- """A HTTP error due to a potentially temporary issue."""
40
+ """An HTTP error due to a potentially temporary issue."""
41
41
 
42
42
 
43
43
  class DeezerForbiddenError(DeezerHTTPError):
deezer/pagination.py CHANGED
@@ -20,14 +20,14 @@ class PaginatedList(Generic[ResourceType]):
20
20
  client: deezer.Client,
21
21
  base_path: str,
22
22
  parent: deezer.Resource | None = None,
23
- **params,
23
+ params: dict | None = None,
24
24
  ):
25
25
  self.__elements: list[ResourceType] = []
26
26
  self.__client = client
27
27
  self.__base_path = base_path
28
- self.__base_params = params
28
+ self.__base_params = params or {}
29
29
  self.__next_path: str | None = base_path
30
- self.__next_params = params
30
+ self.__next_params = params or {}
31
31
  self.__parent = parent
32
32
  self.__total = None
33
33
  self.__iter = iter(self)
@@ -90,7 +90,7 @@ class PaginatedList(Generic[ResourceType]):
90
90
  self.__next_path,
91
91
  parent=self.__parent,
92
92
  paginate_list=True,
93
- **self.__next_params,
93
+ params=self.__next_params,
94
94
  )
95
95
  self.__next_path = None
96
96
  self.__total = response_payload.get("total")
@@ -116,7 +116,7 @@ class PaginatedList(Generic[ResourceType]):
116
116
  self.__base_path,
117
117
  parent=self.__parent,
118
118
  paginate_list=True,
119
- **params,
119
+ params=params,
120
120
  )
121
121
  self.__total = response_payload["total"]
122
122
  assert self.__total is not None # noqa S101
@@ -26,21 +26,21 @@ class Editorial(Resource):
26
26
  picture_big: str
27
27
  picture_xl: str
28
28
 
29
- def get_selection(self, **kwargs) -> list[Album]:
29
+ def get_selection(self) -> list[Album]:
30
30
  """
31
31
  Get a list of albums selected every week by the Deezer Team.
32
32
 
33
33
  :returns: a list of :class:`Album <deezer.Album>` instances
34
34
  """
35
- return self.get_relation("selection", **kwargs)
35
+ return self.get_relation("selection")
36
36
 
37
- def get_chart(self, **kwargs) -> Chart:
37
+ def get_chart(self) -> Chart:
38
38
  """
39
39
  Get top charts for tracks, albums, artists and playlists.
40
40
 
41
41
  :returns: a :class:`~deezer.Chart` instance
42
42
  """
43
- return self.get_relation("charts", resource_type=Chart, **kwargs)
43
+ return self.get_relation("charts", resource_type=Chart)
44
44
 
45
45
  def get_releases(self, **kwargs) -> PaginatedList[Album]:
46
46
  """
@@ -42,7 +42,7 @@ class Episode(Resource):
42
42
  return f"{self.link}?utm_source=deezer&utm_content=episode-{self.id}&utm_medium=web"
43
43
  return super()._infer_missing_field(item)
44
44
 
45
- def add_bookmark(self, offset: int, **kwargs) -> bool:
45
+ def add_bookmark(self, offset: int) -> bool:
46
46
  """
47
47
  Sets a bookmark on the episode.
48
48
 
@@ -52,14 +52,13 @@ class Episode(Resource):
52
52
  return self.client.request(
53
53
  "POST",
54
54
  f"episode/{self.id}/bookmark",
55
- offset=offset,
56
- **kwargs,
55
+ params={"offset": offset},
57
56
  )
58
57
 
59
- def remove_bookmark(self, **kwargs) -> bool:
58
+ def remove_bookmark(self) -> bool:
60
59
  """
61
60
  Removes the bookmark on the episode.
62
61
 
63
62
  :returns: a boolean that tells if the operation was successful
64
63
  """
65
- return self.client.request("DELETE", f"episode/{self.id}/bookmark", **kwargs)
64
+ return self.client.request("DELETE", f"episode/{self.id}/bookmark")
@@ -58,15 +58,15 @@ class Playlist(Resource):
58
58
  """
59
59
  return self.get_paginated_list("fans", **kwargs)
60
60
 
61
- def mark_seen(self, **kwargs) -> bool:
61
+ def mark_seen(self) -> bool:
62
62
  """
63
63
  Mark the playlist as seen.
64
64
 
65
65
  :returns: a boolean that tells if the operation was successful
66
66
  """
67
- return self.client.request("POST", f"playlist/{self.id}/seen", **kwargs)
67
+ return self.client.request("POST", f"playlist/{self.id}/seen")
68
68
 
69
- def add_tracks(self, tracks: Iterable[int | Track], **kwargs) -> bool:
69
+ def add_tracks(self, tracks: Iterable[int | Track]) -> bool:
70
70
  """
71
71
  Add tracks to a playlist.
72
72
 
@@ -75,11 +75,9 @@ class Playlist(Resource):
75
75
  :returns: a boolean that tells if the operation was successful
76
76
  """
77
77
  track_ids_str = ",".join(str(tid) for tid in gen_ids(tracks))
78
- return self.client.request(
79
- "POST", f"playlist/{self.id}/tracks", songs=track_ids_str, **kwargs
80
- )
78
+ return self.client.request("POST", f"playlist/{self.id}/tracks", params={"songs": track_ids_str})
81
79
 
82
- def delete_tracks(self, tracks: Iterable[int | Track], **kwargs) -> bool:
80
+ def delete_tracks(self, tracks: Iterable[int | Track]) -> bool:
83
81
  """
84
82
  Delete tracks from a playlist.
85
83
 
@@ -88,11 +86,9 @@ class Playlist(Resource):
88
86
  :returns: a boolean that tells if the operation was successful
89
87
  """
90
88
  track_ids_str = ",".join(map(str, gen_ids(tracks)))
91
- return self.client.request(
92
- "DELETE", f"playlist/{self.id}/tracks", songs=track_ids_str, **kwargs
93
- )
89
+ return self.client.request("DELETE", f"playlist/{self.id}/tracks", params={"songs": track_ids_str})
94
90
 
95
- def reorder_tracks(self, order: Iterable[int | Track], **kwargs) -> bool:
91
+ def reorder_tracks(self, order: Iterable[int | Track]) -> bool:
96
92
  """
97
93
  Reorder the tracks of a playlist.
98
94
 
@@ -101,6 +97,4 @@ class Playlist(Resource):
101
97
  :returns: a boolean that tells if the operation was successful
102
98
  """
103
99
  order_track_ids_str = ",".join(map(str, gen_ids(order)))
104
- return self.client.request(
105
- "POST", f"playlist/{self.id}/tracks", order=order_track_ids_str, **kwargs
106
- )
100
+ return self.client.request("POST", f"playlist/{self.id}/tracks", params={"order": order_track_ids_str})
@@ -56,7 +56,12 @@ class Resource:
56
56
  result[key] = value
57
57
  return result
58
58
 
59
- def get_relation(self, relation, **kwargs):
59
+ def get_relation(
60
+ self,
61
+ relation: str,
62
+ resource_type: type[Resource] | None = None, # type: ignore[valid-type]
63
+ params: dict | None = None,
64
+ ):
60
65
  """
61
66
  Generic method to load the relation from any resource.
62
67
 
@@ -69,10 +74,11 @@ class Resource:
69
74
  "GET",
70
75
  f"{self.type}/{self.id}/{relation}",
71
76
  parent=self,
72
- **kwargs,
77
+ resource_type=resource_type,
78
+ params=params,
73
79
  )
74
80
 
75
- def post_relation(self, relation, **kwargs):
81
+ def post_relation(self, relation: str, params: dict):
76
82
  """
77
83
  Generic method to make a POST request to a relation from any resource.
78
84
 
@@ -82,10 +88,10 @@ class Resource:
82
88
  return self.client.request(
83
89
  "POST",
84
90
  f"{self.type}/{self.id}/{relation}",
85
- **kwargs,
91
+ params=params,
86
92
  )
87
93
 
88
- def delete_relation(self, relation, **kwargs):
94
+ def delete_relation(self, relation: str, params: dict | None = None):
89
95
  """
90
96
  Generic method to make a DELETE request to a relation from any resource.
91
97
 
@@ -95,20 +101,20 @@ class Resource:
95
101
  return self.client.request(
96
102
  "DELETE",
97
103
  f"{self.type}/{self.id}/{relation}",
98
- **kwargs,
104
+ params=params,
99
105
  )
100
106
 
101
107
  def get_paginated_list(
102
108
  self,
103
109
  relation: str,
104
- **kwargs,
110
+ params: dict | None = None,
105
111
  ) -> PaginatedList:
106
112
  """Build the pagination object based on the relation."""
107
113
  return PaginatedList(
108
114
  client=self.client,
109
115
  base_path=f"{self.type}/{self.id}/{relation}",
110
116
  parent=self,
111
- **kwargs,
117
+ params=params,
112
118
  )
113
119
 
114
120
  def __getattr__(self, item: str) -> Any:
@@ -132,9 +138,7 @@ class Resource:
132
138
  setattr(self, field_name, getattr(full_resource, field_name))
133
139
  self._fields += (field_name,)
134
140
  return getattr(self, item)
135
- raise AttributeError(
136
- f"'{self.__class__.__name__}' object has no attribute '{item}'"
137
- )
141
+ raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{item}'")
138
142
 
139
143
  def _infer_missing_field(self, item: str) -> Any:
140
144
  """
deezer/resources/user.py CHANGED
@@ -48,14 +48,14 @@ class User(Resource):
48
48
  _parse_birthday = staticmethod(parse_date)
49
49
  _parse_inscription_date = staticmethod(parse_date)
50
50
 
51
- def get_albums(self, **kwargs) -> PaginatedList[Album]:
51
+ def get_albums(self, **params) -> PaginatedList[Album]:
52
52
  """
53
53
  Get user's favorite albums.
54
54
 
55
55
  :returns: a :class:`PaginatedList <deezer.PaginatedList>`
56
56
  of :class:`Album <deezer.Album>` instances
57
57
  """
58
- return self.get_paginated_list("albums", **kwargs)
58
+ return self.get_paginated_list("albums", params=params)
59
59
 
60
60
  def add_album(self, album: Album | int):
61
61
  """
@@ -64,7 +64,7 @@ class User(Resource):
64
64
  :param album: an :class:`~deezer.Album` instance or its ID
65
65
  :returns: a boolean that tells if the operation was successful
66
66
  """
67
- return self.post_relation("albums", album_id=get_id(album))
67
+ return self.post_relation("albums", params={"album_id": get_id(album)})
68
68
 
69
69
  def remove_album(self, album: Album | int):
70
70
  """
@@ -73,7 +73,7 @@ class User(Resource):
73
73
  :param album: an :class:`~deezer.Album` instance or its ID
74
74
  :returns: a boolean that tells if the operation was successful
75
75
  """
76
- return self.delete_relation("albums", album_id=get_id(album))
76
+ return self.delete_relation("albums", params={"album_id": get_id(album)})
77
77
 
78
78
  def get_tracks(self, **kwargs) -> PaginatedList[Track]:
79
79
  """
@@ -82,7 +82,7 @@ class User(Resource):
82
82
  :returns: a :class:`PaginatedList <deezer.PaginatedList>`
83
83
  of :class:`Track <deezer.Track>` instances
84
84
  """
85
- return self.get_paginated_list("tracks", **kwargs)
85
+ return self.get_paginated_list("tracks")
86
86
 
87
87
  def add_track(self, track: Track | int):
88
88
  """
@@ -91,7 +91,7 @@ class User(Resource):
91
91
  :param track: a :class:`~deezer.Track` instance or its ID
92
92
  :returns: a boolean that tells if the operation was successful
93
93
  """
94
- return self.post_relation("tracks", track_id=get_id(track))
94
+ return self.post_relation("tracks", params={"track_id": get_id(track)})
95
95
 
96
96
  def remove_track(self, track: Track | int):
97
97
  """
@@ -100,16 +100,16 @@ class User(Resource):
100
100
  :param track: a :class:`~deezer.Track` instance or its ID
101
101
  :returns: a boolean that tells if the operation was successful
102
102
  """
103
- return self.delete_relation("tracks", track_id=get_id(track))
103
+ return self.delete_relation("tracks", params={"track_id": get_id(track)})
104
104
 
105
- def get_artists(self, **kwargs) -> PaginatedList[Artist]:
105
+ def get_artists(self, **params) -> PaginatedList[Artist]:
106
106
  """
107
107
  Get user's favorite artists.
108
108
 
109
109
  :returns: a :class:`PaginatedList <deezer.PaginatedList>`
110
110
  of :class:`Artist <deezer.Artist>` instances
111
111
  """
112
- return self.get_paginated_list("artists", **kwargs)
112
+ return self.get_paginated_list("artists", params=params)
113
113
 
114
114
  def add_artist(self, artist: Artist | int):
115
115
  """
@@ -118,7 +118,7 @@ class User(Resource):
118
118
  :param artist: an :class:`~deezer.Artist` instance or its ID
119
119
  :returns: a boolean that tells if the operation was successful
120
120
  """
121
- return self.post_relation("artists", artist_id=get_id(artist))
121
+ return self.post_relation("artists", params={"artist_id": get_id(artist)})
122
122
 
123
123
  def remove_artist(self, artist: Artist | int):
124
124
  """
@@ -127,25 +127,25 @@ class User(Resource):
127
127
  :param artist: an :class:`~deezer.Artist` instance or its ID
128
128
  :returns: a boolean that tells if the operation was successful
129
129
  """
130
- return self.delete_relation("artists", artist_id=get_id(artist))
130
+ return self.delete_relation("artists", params={"artist_id": get_id(artist)})
131
131
 
132
- def get_followers(self, **kwargs) -> PaginatedList[User]:
132
+ def get_followers(self, **params) -> PaginatedList[User]:
133
133
  """
134
134
  Get user's followers.
135
135
 
136
136
  :returns: a :class:`PaginatedList <deezer.PaginatedList>`
137
137
  of :class:`User <deezer.User>` instances
138
138
  """
139
- return self.get_paginated_list("followers", **kwargs)
139
+ return self.get_paginated_list("followers", params=params)
140
140
 
141
- def get_followings(self, **kwargs) -> PaginatedList[User]:
141
+ def get_followings(self, **params) -> PaginatedList[User]:
142
142
  """
143
143
  Get user's followings.
144
144
 
145
145
  :returns: a :class:`PaginatedList <deezer.PaginatedList>`
146
146
  of :class:`User <deezer.User>` instances
147
147
  """
148
- return self.get_paginated_list("followings", **kwargs)
148
+ return self.get_paginated_list("followings", params=params)
149
149
 
150
150
  def follow(self, user: User | int):
151
151
  """
@@ -154,7 +154,7 @@ class User(Resource):
154
154
  :param user: a :class:`~deezer.User` instance or its ID
155
155
  :returns: a boolean that tells if the operation was successful
156
156
  """
157
- return self.post_relation("followings", user_id=get_id(user))
157
+ return self.post_relation("followings", params={"user_id": get_id(user)})
158
158
 
159
159
  def unfollow(self, user: User | int):
160
160
  """
@@ -163,16 +163,16 @@ class User(Resource):
163
163
  :param user: a :class:`~deezer.User` instance or its ID
164
164
  :returns: a boolean that tells if the operation was successful
165
165
  """
166
- return self.delete_relation("followings", user_id=get_id(user))
166
+ return self.delete_relation("followings", params={"user_id": get_id(user)})
167
167
 
168
- def get_playlists(self, **kwargs) -> PaginatedList[Playlist]:
168
+ def get_playlists(self, **params) -> PaginatedList[Playlist]:
169
169
  """
170
170
  Get user's public playlists.
171
171
 
172
172
  :returns: a :class:`PaginatedList <deezer.PaginatedList>`
173
173
  of :class:`Playlist <deezer.Playlist>` instances
174
174
  """
175
- return self.get_paginated_list("playlists", **kwargs)
175
+ return self.get_paginated_list("playlists", params=params)
176
176
 
177
177
  def add_playlist(self, playlist: Playlist | int):
178
178
  """
@@ -181,7 +181,7 @@ class User(Resource):
181
181
  :param playlist: a :class:`~deezer.Playlist` instance or its ID
182
182
  :returns: a boolean that tells if the operation was successful
183
183
  """
184
- return self.post_relation("playlists", playlist_id=get_id(playlist))
184
+ return self.post_relation("playlists", params={"playlist_id": get_id(playlist)})
185
185
 
186
186
  def remove_playlist(self, playlist: Playlist | int):
187
187
  """
@@ -190,7 +190,7 @@ class User(Resource):
190
190
  :param playlist: a :class:`~deezer.Playlist` instance or its ID
191
191
  :returns: a boolean that tells if the operation was successful
192
192
  """
193
- return self.delete_relation("playlists", playlist_id=get_id(playlist))
193
+ return self.delete_relation("playlists", params={"playlist_id": get_id(playlist)})
194
194
 
195
195
  def create_playlist(self, title: str) -> int:
196
196
  """
@@ -199,7 +199,7 @@ class User(Resource):
199
199
  :param title: the title of the playlist
200
200
  :returns: the ID of the playlist that was created
201
201
  """
202
- result = self.post_relation("playlists", title=title)
202
+ result = self.post_relation("playlists", params={"title": title})
203
203
  # Note: the REST API call returns a dict with just the "id" key in it,
204
204
  # so we return that instead of the full Playlist object
205
205
  return result.id
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: deezer-python
3
- Version: 6.2.0
3
+ Version: 7.0.0
4
4
  Summary: A friendly wrapper library for the Deezer API
5
5
  Home-page: https://github.com/browniebroke/deezer-python
6
6
  License: MIT
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: 3.10
20
20
  Classifier: Programming Language :: Python :: 3.11
21
21
  Classifier: Programming Language :: Python :: 3.12
22
22
  Classifier: Topic :: Software Development :: Libraries
23
- Requires-Dist: requests (>=2.18)
23
+ Requires-Dist: httpx (>=0.23.0)
24
24
  Project-URL: Bug Tracker, https://github.com/browniebroke/deezer-python/issues
25
25
  Project-URL: Changelog, https://deezer-python.readthedocs.io/en/latest/changelog.html
26
26
  Project-URL: Documentation, https://deezer-python.readthedocs.io
@@ -44,10 +44,10 @@ Description-Content-Type: text/markdown
44
44
  </p>
45
45
  <p align="center">
46
46
  <a href="https://python-poetry.org/">
47
- <img src="https://img.shields.io/badge/packaging-poetry-299bd7?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAASCAYAAABrXO8xAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJJSURBVHgBfZLPa1NBEMe/s7tNXoxW1KJQKaUHkXhQvHgW6UHQQ09CBS/6V3hKc/AP8CqCrUcpmop3Cx48eDB4yEECjVQrlZb80CRN8t6OM/teagVxYZi38+Yz853dJbzoMV3MM8cJUcLMSUKIE8AzQ2PieZzFxEJOHMOgMQQ+dUgSAckNXhapU/NMhDSWLs1B24A8sO1xrN4NECkcAC9ASkiIJc6k5TRiUDPhnyMMdhKc+Zx19l6SgyeW76BEONY9exVQMzKExGKwwPsCzza7KGSSWRWEQhyEaDXp6ZHEr416ygbiKYOd7TEWvvcQIeusHYMJGhTwF9y7sGnSwaWyFAiyoxzqW0PM/RjghPxF2pWReAowTEXnDh0xgcLs8l2YQmOrj3N7ByiqEoH0cARs4u78WgAVkoEDIDoOi3AkcLOHU60RIg5wC4ZuTC7FaHKQm8Hq1fQuSOBvX/sodmNJSB5geaF5CPIkUeecdMxieoRO5jz9bheL6/tXjrwCyX/UYBUcjCaWHljx1xiX6z9xEjkYAzbGVnB8pvLmyXm9ep+W8CmsSHQQY77Zx1zboxAV0w7ybMhQmfqdmmw3nEp1I0Z+FGO6M8LZdoyZnuzzBdjISicKRnpxzI9fPb+0oYXsNdyi+d3h9bm9MWYHFtPeIZfLwzmFDKy1ai3p+PDls1Llz4yyFpferxjnyjJDSEy9CaCx5m2cJPerq6Xm34eTrZt3PqxYO1XOwDYZrFlH1fWnpU38Y9HRze3lj0vOujZcXKuuXm3jP+s3KbZVra7y2EAAAAAASUVORK5CYII=" alt="Poetry">
47
+ <img src="https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json" alt="Poetry">
48
48
  </a>
49
- <a href="https://github.com/ambv/black">
50
- <img src="https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square" alt="black">
49
+ <a href="https://github.com/astral-sh/ruff">
50
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff">
51
51
  </a>
52
52
  <a href="https://github.com/pre-commit/pre-commit">
53
53
  <img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" alt="pre-commit">
@@ -0,0 +1,24 @@
1
+ deezer/__init__.py,sha256=5Rja7dJ3fy4eDgn_SU8WvnPFj26DW8UQ7_3UUACF5fw,556
2
+ deezer/auth.py,sha256=mKnPDiNBJKEq7Qo0CDu2kW_agviGpTdCbj_duFpYWco,691
3
+ deezer/client.py,sha256=rOQjcwvEb3tbySLWUqAhNna3zr97MBDMC5JHZm0-53c,24780
4
+ deezer/dates.py,sha256=raTeUAAOkajl0TTzM2cupzieTY0amzTeZCaaT3gp_iU,648
5
+ deezer/exceptions.py,sha256=fDLZ63QllqaTwX83cJm1czwEz39ysAcKPB4ie_rAhYI,2004
6
+ deezer/pagination.py,sha256=XXk8uh-9XjXDxx8Fw-cmX2zq-yGNQf9yhum2AISAVZE,4081
7
+ deezer/resources/__init__.py,sha256=WLHWGhV5rLyzZe9IjAtVWUsLvNJDWoldgE2JIADC0L4,549
8
+ deezer/resources/album.py,sha256=83PXZNSSkxY7qVVgyvgyHceaV4IMoQxfqcbTR5v-l38,1734
9
+ deezer/resources/artist.py,sha256=A2mZDceh82cQOsMl3cczgm97UCCUHehNeoPYR8IWjds,2150
10
+ deezer/resources/chart.py,sha256=l4l5M7bYcW1-lFKZl874CGWi-gjOVBcskJ-zL5DMcXE,2193
11
+ deezer/resources/editorial.py,sha256=eD2W9BctUSdUMo3VTMHEFkzGIRkvolLLz20ZD_pWclw,1373
12
+ deezer/resources/episode.py,sha256=1dOBQeNQeKofO_HaMFCg-rKYaPxyqgIm2eMTw0RA-vk,1722
13
+ deezer/resources/genre.py,sha256=CDWyNyFT3fXQM7jPkz3UtGr8gNtVn9430h11Jl0ZwAc,1355
14
+ deezer/resources/playlist.py,sha256=9ErXYNhGGNQ3c9COfMRfEvTWJ9QlJsGjxipgKtIIq7s,3215
15
+ deezer/resources/podcast.py,sha256=FwfOe-m4xI1iRwauZpMjKzXKfLDryDz9Y5cVVzP8wVo,915
16
+ deezer/resources/radio.py,sha256=moOdlSJABqCcluInvgbHSwIzSIhDvBQaVyDGdgeVr_0,805
17
+ deezer/resources/resource.py,sha256=F_u1hJBqTlF_gIC00fqGfWXuDoxLgEYz5m_qeLYv_hA,5202
18
+ deezer/resources/track.py,sha256=e3DPTsYWq5yNCjMRQhGTrFQHfQUe3xn0mEVYbpL3O_0,1546
19
+ deezer/resources/user.py,sha256=kCgMUEbKt1QJzzBEpYxPNM3IbojMlm1fYYTSgqa4Sug,6899
20
+ deezer/utils.py,sha256=Z4QA5Ud5KPiGGv5buTdUidixmkEF9UhwCoYZiGXE-cE,583
21
+ deezer_python-7.0.0.dist-info/LICENSE.txt,sha256=lGqKEubFaPBoiJVRoHOxg1LYlte1e5kLoq6zWUSRQzQ,1054
22
+ deezer_python-7.0.0.dist-info/METADATA,sha256=qHc5BbQrj_dvLh6DqOhIIdOzI9iWP-uCSgjJ0JqdnX8,13718
23
+ deezer_python-7.0.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
24
+ deezer_python-7.0.0.dist-info/RECORD,,
@@ -1,23 +0,0 @@
1
- deezer/__init__.py,sha256=ohzXzQw4o6AhcuubYDivDtiJMIxd_Q69OU2opi4W4Os,556
2
- deezer/client.py,sha256=Xr_NDiVUT5b-YIkzfresHd76GWrUEgE-MeLYhI7TEKI,25651
3
- deezer/dates.py,sha256=raTeUAAOkajl0TTzM2cupzieTY0amzTeZCaaT3gp_iU,648
4
- deezer/exceptions.py,sha256=LmJ9vU_mRQ9o9T26ihD36cIMWMC1lg4sBBLiKta3pQA,2003
5
- deezer/pagination.py,sha256=MrlUiuKczEHb0bdThwzG9y48M30F4AaobhvReJHzWx8,4041
6
- deezer/resources/__init__.py,sha256=WLHWGhV5rLyzZe9IjAtVWUsLvNJDWoldgE2JIADC0L4,549
7
- deezer/resources/album.py,sha256=83PXZNSSkxY7qVVgyvgyHceaV4IMoQxfqcbTR5v-l38,1734
8
- deezer/resources/artist.py,sha256=A2mZDceh82cQOsMl3cczgm97UCCUHehNeoPYR8IWjds,2150
9
- deezer/resources/chart.py,sha256=l4l5M7bYcW1-lFKZl874CGWi-gjOVBcskJ-zL5DMcXE,2193
10
- deezer/resources/editorial.py,sha256=BbYsJeMeGU65dSrO-Wy_oS52zqnVZZni281zdpdb5HQ,1413
11
- deezer/resources/episode.py,sha256=AuJQZweeu7MMJ89eMPs7RSBoQU4oQeX1O5QMBwDi94E,1762
12
- deezer/resources/genre.py,sha256=CDWyNyFT3fXQM7jPkz3UtGr8gNtVn9430h11Jl0ZwAc,1355
13
- deezer/resources/playlist.py,sha256=8pAMmqUG4S9QLZOX5OrEoj4qA8pggDQBVxbG-DNxqgk,3325
14
- deezer/resources/podcast.py,sha256=FwfOe-m4xI1iRwauZpMjKzXKfLDryDz9Y5cVVzP8wVo,915
15
- deezer/resources/radio.py,sha256=moOdlSJABqCcluInvgbHSwIzSIhDvBQaVyDGdgeVr_0,805
16
- deezer/resources/resource.py,sha256=X_65glzz62jHtVB2U-WtuXM8B5E9U2HbjpcRzOdVOiY,4978
17
- deezer/resources/track.py,sha256=e3DPTsYWq5yNCjMRQhGTrFQHfQUe3xn0mEVYbpL3O_0,1546
18
- deezer/resources/user.py,sha256=6-46PX80xEgtCY2FecwzzgbvYu1aL1WShFXcCOaVGlU,6752
19
- deezer/utils.py,sha256=Z4QA5Ud5KPiGGv5buTdUidixmkEF9UhwCoYZiGXE-cE,583
20
- deezer_python-6.2.0.dist-info/LICENSE.txt,sha256=lGqKEubFaPBoiJVRoHOxg1LYlte1e5kLoq6zWUSRQzQ,1054
21
- deezer_python-6.2.0.dist-info/METADATA,sha256=4POY_crEa5ogOvMKiy6cmuSbFqwi2ON6khahRQWFw8Y,14629
22
- deezer_python-6.2.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
23
- deezer_python-6.2.0.dist-info/RECORD,,