mirror of
https://github.com/spotipy-dev/spotipy.git
synced 2026-06-19 01:03:53 +00:00
Merge branch 'master' into bug/inheriting-exceptions-in-init
This commit is contained in:
commit
6d8d9d1f9b
2
.github/workflows/pythonapp.yml
vendored
2
.github/workflows/pythonapp.yml
vendored
@ -8,7 +8,7 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10"]
|
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
|||||||
13
CHANGELOG.md
13
CHANGELOG.md
@ -7,11 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for audiobook endpoints: get_audiobook, get_audiobooks, and get_audiobook_chapters.
|
||||||
|
- Added integration tests for audiobook endpoints.
|
||||||
|
- Removed `python 2.7` from GitHub Actions CI workflow. Python v2.7 reached end of life support and is no longer supported by Ubuntu 20.04.
|
||||||
|
- Removed `python 3.6` from GitHub Actions CI workflow. Ubuntu 20.04 is not available in GitHub Actions for `python 3.6`.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Changes the YouTube video link for authentication tutorial (the old video was in low definition, the new one is in high definition)
|
- Changes the YouTube video link for authentication tutorial (the old video was in low definition, the new one is in high definition)
|
||||||
|
- Updated links to Spotify in documentation
|
||||||
|
- Improve usability on README.md
|
||||||
|
- Fix `user_playlists_contents` example.
|
||||||
- Updated links to Spotify in documentation
|
- Updated links to Spotify in documentation
|
||||||
- Fixed error obfuscation when Spotify class is being inherited and an error is raised in the Child's `__init__`
|
- Fixed error obfuscation when Spotify class is being inherited and an error is raised in the Child's `__init__`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed unused description parameter in playlist creation example
|
||||||
|
|
||||||
## [2.23.0] - 2023-04-07
|
## [2.23.0] - 2023-04-07
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
49
README.md
49
README.md
@ -1,12 +1,26 @@
|
|||||||
# Spotipy
|
# Spotipy
|
||||||
|
|
||||||
##### A light weight Python library for the Spotify Web API
|
##### Spotipy is a lightweight Python library for the [Spotify Web API](https://developer.spotify.com/documentation/web-api). With Spotipy you get full access to all of the music data provided by the Spotify platform.
|
||||||
|
|
||||||
 [](https://spotipy.readthedocs.io/en/latest/?badge=latest)
|
 [](https://spotipy.readthedocs.io/en/latest/?badge=latest)
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Features](#features)
|
||||||
|
- [Documentation](#documentation)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Quick Start](#quick-start)
|
||||||
|
- [Reporting Issues](#reporting-issues)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
Spotipy supports all of the features of the Spotify Web API including access to all end points, and support for user authorization. For details on the capabilities you are encouraged to review the [Spotify Web API](https://developer.spotify.com/web-api/) documentation.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Spotipy's full documentation is online at [Spotipy Documentation](http://spotipy.readthedocs.org/).
|
Spotipy's [full documentation is online](http://spotipy.readthedocs.org/). Some function may need a [specific scope](https://developer.spotify.com/documentation/web-api/concepts/scopes). If you do not define the scope properly `ERROR 401 Unauthorized, permission missing` may occur.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -30,10 +44,9 @@ pip install spotipy --upgrade
|
|||||||
|
|
||||||
A full set of examples can be found in the [online documentation](http://spotipy.readthedocs.org/) and in the [Spotipy examples directory](https://github.com/plamere/spotipy/tree/master/examples).
|
A full set of examples can be found in the [online documentation](http://spotipy.readthedocs.org/) and in the [Spotipy examples directory](https://github.com/plamere/spotipy/tree/master/examples).
|
||||||
|
|
||||||
To get started, install spotipy and create an app on https://developers.spotify.com/.
|
To get started, [install spotipy](#installation), create a new account or log in on https://developers.spotify.com/. Go to the [dashboard](https://developer.spotify.com/dashboard), create an app and add your new ID and SECRET (ID and SECRET can be found on an app setting) to your environment ([step-by-step video](https://www.youtube.com/watch?v=kaBVN8uP358)):
|
||||||
Add your new ID and SECRET to your environment:
|
|
||||||
|
|
||||||
### Without user authentication
|
### Example without user authentication
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import spotipy
|
import spotipy
|
||||||
@ -46,8 +59,20 @@ results = sp.search(q='weezer', limit=20)
|
|||||||
for idx, track in enumerate(results['tracks']['items']):
|
for idx, track in enumerate(results['tracks']['items']):
|
||||||
print(idx, track['name'])
|
print(idx, track['name'])
|
||||||
```
|
```
|
||||||
|
Expected result:
|
||||||
|
```
|
||||||
|
0 Island In The Sun
|
||||||
|
1 Say It Ain't So
|
||||||
|
2 Buddy Holly
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.
|
||||||
|
18 Troublemaker
|
||||||
|
19 Feels Like Summer
|
||||||
|
```
|
||||||
|
|
||||||
### With user authentication
|
|
||||||
|
### Example with user authentication
|
||||||
|
|
||||||
A redirect URI must be added to your application at [My Dashboard](https://developer.spotify.com/dashboard/applications) to access user authenticated features.
|
A redirect URI must be added to your application at [My Dashboard](https://developer.spotify.com/dashboard/applications) to access user authenticated features.
|
||||||
|
|
||||||
@ -65,6 +90,12 @@ for idx, item in enumerate(results['items']):
|
|||||||
track = item['track']
|
track = item['track']
|
||||||
print(idx, track['artists'][0]['name'], " – ", track['name'])
|
print(idx, track['artists'][0]['name'], " – ", track['name'])
|
||||||
```
|
```
|
||||||
|
Expected result will be the list of music that you liked. For example if you liked Red and Sunflower, the result will be:
|
||||||
|
```
|
||||||
|
0 Post Malone – Sunflower - Spider-Man: Into the Spider-Verse
|
||||||
|
1 Taylor Swift – Red
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Reporting Issues
|
## Reporting Issues
|
||||||
|
|
||||||
@ -77,3 +108,9 @@ Don’t forget to add the *Spotipy* tag, and any other relevant tags as well, be
|
|||||||
If you have suggestions, bugs or other issues specific to this library,
|
If you have suggestions, bugs or other issues specific to this library,
|
||||||
file them [here](https://github.com/plamere/spotipy/issues).
|
file them [here](https://github.com/plamere/spotipy/issues).
|
||||||
Or just send a pull request.
|
Or just send a pull request.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
If you are a developer with Python experience, and you would like to contribute to Spotipy, please be sure to follow the guidelines listed on documentation page
|
||||||
|
|
||||||
|
> #### [Visit the guideline](https://spotipy.readthedocs.io/en/#contribute)
|
||||||
|
|||||||
@ -24,7 +24,7 @@ def main():
|
|||||||
scope = "playlist-modify-public"
|
scope = "playlist-modify-public"
|
||||||
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
|
||||||
user_id = sp.me()['id']
|
user_id = sp.me()['id']
|
||||||
sp.user_playlist_create(user_id, args.playlist)
|
sp.user_playlist_create(user_id, args.playlist, description=args.description)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@ -25,8 +25,7 @@ if __name__ == '__main__':
|
|||||||
print(playlist['name'])
|
print(playlist['name'])
|
||||||
print(' total tracks', playlist['tracks']['total'])
|
print(' total tracks', playlist['tracks']['total'])
|
||||||
|
|
||||||
results = sp.playlist(playlist['id'], fields="tracks,next")
|
tracks = sp.playlist_items(playlist['id'], fields="items,next", additional_types=('tracks', ))
|
||||||
tracks = results['tracks']
|
|
||||||
show_tracks(tracks)
|
show_tracks(tracks)
|
||||||
|
|
||||||
while tracks['next']:
|
while tracks['next']:
|
||||||
|
|||||||
@ -111,14 +111,14 @@ class Spotify(object):
|
|||||||
#
|
#
|
||||||
# [1] https://www.iana.org/assignments/uri-schemes/prov/spotify
|
# [1] https://www.iana.org/assignments/uri-schemes/prov/spotify
|
||||||
# [2] https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids
|
# [2] https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids
|
||||||
_regex_spotify_uri = r'^spotify:(?:(?P<type>track|artist|album|playlist|show|episode):(?P<id>[0-9A-Za-z]+)|user:(?P<username>[0-9A-Za-z]+):playlist:(?P<playlistid>[0-9A-Za-z]+))$' # noqa: E501
|
_regex_spotify_uri = r'^spotify:(?:(?P<type>track|artist|album|playlist|show|episode|audiobook):(?P<id>[0-9A-Za-z]+)|user:(?P<username>[0-9A-Za-z]+):playlist:(?P<playlistid>[0-9A-Za-z]+))$' # noqa: E501
|
||||||
|
|
||||||
# Spotify URLs are defined at [1]. The assumption is made that they are all
|
# Spotify URLs are defined at [1]. The assumption is made that they are all
|
||||||
# pointing to open.spotify.com, so a regex is used to parse them as well,
|
# pointing to open.spotify.com, so a regex is used to parse them as well,
|
||||||
# instead of a more complex URL parsing function.
|
# instead of a more complex URL parsing function.
|
||||||
#
|
#
|
||||||
# [1] https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids
|
# [1] https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids
|
||||||
_regex_spotify_url = r'^(http[s]?:\/\/)?open.spotify.com\/(?P<type>track|artist|album|playlist|show|episode|user)\/(?P<id>[0-9A-Za-z]+)(\?.*)?$' # noqa: E501
|
_regex_spotify_url = r'^(http[s]?:\/\/)?open.spotify.com\/(?P<type>track|artist|album|playlist|show|episode|user|audiobook)\/(?P<id>[0-9A-Za-z]+)(\?.*)?$' # noqa: E501
|
||||||
|
|
||||||
_regex_base62 = r'^[0-9A-Za-z]+$'
|
_regex_base62 = r'^[0-9A-Za-z]+$'
|
||||||
|
|
||||||
@ -2036,3 +2036,51 @@ class Spotify(object):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
def get_audiobook(self, id, market=None):
|
||||||
|
""" Get Spotify catalog information for a single audiobook identified by its unique
|
||||||
|
Spotify ID.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- id - the Spotify ID for the audiobook
|
||||||
|
- market - an ISO 3166-1 alpha-2 country code.
|
||||||
|
"""
|
||||||
|
audiobook_id = self._get_id("audiobook", id)
|
||||||
|
endpoint = f"audiobooks/{audiobook_id}"
|
||||||
|
|
||||||
|
if market:
|
||||||
|
endpoint += f'?market={market}'
|
||||||
|
|
||||||
|
return self._get(endpoint)
|
||||||
|
|
||||||
|
def get_audiobooks(self, ids, market=None):
|
||||||
|
""" Get Spotify catalog information for multiple audiobooks based on their Spotify IDs.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- ids - a list of Spotify IDs for the audiobooks
|
||||||
|
- market - an ISO 3166-1 alpha-2 country code.
|
||||||
|
"""
|
||||||
|
audiobook_ids = [self._get_id("audiobook", id) for id in ids]
|
||||||
|
endpoint = f"audiobooks?ids={','.join(audiobook_ids)}"
|
||||||
|
|
||||||
|
if market:
|
||||||
|
endpoint += f'&market={market}'
|
||||||
|
|
||||||
|
return self._get(endpoint)
|
||||||
|
|
||||||
|
def get_audiobook_chapters(self, id, market=None, limit=20, offset=0):
|
||||||
|
""" Get Spotify catalog information about an audiobook’s chapters.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- id - the Spotify ID for the audiobook
|
||||||
|
- market - an ISO 3166-1 alpha-2 country code.
|
||||||
|
- limit - the maximum number of items to return
|
||||||
|
- offset - the index of the first item to return
|
||||||
|
"""
|
||||||
|
audiobook_id = self._get_id("audiobook", id)
|
||||||
|
endpoint = f"audiobooks/{audiobook_id}/chapters?limit={limit}&offset={offset}"
|
||||||
|
|
||||||
|
if market:
|
||||||
|
endpoint += f'&market={market}'
|
||||||
|
|
||||||
|
return self._get(endpoint)
|
||||||
|
|||||||
@ -55,6 +55,16 @@ class AuthTestSpotipy(unittest.TestCase):
|
|||||||
heavyweight_ep1_url = 'https://open.spotify.com/episode/68kq3bNz6hEuq8NtdfwERG'
|
heavyweight_ep1_url = 'https://open.spotify.com/episode/68kq3bNz6hEuq8NtdfwERG'
|
||||||
reply_all_ep1_urn = 'spotify:episode:1KHjbpnmNpFmNTczQmTZlR'
|
reply_all_ep1_urn = 'spotify:episode:1KHjbpnmNpFmNTczQmTZlR'
|
||||||
|
|
||||||
|
american_gods_urn = 'spotify:audiobook:1IcM9Untg6d3ktuwObYGcN'
|
||||||
|
american_gods_id = '1IcM9Untg6d3ktuwObYGcN'
|
||||||
|
american_gods_url = 'https://open.spotify.com/audiobook/1IcM9Untg6d3ktuwObYGcN'
|
||||||
|
|
||||||
|
four_books = [
|
||||||
|
'spotify:audiobook:1IcM9Untg6d3ktuwObYGcN',
|
||||||
|
'spotify:audiobook:37sRC6carIX2Vf3Vv716T7',
|
||||||
|
'spotify:audiobook:1Gep4UJ95xQawA55OgRI8n',
|
||||||
|
'spotify:audiobook:4Sm381mcf5gBsi9yfhqgVB']
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(self):
|
||||||
self.spotify = Spotify(
|
self.spotify = Spotify(
|
||||||
@ -455,3 +465,31 @@ class AuthTestSpotipy(unittest.TestCase):
|
|||||||
self.assertTrue(isinstance(markets, list))
|
self.assertTrue(isinstance(markets, list))
|
||||||
self.assertIn("US", markets)
|
self.assertIn("US", markets)
|
||||||
self.assertIn("GB", markets)
|
self.assertIn("GB", markets)
|
||||||
|
|
||||||
|
def test_get_audiobook(self):
|
||||||
|
audiobook = self.spotify.get_audiobook(self.american_gods_urn, market="US")
|
||||||
|
print(audiobook)
|
||||||
|
self.assertTrue(audiobook['name'] ==
|
||||||
|
'American Gods: The Tenth Anniversary Edition: A Novel')
|
||||||
|
|
||||||
|
def test_get_audiobook_bad_urn(self):
|
||||||
|
with self.assertRaises(SpotifyException):
|
||||||
|
self.spotify.get_audiobook("bogus_urn", market="US")
|
||||||
|
|
||||||
|
def test_get_audiobooks(self):
|
||||||
|
results = self.spotify.get_audiobooks(self.four_books, market="US")
|
||||||
|
self.assertTrue('audiobooks' in results)
|
||||||
|
self.assertTrue(len(results['audiobooks']) == 4)
|
||||||
|
self.assertTrue(results['audiobooks'][0]['name'] ==
|
||||||
|
'American Gods: The Tenth Anniversary Edition: A Novel')
|
||||||
|
self.assertTrue(results['audiobooks'][1]['name'] == 'The Da Vinci Code: A Novel')
|
||||||
|
self.assertTrue(results['audiobooks'][2]['name'] == 'Outlander')
|
||||||
|
self.assertTrue(results['audiobooks'][3]['name'] == 'Pachinko: A Novel')
|
||||||
|
|
||||||
|
def test_get_audiobook_chapters(self):
|
||||||
|
results = self.spotify.get_audiobook_chapters(
|
||||||
|
self.american_gods_urn, market="US", limit=10, offset=5)
|
||||||
|
self.assertTrue('items' in results)
|
||||||
|
self.assertTrue(len(results['items']) == 10)
|
||||||
|
self.assertTrue(results['items'][0]['chapter_number'] == 5)
|
||||||
|
self.assertTrue(results['items'][9]['chapter_number'] == 14)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user