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? 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. Create a Spotify API object.
@ -50,10 +51,13 @@ class Spotify(object):
A falsy value disables sessions. A falsy value disables sessions.
It should generally be a good idea to keep sessions enabled It should generally be a good idea to keep sessions enabled
for performance reasons (connection pooling). for performance reasons (connection pooling).
:param client_credentials_manager:
SpotifyClientCredentials object
''' '''
self.prefix = 'https://api.spotify.com/v1/' self.prefix = 'https://api.spotify.com/v1/'
self._auth = auth self._auth = auth
self.client_credentials_manager = client_credentials_manager
if isinstance(requests_session, requests.Session): if isinstance(requests_session, requests.Session):
self._session = requests_session self._session = requests_session
@ -67,6 +71,9 @@ class Spotify(object):
def _auth_headers(self): def _auth_headers(self):
if self._auth: if self._auth:
return {'Authorization': 'Bearer {0}'.format(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: else:
return {} return {}

View File

@ -7,9 +7,76 @@ import json
import time import time
import sys import sys
class SpotifyOauthError(Exception): class SpotifyOauthError(Exception):
pass 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): class SpotifyOAuth(object):
''' '''
Implements Authorization Code Flow for Spotify's OAuth implementation. 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_AUTHORIZE_URL = 'https://accounts.spotify.com/authorize'
OAUTH_TOKEN_URL = 'https://accounts.spotify.com/api/token' 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): state=None, scope=None, cache_path=None):
''' '''
Creates a SpotifyOAuth object Creates a SpotifyOAuth object
@ -38,7 +105,7 @@ class SpotifyOAuth(object):
self.state=state self.state=state
self.cache_path = cache_path self.cache_path = cache_path
self.scope=self._normalize_scope(scope) self.scope=self._normalize_scope(scope)
def get_cached_token(self): def get_cached_token(self):
''' Gets a cached auth token ''' Gets a cached auth token
''' '''
@ -75,7 +142,7 @@ class SpotifyOAuth(object):
def _is_token_expired(self, token_info): def _is_token_expired(self, token_info):
now = int(time.time()) now = int(time.time())
return token_info['expires_at'] < now return token_info['expires_at'] < now
def get_authorize_url(self): def get_authorize_url(self):
""" Gets the URL to use to authorize this app """ Gets the URL to use to authorize this app
""" """
@ -122,7 +189,7 @@ class SpotifyOAuth(object):
headers = {'Authorization': 'Basic %s' % auth_header} 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) headers=headers, verify=True)
if response.status_code is not 200: if response.status_code is not 200:
raise SpotifyOauthError(response.reason) raise SpotifyOauthError(response.reason)
@ -146,7 +213,7 @@ class SpotifyOAuth(object):
auth_header = base64.b64encode(self.client_id + ':' + self.client_secret) auth_header = base64.b64encode(self.client_id + ':' + self.client_secret)
headers = {'Authorization': 'Basic %s' % auth_header} 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) headers=headers)
if response.status_code != 200: if response.status_code != 200:
if False: # debugging code if False: # debugging code
@ -163,7 +230,7 @@ class SpotifyOAuth(object):
return token_info return token_info
def _add_custom_values_to_token_info(self, token_info): def _add_custom_values_to_token_info(self, token_info):
''' '''
Store some values that aren't directly provided by a Web API Store some values that aren't directly provided by a Web API
response. 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()