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.
Files changed (23) hide show
  1. {deezer_python-6.1.0 → deezer_python-6.1.1}/PKG-INFO +1 -1
  2. {deezer_python-6.1.0 → deezer_python-6.1.1}/pyproject.toml +43 -5
  3. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/__init__.py +1 -1
  4. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/client.py +17 -18
  5. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/dates.py +2 -0
  6. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/exceptions.py +8 -6
  7. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/pagination.py +7 -2
  8. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/chart.py +15 -5
  9. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/episode.py +1 -1
  10. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/podcast.py +1 -1
  11. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/resource.py +3 -1
  12. {deezer_python-6.1.0 → deezer_python-6.1.1}/LICENSE.txt +0 -0
  13. {deezer_python-6.1.0 → deezer_python-6.1.1}/README.md +0 -0
  14. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/__init__.py +0 -0
  15. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/album.py +0 -0
  16. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/artist.py +0 -0
  17. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/editorial.py +0 -0
  18. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/genre.py +0 -0
  19. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/playlist.py +0 -0
  20. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/radio.py +0 -0
  21. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/track.py +0 -0
  22. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/resources/user.py +0 -0
  23. {deezer_python-6.1.0 → deezer_python-6.1.1}/src/deezer/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: deezer-python
3
- Version: 6.1.0
3
+ Version: 6.1.1
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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "deezer-python"
3
- version = "6.1.0"
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
- version_variable = "src/deezer/__init__.py:__version__"
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.isort]
85
- profile = "black"
86
- known_first_party = "deezer"
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
@@ -15,7 +15,7 @@ from deezer.resources import (
15
15
  User,
16
16
  )
17
17
 
18
- __version__ = "6.0.0"
18
+ __version__ = "6.1.1"
19
19
  __all__ = [
20
20
  "Album",
21
21
  "Artist",
@@ -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 method to retrieve the content of most
40
- sort of Deezer objects, based on their json structure.
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 # nosec B101
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
- if exc.response.status_code in {502, 503, 504}:
29
- return DeezerRetryableHTTPError(exc)
30
- if exc.response.status_code == 403:
31
- return DeezerForbiddenError(exc)
32
- if exc.response.status_code == 404:
33
- return DeezerNotFoundError(exc)
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 # nosec B101
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 # nosec B101
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
- of :class:`Track <deezer.Track>` instances
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
- of :class:`Album <deezer.Album>` instances
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
- of :class:`Artist <deezer.Artist>` instances
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
- of :class:`Playlist <deezer.Playlist>` instances
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
- of :class:`Podcast <deezer.Podcast>` instances
74
+ of :class:`Podcast <deezer.Podcast>` instances
65
75
  """
66
76
  return self.get_paginated_list("podcasts", **kwargs)
@@ -58,7 +58,7 @@ class Episode(Resource):
58
58
 
59
59
  def remove_bookmark(self, **kwargs) -> bool:
60
60
  """
61
- Removes the bookmark on the episode
61
+ Removes the bookmark on the episode.
62
62
 
63
63
  :returns: a boolean that tells if the operation was successful
64
64
  """
@@ -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