Skip to content

Commit

Permalink
Audiobook Support and CI Workflow Update (#1036)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
danhjoseph authored Oct 31, 2023
1 parent d319691 commit 1416d47
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pythonapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 50 additions & 2 deletions spotipy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:(?:(?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
# 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\/(?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]+$'

Expand Down Expand Up @@ -2033,3 +2033,51 @@ def _search_multiple_markets(self, q, limit, offset, type, markets, total):
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)
38 changes: 38 additions & 0 deletions tests/integration/non_user_endpoints/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -455,3 +465,31 @@ def test_available_markets(self):
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)

0 comments on commit 1416d47

Please sign in to comment.