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.
- {deezer_python-7.1.2 → deezer_python-7.3.0}/CHANGELOG.md +13 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/CONTRIBUTING.md +9 -3
- {deezer_python-7.1.2 → deezer_python-7.3.0}/PKG-INFO +3 -3
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/Makefile +4 -0
- deezer_python-7.3.0/docs/api_reference/async_client.rst +7 -0
- deezer_python-7.3.0/docs/api_reference/async_pagination.rst +7 -0
- deezer_python-7.3.0/docs/api_reference/async_resources/album.rst +6 -0
- deezer_python-7.3.0/docs/api_reference/async_resources/artist.rst +6 -0
- deezer_python-7.3.0/docs/api_reference/async_resources/chart.rst +6 -0
- deezer_python-7.3.0/docs/api_reference/async_resources/editorial.rst +6 -0
- deezer_python-7.3.0/docs/api_reference/async_resources/episode.rst +6 -0
- deezer_python-7.3.0/docs/api_reference/async_resources/genre.rst +6 -0
- deezer_python-7.3.0/docs/api_reference/async_resources/playlist.rst +6 -0
- deezer_python-7.3.0/docs/api_reference/async_resources/podcast.rst +6 -0
- deezer_python-7.3.0/docs/api_reference/async_resources/radio.rst +6 -0
- deezer_python-7.3.0/docs/api_reference/async_resources/track.rst +6 -0
- deezer_python-7.3.0/docs/api_reference/async_resources/user.rst +6 -0
- deezer_python-7.3.0/docs/api_reference/async_resources.md +21 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/toc.rst +3 -0
- deezer_python-7.3.0/docs/async.md +126 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/index.md +1 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/pyproject.toml +27 -48
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/__init__.py +1 -1
- deezer_python-7.3.0/src/deezer/_mixin.py +89 -0
- deezer_python-7.3.0/src/deezer/asyncio/__init__.py +35 -0
- deezer_python-7.3.0/src/deezer/asyncio/client.py +443 -0
- deezer_python-7.3.0/src/deezer/asyncio/pagination.py +142 -0
- deezer_python-7.3.0/src/deezer/asyncio/resources/__init__.py +29 -0
- deezer_python-7.3.0/src/deezer/asyncio/resources/album.py +72 -0
- deezer_python-7.3.0/src/deezer/asyncio/resources/artist.py +48 -0
- deezer_python-7.3.0/src/deezer/asyncio/resources/chart.py +36 -0
- deezer_python-7.3.0/src/deezer/asyncio/resources/editorial.py +33 -0
- deezer_python-7.3.0/src/deezer/asyncio/resources/episode.py +44 -0
- deezer_python-7.3.0/src/deezer/asyncio/resources/genre.py +32 -0
- deezer_python-7.3.0/src/deezer/asyncio/resources/playlist.py +61 -0
- deezer_python-7.3.0/src/deezer/asyncio/resources/podcast.py +29 -0
- deezer_python-7.3.0/src/deezer/asyncio/resources/radio.py +23 -0
- deezer_python-7.3.0/src/deezer/asyncio/resources/resource.py +103 -0
- deezer_python-7.3.0/src/deezer/asyncio/resources/track.py +56 -0
- deezer_python-7.3.0/src/deezer/asyncio/resources/user.py +110 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/auth.py +1 -1
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/client.py +3 -71
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer_python.egg-info/PKG-INFO +3 -3
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer_python.egg-info/SOURCES.txt +32 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/LICENSE.txt +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/MANIFEST.in +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/README.md +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/_static/.placeholder +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/client.rst +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/pagination.rst +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/album.rst +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/artist.rst +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/chart.rst +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/editorial.rst +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/episode.rst +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/genre.rst +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/playlist.rst +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/podcast.rst +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/radio.rst +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/track.rst +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources/user.rst +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/api_reference/resources.md +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/changelog.md +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/conf.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/contributing.md +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/installation.md +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/make.bat +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/pagination.md +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/docs/usage.md +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/setup.cfg +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/dates.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/exceptions.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/pagination.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/__init__.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/album.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/artist.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/chart.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/editorial.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/episode.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/genre.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/playlist.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/podcast.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/radio.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/resource.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/track.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/resources/user.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer/utils.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer_python.egg-info/dependency_links.txt +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer_python.egg-info/requires.txt +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/src/deezer_python.egg-info/top_level.txt +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/tests/test_client.py +0 -0
- {deezer_python-7.1.2 → deezer_python-7.3.0}/tests/test_exceptions.py +0 -0
- {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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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,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
|
+
```
|
|
@@ -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`.
|
|
@@ -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.
|
|
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.
|
|
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>=
|
|
51
|
-
"pytest>=
|
|
52
|
-
"pytest-
|
|
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
|
-
"
|
|
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.
|
|
102
|
-
|
|
103
|
-
pythonpath = [ "src" ]
|
|
99
|
+
[tool.pyproject-fmt]
|
|
100
|
+
max_supported_python = "3.14"
|
|
104
101
|
|
|
105
|
-
[tool.
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
[tool.pytest]
|
|
103
|
+
minversion = "9.0"
|
|
104
|
+
addopts = [ "-v", "-Wdefault", "--cov=deezer" ]
|
|
105
|
+
pythonpath = [ "src" ]
|
|
108
106
|
|
|
109
|
-
[tool.coverage
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
|
@@ -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
|
+
]
|