mirror of
https://github.com/c0de-archive/spotipy.git
synced 2024-11-10 17:47:25 +00:00
Add client credentials flow
This commit is contained in:
parent
31894b8087
commit
8d4643c2e1
10
examples/client_credentials_flow.py
Normal file
10
examples/client_credentials_flow.py
Normal 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)
|
@ -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 {}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
'''
|
'''
|
||||||
|
27
tests/client_credentials_tests.py
Normal file
27
tests/client_credentials_tests.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user