spotapi 1.2.7__tar.gz → 2.0.0b1__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.
- spotapi-2.0.0b1/.gitignore +166 -0
- spotapi-2.0.0b1/CHANGELOG.md +90 -0
- spotapi-2.0.0b1/CODE_OF_CONDUCT.md +128 -0
- spotapi-2.0.0b1/CONTRIBUTING.md +247 -0
- spotapi-2.0.0b1/LEGAL_NOTICE.md +46 -0
- spotapi-2.0.0b1/MIGRATION.md +182 -0
- spotapi-2.0.0b1/PKG-INFO +946 -0
- spotapi-2.0.0b1/README.md +239 -0
- spotapi-2.0.0b1/docs/album.md +59 -0
- spotapi-2.0.0b1/docs/artist.md +107 -0
- spotapi-2.0.0b1/docs/creator.md +56 -0
- spotapi-2.0.0b1/docs/family.md +62 -0
- spotapi-2.0.0b1/docs/language.md +90 -0
- spotapi-2.0.0b1/docs/login.md +103 -0
- spotapi-2.0.0b1/docs/password.md +38 -0
- spotapi-2.0.0b1/docs/player.md +183 -0
- spotapi-2.0.0b1/docs/playlist.md +179 -0
- spotapi-2.0.0b1/docs/public.md +143 -0
- spotapi-2.0.0b1/docs/song.md +126 -0
- spotapi-2.0.0b1/docs/status.md +148 -0
- spotapi-2.0.0b1/docs/user.md +67 -0
- spotapi-2.0.0b1/docs/websocket.md +29 -0
- spotapi-2.0.0b1/example.py +116 -0
- spotapi-2.0.0b1/image.png +0 -0
- spotapi-2.0.0b1/pyproject.toml +86 -0
- spotapi-2.0.0b1/requirements.txt +2 -0
- {spotapi-1.2.7 → spotapi-2.0.0b1}/setup.py +2 -2
- spotapi-2.0.0b1/spotapi/sync/__init__.py +25 -0
- spotapi-2.0.0b1/spotapi/sync/_tests/.env.sample +3 -0
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/_tests/__init__.py +1 -1
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/_tests/annotations_test.py +2 -4
- spotapi-2.0.0b1/spotapi/sync/_tests/client_refresh_test.py +224 -0
- spotapi-2.0.0b1/spotapi/sync/_tests/features/artist_test.py +196 -0
- spotapi-2.0.0b1/spotapi/sync/_tests/features/event_test.py +53 -0
- spotapi-2.0.0b1/spotapi/sync/_tests/features/player_test.py +112 -0
- spotapi-2.0.0b1/spotapi/sync/_tests/features/private_playlist_test.py +70 -0
- spotapi-2.0.0b1/spotapi/sync/_tests/features/public_playlist_test.py +32 -0
- spotapi-2.0.0b1/spotapi/sync/_tests/features/session.py +36 -0
- spotapi-2.0.0b1/spotapi/sync/_tests/features/song_test.py +67 -0
- spotapi-2.0.0b1/spotapi/sync/_tests/features/status_test.py +126 -0
- spotapi-2.0.0b1/spotapi/sync/_tests/features/user_test.py +63 -0
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/album.py +10 -9
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/artist.py +117 -17
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/client.py +85 -26
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/creator.py +10 -18
- spotapi-2.0.0b1/spotapi/sync/exceptions/__init__.py +1 -0
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/family.py +5 -5
- spotapi-2.0.0b1/spotapi/sync/http/__init__.py +2 -0
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/http/data.py +1 -1
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/http/request.py +61 -61
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/login.py +10 -20
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/password.py +6 -8
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/player.py +14 -26
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/playlist.py +80 -30
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/podcast.py +9 -12
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/public.py +3 -7
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/solvers/__init__.py +3 -3
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/solvers/capmonster.py +6 -14
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/solvers/capsolver.py +7 -17
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/song.py +18 -20
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/status.py +7 -15
- spotapi-2.0.0b1/spotapi/sync/types/__init__.py +2 -0
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/types/data.py +20 -54
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/types/interfaces.py +12 -25
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/user.py +3 -3
- spotapi-2.0.0b1/spotapi/sync/utils/__init__.py +3 -0
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/utils/logger.py +5 -9
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/utils/saver.py +7 -11
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/websocket.py +5 -5
- spotapi-2.0.0b1/spotapi/v2/__init__.py +5 -0
- spotapi-2.0.0b1/spotapi/v2/base.py +100 -0
- spotapi-2.0.0b1/spotapi/v2/client.py +101 -0
- spotapi-2.0.0b1/spotapi/v2/connection/__init__.py +12 -0
- spotapi-2.0.0b1/spotapi/v2/connection/http.py +263 -0
- spotapi-2.0.0b1/spotapi/v2/connection/types.py +76 -0
- spotapi-2.0.0b1/spotapi/v2/connection/websocket.py +199 -0
- spotapi-2.0.0b1/spotapi/v2/datastruct/__init__.py +9 -0
- spotapi-2.0.0b1/spotapi/v2/datastruct/event_handler.py +62 -0
- spotapi-2.0.0b1/spotapi/v2/datastruct/object_dict.py +129 -0
- spotapi-2.0.0b1/spotapi/v2/datastruct/pool.py +156 -0
- spotapi-2.0.0b1/spotapi/v2/public.py +151 -0
- spotapi-2.0.0b1/spotapi/v2/py.typed +0 -0
- spotapi-2.0.0b1/spotapi/v2/session.py +361 -0
- spotapi-2.0.0b1/spotapi/v2/specialized/__init__.py +3 -0
- spotapi-2.0.0b1/spotapi/v2/specialized/data_wrappers/__init__.py +163 -0
- spotapi-2.0.0b1/spotapi/v2/specialized/data_wrappers/album.py +93 -0
- spotapi-2.0.0b1/spotapi/v2/specialized/data_wrappers/artist.py +66 -0
- spotapi-2.0.0b1/spotapi/v2/specialized/data_wrappers/common.py +139 -0
- spotapi-2.0.0b1/spotapi/v2/specialized/data_wrappers/playlist.py +81 -0
- spotapi-2.0.0b1/spotapi/v2/specialized/data_wrappers/podcast.py +75 -0
- spotapi-2.0.0b1/spotapi/v2/specialized/data_wrappers/track.py +113 -0
- spotapi-2.0.0b1/spotapi/v2/specialized/totp.py +66 -0
- spotapi-2.0.0b1/spotapi/v2/types/__init__.py +15 -0
- spotapi-2.0.0b1/spotapi/v2/types/exceptions.py +22 -0
- spotapi-2.0.0b1/spotapi/v2/types/logger.py +724 -0
- spotapi-2.0.0b1/spotapi/v2/utils/__init__.py +15 -0
- spotapi-2.0.0b1/spotapi/v2/utils/cache.py +62 -0
- spotapi-2.0.0b1/spotapi/v2/utils/random.py +22 -0
- spotapi-2.0.0b1/spotapi/v2/utils/strings.py +131 -0
- spotapi-1.2.7/PKG-INFO +0 -168
- spotapi-1.2.7/README.md +0 -151
- spotapi-1.2.7/setup.cfg +0 -4
- spotapi-1.2.7/spotapi/__init__.py +0 -26
- spotapi-1.2.7/spotapi/exceptions/__init__.py +0 -1
- spotapi-1.2.7/spotapi/http/__init__.py +0 -2
- spotapi-1.2.7/spotapi/types/__init__.py +0 -2
- spotapi-1.2.7/spotapi/utils/__init__.py +0 -3
- spotapi-1.2.7/spotapi.egg-info/PKG-INFO +0 -168
- spotapi-1.2.7/spotapi.egg-info/SOURCES.txt +0 -43
- spotapi-1.2.7/spotapi.egg-info/dependency_links.txt +0 -1
- spotapi-1.2.7/spotapi.egg-info/requires.txt +0 -18
- spotapi-1.2.7/spotapi.egg-info/top_level.txt +0 -1
- {spotapi-1.2.7 → spotapi-2.0.0b1}/LICENSE +0 -0
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/exceptions/errors.py +0 -0
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/types/alias.py +0 -0
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/types/annotations.py +0 -0
- {spotapi-1.2.7/spotapi → spotapi-2.0.0b1/spotapi/sync}/utils/strings.py +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.vscode/
|
|
11
|
+
.Python
|
|
12
|
+
build/
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
wheels/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
# Usually these files are written by a python script from a template
|
|
32
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
33
|
+
*.manifest
|
|
34
|
+
*.spec
|
|
35
|
+
|
|
36
|
+
# Installer logs
|
|
37
|
+
pip-log.txt
|
|
38
|
+
pip-delete-this-directory.txt
|
|
39
|
+
|
|
40
|
+
# Unit test / coverage reports
|
|
41
|
+
htmlcov/
|
|
42
|
+
.tox/
|
|
43
|
+
.nox/
|
|
44
|
+
.coverage
|
|
45
|
+
.coverage.*
|
|
46
|
+
.cache
|
|
47
|
+
nosetests.xml
|
|
48
|
+
coverage.xml
|
|
49
|
+
*.cover
|
|
50
|
+
*.py,cover
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
cover/
|
|
54
|
+
|
|
55
|
+
# Translations
|
|
56
|
+
*.mo
|
|
57
|
+
*.pot
|
|
58
|
+
|
|
59
|
+
# Django stuff:
|
|
60
|
+
*.log
|
|
61
|
+
local_settings.py
|
|
62
|
+
db.sqlite3
|
|
63
|
+
db.sqlite3-journal
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
.pybuilder/
|
|
77
|
+
target/
|
|
78
|
+
|
|
79
|
+
# Jupyter Notebook
|
|
80
|
+
.ipynb_checkpoints
|
|
81
|
+
|
|
82
|
+
# IPython
|
|
83
|
+
profile_default/
|
|
84
|
+
ipython_config.py
|
|
85
|
+
|
|
86
|
+
# pyenv
|
|
87
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
88
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
89
|
+
# .python-version
|
|
90
|
+
|
|
91
|
+
# pipenv
|
|
92
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
93
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
94
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
95
|
+
# install all needed dependencies.
|
|
96
|
+
#Pipfile.lock
|
|
97
|
+
|
|
98
|
+
# poetry
|
|
99
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
100
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
101
|
+
# commonly ignored for libraries.
|
|
102
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
103
|
+
#poetry.lock
|
|
104
|
+
|
|
105
|
+
# pdm
|
|
106
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
107
|
+
#pdm.lock
|
|
108
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
109
|
+
# in version control.
|
|
110
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
111
|
+
.pdm.toml
|
|
112
|
+
.pdm-python
|
|
113
|
+
.pdm-build/
|
|
114
|
+
|
|
115
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
116
|
+
__pypackages__/
|
|
117
|
+
|
|
118
|
+
# Celery stuff
|
|
119
|
+
celerybeat-schedule
|
|
120
|
+
celerybeat.pid
|
|
121
|
+
|
|
122
|
+
# SageMath parsed files
|
|
123
|
+
*.sage.py
|
|
124
|
+
|
|
125
|
+
# Environments
|
|
126
|
+
.env
|
|
127
|
+
.venv
|
|
128
|
+
env/
|
|
129
|
+
venv/
|
|
130
|
+
ENV/
|
|
131
|
+
env.bak/
|
|
132
|
+
venv.bak/
|
|
133
|
+
|
|
134
|
+
# Spyder project settings
|
|
135
|
+
.spyderproject
|
|
136
|
+
.spyproject
|
|
137
|
+
|
|
138
|
+
# Rope project settings
|
|
139
|
+
.ropeproject
|
|
140
|
+
|
|
141
|
+
# mkdocs documentation
|
|
142
|
+
/site
|
|
143
|
+
|
|
144
|
+
# mypy
|
|
145
|
+
.mypy_cache/
|
|
146
|
+
.dmypy.json
|
|
147
|
+
dmypy.json
|
|
148
|
+
|
|
149
|
+
# Pyre type checker
|
|
150
|
+
.pyre/
|
|
151
|
+
|
|
152
|
+
# pytype static type analyzer
|
|
153
|
+
.pytype/
|
|
154
|
+
|
|
155
|
+
# Cython debug symbols
|
|
156
|
+
cython_debug/
|
|
157
|
+
|
|
158
|
+
# PyCharm
|
|
159
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
160
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
161
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
162
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
163
|
+
#.idea/
|
|
164
|
+
main.py
|
|
165
|
+
build.bat
|
|
166
|
+
sessions.json
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to SpotAPI are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
SpotAPI adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## [Unreleased] — `async-v2` beta
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Full async rewrite** — every public method is now a coroutine or
|
|
15
|
+
`AsyncGenerator`. No sync wrappers, no `asyncio.run()` hidden inside the
|
|
16
|
+
library.
|
|
17
|
+
- **`Public` search generators** — `search_tracks`, `search_artists`,
|
|
18
|
+
`search_albums`, `search_playlists`, `search_podcasts` all return
|
|
19
|
+
`AsyncGenerator[Sequence[T]]` and paginate automatically.
|
|
20
|
+
- **Typed data wrappers** — `Track`, `Artist`, `Album`, `Playlist`, `Podcast`
|
|
21
|
+
are fully typed `@dataclass(slots=True)` classes with `__repr__`.
|
|
22
|
+
- **Shared visual types** — `_common.py` extracts the repeated color/theme
|
|
23
|
+
structs that Spotify returns on every entity, eliminating ~300 lines of
|
|
24
|
+
duplication across the data wrapper modules.
|
|
25
|
+
- **`from_dict` deserializer** — recursive, camelCase-aware dataclass factory
|
|
26
|
+
with `lru_cache`-backed annotation resolution.
|
|
27
|
+
- **`ObjectDict`** — attribute-access `dict` subclass for ergonomic raw JSON
|
|
28
|
+
traversal, with optional thread-safe write locking.
|
|
29
|
+
- **`Pool[T]`** — generic async object pool with factory, pre-warm, capped
|
|
30
|
+
eviction, and teardown callbacks.
|
|
31
|
+
- **`EventDispatcher`** — async event system used by `WebSocketClient`.
|
|
32
|
+
- **`WebSocketClient`** — async WebSocket wrapper with heartbeat manager,
|
|
33
|
+
`@event` decorator, and `asynccontextmanager`-based connection lifecycle.
|
|
34
|
+
- **`HTTPClient`** — `wreq`-backed HTTP client with:
|
|
35
|
+
- Exponential backoff + jitter retry strategy.
|
|
36
|
+
- Randomised browser profile emulation (Chrome, Edge, Firefox, Opera).
|
|
37
|
+
- Randomised OS emulation (Windows 30×, macOS 6×, Linux 2×).
|
|
38
|
+
- Shared `ClientPool` for connection reuse.
|
|
39
|
+
- **`BundleSession`** — parses Spotify's HTML to extract JS bundle URLs,
|
|
40
|
+
fetches and caches them, and derives persisted query hashes.
|
|
41
|
+
- **`AuthSession`** — TOTP-based access token + client token acquisition with
|
|
42
|
+
a background auto-refresh task. No CAPTCHA solver required.
|
|
43
|
+
- **`timed_cache`** — TTL-aware async/sync cache decorator used for the
|
|
44
|
+
expensive bundle-fetch step.
|
|
45
|
+
- **Five loggers**: `LoggerColour`, `StandardLogger`, `NoopLogger`,
|
|
46
|
+
`InbuiltLogger`, `JsonLogger`, and `MultiLogger` fan-out — all implementing
|
|
47
|
+
`LoggerProtocol`.
|
|
48
|
+
- **`py.typed` marker** — signals full PEP 561 type-checker support.
|
|
49
|
+
- `pyproject.toml` replaces `setup.py`.
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- `types/__init__.py` had duplicate star imports causing symbols to be
|
|
54
|
+
registered twice; replaced with explicit named imports.
|
|
55
|
+
- `utils/__init__.py` used a broken double-import pattern that shadowed the
|
|
56
|
+
module-level `__all__`; replaced with explicit named imports.
|
|
57
|
+
- `Pool.put()` called the deprecated `asyncio.get_event_loop()` (deprecated
|
|
58
|
+
in Python 3.11); replaced with `asyncio.get_running_loop()`.
|
|
59
|
+
- `_BaseLogger.log()` acquired the threading lock then returned early without
|
|
60
|
+
releasing it when `is_enabled()` was `False`; replaced the manual
|
|
61
|
+
acquire/release with a `with` statement via `contextlib.nullcontext`.
|
|
62
|
+
- `totp.py` had an unreachable `return FALLBACK` statement after a
|
|
63
|
+
`raise RuntimeError`; removed the dead code.
|
|
64
|
+
- Data wrappers (`track.py`, `artist.py`, `album.py`, `playlist.py`,
|
|
65
|
+
`podcast.py`) each redefined identical color/theme dataclasses; extracted
|
|
66
|
+
to `_common.py` and imported from there.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- Package layout reorganised into `connection/`, `datastruct/`,
|
|
71
|
+
`specialized/`, `types/`, `utils/` sub-packages.
|
|
72
|
+
- `setup.py` replaced by `pyproject.toml` (Hatchling build backend).
|
|
73
|
+
- Minimum Python version raised from 3.11 to **3.11**.
|
|
74
|
+
|
|
75
|
+
### Removed
|
|
76
|
+
|
|
77
|
+
- `annotations.py` runtime type-enforcement decorator (`@enforce_types`,
|
|
78
|
+
`@enforce`) — moved to a dev-only tool; it has no callers in v2 and
|
|
79
|
+
imposes overhead on every method call.
|
|
80
|
+
- `parse_json_string` from `utils/strings.py` — v1 leftover with no callers
|
|
81
|
+
in v2 (Spotify's `correlationId` is now parsed via the `appServerConfig`
|
|
82
|
+
base64 blob).
|
|
83
|
+
- `setup.py`, `requirements.txt` — superseded by `pyproject.toml`.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## [1.2.8] — 2026-06-14 *(latest stable)*
|
|
88
|
+
|
|
89
|
+
> See the [main branch](https://github.com/Aran404/SpotAPI/tree/main) for the
|
|
90
|
+
> v1 changelog.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity
|
|
10
|
+
and orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
|
18
|
+
community include:
|
|
19
|
+
|
|
20
|
+
* Demonstrating empathy and kindness toward other people
|
|
21
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
22
|
+
* Giving and gracefully accepting constructive feedback
|
|
23
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
24
|
+
and learning from the experience
|
|
25
|
+
* Focusing on what is best not just for us as individuals, but for the
|
|
26
|
+
overall community
|
|
27
|
+
|
|
28
|
+
Examples of unacceptable behavior include:
|
|
29
|
+
|
|
30
|
+
* The use of sexualized language or imagery, and sexual attention or
|
|
31
|
+
advances of any kind
|
|
32
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
33
|
+
* Public or private harassment
|
|
34
|
+
* Publishing others' private information, such as a physical or email
|
|
35
|
+
address, without their explicit permission
|
|
36
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
37
|
+
professional setting
|
|
38
|
+
|
|
39
|
+
## Enforcement Responsibilities
|
|
40
|
+
|
|
41
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
|
42
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
|
43
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
44
|
+
or harmful.
|
|
45
|
+
|
|
46
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
|
47
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
48
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
49
|
+
decisions when appropriate.
|
|
50
|
+
|
|
51
|
+
## Scope
|
|
52
|
+
|
|
53
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
|
54
|
+
an individual is officially representing the community in public spaces.
|
|
55
|
+
Examples of representing our community include using an official e-mail address,
|
|
56
|
+
posting via an official social media account, or acting as an appointed
|
|
57
|
+
representative at an online or offline event.
|
|
58
|
+
|
|
59
|
+
## Enforcement
|
|
60
|
+
|
|
61
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
62
|
+
reported to the community leaders responsible for enforcement at
|
|
63
|
+
Email: aransservices1@gmail.com.
|
|
64
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
65
|
+
|
|
66
|
+
All community leaders are obligated to respect the privacy and security of the
|
|
67
|
+
reporter of any incident.
|
|
68
|
+
|
|
69
|
+
## Enforcement Guidelines
|
|
70
|
+
|
|
71
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
|
72
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
|
73
|
+
|
|
74
|
+
### 1. Correction
|
|
75
|
+
|
|
76
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
77
|
+
unprofessional or unwelcome in the community.
|
|
78
|
+
|
|
79
|
+
**Consequence**: A private, written warning from community leaders, providing
|
|
80
|
+
clarity around the nature of the violation and an explanation of why the
|
|
81
|
+
behavior was inappropriate. A public apology may be requested.
|
|
82
|
+
|
|
83
|
+
### 2. Warning
|
|
84
|
+
|
|
85
|
+
**Community Impact**: A violation through a single incident or series
|
|
86
|
+
of actions.
|
|
87
|
+
|
|
88
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
|
89
|
+
interaction with the people involved, including unsolicited interaction with
|
|
90
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
|
91
|
+
includes avoiding interactions in community spaces as well as external channels
|
|
92
|
+
like social media. Violating these terms may lead to a temporary or
|
|
93
|
+
permanent ban.
|
|
94
|
+
|
|
95
|
+
### 3. Temporary Ban
|
|
96
|
+
|
|
97
|
+
**Community Impact**: A serious violation of community standards, including
|
|
98
|
+
sustained inappropriate behavior.
|
|
99
|
+
|
|
100
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
|
101
|
+
communication with the community for a specified period of time. No public or
|
|
102
|
+
private interaction with the people involved, including unsolicited interaction
|
|
103
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
|
104
|
+
Violating these terms may lead to a permanent ban.
|
|
105
|
+
|
|
106
|
+
### 4. Permanent Ban
|
|
107
|
+
|
|
108
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
|
109
|
+
standards, including sustained inappropriate behavior, harassment of an
|
|
110
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
|
111
|
+
|
|
112
|
+
**Consequence**: A permanent ban from any sort of public interaction within
|
|
113
|
+
the community.
|
|
114
|
+
|
|
115
|
+
## Attribution
|
|
116
|
+
|
|
117
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
118
|
+
version 2.0, available at
|
|
119
|
+
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
|
120
|
+
|
|
121
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
|
122
|
+
enforcement ladder](https://github.com/mozilla/diversity).
|
|
123
|
+
|
|
124
|
+
[homepage]: https://www.contributor-covenant.org
|
|
125
|
+
|
|
126
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
|
127
|
+
https://www.contributor-covenant.org/faq. Translations are available at
|
|
128
|
+
https://www.contributor-covenant.org/translations.
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Contributing to SpotAPI
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing to SpotAPI! This document covers
|
|
4
|
+
everything you need to get set up, the conventions used in v2, and the
|
|
5
|
+
tasks that are open for contribution right now.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of contents
|
|
10
|
+
|
|
11
|
+
- [Code of conduct](#code-of-conduct)
|
|
12
|
+
- [Quick setup](#quick-setup)
|
|
13
|
+
- [Branch layout](#branch-layout)
|
|
14
|
+
- [What needs doing](#what-needs-doing)
|
|
15
|
+
- [Good first issues](#good-first-issues)
|
|
16
|
+
- [Conventions](#conventions)
|
|
17
|
+
- [Submitting a pull request](#submitting-a-pull-request)
|
|
18
|
+
- [Running tests](#running-tests)
|
|
19
|
+
- [Running the linter](#running-the-linter)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Code of conduct
|
|
24
|
+
|
|
25
|
+
This project follows the [Contributor Covenant](CODE_OF_CONDUCT.md). Please
|
|
26
|
+
read it before participating.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick setup
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# 1. Fork the repo on GitHub, then clone your fork
|
|
34
|
+
git clone https://github.com/Aran404/SpotAPI
|
|
35
|
+
cd SpotAPI
|
|
36
|
+
|
|
37
|
+
# 2. Check out the active development branch
|
|
38
|
+
git checkout async-v2
|
|
39
|
+
|
|
40
|
+
# 3. Install the package in editable mode with dev extras
|
|
41
|
+
pip install -e ".[dev]"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Python **3.11 or later** is required. We recommend using
|
|
45
|
+
[`pyenv`](https://github.com/pyenv/pyenv) to manage versions.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Branch layout
|
|
50
|
+
|
|
51
|
+
| Branch | Purpose |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `main` | Stable v1 releases — do not target PRs here for v2 work |
|
|
54
|
+
| `async-v2` | Active v2 development — **target all v2 PRs here** |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## What needs doing
|
|
59
|
+
|
|
60
|
+
The async rewrite is in progress. The public search layer is complete. The
|
|
61
|
+
following items are not exhaustive but represent the next areas to tackle, roughly in priority order:
|
|
62
|
+
|
|
63
|
+
### High priority
|
|
64
|
+
|
|
65
|
+
#### Many of these will be coded by me in the coming few days-weeks.
|
|
66
|
+
|
|
67
|
+
- [ ] **Private playlist management** — `create_playlist`, `add_track`,
|
|
68
|
+
`remove_track`, `edit_playlist`
|
|
69
|
+
- [ ] **Player control** — `play`, `pause`, `skip`, `seek`, `set_volume`,
|
|
70
|
+
`set_repeat`, `set_shuffle`
|
|
71
|
+
- [ ] **Websocket Handler** - `current_state`, `next_state`, `prev_state`
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
### Medium priority
|
|
75
|
+
|
|
76
|
+
- [ ] **async test suite** — `pytest-asyncio` tests for public search,
|
|
77
|
+
`Pool`, `ObjectDict`, `EventDispatcher`, `timed_cache`
|
|
78
|
+
- [ ] **Additional search methods** in `public.py` — see
|
|
79
|
+
[good first issues](#good-first-issues) below
|
|
80
|
+
- [ ] **User profile** — `get_profile`, `follow_artist`, `follow_user`
|
|
81
|
+
- [ ] **MkDocs documentation site** — docstrings already exist, just needs
|
|
82
|
+
the `mkdocs.yml` + `docs/` pages wired up
|
|
83
|
+
|
|
84
|
+
### Lower priority
|
|
85
|
+
|
|
86
|
+
- [ ] **Family plan management** (`Family`) async port
|
|
87
|
+
- [ ] **Password reset** (`Password`) async port
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Good first issues
|
|
92
|
+
|
|
93
|
+
These are self-contained additions that follow an established pattern in the
|
|
94
|
+
codebase. Each one is approximately 8–12 lines of code.
|
|
95
|
+
|
|
96
|
+
The existing `search_tracks`, `search_artists`, `search_albums`,
|
|
97
|
+
`search_podcasts`, and `search_playlists` methods in `v2/public.py` all follow
|
|
98
|
+
this pattern:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
@staticmethod
|
|
102
|
+
async def search_tracks(query: str, /) -> AsyncGenerator[Sequence[Track]]:
|
|
103
|
+
"""Search for tracks matching *query*.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
query : str
|
|
108
|
+
Free-text search query.
|
|
109
|
+
|
|
110
|
+
Yields
|
|
111
|
+
------
|
|
112
|
+
Sequence[Track]
|
|
113
|
+
One page of :class:`~spotapi.v2.specialized.data_wrappers.track.Track` results.
|
|
114
|
+
"""
|
|
115
|
+
async for page in Public.search(Track, Operations.SEARCH_TRACKS, query):
|
|
116
|
+
yield page
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The following methods still need implementation following the existing pattern. Each issue identifies the operation enum value
|
|
120
|
+
and the data wrapper type to use. The list below is not exhaustive, and additional methods may need to be implemented.
|
|
121
|
+
|
|
122
|
+
| GitHub issue | Method to add | `Operations` enum | Return type |
|
|
123
|
+
|---|---|---|---|
|
|
124
|
+
| [#GFI-1] | `search_users` | `SEARCH_USERS` | Needs a `User` data wrapper |
|
|
125
|
+
| [#GFI-2] | `search_audiobooks` | `SEARCH_AUDIOBOOKS` | Needs an `Audiobook` data wrapper |
|
|
126
|
+
| [#GFI-3] | `search_episodes` | `SEARCH_EPISODES` | Needs an `Episode` data wrapper |
|
|
127
|
+
| [#GFI-4] | `search_top_results` | `SEARCH_TOP_RESULTS` | `ObjectDict` (raw) |
|
|
128
|
+
|
|
129
|
+
**To contribute one of these:**
|
|
130
|
+
|
|
131
|
+
1. Create the data wrapper in `v2/specialized/data_wrappers/<type>.py`
|
|
132
|
+
following the existing wrappers as a template. Include `__repr__`.
|
|
133
|
+
2. Import it in `v2/specialized/data_wrappers/__init__.py` and add it to
|
|
134
|
+
`__all__`.
|
|
135
|
+
3. Add the static method to `Public` in `v2/public.py` with a NumPy-style
|
|
136
|
+
docstring.
|
|
137
|
+
4. Add it to `__all__` in `v2/public.py`.
|
|
138
|
+
5. Open a PR targeting `async-v2`.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Conventions
|
|
143
|
+
|
|
144
|
+
### Style
|
|
145
|
+
|
|
146
|
+
SpotAPI v2 is formatted with **Black** (formatter) and **Pyright** (strict). Run it before
|
|
147
|
+
committing:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
black ./
|
|
151
|
+
pyright --strict
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Type annotations
|
|
155
|
+
|
|
156
|
+
- All public functions and methods must be fully annotated.
|
|
157
|
+
- `from __future__ import annotations` at the top of every file.
|
|
158
|
+
- No `Any` in public API signatures unless unavoidable.
|
|
159
|
+
- Run `mypy v2/` to verify.
|
|
160
|
+
|
|
161
|
+
### Docstrings
|
|
162
|
+
|
|
163
|
+
Use **NumPy-style** docstrings (same as discord.py and NumPy itself):
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
def my_function(param: str, /, *, flag: bool = False) -> list[str]:
|
|
167
|
+
"""One-line summary ending with a period.
|
|
168
|
+
|
|
169
|
+
Optional extended description. May span multiple paragraphs.
|
|
170
|
+
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
param : str
|
|
174
|
+
Description of *param*.
|
|
175
|
+
flag : bool
|
|
176
|
+
Description of *flag*. Defaults to ``False``.
|
|
177
|
+
|
|
178
|
+
Returns
|
|
179
|
+
-------
|
|
180
|
+
list[str]
|
|
181
|
+
Description of the return value.
|
|
182
|
+
|
|
183
|
+
Raises
|
|
184
|
+
------
|
|
185
|
+
ValueError
|
|
186
|
+
If *param* is empty.
|
|
187
|
+
|
|
188
|
+
Examples
|
|
189
|
+
--------
|
|
190
|
+
::
|
|
191
|
+
|
|
192
|
+
result = my_function("hello", flag=True)
|
|
193
|
+
"""
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### `__all__`
|
|
197
|
+
|
|
198
|
+
Every module must define `__all__` as an explicit `tuple[str, ...]`. Star
|
|
199
|
+
imports from modules without `__all__` are not permitted.
|
|
200
|
+
|
|
201
|
+
### `__repr__`
|
|
202
|
+
|
|
203
|
+
All public data classes must implement `__repr__`. Use angle-bracket format:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
def __repr__(self) -> str:
|
|
207
|
+
return f"<Track id={self.id!r} name={self.name!r}>"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Dataclasses
|
|
211
|
+
|
|
212
|
+
Use `@dataclass(slots=True)` for all data wrappers. Use `frozen=True` for
|
|
213
|
+
immutable value objects (e.g. `ResponseSuccess`, `LogRecord`).
|
|
214
|
+
|
|
215
|
+
### Commit messages
|
|
216
|
+
|
|
217
|
+
Follow [Conventional Commits](https://www.conventionalcommits.org/):
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
feat(public): add search_users method
|
|
221
|
+
fix(pool): replace deprecated get_event_loop with get_running_loop
|
|
222
|
+
docs(contributing): add good first issue table
|
|
223
|
+
refactor(data_wrappers): extract shared color structs to _common.py
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Submitting a pull request
|
|
229
|
+
|
|
230
|
+
1. Fork the repo and create a branch off `async-v2`:
|
|
231
|
+
```bash
|
|
232
|
+
git checkout -b feat/search-users async-v2
|
|
233
|
+
```
|
|
234
|
+
2. Make your changes following the conventions above.
|
|
235
|
+
3. Run the linter and tests:
|
|
236
|
+
```bash
|
|
237
|
+
black ./
|
|
238
|
+
pyright --strict
|
|
239
|
+
```
|
|
240
|
+
4. Push and open a PR against `async-v2` (not `main`).
|
|
241
|
+
5. Fill in the PR template — include a short description of what you changed
|
|
242
|
+
and why, and reference the issue number if applicable.
|
|
243
|
+
|
|
244
|
+
PRs that do not pass one of: `pyright`, `ruff`, or `mypy` will not be reviewed until
|
|
245
|
+
they do.
|
|
246
|
+
|
|
247
|
+
---
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
## Legal Notice
|
|
2
|
+
|
|
3
|
+
This repository is in no way affiliated with, authorized, maintained, sponsored or endorsed by Spotify Inc. (spotify.com) or any of its affiliates or subsidiaries. This project is intended **for educational purposes only**.
|
|
4
|
+
|
|
5
|
+
Please note the following:
|
|
6
|
+
|
|
7
|
+
## Legal Notice
|
|
8
|
+
|
|
9
|
+
### **Affiliation Disclaimer**
|
|
10
|
+
The project is intended for educational purposes only. The APIs, services, trademarks, and other intellectual property mentioned in this repository are the property of their respective owners, with no claim of ownership or affiliation by this project.
|
|
11
|
+
|
|
12
|
+
### **Liability Limitation**
|
|
13
|
+
Under no circumstances shall the author of this repository be liable for any direct, indirect, incidental, special, consequential, or punitive damages, including but not limited to, loss of profits, data, or use, arising out of or in connection with the repository, regardless of whether such damages were foreseeable and whether the author was advised of the possibility of such damages.
|
|
14
|
+
|
|
15
|
+
### **No Warranties**
|
|
16
|
+
The repository is provided on an "as is" and "as available" basis without any warranties of any kind, either express or implied, including but not limited to, implied warranties of merchantability, fitness for a particular purpose, or non-infringement.
|
|
17
|
+
|
|
18
|
+
### **User Responsibility**
|
|
19
|
+
Users assume all risk for their use of this repository and are solely responsible for any damage or loss, including but not limited to financial loss, of any kind, to any party, that results from the use or misuse of the repository and its contents.
|
|
20
|
+
|
|
21
|
+
### **Legal Compliance**
|
|
22
|
+
Users are responsible for ensuring their use of the repository and its contents complies with all local, state, national, and international laws and regulations.
|
|
23
|
+
|
|
24
|
+
### **Indemnification**
|
|
25
|
+
Users agree to indemnify, defend, and hold harmless the author from any claims, liabilities, damages, losses, or expenses, including legal fees, arising out of or in any way connected with their use of this repository, violation of these terms, or infringement of any intellectual property or other rights of any person or entity.
|
|
26
|
+
|
|
27
|
+
### **No Endorsement**
|
|
28
|
+
The inclusion of third-party content does not imply endorsement or recommendation of such content by the author.
|
|
29
|
+
|
|
30
|
+
### **Governing Law and Jurisdiction**
|
|
31
|
+
Any disputes arising out of or related to the use of this repository shall be governed by the laws of the author's jurisdiction, without regard to its conflict of law principles.
|
|
32
|
+
|
|
33
|
+
### **Severability**
|
|
34
|
+
If any provision of this notice is found to be unlawful, void, or unenforceable, then that provision shall be deemed severable from this notice and shall not affect the validity and enforceability of any remaining provisions.
|
|
35
|
+
|
|
36
|
+
### **Acknowledgment of Understanding**
|
|
37
|
+
By using this repository, users acknowledge that they have read, understood, and agree to be bound by these terms.
|
|
38
|
+
|
|
39
|
+
### **Updates and Changes**
|
|
40
|
+
The author reserves the right to modify, update, or remove any content, information, or features in this repository at any time without prior notice. Users are responsible for regularly reviewing the content and any changes made to this repository.
|
|
41
|
+
|
|
42
|
+
### **Unforeseen Consequences**
|
|
43
|
+
The author of this repository is not responsible for any consequences, damages, or losses arising from the use or misuse of this repository or the content provided by the third-party APIs. Users are solely responsible for their actions and any repercussions that may follow.
|
|
44
|
+
|
|
45
|
+
### **Educational Purpose**
|
|
46
|
+
Please note that this project and its content are provided strictly for educational purposes. Users acknowledge that they are using the APIs at their own risk and agree to comply with any applicable laws and regulations.
|