deezer-python 7.2.0__tar.gz → 7.4.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.2.0 → deezer_python-7.4.0}/CHANGELOG.md +12 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/CONTRIBUTING.md +3 -3
- {deezer_python-7.2.0 → deezer_python-7.4.0}/PKG-INFO +4 -3
- {deezer_python-7.2.0 → deezer_python-7.4.0}/README.md +2 -2
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/Makefile +4 -0
- deezer_python-7.4.0/docs/api_reference/async_client.rst +7 -0
- deezer_python-7.4.0/docs/api_reference/async_pagination.rst +7 -0
- deezer_python-7.4.0/docs/api_reference/async_resources/album.rst +6 -0
- deezer_python-7.4.0/docs/api_reference/async_resources/artist.rst +6 -0
- deezer_python-7.4.0/docs/api_reference/async_resources/chart.rst +6 -0
- deezer_python-7.4.0/docs/api_reference/async_resources/editorial.rst +6 -0
- deezer_python-7.4.0/docs/api_reference/async_resources/episode.rst +6 -0
- deezer_python-7.4.0/docs/api_reference/async_resources/genre.rst +6 -0
- deezer_python-7.4.0/docs/api_reference/async_resources/playlist.rst +6 -0
- deezer_python-7.4.0/docs/api_reference/async_resources/podcast.rst +6 -0
- deezer_python-7.4.0/docs/api_reference/async_resources/radio.rst +6 -0
- deezer_python-7.4.0/docs/api_reference/async_resources/track.rst +6 -0
- deezer_python-7.4.0/docs/api_reference/async_resources/user.rst +6 -0
- deezer_python-7.4.0/docs/api_reference/async_resources.md +21 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/toc.rst +3 -0
- deezer_python-7.4.0/docs/async.md +126 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/index.md +1 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/pyproject.toml +24 -47
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/__init__.py +1 -1
- deezer_python-7.4.0/src/deezer/_mixin.py +90 -0
- deezer_python-7.4.0/src/deezer/asyncio/__init__.py +35 -0
- deezer_python-7.4.0/src/deezer/asyncio/client.py +443 -0
- deezer_python-7.4.0/src/deezer/asyncio/pagination.py +142 -0
- deezer_python-7.4.0/src/deezer/asyncio/resources/__init__.py +29 -0
- deezer_python-7.4.0/src/deezer/asyncio/resources/album.py +72 -0
- deezer_python-7.4.0/src/deezer/asyncio/resources/artist.py +48 -0
- deezer_python-7.4.0/src/deezer/asyncio/resources/chart.py +36 -0
- deezer_python-7.4.0/src/deezer/asyncio/resources/editorial.py +33 -0
- deezer_python-7.4.0/src/deezer/asyncio/resources/episode.py +44 -0
- deezer_python-7.4.0/src/deezer/asyncio/resources/genre.py +32 -0
- deezer_python-7.4.0/src/deezer/asyncio/resources/playlist.py +61 -0
- deezer_python-7.4.0/src/deezer/asyncio/resources/podcast.py +29 -0
- deezer_python-7.4.0/src/deezer/asyncio/resources/radio.py +23 -0
- deezer_python-7.4.0/src/deezer/asyncio/resources/resource.py +103 -0
- deezer_python-7.4.0/src/deezer/asyncio/resources/track.py +56 -0
- deezer_python-7.4.0/src/deezer/asyncio/resources/user.py +110 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/auth.py +1 -1
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/client.py +3 -71
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer_python.egg-info/PKG-INFO +4 -3
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer_python.egg-info/SOURCES.txt +32 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/LICENSE.txt +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/MANIFEST.in +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/_static/.placeholder +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/client.rst +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/pagination.rst +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/resources/album.rst +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/resources/artist.rst +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/resources/chart.rst +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/resources/editorial.rst +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/resources/episode.rst +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/resources/genre.rst +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/resources/playlist.rst +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/resources/podcast.rst +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/resources/radio.rst +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/resources/track.rst +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/resources/user.rst +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/api_reference/resources.md +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/changelog.md +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/conf.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/contributing.md +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/installation.md +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/make.bat +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/pagination.md +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/docs/usage.md +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/setup.cfg +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/dates.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/exceptions.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/pagination.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/resources/__init__.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/resources/album.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/resources/artist.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/resources/chart.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/resources/editorial.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/resources/episode.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/resources/genre.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/resources/playlist.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/resources/podcast.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/resources/radio.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/resources/resource.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/resources/track.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/resources/user.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer/utils.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer_python.egg-info/dependency_links.txt +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer_python.egg-info/requires.txt +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/src/deezer_python.egg-info/top_level.txt +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/tests/test_client.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/tests/test_exceptions.py +0 -0
- {deezer_python-7.2.0 → deezer_python-7.4.0}/tests/test_pagination.py +0 -0
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## v7.4.0 (2026-08-07)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Add support for python 3.15 ([`5de06d6`](https://github.com/browniebroke/deezer-python/commit/5de06d645a7b5fa92bdcb6c53687b8d2f7ea0f55))
|
|
8
|
+
|
|
9
|
+
## v7.3.0 (2026-04-06)
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- Add async deezer client and resources in `deezer.asyncio` ([`cf30c7c`](https://github.com/browniebroke/deezer-python/commit/cf30c7cf7f9d58d0815456d49fd9095d1d364154))
|
|
14
|
+
|
|
3
15
|
## v7.2.0 (2025-09-18)
|
|
4
16
|
|
|
5
17
|
### Features
|
|
@@ -65,16 +65,16 @@ 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 [
|
|
68
|
+
6. Linting is done through [prek](https://prek.j178.dev/). Provided you have the tool installed globally, you can run them all as one-off:
|
|
69
69
|
|
|
70
70
|
```shell
|
|
71
|
-
$
|
|
71
|
+
$ prek run -a
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
Or better, install the hooks once and have them run automatically each time you commit:
|
|
75
75
|
|
|
76
76
|
```shell
|
|
77
|
-
$
|
|
77
|
+
$ prek install -f
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
7. Commit your changes, quoting GitHub issue in the commit message, if applicable, and push your branch to GitHub:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deezer-python
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.4.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
|
|
@@ -21,6 +21,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
24
25
|
Classifier: Topic :: Software Development :: Libraries
|
|
25
26
|
Requires-Python: >=3.10
|
|
26
27
|
Description-Content-Type: text/markdown
|
|
@@ -48,8 +49,8 @@ Dynamic: license-file
|
|
|
48
49
|
<a href="https://github.com/astral-sh/ruff">
|
|
49
50
|
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff">
|
|
50
51
|
</a>
|
|
51
|
-
<a href="https://github.com/
|
|
52
|
-
<img src="https://img.shields.io/
|
|
52
|
+
<a href="https://github.com/j178/prek">
|
|
53
|
+
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/j178/prek/master/docs/assets/badge-v0.json" alt="prek">
|
|
53
54
|
</a>
|
|
54
55
|
</p>
|
|
55
56
|
<p align="center">
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
<a href="https://github.com/astral-sh/ruff">
|
|
19
19
|
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff">
|
|
20
20
|
</a>
|
|
21
|
-
<a href="https://github.com/
|
|
22
|
-
<img src="https://img.shields.io/
|
|
21
|
+
<a href="https://github.com/j178/prek">
|
|
22
|
+
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/j178/prek/master/docs/assets/badge-v0.json" alt="prek">
|
|
23
23
|
</a>
|
|
24
24
|
</p>
|
|
25
25
|
<p align="center">
|
|
@@ -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.4.0"
|
|
9
8
|
description = "A friendly wrapper library for the Deezer API"
|
|
10
9
|
readme = "README.md"
|
|
11
10
|
keywords = [
|
|
@@ -30,9 +29,9 @@ classifiers = [
|
|
|
30
29
|
"Programming Language :: Python :: 3.12",
|
|
31
30
|
"Programming Language :: Python :: 3.13",
|
|
32
31
|
"Programming Language :: Python :: 3.14",
|
|
32
|
+
"Programming Language :: Python :: 3.15",
|
|
33
33
|
"Topic :: Software Development :: Libraries",
|
|
34
34
|
]
|
|
35
|
-
|
|
36
35
|
dependencies = [
|
|
37
36
|
"httpx>=0.23",
|
|
38
37
|
]
|
|
@@ -47,14 +46,15 @@ urls.Twitter = "https://twitter.com/_BrunoAlla"
|
|
|
47
46
|
dev = [
|
|
48
47
|
"coverage>=7,<8",
|
|
49
48
|
"deezer-oauth-cli>=1,<2",
|
|
50
|
-
"environs>=
|
|
51
|
-
"pytest>=
|
|
49
|
+
"environs>=15,<16",
|
|
50
|
+
"pytest>=9,<10",
|
|
51
|
+
"pytest-asyncio",
|
|
52
52
|
"pytest-cov>=7,<8",
|
|
53
53
|
"pytest-mock>=3.6,<4",
|
|
54
54
|
"pytest-vcr>=1,<2",
|
|
55
|
-
"
|
|
55
|
+
"ty==0.0.69",
|
|
56
|
+
"vcrpy>=8,<9",
|
|
56
57
|
]
|
|
57
|
-
|
|
58
58
|
docs = [
|
|
59
59
|
"furo>=2023.5.20; python_version>='3.11'",
|
|
60
60
|
"myst-parser>=0.16; python_version>='3.11'",
|
|
@@ -64,7 +64,6 @@ docs = [
|
|
|
64
64
|
|
|
65
65
|
[tool.ruff]
|
|
66
66
|
line-length = 120
|
|
67
|
-
|
|
68
67
|
lint.select = [
|
|
69
68
|
"B", # flake8-bugbear
|
|
70
69
|
"C4", # flake8-comprehensions
|
|
@@ -99,64 +98,42 @@ lint.per-file-ignores."tests/**/*" = [
|
|
|
99
98
|
lint.isort.known-first-party = [ "deezer" ]
|
|
100
99
|
|
|
101
100
|
[tool.pyproject-fmt]
|
|
102
|
-
max_supported_python = "3.
|
|
101
|
+
max_supported_python = "3.15"
|
|
103
102
|
|
|
104
|
-
[tool.pytest
|
|
105
|
-
addopts = "-v -Wdefault --cov=deezer"
|
|
103
|
+
[tool.pytest]
|
|
104
|
+
addopts = [ "-v", "-Wdefault", "--cov=deezer" ]
|
|
105
|
+
minversion = "9.0"
|
|
106
106
|
pythonpath = [ "src" ]
|
|
107
107
|
|
|
108
|
-
[tool.coverage
|
|
109
|
-
branch = true
|
|
110
|
-
source = [ "deezer" ]
|
|
111
|
-
|
|
112
|
-
[tool.coverage.report]
|
|
113
|
-
ignore_errors = true
|
|
114
|
-
exclude_lines = [
|
|
115
|
-
"pragma: no cover",
|
|
108
|
+
[tool.coverage]
|
|
109
|
+
run.branch = true
|
|
110
|
+
run.source = [ "deezer" ]
|
|
111
|
+
report.exclude_lines = [
|
|
116
112
|
"@overload",
|
|
117
113
|
"if TYPE_CHECKING",
|
|
114
|
+
"pragma: no cover",
|
|
118
115
|
"raise NotImplementedError",
|
|
119
116
|
]
|
|
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
|
|
117
|
+
report.ignore_errors = true
|
|
131
118
|
|
|
132
119
|
[tool.semantic_release]
|
|
133
|
-
version_toml = [ "pyproject.toml:project.version" ]
|
|
134
120
|
version_variables = [
|
|
135
121
|
"src/deezer/__init__.py:__version__",
|
|
136
122
|
]
|
|
123
|
+
version_toml = [ "pyproject.toml:project.version" ]
|
|
137
124
|
build_command = """
|
|
138
125
|
pip install uv
|
|
139
126
|
uv lock
|
|
140
127
|
git add uv.lock
|
|
141
128
|
uv build
|
|
142
129
|
"""
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
130
|
+
branches.main.match = "main"
|
|
131
|
+
branches.noop.match = "(?!main$)"
|
|
132
|
+
branches.noop.prerelease = true
|
|
133
|
+
branches.stable.match = "(?!stable/*$)"
|
|
134
|
+
changelog.environment.keep_trailing_newline = true
|
|
135
|
+
changelog.exclude_commit_patterns = [
|
|
146
136
|
"chore.*",
|
|
147
137
|
"ci.*",
|
|
148
138
|
"Merge pull request .*",
|
|
149
139
|
]
|
|
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
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
resource_type_name = result.get("type")
|
|
79
|
+
if isinstance(resource_type_name, str) and resource_type_name in self.objects_types:
|
|
80
|
+
object_class = self.objects_types[resource_type_name]
|
|
81
|
+
elif "type" in result or (not resource_type and "id" in result):
|
|
82
|
+
object_class = self._resource_base_class
|
|
83
|
+
elif resource_type:
|
|
84
|
+
object_class = resource_type
|
|
85
|
+
elif item.get("results") is True:
|
|
86
|
+
return True
|
|
87
|
+
else:
|
|
88
|
+
raise DeezerUnknownResource(f"Unable to find resource type for {result!r}")
|
|
89
|
+
assert object_class is not None # noqa S101
|
|
90
|
+
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
|
+
]
|