deezer-python 7.2.0__tar.gz → 7.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. {deezer_python-7.2.0 → deezer_python-7.3.0}/CHANGELOG.md +6 -0
  2. {deezer_python-7.2.0 → deezer_python-7.3.0}/CONTRIBUTING.md +9 -3
  3. {deezer_python-7.2.0 → deezer_python-7.3.0}/PKG-INFO +1 -1
  4. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/Makefile +4 -0
  5. deezer_python-7.3.0/docs/api_reference/async_client.rst +7 -0
  6. deezer_python-7.3.0/docs/api_reference/async_pagination.rst +7 -0
  7. deezer_python-7.3.0/docs/api_reference/async_resources/album.rst +6 -0
  8. deezer_python-7.3.0/docs/api_reference/async_resources/artist.rst +6 -0
  9. deezer_python-7.3.0/docs/api_reference/async_resources/chart.rst +6 -0
  10. deezer_python-7.3.0/docs/api_reference/async_resources/editorial.rst +6 -0
  11. deezer_python-7.3.0/docs/api_reference/async_resources/episode.rst +6 -0
  12. deezer_python-7.3.0/docs/api_reference/async_resources/genre.rst +6 -0
  13. deezer_python-7.3.0/docs/api_reference/async_resources/playlist.rst +6 -0
  14. deezer_python-7.3.0/docs/api_reference/async_resources/podcast.rst +6 -0
  15. deezer_python-7.3.0/docs/api_reference/async_resources/radio.rst +6 -0
  16. deezer_python-7.3.0/docs/api_reference/async_resources/track.rst +6 -0
  17. deezer_python-7.3.0/docs/api_reference/async_resources/user.rst +6 -0
  18. deezer_python-7.3.0/docs/api_reference/async_resources.md +21 -0
  19. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/toc.rst +3 -0
  20. deezer_python-7.3.0/docs/async.md +126 -0
  21. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/index.md +1 -0
  22. {deezer_python-7.2.0 → deezer_python-7.3.0}/pyproject.toml +21 -45
  23. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/__init__.py +1 -1
  24. deezer_python-7.3.0/src/deezer/_mixin.py +89 -0
  25. deezer_python-7.3.0/src/deezer/asyncio/__init__.py +35 -0
  26. deezer_python-7.3.0/src/deezer/asyncio/client.py +443 -0
  27. deezer_python-7.3.0/src/deezer/asyncio/pagination.py +142 -0
  28. deezer_python-7.3.0/src/deezer/asyncio/resources/__init__.py +29 -0
  29. deezer_python-7.3.0/src/deezer/asyncio/resources/album.py +72 -0
  30. deezer_python-7.3.0/src/deezer/asyncio/resources/artist.py +48 -0
  31. deezer_python-7.3.0/src/deezer/asyncio/resources/chart.py +36 -0
  32. deezer_python-7.3.0/src/deezer/asyncio/resources/editorial.py +33 -0
  33. deezer_python-7.3.0/src/deezer/asyncio/resources/episode.py +44 -0
  34. deezer_python-7.3.0/src/deezer/asyncio/resources/genre.py +32 -0
  35. deezer_python-7.3.0/src/deezer/asyncio/resources/playlist.py +61 -0
  36. deezer_python-7.3.0/src/deezer/asyncio/resources/podcast.py +29 -0
  37. deezer_python-7.3.0/src/deezer/asyncio/resources/radio.py +23 -0
  38. deezer_python-7.3.0/src/deezer/asyncio/resources/resource.py +103 -0
  39. deezer_python-7.3.0/src/deezer/asyncio/resources/track.py +56 -0
  40. deezer_python-7.3.0/src/deezer/asyncio/resources/user.py +110 -0
  41. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/auth.py +1 -1
  42. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/client.py +3 -71
  43. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer_python.egg-info/PKG-INFO +1 -1
  44. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer_python.egg-info/SOURCES.txt +32 -0
  45. {deezer_python-7.2.0 → deezer_python-7.3.0}/LICENSE.txt +0 -0
  46. {deezer_python-7.2.0 → deezer_python-7.3.0}/MANIFEST.in +0 -0
  47. {deezer_python-7.2.0 → deezer_python-7.3.0}/README.md +0 -0
  48. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/_static/.placeholder +0 -0
  49. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/client.rst +0 -0
  50. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/pagination.rst +0 -0
  51. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/resources/album.rst +0 -0
  52. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/resources/artist.rst +0 -0
  53. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/resources/chart.rst +0 -0
  54. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/resources/editorial.rst +0 -0
  55. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/resources/episode.rst +0 -0
  56. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/resources/genre.rst +0 -0
  57. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/resources/playlist.rst +0 -0
  58. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/resources/podcast.rst +0 -0
  59. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/resources/radio.rst +0 -0
  60. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/resources/track.rst +0 -0
  61. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/resources/user.rst +0 -0
  62. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/api_reference/resources.md +0 -0
  63. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/changelog.md +0 -0
  64. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/conf.py +0 -0
  65. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/contributing.md +0 -0
  66. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/installation.md +0 -0
  67. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/make.bat +0 -0
  68. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/pagination.md +0 -0
  69. {deezer_python-7.2.0 → deezer_python-7.3.0}/docs/usage.md +0 -0
  70. {deezer_python-7.2.0 → deezer_python-7.3.0}/setup.cfg +0 -0
  71. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/dates.py +0 -0
  72. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/exceptions.py +0 -0
  73. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/pagination.py +0 -0
  74. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/resources/__init__.py +0 -0
  75. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/resources/album.py +0 -0
  76. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/resources/artist.py +0 -0
  77. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/resources/chart.py +0 -0
  78. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/resources/editorial.py +0 -0
  79. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/resources/episode.py +0 -0
  80. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/resources/genre.py +0 -0
  81. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/resources/playlist.py +0 -0
  82. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/resources/podcast.py +0 -0
  83. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/resources/radio.py +0 -0
  84. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/resources/resource.py +0 -0
  85. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/resources/track.py +0 -0
  86. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/resources/user.py +0 -0
  87. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer/utils.py +0 -0
  88. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer_python.egg-info/dependency_links.txt +0 -0
  89. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer_python.egg-info/requires.txt +0 -0
  90. {deezer_python-7.2.0 → deezer_python-7.3.0}/src/deezer_python.egg-info/top_level.txt +0 -0
  91. {deezer_python-7.2.0 → deezer_python-7.3.0}/tests/test_client.py +0 -0
  92. {deezer_python-7.2.0 → deezer_python-7.3.0}/tests/test_exceptions.py +0 -0
  93. {deezer_python-7.2.0 → deezer_python-7.3.0}/tests/test_pagination.py +0 -0
@@ -1,5 +1,11 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v7.3.0 (2026-04-06)
4
+
5
+ ### Features
6
+
7
+ - Add async deezer client and resources in `deezer.asyncio` ([`cf30c7c`](https://github.com/browniebroke/deezer-python/commit/cf30c7cf7f9d58d0815456d49fd9095d1d364154))
8
+
3
9
  ## v7.2.0 (2025-09-18)
4
10
 
5
11
  ### Features
@@ -65,7 +65,13 @@ Ready to contribute? Here's how to set up `deezer-python` for local development.
65
65
  $ uv run pytest
66
66
  ```
67
67
 
68
- 6. Linting is done through [pre-commit](https://pre-commit.com). Provided you have the tool installed globally, you can run them all as one-off:
68
+ 6. Type checking is done with [ty](https://docs.astral.sh/ty/), and is run with:
69
+
70
+ ```shell
71
+ uv run ty check
72
+ ```
73
+
74
+ 7. Linting is done through [pre-commit](https://pre-commit.com). Provided you have the tool installed globally, you can run them all as one-off:
69
75
 
70
76
  ```shell
71
77
  $ pre-commit run -a
@@ -77,7 +83,7 @@ Ready to contribute? Here's how to set up `deezer-python` for local development.
77
83
  $ pre-commit install
78
84
  ```
79
85
 
80
- 7. Commit your changes, quoting GitHub issue in the commit message, if applicable, and push your branch to GitHub:
86
+ 8. Commit your changes, quoting GitHub issue in the commit message, if applicable, and push your branch to GitHub:
81
87
 
82
88
  ```shell
83
89
  $ git add .
@@ -87,7 +93,7 @@ Ready to contribute? Here's how to set up `deezer-python` for local development.
87
93
 
88
94
  Note: the commit message should follow [the conventional commits](https://www.conventionalcommits.org). We run [`commitlint` on CI](https://github.com/marketplace/actions/commit-linter) to validate it, and if you've installed pre-commit hooks at the previous step, the message will be checked at commit time.
89
95
 
90
- 8. Submit a pull request on GitHub.
96
+ 9. Submit a pull request on GitHub.
91
97
 
92
98
  ## Obtain an API token
93
99
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deezer-python
3
- Version: 7.2.0
3
+ Version: 7.3.0
4
4
  Summary: A friendly wrapper library for the Deezer API
5
5
  Author-email: Bruno Alla <alla.brunoo@gmail.com>
6
6
  License-Expression: MIT
@@ -12,6 +12,10 @@ BUILDDIR = _build
12
12
  help:
13
13
  @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14
14
 
15
+ # Build, watch and serve docs with live reload
16
+ watch:
17
+ sphinx-autobuild -b html -c . $(SOURCEDIR) $(BUILDDIR)/html
18
+
15
19
  .PHONY: help Makefile
16
20
 
17
21
  # Catch-all target: route all unknown targets to Sphinx using the new
@@ -0,0 +1,7 @@
1
+ Async Client
2
+ ------------
3
+
4
+ The async client class is the main entry point to start querying the `Deezer API <https://developers.deezer.com/api>`_ using ``async``/``await``.
5
+
6
+ .. autoclass:: deezer.asyncio.AsyncClient
7
+ :members:
@@ -0,0 +1,7 @@
1
+ .. _async-pagination-reference:
2
+
3
+ Async Pagination
4
+ ----------------
5
+
6
+ .. autoclass:: deezer.asyncio.AsyncPaginatedList
7
+ :members:
@@ -0,0 +1,6 @@
1
+ AsyncAlbum
2
+ ----------
3
+
4
+ .. autoclass:: deezer.asyncio.AsyncAlbum
5
+ :members:
6
+ :undoc-members:
@@ -0,0 +1,6 @@
1
+ AsyncArtist
2
+ -----------
3
+
4
+ .. autoclass:: deezer.asyncio.AsyncArtist
5
+ :members:
6
+ :undoc-members:
@@ -0,0 +1,6 @@
1
+ AsyncChart
2
+ ----------
3
+
4
+ .. autoclass:: deezer.asyncio.AsyncChart
5
+ :members:
6
+ :undoc-members:
@@ -0,0 +1,6 @@
1
+ AsyncEditorial
2
+ --------------
3
+
4
+ .. autoclass:: deezer.asyncio.AsyncEditorial
5
+ :members:
6
+ :undoc-members:
@@ -0,0 +1,6 @@
1
+ AsyncEpisode
2
+ ------------
3
+
4
+ .. autoclass:: deezer.asyncio.AsyncEpisode
5
+ :members:
6
+ :undoc-members:
@@ -0,0 +1,6 @@
1
+ AsyncGenre
2
+ ----------
3
+
4
+ .. autoclass:: deezer.asyncio.AsyncGenre
5
+ :members:
6
+ :undoc-members:
@@ -0,0 +1,6 @@
1
+ AsyncPlaylist
2
+ -------------
3
+
4
+ .. autoclass:: deezer.asyncio.AsyncPlaylist
5
+ :members:
6
+ :undoc-members:
@@ -0,0 +1,6 @@
1
+ AsyncPodcast
2
+ ------------
3
+
4
+ .. autoclass:: deezer.asyncio.AsyncPodcast
5
+ :members:
6
+ :undoc-members:
@@ -0,0 +1,6 @@
1
+ AsyncRadio
2
+ ----------
3
+
4
+ .. autoclass:: deezer.asyncio.AsyncRadio
5
+ :members:
6
+ :undoc-members:
@@ -0,0 +1,6 @@
1
+ AsyncTrack
2
+ ----------
3
+
4
+ .. autoclass:: deezer.asyncio.AsyncTrack
5
+ :members:
6
+ :undoc-members:
@@ -0,0 +1,6 @@
1
+ AsyncUser
2
+ ---------
3
+
4
+ .. autoclass:: deezer.asyncio.AsyncUser
5
+ :members:
6
+ :undoc-members:
@@ -0,0 +1,21 @@
1
+ (async-resources-reference)=
2
+
3
+ # Async Resources
4
+
5
+ A collection of async Python classes modelling each type of content that is returned by the Deezer API when using the {class}`~deezer.asyncio.AsyncClient`.
6
+
7
+ ```{toctree}
8
+ :maxdepth: 1
9
+
10
+ async_resources/album
11
+ async_resources/artist
12
+ async_resources/chart
13
+ async_resources/editorial
14
+ async_resources/episode
15
+ async_resources/genre
16
+ async_resources/playlist
17
+ async_resources/podcast
18
+ async_resources/radio
19
+ async_resources/track
20
+ async_resources/user
21
+ ```
@@ -12,3 +12,6 @@ This is the auto-generated documentation for all the main modules in the library
12
12
  client
13
13
  pagination
14
14
  resources
15
+ async_client
16
+ async_pagination
17
+ async_resources
@@ -0,0 +1,126 @@
1
+ (async-guide)=
2
+
3
+ # Async Client
4
+
5
+ If you are working with an async framework like [aiohttp](https://docs.aiohttp.org/) or [FastAPI](https://fastapi.tiangolo.com/), you can use the async client from the {mod}`deezer.asyncio` sub-package. It provides the same functionality as the synchronous {class}`Client <deezer.Client>`, but with `async`/`await` support.
6
+
7
+ ## Getting started
8
+
9
+ The async client should be used as an async context manager:
10
+
11
+ ```python
12
+ from deezer.asyncio import AsyncClient
13
+
14
+ async with AsyncClient() as client:
15
+ album = await client.get_album(302127)
16
+ print(album.title)
17
+ ```
18
+
19
+ As with the sync client, you can pass an `access_token` for authenticated requests:
20
+
21
+ ```python
22
+ async with AsyncClient(access_token="your-token") as client:
23
+ user = await client.get_user()
24
+ print(user.name)
25
+ ```
26
+
27
+ ## Fetching resources
28
+
29
+ All the methods that fetch resources from the Deezer API are coroutines and need to be awaited:
30
+
31
+ ```python
32
+ async with AsyncClient() as client:
33
+ artist = await client.get_artist(27)
34
+ album = await client.get_album(302127)
35
+ track = await client.get_track(3135556)
36
+ ```
37
+
38
+ The returned objects are async resource classes (e.g. {class}`~deezer.asyncio.AsyncArtist`, {class}`~deezer.asyncio.AsyncAlbum`), which mirror their sync counterparts.
39
+
40
+ ## Navigating relationships
41
+
42
+ Relationship methods that return a single resource or a plain list need to be awaited:
43
+
44
+ ```python
45
+ async with AsyncClient() as client:
46
+ track = await client.get_track(3135556)
47
+
48
+ # These return a single resource
49
+ artist = await track.get_artist()
50
+ album = await track.get_album()
51
+
52
+ # This returns a plain list
53
+ radio = await artist.get_radio()
54
+ ```
55
+
56
+ Methods that return paginated responses also need to be awaited. The first page of results is fetched eagerly, making the returned {class}`~deezer.asyncio.AsyncPaginatedList` immediately usable:
57
+
58
+ ```python
59
+ async with AsyncClient() as client:
60
+ artist = await client.get_artist(27)
61
+
62
+ # Await to get the paginated list (first page is fetched)
63
+ albums = await artist.get_albums()
64
+ ```
65
+
66
+ ## Async Pagination
67
+
68
+ For endpoints returning paginated responses, items are wrapped in an {class}`~deezer.asyncio.AsyncPaginatedList`. This works similarly to the synchronous {class}`~deezer.PaginatedList` described in the {ref}`pagination guide <pagination-guide>`, but uses async iteration and awaitable methods.
69
+
70
+ ### Iterating over elements
71
+
72
+ Use `async for` to iterate over all elements, transparently fetching additional pages as needed:
73
+
74
+ ```python
75
+ albums = await artist.get_albums()
76
+
77
+ async for album in albums:
78
+ print(album.title)
79
+ ```
80
+
81
+ ### Total number
82
+
83
+ The total number of items is available via the {attr}`~deezer.asyncio.AsyncPaginatedList.total` property. Since the first page is fetched eagerly, the total is already available without any extra API call:
84
+
85
+ ```python
86
+ total = albums.total
87
+ # Or using len():
88
+ total = len(albums)
89
+ ```
90
+
91
+ ### Accessing elements by index
92
+
93
+ Use the {meth}`~deezer.asyncio.AsyncPaginatedList.get` method to access an element by index:
94
+
95
+ ```python
96
+ first_album = await albums.get(0)
97
+ fifth_album = await albums.get(4)
98
+ ```
99
+
100
+ As with the sync version, accessing a large index may trigger extra API calls to fetch preceding pages.
101
+
102
+ ```{note}
103
+ Unlike the synchronous {class}`~deezer.PaginatedList`, the async version does not support the `[]` syntax for indexing or slicing. Use the {meth}`~deezer.asyncio.AsyncPaginatedList.get` method for index-based access, or {meth}`~deezer.asyncio.AsyncPaginatedList.collect` to get a plain list that supports regular slicing.
104
+ ```
105
+
106
+ ### Collecting all elements
107
+
108
+ To fetch all pages and get a plain list, use {meth}`~deezer.asyncio.AsyncPaginatedList.collect`:
109
+
110
+ ```python
111
+ all_albums = await albums.collect()
112
+ ```
113
+
114
+ ### Search
115
+
116
+ Search methods also return {class}`~deezer.asyncio.AsyncPaginatedList` and need to be awaited:
117
+
118
+ ```python
119
+ async with AsyncClient() as client:
120
+ results = await client.search("Daft Punk")
121
+
122
+ async for track in results:
123
+ print(track.title)
124
+ ```
125
+
126
+ Variant search methods are also available: {meth}`~deezer.asyncio.AsyncClient.search_albums`, {meth}`~deezer.asyncio.AsyncClient.search_artists`, and {meth}`~deezer.asyncio.AsyncClient.search_playlists`.
@@ -15,6 +15,7 @@ Deezer-Python: a friendly wrapper around the [Deezer API](http://developers.deez
15
15
  installation
16
16
  usage
17
17
  pagination
18
+ async
18
19
  ```
19
20
 
20
21
  ```{toctree}
@@ -1,11 +1,10 @@
1
1
  [build-system]
2
2
  build-backend = "setuptools.build_meta"
3
-
4
3
  requires = [ "setuptools>=77.0.3" ]
5
4
 
6
5
  [project]
7
6
  name = "deezer-python"
8
- version = "7.2.0"
7
+ version = "7.3.0"
9
8
  description = "A friendly wrapper library for the Deezer API"
10
9
  readme = "README.md"
11
10
  keywords = [
@@ -32,7 +31,6 @@ classifiers = [
32
31
  "Programming Language :: Python :: 3.14",
33
32
  "Topic :: Software Development :: Libraries",
34
33
  ]
35
-
36
34
  dependencies = [
37
35
  "httpx>=0.23",
38
36
  ]
@@ -47,14 +45,15 @@ urls.Twitter = "https://twitter.com/_BrunoAlla"
47
45
  dev = [
48
46
  "coverage>=7,<8",
49
47
  "deezer-oauth-cli>=1,<2",
50
- "environs>=14,<15",
51
- "pytest>=8,<9",
48
+ "environs>=15,<16",
49
+ "pytest>=9,<10",
50
+ "pytest-asyncio",
52
51
  "pytest-cov>=7,<8",
53
52
  "pytest-mock>=3.6,<4",
54
53
  "pytest-vcr>=1,<2",
55
- "vcrpy>=7,<8",
54
+ "ty>=0.0.6,<0.1",
55
+ "vcrpy>=8,<9",
56
56
  ]
57
-
58
57
  docs = [
59
58
  "furo>=2023.5.20; python_version>='3.11'",
60
59
  "myst-parser>=0.16; python_version>='3.11'",
@@ -64,7 +63,6 @@ docs = [
64
63
 
65
64
  [tool.ruff]
66
65
  line-length = 120
67
-
68
66
  lint.select = [
69
67
  "B", # flake8-bugbear
70
68
  "C4", # flake8-comprehensions
@@ -101,33 +99,21 @@ lint.isort.known-first-party = [ "deezer" ]
101
99
  [tool.pyproject-fmt]
102
100
  max_supported_python = "3.14"
103
101
 
104
- [tool.pytest.ini_options]
105
- addopts = "-v -Wdefault --cov=deezer"
102
+ [tool.pytest]
103
+ minversion = "9.0"
104
+ addopts = [ "-v", "-Wdefault", "--cov=deezer" ]
106
105
  pythonpath = [ "src" ]
107
106
 
108
- [tool.coverage.run]
109
- branch = true
110
- source = [ "deezer" ]
111
-
112
- [tool.coverage.report]
113
- ignore_errors = true
114
- exclude_lines = [
115
- "pragma: no cover",
107
+ [tool.coverage]
108
+ run.branch = true
109
+ run.source = [ "deezer" ]
110
+ report.exclude_lines = [
116
111
  "@overload",
117
112
  "if TYPE_CHECKING",
113
+ "pragma: no cover",
118
114
  "raise NotImplementedError",
119
115
  ]
120
-
121
- [tool.mypy]
122
- # suppress errors about unsatisfied imports
123
- ignore_missing_imports = true
124
-
125
- no_implicit_optional = true
126
-
127
- # ensure all execution paths are returning
128
- warn_no_return = true
129
-
130
- show_error_codes = true
116
+ report.ignore_errors = true
131
117
 
132
118
  [tool.semantic_release]
133
119
  version_toml = [ "pyproject.toml:project.version" ]
@@ -140,23 +126,13 @@ uv lock
140
126
  git add uv.lock
141
127
  uv build
142
128
  """
143
-
144
- [tool.semantic_release.changelog]
145
- exclude_commit_patterns = [
129
+ branches.main.match = "main"
130
+ branches.noop.match = "(?!main$)"
131
+ branches.noop.prerelease = true
132
+ branches.stable.match = "(?!stable/*$)"
133
+ changelog.exclude_commit_patterns = [
146
134
  "chore.*",
147
135
  "ci.*",
148
136
  "Merge pull request .*",
149
137
  ]
150
-
151
- [tool.semantic_release.changelog.environment]
152
- keep_trailing_newline = true
153
-
154
- [tool.semantic_release.branches.main]
155
- match = "main"
156
-
157
- [tool.semantic_release.branches.stable]
158
- match = "(?!stable/*$)"
159
-
160
- [tool.semantic_release.branches.noop]
161
- match = "(?!main$)"
162
- prerelease = true
138
+ changelog.environment.keep_trailing_newline = true
@@ -15,7 +15,7 @@ from deezer.resources import (
15
15
  User,
16
16
  )
17
17
 
18
- __version__ = "7.2.0"
18
+ __version__ = "7.3.0"
19
19
  __all__ = [
20
20
  "Album",
21
21
  "Artist",
@@ -0,0 +1,89 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, ClassVar
4
+
5
+ from deezer.exceptions import DeezerUnknownResource
6
+ from deezer.resources import (
7
+ Album,
8
+ Artist,
9
+ Chart,
10
+ Editorial,
11
+ Episode,
12
+ Genre,
13
+ Playlist,
14
+ Podcast,
15
+ Radio,
16
+ Resource,
17
+ Track,
18
+ User,
19
+ )
20
+
21
+
22
+ class DeezerMixin:
23
+ """Mixin providing shared logic for both sync and async Deezer clients."""
24
+
25
+ _resource_base_class: ClassVar[type[Resource]] = Resource
26
+
27
+ objects_types: ClassVar[dict[str, type[Resource] | None]] = {
28
+ "album": Album,
29
+ "artist": Artist,
30
+ "chart": Chart,
31
+ "editorial": Editorial,
32
+ "episode": Episode,
33
+ "genre": Genre,
34
+ "playlist": Playlist,
35
+ "podcast": Podcast,
36
+ "radio": Radio,
37
+ "search": None,
38
+ "track": Track,
39
+ "user": User,
40
+ }
41
+
42
+ def _process_json(
43
+ self,
44
+ item: dict[str, Any],
45
+ parent: Any = None,
46
+ resource_type: type[Any] | None = None,
47
+ resource_id: int | None = None,
48
+ paginate_list=False,
49
+ ):
50
+ """
51
+ Recursively convert dictionary to :class:`~deezer.Resource` object.
52
+
53
+ :param item: the JSON response as dict.
54
+ :param parent: A reference to the parent resource, to avoid fetching again.
55
+ :param resource_type: The resource class to use as top level.
56
+ :param resource_id: The resource id to use as top level.
57
+ :param paginate_list: Whether to wrap list into a pagination object.
58
+ :returns: instance of :class:`~deezer.Resource`
59
+ """
60
+ if "data" in item:
61
+ parsed_data = [self._process_json(i, parent, paginate_list=False) for i in item["data"]]
62
+ if not paginate_list:
63
+ return parsed_data
64
+ item["data"] = parsed_data
65
+ return item
66
+
67
+ result = {}
68
+ for key, value in item.items():
69
+ if isinstance(value, dict) and ("type" in value or "data" in value):
70
+ value = self._process_json(value, parent)
71
+ result[key] = value
72
+ if parent is not None:
73
+ result[parent.type] = parent
74
+
75
+ if "id" not in result and resource_id is not None:
76
+ result["id"] = resource_id
77
+
78
+ if "type" in result and result["type"] in self.objects_types:
79
+ object_class = self.objects_types[result["type"]]
80
+ elif "type" in result or (not resource_type and "id" in result):
81
+ object_class = self._resource_base_class
82
+ elif resource_type:
83
+ object_class = resource_type
84
+ elif item.get("results") is True:
85
+ return True
86
+ else:
87
+ raise DeezerUnknownResource(f"Unable to find resource type for {result!r}")
88
+ assert object_class is not None # noqa S101
89
+ return object_class(self, result)
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from .client import AsyncClient
4
+ from .pagination import AsyncPaginatedList
5
+ from .resources import (
6
+ AsyncAlbum,
7
+ AsyncArtist,
8
+ AsyncChart,
9
+ AsyncEditorial,
10
+ AsyncEpisode,
11
+ AsyncGenre,
12
+ AsyncPlaylist,
13
+ AsyncPodcast,
14
+ AsyncRadio,
15
+ AsyncResource,
16
+ AsyncTrack,
17
+ AsyncUser,
18
+ )
19
+
20
+ __all__ = [
21
+ "AsyncAlbum",
22
+ "AsyncArtist",
23
+ "AsyncChart",
24
+ "AsyncClient",
25
+ "AsyncEditorial",
26
+ "AsyncEpisode",
27
+ "AsyncGenre",
28
+ "AsyncPaginatedList",
29
+ "AsyncPlaylist",
30
+ "AsyncPodcast",
31
+ "AsyncRadio",
32
+ "AsyncResource",
33
+ "AsyncTrack",
34
+ "AsyncUser",
35
+ ]