From 1416d47cba95428e3a22b22d105d39aa9c08d904 Mon Sep 17 00:00:00 2001 From: Dan Joseph Date: Mon, 30 Oct 2023 21:32:46 -0400 Subject: [PATCH 1/5] Audiobook Support and CI Workflow Update (#1036) * Implement audiobook endpoints * Update GitHub CI Workflow: Removed Python v2.7 * Update GitHub CI Workflow: Removed Python v3.6 * Add integration tests for audiobook endpoints --- .github/workflows/pythonapp.yml | 2 +- CHANGELOG.md | 7 +++ spotipy/client.py | 52 +++++++++++++++++++- tests/integration/non_user_endpoints/test.py | 38 ++++++++++++++ 4 files changed, 96 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index a3d5dcc..7c83ab3 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-20.04 strategy: 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: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index f69d8fc..99325b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 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 - 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 diff --git a/spotipy/client.py b/spotipy/client.py index d3b918f..a026e41 100644 --- a/spotipy/client.py +++ b/spotipy/client.py @@ -111,14 +111,14 @@ class Spotify(object): # # [1] https://www.iana.org/assignments/uri-schemes/prov/spotify # [2] https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids - _regex_spotify_uri = r'^spotify:(?:(?Ptrack|artist|album|playlist|show|episode):(?P[0-9A-Za-z]+)|user:(?P[0-9A-Za-z]+):playlist:(?P[0-9A-Za-z]+))$' # noqa: E501 + _regex_spotify_uri = r'^spotify:(?:(?Ptrack|artist|album|playlist|show|episode|audiobook):(?P[0-9A-Za-z]+)|user:(?P[0-9A-Za-z]+):playlist:(?P[0-9A-Za-z]+))$' # noqa: E501 # 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, # instead of a more complex URL parsing function. # # [1] https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids - _regex_spotify_url = r'^(http[s]?:\/\/)?open.spotify.com\/(?Ptrack|artist|album|playlist|show|episode|user)\/(?P[0-9A-Za-z]+)(\?.*)?$' # noqa: E501 + _regex_spotify_url = r'^(http[s]?:\/\/)?open.spotify.com\/(?Ptrack|artist|album|playlist|show|episode|user|audiobook)\/(?P[0-9A-Za-z]+)(\?.*)?$' # noqa: E501 _regex_base62 = r'^[0-9A-Za-z]+$' @@ -2033,3 +2033,51 @@ class Spotify(object): 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) diff --git a/tests/integration/non_user_endpoints/test.py b/tests/integration/non_user_endpoints/test.py index fe58160..ca2faac 100644 --- a/tests/integration/non_user_endpoints/test.py +++ b/tests/integration/non_user_endpoints/test.py @@ -55,6 +55,16 @@ class AuthTestSpotipy(unittest.TestCase): heavyweight_ep1_url = 'https://open.spotify.com/episode/68kq3bNz6hEuq8NtdfwERG' 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 def setUpClass(self): self.spotify = Spotify( @@ -455,3 +465,31 @@ class AuthTestSpotipy(unittest.TestCase): self.assertTrue(isinstance(markets, list)) self.assertIn("US", 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) From a14a28e10c1889cce83eec7a7e1ad4b5944a452d Mon Sep 17 00:00:00 2001 From: chaisupt <72107485+chaisupt@users.noreply.github.com> Date: Fri, 29 Dec 2023 06:50:13 -0800 Subject: [PATCH 2/5] Improve usability on README.md (#983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update README.md Improve usability of the README.md file * Update CHANGELOG.md change log following README changes * Update CHANGELOG.md * Update README.md Co-authored-by: Hugo van Kemenade * Update README.md Co-authored-by: Hugo van Kemenade * Update README.md Co-authored-by: Hugo van Kemenade * Update README.md Co-authored-by: Hugo van Kemenade * Update README.md • Removed the License Section from the table of contents due to the new GitHub tab UI • Updated link to not specify any specific version * Update CHANGELOG.md --------- Co-authored-by: Hugo van Kemenade --- CHANGELOG.md | 3 ++- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99325b5..a0601dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - 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 +- Updated links to Spotify in documentation +- Improve usability on README.md ## [2.23.0] - 2023-04-07 diff --git a/README.md b/README.md index aff9a39..c5f22f9 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,26 @@ # 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. ![Tests](https://github.com/plamere/spotipy/workflows/Tests/badge.svg?branch=master) [![Documentation Status](https://readthedocs.org/projects/spotipy/badge/?version=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 -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 @@ -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). -To get started, install spotipy and create an app on https://developers.spotify.com/. -Add your new ID and SECRET to your environment: +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)): -### Without user authentication +### Example without user authentication ```python import spotipy @@ -46,8 +59,20 @@ results = sp.search(q='weezer', limit=20) for idx, track in enumerate(results['tracks']['items']): 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. @@ -65,6 +90,12 @@ for idx, item in enumerate(results['items']): track = item['track'] 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 @@ -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, file them [here](https://github.com/plamere/spotipy/issues). 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) From e3629cdacbf92be45fd2bbcea43b53e0b6436e29 Mon Sep 17 00:00:00 2001 From: Colin Wong Date: Sat, 6 Jan 2024 00:20:53 -0600 Subject: [PATCH 3/5] Use unused description parameter in example --- examples/create_playlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/create_playlist.py b/examples/create_playlist.py index b9f38f9..702c25c 100644 --- a/examples/create_playlist.py +++ b/examples/create_playlist.py @@ -24,7 +24,7 @@ def main(): scope = "playlist-modify-public" sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope)) 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__': From d9a5f008ff214ac0a6eab4f19c2922b1282ff27e Mon Sep 17 00:00:00 2001 From: Colin Wong Date: Sat, 6 Jan 2024 00:25:18 -0600 Subject: [PATCH 4/5] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0601dd..b0fdaad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated links to Spotify in documentation - Improve usability on README.md +### Fixed +- Fixed unused description parameter in playlist creation example + ## [2.23.0] - 2023-04-07 ### Added From 97c9917a024022023ef574349b408e85e8e12e65 Mon Sep 17 00:00:00 2001 From: Jack Dane Date: Tue, 2 Jan 2024 14:06:18 +0000 Subject: [PATCH 5/5] Use the playlist_items function to retrieve tracks. * It will request the same endpoint as sp.next with the same fields for consistency. --- CHANGELOG.md | 1 + examples/user_playlists_contents.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0601dd..34c1b41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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. ## [2.23.0] - 2023-04-07 diff --git a/examples/user_playlists_contents.py b/examples/user_playlists_contents.py index 13b576f..9379a0b 100644 --- a/examples/user_playlists_contents.py +++ b/examples/user_playlists_contents.py @@ -25,8 +25,7 @@ if __name__ == '__main__': print(playlist['name']) print(' total tracks', playlist['tracks']['total']) - results = sp.playlist(playlist['id'], fields="tracks,next") - tracks = results['tracks'] + tracks = sp.playlist_items(playlist['id'], fields="items,next", additional_types=('tracks', )) show_tracks(tracks) while tracks['next']: