deezer-python 6.1.0__tar.gz → 6.1.1__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-6.1.0 → deezer_python-6.1.1}/PKG-INFO +1 -1
- {deezer_python-6.1.0 → deezer_python-6.1.1}/pyproject.toml +43 -5
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/__init__.py +1 -1
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/client.py +17 -18
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/dates.py +2 -0
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/exceptions.py +8 -6
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/pagination.py +7 -2
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/chart.py +15 -5
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/episode.py +1 -1
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/podcast.py +1 -1
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/resource.py +3 -1
- {deezer_python-6.1.0 → deezer_python-6.1.1}/LICENSE.txt +0 -0
- {deezer_python-6.1.0 → deezer_python-6.1.1}/README.md +0 -0
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/__init__.py +0 -0
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/album.py +0 -0
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/artist.py +0 -0
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/editorial.py +0 -0
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/genre.py +0 -0
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/playlist.py +0 -0
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/radio.py +0 -0
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/track.py +0 -0
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/user.py +0 -0
- {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "deezer-python"
|
|
3
|
-
version = "6.1.
|
|
3
|
+
version = "6.1.1"
|
|
4
4
|
description = "A friendly wrapper library for the Deezer API"
|
|
5
5
|
authors = ["Bruno Alla <alla.brunoo@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -51,7 +51,9 @@ deezer-oauth-cli = "^1.0.0"
|
|
|
51
51
|
|
|
52
52
|
[tool.semantic_release]
|
|
53
53
|
version_toml = ["pyproject.toml:tool.poetry.version"]
|
|
54
|
-
|
|
54
|
+
version_variables = [
|
|
55
|
+
"src/deezer/__init__.py:__version__",
|
|
56
|
+
]
|
|
55
57
|
branch = "main"
|
|
56
58
|
build_command = "pip install poetry && poetry build"
|
|
57
59
|
|
|
@@ -81,9 +83,45 @@ exclude_lines = [
|
|
|
81
83
|
"raise NotImplementedError",
|
|
82
84
|
]
|
|
83
85
|
|
|
84
|
-
[tool.
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
[tool.ruff]
|
|
87
|
+
target-version = "py38"
|
|
88
|
+
line-length = 120
|
|
89
|
+
ignore = [
|
|
90
|
+
"D203", # 1 blank line required before class docstring
|
|
91
|
+
"D212", # Multi-line docstring summary should start at the first line
|
|
92
|
+
"D100", # Missing docstring in public module
|
|
93
|
+
"D104", # Missing docstring in public package
|
|
94
|
+
"D107", # Missing docstring in `__init__`
|
|
95
|
+
"D401", # First line of docstring should be in imperative mood
|
|
96
|
+
]
|
|
97
|
+
select = [
|
|
98
|
+
"B", # flake8-bugbear
|
|
99
|
+
"D", # flake8-docstrings
|
|
100
|
+
"C4", # flake8-comprehensions
|
|
101
|
+
"S", # flake8-bandit
|
|
102
|
+
"F", # pyflake
|
|
103
|
+
"E", # pycodestyle
|
|
104
|
+
"W", # pycodestyle
|
|
105
|
+
"UP", # pyupgrade
|
|
106
|
+
"I", # isort
|
|
107
|
+
"RUF", # ruff specific
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
[tool.ruff.per-file-ignores]
|
|
111
|
+
"tests/**/*" = [
|
|
112
|
+
"D100",
|
|
113
|
+
"D101",
|
|
114
|
+
"D102",
|
|
115
|
+
"D103",
|
|
116
|
+
"D104",
|
|
117
|
+
"S101",
|
|
118
|
+
]
|
|
119
|
+
"setup.py" = ["D100"]
|
|
120
|
+
"conftest.py" = ["D100"]
|
|
121
|
+
"docs/conf.py" = ["D100"]
|
|
122
|
+
|
|
123
|
+
[tool.ruff.isort]
|
|
124
|
+
known-first-party = ["deezer"]
|
|
87
125
|
|
|
88
126
|
[tool.mypy]
|
|
89
127
|
# suppress errors about unsatisfied imports
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any, ClassVar
|
|
4
4
|
|
|
5
5
|
import requests
|
|
6
6
|
|
|
@@ -36,8 +36,8 @@ class Client:
|
|
|
36
36
|
>>> import deezer
|
|
37
37
|
>>> client = deezer.Client(app_id='foo', app_secret='bar')
|
|
38
38
|
|
|
39
|
-
This client provides several
|
|
40
|
-
|
|
39
|
+
This client provides several methods to retrieve the content most
|
|
40
|
+
kinds of Deezer objects, based on their json structure.
|
|
41
41
|
|
|
42
42
|
Headers can be forced by using the ``headers`` kwarg.
|
|
43
43
|
For example, use ``Accept-Language`` header to force the output language.
|
|
@@ -51,7 +51,7 @@ class Client:
|
|
|
51
51
|
:param headers: a dictionary of headers to be used.
|
|
52
52
|
"""
|
|
53
53
|
|
|
54
|
-
objects_types = {
|
|
54
|
+
objects_types: ClassVar[dict[str, type[Resource] | None]] = {
|
|
55
55
|
"album": Album,
|
|
56
56
|
"artist": Artist,
|
|
57
57
|
"chart": Chart,
|
|
@@ -88,8 +88,7 @@ class Client:
|
|
|
88
88
|
paginate_list=False,
|
|
89
89
|
):
|
|
90
90
|
"""
|
|
91
|
-
Recursively convert dictionary
|
|
92
|
-
to :class:`~deezer.Resource` object
|
|
91
|
+
Recursively convert dictionary to :class:`~deezer.Resource` object.
|
|
93
92
|
|
|
94
93
|
:param item: the JSON response as dict.
|
|
95
94
|
:param parent: A reference to the parent resource, to avoid fetching again.
|
|
@@ -129,7 +128,7 @@ class Client:
|
|
|
129
128
|
return True
|
|
130
129
|
else:
|
|
131
130
|
raise DeezerUnknownResource(f"Unable to find resource type for {result!r}")
|
|
132
|
-
assert object_class is not None #
|
|
131
|
+
assert object_class is not None # noqa S101
|
|
133
132
|
return object_class(self, result)
|
|
134
133
|
|
|
135
134
|
def request(
|
|
@@ -281,7 +280,7 @@ class Client:
|
|
|
281
280
|
|
|
282
281
|
def get_genre(self, genre_id: int) -> Genre:
|
|
283
282
|
"""
|
|
284
|
-
Get the genre with the given ID
|
|
283
|
+
Get the genre with the given ID.
|
|
285
284
|
|
|
286
285
|
:returns: a :class:`~deezer.Genre` object
|
|
287
286
|
"""
|
|
@@ -419,7 +418,7 @@ class Client:
|
|
|
419
418
|
|
|
420
419
|
def add_user_album(self, album_id: int) -> bool:
|
|
421
420
|
"""
|
|
422
|
-
Add an album to the user's library
|
|
421
|
+
Add an album to the user's library.
|
|
423
422
|
|
|
424
423
|
:param album_id: the ID of the album to add.
|
|
425
424
|
:return: boolean whether the operation succeeded.
|
|
@@ -428,7 +427,7 @@ class Client:
|
|
|
428
427
|
|
|
429
428
|
def remove_user_album(self, album_id: int) -> bool:
|
|
430
429
|
"""
|
|
431
|
-
Remove an album from the user's library
|
|
430
|
+
Remove an album from the user's library.
|
|
432
431
|
|
|
433
432
|
:param album_id: the ID of the album to remove.
|
|
434
433
|
:return: boolean whether the operation succeeded.
|
|
@@ -448,7 +447,7 @@ class Client:
|
|
|
448
447
|
|
|
449
448
|
def add_user_artist(self, artist_id: int) -> bool:
|
|
450
449
|
"""
|
|
451
|
-
Add an artist to the user's library
|
|
450
|
+
Add an artist to the user's library.
|
|
452
451
|
|
|
453
452
|
:param artist_id: the ID of the artist to add.
|
|
454
453
|
:return: boolean whether the operation succeeded.
|
|
@@ -457,7 +456,7 @@ class Client:
|
|
|
457
456
|
|
|
458
457
|
def remove_user_artist(self, artist_id: int) -> bool:
|
|
459
458
|
"""
|
|
460
|
-
Remove an artist from the user's library
|
|
459
|
+
Remove an artist from the user's library.
|
|
461
460
|
|
|
462
461
|
:param artist_id: the ID of the artist to remove.
|
|
463
462
|
:return: boolean whether the operation succeeded.
|
|
@@ -526,7 +525,7 @@ class Client:
|
|
|
526
525
|
|
|
527
526
|
def add_user_track(self, track_id: int) -> bool:
|
|
528
527
|
"""
|
|
529
|
-
Add a track to the user's library
|
|
528
|
+
Add a track to the user's library.
|
|
530
529
|
|
|
531
530
|
:param track_id: the ID of the track to add.
|
|
532
531
|
:return: boolean whether the operation succeeded.
|
|
@@ -535,7 +534,7 @@ class Client:
|
|
|
535
534
|
|
|
536
535
|
def remove_user_track(self, track_id: int) -> bool:
|
|
537
536
|
"""
|
|
538
|
-
Remove a track from the user's library
|
|
537
|
+
Remove a track from the user's library.
|
|
539
538
|
|
|
540
539
|
:param track_id: the ID of the track to remove.
|
|
541
540
|
:return: boolean whether the operation succeeded.
|
|
@@ -544,7 +543,7 @@ class Client:
|
|
|
544
543
|
|
|
545
544
|
def remove_user_playlist(self, playlist_id: int) -> bool:
|
|
546
545
|
"""
|
|
547
|
-
Remove a playlist from the user's library
|
|
546
|
+
Remove a playlist from the user's library.
|
|
548
547
|
|
|
549
548
|
:param playlist_id: the ID of the playlist to remove.
|
|
550
549
|
:return: boolean whether the operation succeeded.
|
|
@@ -553,7 +552,7 @@ class Client:
|
|
|
553
552
|
|
|
554
553
|
def add_user_playlist(self, playlist_id: int) -> bool:
|
|
555
554
|
"""
|
|
556
|
-
Add a playlist to the user's library
|
|
555
|
+
Add a playlist to the user's library.
|
|
557
556
|
|
|
558
557
|
:param playlist_id: the ID of the playlist to add.
|
|
559
558
|
:return: boolean whether the operation succeeded.
|
|
@@ -562,7 +561,7 @@ class Client:
|
|
|
562
561
|
|
|
563
562
|
def create_playlist(self, playlist_name) -> int:
|
|
564
563
|
"""
|
|
565
|
-
Create a playlist on the user's account
|
|
564
|
+
Create a playlist on the user's account.
|
|
566
565
|
|
|
567
566
|
:param playlist_name: the name of the playlist.
|
|
568
567
|
:return: the ID of the playlist that was created
|
|
@@ -574,7 +573,7 @@ class Client:
|
|
|
574
573
|
|
|
575
574
|
def delete_playlist(self, playlist_id) -> bool:
|
|
576
575
|
"""
|
|
577
|
-
Delete a playlist from the user's account
|
|
576
|
+
Delete a playlist from the user's account.
|
|
578
577
|
|
|
579
578
|
:param playlist_id: the ID of the playlist to remove.
|
|
580
579
|
:return: boolean whether the operation succeeded.
|
|
@@ -7,6 +7,7 @@ DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def parse_date(date_str: str) -> dt.date | None:
|
|
10
|
+
"""Parse a date from a string to a date object."""
|
|
10
11
|
datetime = parse_datetime(date_str, DATE_FORMAT)
|
|
11
12
|
return datetime.date() if datetime else None
|
|
12
13
|
|
|
@@ -15,6 +16,7 @@ def parse_datetime(
|
|
|
15
16
|
datetime_str: str,
|
|
16
17
|
date_format: str = DATETIME_FORMAT,
|
|
17
18
|
) -> dt.datetime | None:
|
|
19
|
+
"""Parse a datetime from a string to a datetime object."""
|
|
18
20
|
if not datetime_str or datetime_str.startswith("0000-00-00"):
|
|
19
21
|
return None
|
|
20
22
|
return dt.datetime.strptime(datetime_str, date_format)
|
|
@@ -25,12 +25,14 @@ class DeezerHTTPError(DeezerAPIException):
|
|
|
25
25
|
|
|
26
26
|
@classmethod
|
|
27
27
|
def from_http_error(cls, exc: requests.HTTPError) -> DeezerHTTPError:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
"""Initialise the appropriate internal exception from a HTTPError."""
|
|
29
|
+
if exc.response is not None:
|
|
30
|
+
if exc.response.status_code in {502, 503, 504}:
|
|
31
|
+
return DeezerRetryableHTTPError(exc)
|
|
32
|
+
if exc.response.status_code == 403:
|
|
33
|
+
return DeezerForbiddenError(exc)
|
|
34
|
+
if exc.response.status_code == 404:
|
|
35
|
+
return DeezerNotFoundError(exc)
|
|
34
36
|
return DeezerHTTPError(exc)
|
|
35
37
|
|
|
36
38
|
|
|
@@ -33,6 +33,7 @@ class PaginatedList(Generic[ResourceType]):
|
|
|
33
33
|
self.__iter = iter(self)
|
|
34
34
|
|
|
35
35
|
def __repr__(self) -> str:
|
|
36
|
+
"""Convenient representation giving a preview of the content."""
|
|
36
37
|
repr_size = 5
|
|
37
38
|
data: list[ResourceType | str] = list(self[: repr_size + 1])
|
|
38
39
|
if len(data) > repr_size:
|
|
@@ -51,6 +52,7 @@ class PaginatedList(Generic[ResourceType]):
|
|
|
51
52
|
self,
|
|
52
53
|
index: int | slice,
|
|
53
54
|
) -> ResourceType | list[ResourceType]:
|
|
55
|
+
"""Get an item or a slice of items from the list."""
|
|
54
56
|
if isinstance(index, int):
|
|
55
57
|
self._fetch_to_index(index)
|
|
56
58
|
return self.__elements[index]
|
|
@@ -62,14 +64,17 @@ class PaginatedList(Generic[ResourceType]):
|
|
|
62
64
|
return self.__elements[index]
|
|
63
65
|
|
|
64
66
|
def __iter__(self) -> Generator[ResourceType, None, None]:
|
|
67
|
+
"""Iterate over the internal, fetching new pages as needed."""
|
|
65
68
|
yield from self.__elements
|
|
66
69
|
while self._could_grow():
|
|
67
70
|
yield from self._grow()
|
|
68
71
|
|
|
69
72
|
def __next__(self) -> ResourceType:
|
|
73
|
+
"""Get the next item from the list."""
|
|
70
74
|
return next(self.__iter)
|
|
71
75
|
|
|
72
76
|
def __len__(self) -> int:
|
|
77
|
+
"""Get the total number of items across all pages."""
|
|
73
78
|
return self.total
|
|
74
79
|
|
|
75
80
|
def _could_grow(self) -> bool:
|
|
@@ -81,7 +86,7 @@ class PaginatedList(Generic[ResourceType]):
|
|
|
81
86
|
return new_elements
|
|
82
87
|
|
|
83
88
|
def _fetch_next_page(self) -> list[ResourceType]:
|
|
84
|
-
assert self.__next_path is not None #
|
|
89
|
+
assert self.__next_path is not None # noqa S101
|
|
85
90
|
response_payload = self.__client.request(
|
|
86
91
|
"GET",
|
|
87
92
|
self.__next_path,
|
|
@@ -116,5 +121,5 @@ class PaginatedList(Generic[ResourceType]):
|
|
|
116
121
|
**params,
|
|
117
122
|
)
|
|
118
123
|
self.__total = response_payload["total"]
|
|
119
|
-
assert self.__total is not None #
|
|
124
|
+
assert self.__total is not None # noqa S101
|
|
120
125
|
return self.__total
|
|
@@ -32,35 +32,45 @@ class Chart(Resource):
|
|
|
32
32
|
|
|
33
33
|
def get_tracks(self, **kwargs) -> PaginatedList[Track]:
|
|
34
34
|
"""
|
|
35
|
+
Return the chart for tracks.
|
|
36
|
+
|
|
35
37
|
:returns: a :class:`PaginatedList <deezer.PaginatedList>`
|
|
36
|
-
|
|
38
|
+
of :class:`Track <deezer.Track>` instances
|
|
37
39
|
"""
|
|
38
40
|
return self.get_paginated_list("tracks", **kwargs)
|
|
39
41
|
|
|
40
42
|
def get_albums(self, **kwargs) -> PaginatedList[Album]:
|
|
41
43
|
"""
|
|
44
|
+
Return the chart for albums.
|
|
45
|
+
|
|
42
46
|
:returns: a :class:`PaginatedList <deezer.PaginatedList>`
|
|
43
|
-
|
|
47
|
+
of :class:`Album <deezer.Album>` instances
|
|
44
48
|
"""
|
|
45
49
|
return self.get_paginated_list("albums", **kwargs)
|
|
46
50
|
|
|
47
51
|
def get_artists(self, **kwargs) -> PaginatedList[Artist]:
|
|
48
52
|
"""
|
|
53
|
+
Return the chart for artists.
|
|
54
|
+
|
|
49
55
|
:returns: a :class:`PaginatedList <deezer.PaginatedList>`
|
|
50
|
-
|
|
56
|
+
of :class:`Artist <deezer.Artist>` instances
|
|
51
57
|
"""
|
|
52
58
|
return self.get_paginated_list("artists", **kwargs)
|
|
53
59
|
|
|
54
60
|
def get_playlists(self, **kwargs) -> PaginatedList[Playlist]:
|
|
55
61
|
"""
|
|
62
|
+
Return the chart for playlists.
|
|
63
|
+
|
|
56
64
|
:returns: a :class:`PaginatedList <deezer.PaginatedList>`
|
|
57
|
-
|
|
65
|
+
of :class:`Playlist <deezer.Playlist>` instances
|
|
58
66
|
"""
|
|
59
67
|
return self.get_paginated_list("playlists", **kwargs)
|
|
60
68
|
|
|
61
69
|
def get_podcasts(self, **kwargs) -> PaginatedList[Podcast]:
|
|
62
70
|
"""
|
|
71
|
+
Return the chart for podcasts.
|
|
72
|
+
|
|
63
73
|
:returns: a :class:`PaginatedList <deezer.PaginatedList>`
|
|
64
|
-
|
|
74
|
+
of :class:`Podcast <deezer.Podcast>` instances
|
|
65
75
|
"""
|
|
66
76
|
return self.get_paginated_list("podcasts", **kwargs)
|
|
@@ -32,7 +32,7 @@ class Podcast(Resource):
|
|
|
32
32
|
|
|
33
33
|
def get_episodes(self, **kwargs) -> PaginatedList[Episode]:
|
|
34
34
|
"""
|
|
35
|
-
Get episodes from a podcast
|
|
35
|
+
Get episodes from a podcast.
|
|
36
36
|
|
|
37
37
|
:returns: a :class:`PaginatedList <deezer.PaginatedList>`
|
|
38
38
|
of :class:`Episode <deezer.Episode>` instances
|
|
@@ -34,6 +34,7 @@ class Resource:
|
|
|
34
34
|
setattr(self, key, json[key])
|
|
35
35
|
|
|
36
36
|
def __repr__(self):
|
|
37
|
+
"""Convenient representation giving a preview of the item."""
|
|
37
38
|
name = getattr(self, "name", None)
|
|
38
39
|
title = getattr(self, "title", None)
|
|
39
40
|
id_ = getattr(self, "id", None)
|
|
@@ -101,7 +102,8 @@ class Resource:
|
|
|
101
102
|
self,
|
|
102
103
|
relation: str,
|
|
103
104
|
**kwargs,
|
|
104
|
-
):
|
|
105
|
+
) -> PaginatedList:
|
|
106
|
+
"""Build the pagination object based on the relation."""
|
|
105
107
|
return PaginatedList(
|
|
106
108
|
client=self.client,
|
|
107
109
|
base_path=f"{self.type}/{self.id}/{relation}",
|
|
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
|