Skip to content

Commit 9c8f45e

Browse files
Day 19 - The Spotify API
1 parent 1613757 commit 9c8f45e

14 files changed

+30731
-0
lines changed

tutorial-reference/Day 19/Pipfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[[source]]
2+
name = "pypi"
3+
url = "https://pypi.org/simple"
4+
verify_ssl = true
5+
6+
[dev-packages]
7+
8+
[packages]
9+
jupyter = "*"
10+
nbconvert = "*"
11+
12+
[requires]
13+
python_version = "3.8"

tutorial-reference/Day 19/Pipfile.lock

Lines changed: 404 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#!/usr/bin/env python
2+
# coding: utf-8
3+
4+
# In[1]:
5+
6+
7+
import base64
8+
import datetime
9+
from urllib.parse import urlencode
10+
11+
import requests
12+
13+
14+
# In[14]:
15+
16+
17+
class SpotifyAPI(object):
18+
access_token = None
19+
access_token_expires = datetime.datetime.now()
20+
access_token_did_expire = True
21+
client_id = None
22+
client_secret = None
23+
token_url = "https://accounts.spotify.com/api/token"
24+
25+
def __init__(self, client_id, client_secret, *args, **kwargs):
26+
super().__init__(*args, **kwargs)
27+
self.client_id = client_id
28+
self.client_secret = client_secret
29+
30+
def get_client_credentials(self):
31+
"""
32+
Returns a base64 encoded string
33+
"""
34+
client_id = self.client_id
35+
client_secret = self.client_secret
36+
if client_secret == None or client_id == None:
37+
raise Exception("You must set client_id and client_secret")
38+
client_creds = f"{client_id}:{client_secret}"
39+
client_creds_b64 = base64.b64encode(client_creds.encode())
40+
return client_creds_b64.decode()
41+
42+
def get_token_headers(self):
43+
client_creds_b64 = self.get_client_credentials()
44+
return {
45+
"Authorization": f"Basic {client_creds_b64}"
46+
}
47+
48+
def get_token_data(self):
49+
return {
50+
"grant_type": "client_credentials"
51+
}
52+
53+
def perform_auth(self):
54+
token_url = self.token_url
55+
token_data = self.get_token_data()
56+
token_headers = self.get_token_headers()
57+
r = requests.post(token_url, data=token_data, headers=token_headers)
58+
if r.status_code not in range(200, 299):
59+
raise Exception("Could not authenticate client.")
60+
# return False
61+
data = r.json()
62+
now = datetime.datetime.now()
63+
access_token = data['access_token']
64+
expires_in = data['expires_in'] # seconds
65+
expires = now + datetime.timedelta(seconds=expires_in)
66+
self.access_token = access_token
67+
self.access_token_expires = expires
68+
self.access_token_did_expire = expires < now
69+
return True
70+
71+
def get_access_token(self):
72+
token = self.access_token
73+
expires = self.access_token_expires
74+
now = datetime.datetime.now()
75+
if expires < now:
76+
self.perform_auth()
77+
return self.get_access_token()
78+
elif token == None:
79+
self.perform_auth()
80+
return self.get_access_token()
81+
return token
82+
83+
def get_resource_header(self):
84+
access_token = self.get_access_token()
85+
headers = {
86+
"Authorization": f"Bearer {access_token}"
87+
}
88+
return headers
89+
90+
91+
def get_resource(self, lookup_id, resource_type='albums', version='v1'):
92+
endpoint = f"https://api.spotify.com/{version}/{resource_type}/{lookup_id}"
93+
headers = self.get_resource_header()
94+
r = requests.get(endpoint, headers=headers)
95+
if r.status_code not in range(200, 299):
96+
return {}
97+
return r.json()
98+
99+
def get_album(self, _id):
100+
return self.get_resource(_id, resource_type='albums')
101+
102+
def get_artist(self, _id):
103+
return self.get_resource(_id, resource_type='artists')
104+
105+
def base_search(self, query_params): # type
106+
headers = self.get_resource_header()
107+
endpoint = "https://api.spotify.com/v1/search"
108+
lookup_url = f"{endpoint}?{query_params}"
109+
r = requests.get(lookup_url, headers=headers)
110+
if r.status_code not in range(200, 299):
111+
return {}
112+
return r.json()
113+
114+
def search(self, query=None, operator=None, operator_query=None, search_type='artist' ):
115+
if query == None:
116+
raise Exception("A query is required")
117+
if isinstance(query, dict):
118+
query = " ".join([f"{k}:{v}" for k,v in query.items()])
119+
if operator != None and operator_query != None:
120+
if operator.lower() == "or" or operator.lower() == "not":
121+
operator = operator.upper()
122+
if isinstance(operator_query, str):
123+
query = f"{query} {operator} {operator_query}"
124+
query_params = urlencode({"q": query, "type": search_type.lower()})
125+
print(query_params)
126+
return self.base_search(query_params)
127+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
jupyter nbconvert --output-dir='./client' --to python notebooks/spotify_client.ipynb
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
jupyter nbconvert --output-dir='./client' --to python notebooks/spotify_client.ipynb
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 1,
6+
"metadata": {},
7+
"outputs": [
8+
{
9+
"name": "stdout",
10+
"output_type": "stream",
11+
"text": [
12+
"Requirement already satisfied: requests in /Users/cfe/.local/share/virtualenvs/Day_19-wbNp0utX/lib/python3.8/site-packages (2.23.0)\r\n",
13+
"Requirement already satisfied: idna<3,>=2.5 in /Users/cfe/.local/share/virtualenvs/Day_19-wbNp0utX/lib/python3.8/site-packages (from requests) (2.9)\r\n",
14+
"Requirement already satisfied: certifi>=2017.4.17 in /Users/cfe/.local/share/virtualenvs/Day_19-wbNp0utX/lib/python3.8/site-packages (from requests) (2020.4.5.1)\r\n",
15+
"Requirement already satisfied: chardet<4,>=3.0.2 in /Users/cfe/.local/share/virtualenvs/Day_19-wbNp0utX/lib/python3.8/site-packages (from requests) (3.0.4)\r\n",
16+
"Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /Users/cfe/.local/share/virtualenvs/Day_19-wbNp0utX/lib/python3.8/site-packages (from requests) (1.25.9)\r\n"
17+
]
18+
}
19+
],
20+
"source": [
21+
"!pip install requests"
22+
]
23+
},
24+
{
25+
"cell_type": "code",
26+
"execution_count": 2,
27+
"metadata": {},
28+
"outputs": [],
29+
"source": [
30+
"import requests\n",
31+
"import datetime"
32+
]
33+
},
34+
{
35+
"cell_type": "code",
36+
"execution_count": 3,
37+
"metadata": {},
38+
"outputs": [],
39+
"source": [
40+
"import base64"
41+
]
42+
},
43+
{
44+
"cell_type": "code",
45+
"execution_count": 4,
46+
"metadata": {},
47+
"outputs": [],
48+
"source": [
49+
"client_id = '234d56555aa14d96a811661dfcf00d64'\n",
50+
"client_secret = '0fb68f689a0c4faabbadc9f88571d739'"
51+
]
52+
},
53+
{
54+
"cell_type": "code",
55+
"execution_count": 5,
56+
"metadata": {},
57+
"outputs": [],
58+
"source": [
59+
"# do a lookup for a token\n",
60+
"# this token is for future requests"
61+
]
62+
},
63+
{
64+
"cell_type": "code",
65+
"execution_count": 6,
66+
"metadata": {},
67+
"outputs": [
68+
{
69+
"data": {
70+
"text/plain": [
71+
"str"
72+
]
73+
},
74+
"execution_count": 6,
75+
"metadata": {},
76+
"output_type": "execute_result"
77+
}
78+
],
79+
"source": [
80+
"client_creds = f\"{client_id}:{client_secret}\"\n",
81+
"type(client_creds)"
82+
]
83+
},
84+
{
85+
"cell_type": "code",
86+
"execution_count": 7,
87+
"metadata": {},
88+
"outputs": [
89+
{
90+
"data": {
91+
"text/plain": [
92+
"bytes"
93+
]
94+
},
95+
"execution_count": 7,
96+
"metadata": {},
97+
"output_type": "execute_result"
98+
}
99+
],
100+
"source": [
101+
"client_creds_b64 = base64.b64encode(client_creds.encode())\n",
102+
"type(client_creds_b64)"
103+
]
104+
},
105+
{
106+
"cell_type": "code",
107+
"execution_count": 8,
108+
"metadata": {},
109+
"outputs": [],
110+
"source": [
111+
"# base64.b64decode(client_creds_b64)"
112+
]
113+
},
114+
{
115+
"cell_type": "code",
116+
"execution_count": 9,
117+
"metadata": {},
118+
"outputs": [],
119+
"source": [
120+
"token_url = \"https://accounts.spotify.com/api/token\"\n",
121+
"method = \"POST\"\n",
122+
"token_data = {\n",
123+
" \"grant_type\": \"client_credentials\"\n",
124+
"}\n",
125+
"token_headers = {\n",
126+
" \"Authorization\": f\"Basic {client_creds_b64.decode()}\" # <base64 encoded client_id:client_secret>\n",
127+
"}\n"
128+
]
129+
},
130+
{
131+
"cell_type": "code",
132+
"execution_count": 10,
133+
"metadata": {},
134+
"outputs": [
135+
{
136+
"name": "stdout",
137+
"output_type": "stream",
138+
"text": [
139+
"{'access_token': 'BQDtxCXrBEvd4H6rkSSJq1KOW_xUyLEeMaBk7qjay4wgYOlxpvv_lVo07CcJYAJ_Hcao73sYW0mpgfMdi4o', 'token_type': 'Bearer', 'expires_in': 3600, 'scope': ''}\n"
140+
]
141+
}
142+
],
143+
"source": [
144+
"r = requests.post(token_url, data=token_data, headers=token_headers)\n",
145+
"print(r.json())\n",
146+
"valid_request = r.status_code in range(200, 299)\n"
147+
]
148+
},
149+
{
150+
"cell_type": "code",
151+
"execution_count": 11,
152+
"metadata": {},
153+
"outputs": [],
154+
"source": [
155+
"if valid_request:\n",
156+
" token_response_data = r.json()\n",
157+
" now = datetime.datetime.now()\n",
158+
" access_token = token_response_data['access_token']\n",
159+
" expires_in = token_response_data['expires_in'] # seconds\n",
160+
" expires = now + datetime.timedelta(seconds=expires_in)\n",
161+
" did_expire = expires < now"
162+
]
163+
}
164+
],
165+
"metadata": {
166+
"kernelspec": {
167+
"display_name": "Python 3",
168+
"language": "python",
169+
"name": "python3"
170+
},
171+
"language_info": {
172+
"codemirror_mode": {
173+
"name": "ipython",
174+
"version": 3
175+
},
176+
"file_extension": ".py",
177+
"mimetype": "text/x-python",
178+
"name": "python",
179+
"nbconvert_exporter": "python",
180+
"pygments_lexer": "ipython3",
181+
"version": "3.8.2"
182+
}
183+
},
184+
"nbformat": 4,
185+
"nbformat_minor": 4
186+
}

0 commit comments

Comments
 (0)