Add client credentials flow

This commit is contained in:
Faruk Sahin 2015-04-01 16:03:29 +02:00
parent 31894b8087
commit 8d4643c2e1
4 changed files with 118 additions and 7 deletions

View File

@ -0,0 +1,10 @@
from spotipy.oauth2 import SpotifyClientCredentials
import spotipy
import pprint
client_credentials_manager = SpotifyClientCredentials()
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
search_str = 'Muse'
result = sp.search(search_str)
pprint.pprint(result)

View File

@ -40,7 +40,8 @@ class Spotify(object):
trace = False # Enable tracing?
def __init__(self, auth=None, requests_session=True):
def __init__(self, auth=None, requests_session=True,
client_credentials_manager=None):
'''
Create a Spotify API object.
@ -50,10 +51,13 @@ class Spotify(object):
A falsy value disables sessions.
It should generally be a good idea to keep sessions enabled
for performance reasons (connection pooling).
:param client_credentials_manager:
SpotifyClientCredentials object
'''
self.prefix = 'https://api.spotify.com/v1/'
self._auth = auth
self.client_credentials_manager = client_credentials_manager
if isinstance(requests_session, requests.Session):
self._session = requests_session
@ -67,6 +71,9 @@ class Spotify(object):
def _auth_headers(self):
if self._auth:
return {'Authorization': 'Bearer {0}'.format(self._auth)}
elif self.client_credentials_manager:
token = self.client_credentials_manager.get_access_token()
return {'Authorization': 'Bearer {0}'.format(token)}
else:
return {}

View File

@ -7,9 +7,76 @@ import json
import time
import sys
class SpotifyOauthError(Exception):
pass
class SpotifyClientCredentials(object):
OAUTH_TOKEN_URL = 'https://accounts.spotify.com/api/token'
def __init__(self, client_id=None, client_secret=None):
"""
You can either provid a client_id and client_secret to the
constructor or set SPOTIPY_CLIENT_ID and SPOTIPY_CLIENT_SECRET
environment variables
"""
if not client_id:
client_id = os.getenv('SPOTIPY_CLIENT_ID')
if not client_secret:
client_secret = os.getenv('SPOTIPY_CLIENT_SECRET')
if not client_id:
raise SpotifyOauthError('No client id')
if not client_secret:
raise SpotifyOauthError('No client secret')
self.client_id = client_id
self.client_secret = client_secret
self.token_info = None
def get_access_token(self):
"""
If a valid access token is in memory, returns it
Else feches a new token and returns it
"""
if self.token_info and not self._is_token_expired(self.token_info):
return self.token_info['access_token']
token_info = self._request_access_token()
token_info = self._add_custom_values_to_token_info(token_info)
self.token_info = token_info
return self.token_info['access_token']
def _request_access_token(self):
"""Gets client credentials access token """
payload = { 'grant_type': 'client_credentials'}
auth_header = base64.b64encode(self.client_id + ':' + self.client_secret)
headers = {'Authorization': 'Basic %s' % auth_header}
response = requests.post(self.OAUTH_TOKEN_URL, data=payload,
headers=headers, verify=True)
if response.status_code is not 200:
raise SpotifyOauthError(response.reason)
token_info = response.json()
return token_info
def _is_token_expired(self, token_info):
now = int(time.time())
return token_info['expires_at'] < now
def _add_custom_values_to_token_info(self, token_info):
"""
Store some values that aren't directly provided by a Web API
response.
"""
token_info['expires_at'] = int(time.time()) + token_info['expires_in']
return token_info
class SpotifyOAuth(object):
'''
Implements Authorization Code Flow for Spotify's OAuth implementation.
@ -18,7 +85,7 @@ class SpotifyOAuth(object):
OAUTH_AUTHORIZE_URL = 'https://accounts.spotify.com/authorize'
OAUTH_TOKEN_URL = 'https://accounts.spotify.com/api/token'
def __init__(self, client_id, client_secret, redirect_uri,
def __init__(self, client_id, client_secret, redirect_uri,
state=None, scope=None, cache_path=None):
'''
Creates a SpotifyOAuth object
@ -38,7 +105,7 @@ class SpotifyOAuth(object):
self.state=state
self.cache_path = cache_path
self.scope=self._normalize_scope(scope)
def get_cached_token(self):
''' Gets a cached auth token
'''
@ -75,7 +142,7 @@ class SpotifyOAuth(object):
def _is_token_expired(self, token_info):
now = int(time.time())
return token_info['expires_at'] < now
def get_authorize_url(self):
""" Gets the URL to use to authorize this app
"""
@ -122,7 +189,7 @@ class SpotifyOAuth(object):
headers = {'Authorization': 'Basic %s' % auth_header}
response = requests.post(self.OAUTH_TOKEN_URL, data=payload,
response = requests.post(self.OAUTH_TOKEN_URL, data=payload,
headers=headers, verify=True)
if response.status_code is not 200:
raise SpotifyOauthError(response.reason)
@ -146,7 +213,7 @@ class SpotifyOAuth(object):
auth_header = base64.b64encode(self.client_id + ':' + self.client_secret)
headers = {'Authorization': 'Basic %s' % auth_header}
response = requests.post(self.OAUTH_TOKEN_URL, data=payload,
response = requests.post(self.OAUTH_TOKEN_URL, data=payload,
headers=headers)
if response.status_code != 200:
if False: # debugging code
@ -163,7 +230,7 @@ class SpotifyOAuth(object):
return token_info
def _add_custom_values_to_token_info(self, token_info):
'''
'''
Store some values that aren't directly provided by a Web API
response.
'''

View File

@ -0,0 +1,27 @@
# -*- coding: latin-1 -*-
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
import unittest
'''
Client Credentials Requests Tests
'''
class ClientCredentialsTestSpotipy(unittest.TestCase):
'''
These tests require user authentication
'''
muse_urn = 'spotify:artist:12Chz98pHFMPJEknJQMWvI'
def test_request_with_token(self):
artist = spotify.artist(self.muse_urn)
self.assertTrue(artist['name'] == u'Muse')
if __name__ == '__main__':
spotify_cc = SpotifyClientCredentials()
spotify = spotipy.Spotify(client_credentials_manager=spotify_cc)
spotify.trace = False
unittest.main()