Spotify API Python - python

I am following a tutorial from CodingEntrepreneurs and i have come across a road bump where it returns a 400 error when i run it.
Here is my code
import base64, requests
import datetime
from urllib.parse import urlencode
client_id = "my id"
client_secret = "my secret"
class SpotifyAPI(object):
access_token = None
access_token_expires = datetime.datetime.now()
access_token_did_expire = True
client_id = None
client_secret = None
token_url = "https://accounts.spotify.com/api/token"
def __init__(self, client_id, client_secret, *args, **kwargs):
super().__init__(*args, **kwargs)
self.client_id = client_id
self.client_secret = client_secret
def getClientCreds(self):
'''Returns b64 encoded string'''
client_id = self.client_id
client_secret = self.client_secret
if client_id == None or client_secret == None:
raise Exception('Must set a client id and secret')
client_creds = f"{client_id}:{client_secret}"
client_creds_b64 = base64.b64encode(client_creds.encode())
return client_creds_b64.decode()
def getTokenHeader(self):
client_creds_b64 = self.getClientCreds()
return {
'Authorization':f"Basic {client_creds_b64}"
}
def getTokenData(self):
return {
"grant_type":"client_credentials"
}
def perform_auth(self):
token_url = self.token_url
token_data = self.getTokenData()
token_header = self.getTokenHeader()
r = requests.post(token_url, data=token_data, headers=token_header)
if r.status_code not in range(200,299):
return False
now = datetime.datetime.now()
token_response_data = r.json()
access_token = token_response_data['access_token']
expires_in = token_response_data['expires_in']
expires = now + datetime.timedelta(seconds=expires_in)
self.access_token = access_token
self.access_token_expires = expires
self.access_token_did_expire = expires < now
return True
spotify = SpotifyAPI(client_id, client_secret)
print(spotify.perform_auth())
token = spotify.access_token
header = {
"Authorization": f"Bearer{token}",
}
endpoint = "https://api.spotify.com/v1/search"
data = urlencode({"q": "Time", "type": "track"})
lookupURL = f"{endpoint}?{data}"
r = requests.get(lookupURL, headers=header)
print(r.json())
When i run this it returns this
"
True
{'error': {'status': 400, 'message': 'Only valid bearer authentication supported'}}
"
Please could someone help and explain the solution.
Thanks,
Sam :)

I think it could be a problem here, as you are leaving no space between the Bearer keyword and the token:
# previous
header = {
"Authorization": f"Bearer{token}",
}
# correct
header = {
"Authorization": f"Bearer {token}",
}

Related

The request that I send to the Spotify API responds with 404

Here is the code of mine. I censored the CLIENT_SECRET and the CLIENT_ID.
import requests
import json
import base64
CLIENT_ID = "CLIENT_ID"
CLIENT_SECRET = "CLIENT_SECRET"
def get_acces_token(CLIENT_ID: str, CLIENT_SECRET: str) -> str:
authURL = "https://accounts.spotify.com/api/token"
authHeader = {}
authData = {
"grant_type" : "client_credentials"
}
# Base 64
combinated = f"{CLIENT_ID}:{CLIENT_SECRET}"
message = combinated
message_bytes = message.encode('ascii')
base64_bytes = base64.b64encode(message_bytes)
base64_combinated = base64_bytes.decode('ascii')
# request
authHeader['Authorization'] = f"Basic {base64_combinated}"
res = requests.post(authURL,headers = authHeader,data = authData)
ACCCES_TOKEN = res.json()['access_token']
return ACCCES_TOKEN
def get_playlist_tracks(acces_token: str, playlist_id:str) -> dict:
playlist_end_point = f"https://accounts.spotify.com/api/playlists/{playlist_id}"
get_header = {
"Authorization" : f"Bearer {acces_token}"
}
res = requests.get(playlist_end_point,headers=get_header)
return res
token = get_acces_token(CLIENT_ID=CLIENT_ID,CLIENT_SECRET=CLIENT_SECRET)
playlist_id = "3SPj0H71ii0CnhdO6CrhkL?si=c8c41e7c79764265"
tracklist = get_playlist_tracks(token,playlist_id)
print(tracklist)
I don't know why does it respond with 404. The access_token is good. I think that the playlist_end_point should be wrong. Do you have any idea what's wrong in the code?
According to this reference, the right endpoint is https://api.spotify.com/v1/playlists/{playlist_id}/tracks

Python Mocking a request for a bearer token?

I'm trying to figure out how to mock my request for a bearer token in python.
I have a class:
class grab_apitokens(object):
def __init__(self, consumer_key, first_api_url, second_api_user, second_api_password, second_api_url):
self.consumer_key = consumer_key
self.second_api_user = second_api_user
self.second_api_password = second_api_password
self.first_api_url = first_api_url
self.second_api_url = second_api_url
def logintofirstsite(self):
b64val = base64.b64encode(self.consumer_key.encode()).decode()
headers = {"Authorization": "Basic %s" % b64val}
data = {'grant_type': 'client_credentials', 'validity_period': '3600'}
try:
response = requests.post(self.first_api_url, headers=headers, data=data)
decodedresponse = json.loads(response.content.decode())
access_token = decodedresponse['access_token']
return access_token
except:
return None
def logintosecondsite(self):
header = {"accept": "application/json", "Content-Type": "application/x-www-form-urlencoded"}
logindata = {'grant_type': 'password',
'username': "" + self.second_api_user + "", 'password': "" + self.second_api_password + ""
}
try:
returnedfromsite = requests.post(self.second_api_url + '/api/V1/token',
headers=header, data=logindata)
return returnedfromsite.json()['access_token']
except:
return None
What I can't figure out is how to mock that requests call and what it would look like in Python.
My test currently looks like:
class MyTestCase(unittest.TestCase):
def setUp(self) -> None: # PROBLEM BEGINS HERE
self.grab_apitokens = grab_apitokens(actual_key, actual_site1, actual_user, actual_pass, actual_2nd_site)
#patch('grab_apitokens.requests.get')
def test_login(self, mock_get):
mock_get.return_value.ok = True
response = self.grab_apitokens.logintosite()
assert_is_not_none(response)
# self.assertEqual(True, False)
if __name__ == '__main__':
unittest.main()
How would I mock the requests.post functionality?
With the help of a good mentor I figured out that my approach was all wrong. Here's what I ended up with for the unit test:
class MyTestCase(unittest.TestCase):
def setUp(self) -> None:
self.grab_apitokens = grab_apitokens("complete","gibberish","it really doesnt","matter","what is","in","here")
#patch('grab_apitokens.requests.posts')
def test_login(self, mock_get):
mock_json = {'token': 'foo'}
mock_get.return_value = Mock(ok=True)
mock_get.return_value.json.return_value = mock_json
mock_get.return_value.content = b'{"token": "foo"}'
response = self.grab_apitokens.logintofirstsite()
assert_equal(response, "foo")
if __name__ == '__main_
To understand what this does, I needed to know that what we're really mocking isn't the method logintofirstsite(), we're mocking the response that requests.post is making in the method. With mock_get, we're inject requests.posts to always be: {'token': 'foo'} . So everything after
response = requests.post(self.first_api_url, headers=headers, data=data)
in logintofirstsite() is what I'm really testing. Which is:
decodedresponse = json.loads(response.content.decode())
access_token = decodedresponse['token']
return access_token
The setup before the requests.post call doesn't matter one bit. Since with {'token': 'foo'} is what my requests.post call returns, the returned value after that bit of logic is 'foo', and so the assert_equal back in MyTestCase passes.

aiohttp - before request for each API call

When I was using Flask, every API call is authenticated before processed:
app = connexion.App(__name__, specification_dir='./swagger/', swagger_json=True, swagger_ui=True, server='tornado')
app.app.json_encoder = encoder.JSONEncoder
app.add_api('swagger.yaml', arguments={'title': 'ABCD API'})
# add CORS support
CORS(app.app)
#app.app.before_request
def before_request_func():
app_id = request.headers.get("X-AppId")
token = request.headers.get("X-Token")
user, success = security.Security().authorize(token)
if not success:
status_code = 401
response = {
'code': status_code,
'message': 'Unauthorized user'
}
return jsonify(response), status_code
g.user = user
When I changed it to AioHttp, my authentication is not properly setup:
options = {'swagger_path': 'swagger/', "swagger_ui": True}
app = connexion.AioHttpApp(__name__, specification_dir='swagger/', options=options)
app.add_api('swagger.yaml', arguments={'title': ' ABCD API'})
app = web.Application(middlewares=[auth_through_token])
async def auth_through_token(app: web.Application, handler: Any) -> Callable:
#web.middleware
async def middleware_handler(request: web.Request) -> web.Response:
headers = request.headers
x_auth_token = headers.get("X-Token")
app_id = headers.get("X-AppId")
user, success = security.Security().authorize(x_auth_token)
if not success:
return web.json_response(status=401, data={
"error": {
"message": ("Not authorized. Reason: {}"
)
}
})
response = await handler(request)
return response
return middleware_handler
My request is not getting redirected to the API method.
Could anyone please help me to set up, my before_request authentication for every API?
Thanks.
Firstly, you have to move middleware_handler out from auth_through_token.
Then,
Quote your code:
options = {'swagger_path': 'swagger/', "swagger_ui": True}
app = connexion.AioHttpApp(__name__, specification_dir='swagger/', options=options)
app.add_api('swagger.yaml', arguments={'title': ' ABCD API'})
app = web.Application(middlewares=[auth_through_token])
You have to remove the last line and change the first line to:
options = {'swagger_path': 'swagger/', "swagger_ui": True, 'middlewares': [middleware_handler]}
So finally the code should look like:
options = {'swagger_path': 'swagger/', "swagger_ui": True, 'middlewares': [middleware_handler]}
app = connexion.AioHttpApp(__name__, specification_dir='swagger/', options=options)
app.add_api('swagger.yaml', arguments={'title': ' ABCD API'})
#web.middleware
async def middleware_handler(request: web.Request, handler: Any) -> web.Response:
headers = request.headers
x_auth_token = headers.get("X-Token")
app_id = headers.get("X-AppId")
user, success = security.Security().authorize(x_auth_token)
if not success:
return web.json_response(status=401, data={
"error": {
"message": ("Not authorized. Reason: {}"
)
}
})
response = await handler(request)
return response

send multiple requests with Token authentication?

Currently my API requires to use Token authentication.
POST /api/authorize HTTP/1.1
"version": "20151130", // The version of the REST API you are using
"client_id": "01234567890123456789", // Your 20 char public client id
"client_secret": "0123456789..." // Your 40 char client secret
I get the response:
{
"auth_token": "abcdef...",
"auth_expires": "20180524T062442Z"
}
My current pattern is like this:
I have a list of items I need to pass to the API via POST method.
This is my main function: ProcessProducts which receives a Pandas Dataframe which includes a product per row.
def ProcessProducts(products):
all_results = []
for _, product in products.iterrows():
results = GetProductData(product)
if results:
all_results.extend(results)
return all_results
def GetAuthorizationToken():
payload = {
'version': api_version,
'client_id': api_client_id,
'client_secret': api_client_secret
}
request_url = '%s%s' % (api_host, '/api/authorize')
r = requests.post(request_url, data=payload)
if r.status_code != 200:
raise Exception('Failed to authorize: ' + r.text)
token_data = json.loads(r.text)
api_auth_token = token_data['auth_token']
api_auth_expires = token_data['auth_expires']
return {
"X-API-Version": api_version,
"Authorization": "token %s" % api_auth_token
}
Client function...
def GetProductData(self, product):
"""Gets Product information from API."""
url = '%s%s' % (self.api_url, _COMPANY_DATA)
request = json.dumps({'products': [product]})
form_data = {'request': request, 'start': 1, 'limit': 1000}
logging.info('Looking up: %s', url)
auth_headers = GetAuthorizationToken()
response = _SendApiRequest(url, auth_headers, form_data)
return _HandleResponse(response)
def _SendApiRequest(self, url, auth_headers, form_data):
session = requests.Session()
try:
response = session.post(
url,
timeout=(_CONNECT_TIMEOUT_SECONDS, _READ_TIMEOUT_SECONDS),
headers=auth_headers,
data=form_data,
verify=True) # Check for valid public/signed HTTPS certificate.
response.raise_for_status()
return response
except requests.exceptions.HTTPError as err:
logging.exception(err)
Questions:
API returns the code expiry field "auth_expires", Where may be the best way to check in code when token expires so I can request a new one?
Is there a better Pattern to call API, so I can control the QPS rate as well (Use RateLimiter). Right now I'm creating a Session per request, which may not be ideal.

Meeting room calendars missing - Office 365 API

I am trying to get the list of all calendars for a user. This user has delegate permissions to view the calendar of all the meeting rooms (resources). If I log into the user's account and I am able to see the meeting room calendars in the "Other Calendars" section. I also created my own calendar called "Test" in the "Other Calendars" section.
When I get all the calendar groups first and then iterate through the calendar group list and get the calendars, the list for "Other Calendars" only has "Test" calendar.
Not sure why this is the case. The user is a global administrator as well.
def get_access_info_from_authcode(auth_code, redirect_uri):
post_data = { 'grant_type': 'authorization_code',
'code': auth_code,
'redirect_uri': redirect_uri,
'scope': ' '.join(str(i) for i in scopes),
'client_id': client_registration.client_id(),
'client_secret': client_registration.client_secret()
}
r = requests.post(access_token_url, data = post_data, verify = verifySSL)
try:
return r.json()
except:
return 'Error retrieving token: {0} - {1}'.format(r.status_code, r.text)
def get_access_token_from_refresh_token(refresh_token, resource_id):
post_data = { 'grant_type' : 'refresh_token',
'client_id' : client_registration.client_id(),
'client_secret' : client_registration.client_secret(),
'refresh_token' : refresh_token,
'resource' : resource_id }
r = requests.post(access_token_url, data = post_data, verify = verifySSL)
# Return the token as a JSON object
return r.json()
def get_calendars_from_calendar_groups(calendar_endpoint, token, calendar_groups):
results = []
for group_id in calendar_groups:
get_calendars = '{0}/me/calendargroups/{1}/calendars'.format(calendar_endpoint, group_id)
r = make_api_call('GET', get_calendars, token)
if (r.status_code == requests.codes.unauthorized):
logger.debug('Response Headers: {0}'.format(r.headers))
logger.debug('Response: {0}'.format(r.json()))
results.append(None)
results.append(r.json())
return results
def get_calendars(calendar_endpoint, token, parameters=None):
if (not parameters is None):
logger.debug(' parameters: {0}'.format(parameters))
get_calendars = '{0}/me/calendars'.format(calendar_endpoint)
if (not parameters is None):
get_calendars = '{0}{1}'.format(get_calendars, parameters)
r = make_api_call('GET', get_calendars, token)
if(r.status_code == requests.codes.unauthorized):
logger.debug('Unauthorized request. Leaving get_calendars.')
return None
return r.json()
Logic + Code:
Step 1) Get the authorization URL:
authority = "https://login.microsoftonline.com/common"
authorize_url = '{0}{1}'.format(authority, '/oauth2/authorize?client_id={0}&redirect_uri={1}&response_type=code&state={2}&prompt=consent')
Step 2) Opening the URL takes us to https://login.microsoftonline.com/common where I login as the user:
Step 3) This redirects back to my localhost then the following:
discovery_result = exchoauth.get_access_info_from_authcode(auth_code, Office365.redirect_uri)
refresh_token = discovery_result['refresh_token']
client_id = client_registration.client_id()
client_secret = client_registration.client_secret()
access_token_json = exchoauth.get_access_token_from_refresh_token(refresh_token, Office365.resource_id)
access_token = access_token_json['access_token']
calendar_groups_json = exchoauth.get_calendar_groups(Office365.api_endpoint, access_token)
cal_groups = {}
if calendar_groups_json is not None:
for entry in calendar_groups_json['value']:
cal_group_id = entry['Id']
cal_group_name = entry['Name']
cal_groups[cal_group_id] = cal_group_name
calendars_json_list = exchoauth.get_calendars_from_calendar_groups(Office365.api_endpoint,
access_token, cal_groups)
for calendars_json in calendars_json_list:
if calendars_json is not None:
for ent in calendars_json['value']:
cal_id = ent['Id']
cal_name = ent['Name']
calendar_ids[cal_id] = cal_name
Let me know if you need any other information
The delegate-token which request with the Auth code grant flow only able to get the calendars of sign-in user.
If you want to the get the events from the specific room, you can use the app-only token request with client credential flow.
Here is an helpful link for your reference.

Categories

Resources