deezer-python 7.1.2__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.1.2 → deezer_python-7.3.0}/CHANGELOG.md +13 -0
  2. {deezer_python-7.1.2 → deezer_python-7.3.0}/CONTRIBUTING.md +9 -3
  3. {deezer_python-7.1.2 → deezer_python-7.3.0}/PKG-INFO +3 -3
  4. {deezer_python-7.1.2 → 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.1.2 → 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.1.2 → deezer_python-7.3.0}/docs/index.md +1 -0
  22. {deezer_python-7.1.2 → deezer_python-7.3.0}/pyproject.toml +27 -48
  23. {deezer_python-7.1.2 → 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.1.2 → deezer_python-7.3.0}/src/deezer/auth.py +1 -1
  42. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/client.py +3 -71
  43. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer_python.egg-info/PKG-INFO +3 -3
  44. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer_python.egg-info/SOURCES.txt +32 -0
  45. {deezer_python-7.1.2 → deezer_python-7.3.0}/LICENSE.txt +0 -0
  46. {deezer_python-7.1.2 → deezer_python-7.3.0}/MANIFEST.in +0 -0
  47. {deezer_python-7.1.2 → deezer_python-7.3.0}/README.md +0 -0
  48. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/_static/.placeholder +0 -0
  49. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/client.rst +0 -0
  50. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/pagination.rst +0 -0
  51. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/album.rst +0 -0
  52. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/artist.rst +0 -0
  53. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/chart.rst +0 -0
  54. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/editorial.rst +0 -0
  55. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/episode.rst +0 -0
  56. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/genre.rst +0 -0
  57. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/playlist.rst +0 -0
  58. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/podcast.rst +0 -0
  59. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/radio.rst +0 -0
  60. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/track.rst +0 -0
  61. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/user.rst +0 -0
  62. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources.md +0 -0
  63. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/changelog.md +0 -0
  64. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/conf.py +0 -0
  65. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/contributing.md +0 -0
  66. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/installation.md +0 -0
  67. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/make.bat +0 -0
  68. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/pagination.md +0 -0
  69. {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/usage.md +0 -0
  70. {deezer_python-7.1.2 → deezer_python-7.3.0}/setup.cfg +0 -0
  71. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/dates.py +0 -0
  72. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/exceptions.py +0 -0
  73. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/pagination.py +0 -0
  74. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/__init__.py +0 -0
  75. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/album.py +0 -0
  76. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/artist.py +0 -0
  77. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/chart.py +0 -0
  78. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/editorial.py +0 -0
  79. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/episode.py +0 -0
  80. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/genre.py +0 -0
  81. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/playlist.py +0 -0
  82. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/podcast.py +0 -0
  83. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/radio.py +0 -0
  84. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/resource.py +0 -0
  85. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/track.py +0 -0
  86. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/user.py +0 -0
  87. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/utils.py +0 -0
  88. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer_python.egg-info/dependency_links.txt +0 -0
  89. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer_python.egg-info/requires.txt +0 -0
  90. {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer_python.egg-info/top_level.txt +0 -0
  91. {deezer_python-7.1.2 → deezer_python-7.3.0}/tests/test_client.py +0 -0
  92. {deezer_python-7.1.2 → deezer_python-7.3.0}/tests/test_exceptions.py +0 -0
  93. {deezer_python-7.1.2 → deezer_python-7.3.0}/tests/test_pagination.py +0 -0
@@ -1,5 +1,18 @@
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
+
9
+ ## v7.2.0 (2025-09-18)
10
+
11
+ ### Features
12
+
13
+ - Add support for python 3.14 ([`81adb2e`](https://github.com/browniebroke/deezer-python/commit/81adb2ebe81cbfe7344ebf415572e48f14b69ae0))
14
+ - Drop support for python 3.9 ([`cd837d2`](https://github.com/browniebroke/deezer-python/commit/cd837d2ce61a51d34c49b86db0dc84f205e742b2))
15
+
3
16
  ## v7.1.2 (2025-08-25)
4
17
 
5
18
  ### Bug fixes
@@ -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.1.2
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
@@ -16,13 +16,13 @@ Classifier: Intended Audience :: Developers
16
16
  Classifier: Natural Language :: English
17
17
  Classifier: Operating System :: OS Independent
18
18
  Classifier: Programming Language :: Python :: 3 :: Only
19
- Classifier: Programming Language :: Python :: 3.9
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Programming Language :: Python :: 3.12
23
22
  Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
24
  Classifier: Topic :: Software Development :: Libraries
25
- Requires-Python: >=3.9
25
+ Requires-Python: >=3.10
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE.txt
28
28
  Requires-Dist: httpx>=0.23
@@ -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.1.2"
7
+ version = "7.3.0"
9
8
  description = "A friendly wrapper library for the Deezer API"
10
9
  readme = "README.md"
11
10
  keywords = [
@@ -18,21 +17,20 @@ license = "MIT"
18
17
  authors = [
19
18
  { name = "Bruno Alla", email = "alla.brunoo@gmail.com" },
20
19
  ]
21
- requires-python = ">=3.9"
20
+ requires-python = ">=3.10"
22
21
  classifiers = [
23
22
  "Development Status :: 5 - Production/Stable",
24
23
  "Intended Audience :: Developers",
25
24
  "Natural Language :: English",
26
25
  "Operating System :: OS Independent",
27
26
  "Programming Language :: Python :: 3 :: Only",
28
- "Programming Language :: Python :: 3.9",
29
27
  "Programming Language :: Python :: 3.10",
30
28
  "Programming Language :: Python :: 3.11",
31
29
  "Programming Language :: Python :: 3.12",
32
30
  "Programming Language :: Python :: 3.13",
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",
52
- "pytest-cov>=6,<7",
48
+ "environs>=15,<16",
49
+ "pytest>=9,<10",
50
+ "pytest-asyncio",
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
@@ -98,33 +96,24 @@ lint.per-file-ignores."tests/**/*" = [
98
96
  ]
99
97
  lint.isort.known-first-party = [ "deezer" ]
100
98
 
101
- [tool.pytest.ini_options]
102
- addopts = "-v -Wdefault --cov=deezer"
103
- pythonpath = [ "src" ]
99
+ [tool.pyproject-fmt]
100
+ max_supported_python = "3.14"
104
101
 
105
- [tool.coverage.run]
106
- branch = true
107
- source = [ "deezer" ]
102
+ [tool.pytest]
103
+ minversion = "9.0"
104
+ addopts = [ "-v", "-Wdefault", "--cov=deezer" ]
105
+ pythonpath = [ "src" ]
108
106
 
109
- [tool.coverage.report]
110
- ignore_errors = true
111
- exclude_lines = [
112
- "pragma: no cover",
107
+ [tool.coverage]
108
+ run.branch = true
109
+ run.source = [ "deezer" ]
110
+ report.exclude_lines = [
113
111
  "@overload",
114
112
  "if TYPE_CHECKING",
113
+ "pragma: no cover",
115
114
  "raise NotImplementedError",
116
115
  ]
117
-
118
- [tool.mypy]
119
- # suppress errors about unsatisfied imports
120
- ignore_missing_imports = true
121
-
122
- no_implicit_optional = true
123
-
124
- # ensure all execution paths are returning
125
- warn_no_return = true
126
-
127
- show_error_codes = true
116
+ report.ignore_errors = true
128
117
 
129
118
  [tool.semantic_release]
130
119
  version_toml = [ "pyproject.toml:project.version" ]
@@ -137,23 +126,13 @@ uv lock
137
126
  git add uv.lock
138
127
  uv build
139
128
  """
140
-
141
- [tool.semantic_release.changelog]
142
- 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 = [
143
134
  "chore.*",
144
135
  "ci.*",
145
136
  "Merge pull request .*",
146
137
  ]
147
-
148
- [tool.semantic_release.changelog.environment]
149
- keep_trailing_newline = true
150
-
151
- [tool.semantic_release.branches.main]
152
- match = "main"
153
-
154
- [tool.semantic_release.branches.stable]
155
- match = "(?!stable/*$)"
156
-
157
- [tool.semantic_release.branches.noop]
158
- match = "(?!main$)"
159
- 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.1.2"
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
+ ]