-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathspotify_client.py
More file actions
246 lines (196 loc) · 10.1 KB
/
Copy pathspotify_client.py
File metadata and controls
246 lines (196 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
import requests
from typing import Optional, List, Dict, Any
from logger_config import setup_logger
logger = setup_logger(__name__)
class SpotifyClient:
def __init__(self, access_token: str):
# Add debug prints inside __init__ where we know they'll be executed
print("=== SPOTIFY LOGGER DEBUG ===")
print(f"Logger name: {logger.name}")
print(f"Logger level: {logger.level}")
print("Handlers:")
for h in logger.handlers:
print(f"- Handler: {type(h).__name__}")
print(f" Level: {h.level}")
print(f" Formatter: {h.formatter._fmt if h.formatter else 'None'}")
self.access_token = access_token
self.base_url = 'https://api.spotify.com/v1'
self.headers = {
'Authorization': f'Bearer {access_token}'
}
logger.warning("LOGGER WARNING TEST")
def _make_request(self, endpoint: str, params: Optional[Dict] = None) -> Optional[Dict]:
"""
Make a GET request to the Spotify API
"""
url = f'{self.base_url}/{endpoint}'
logger.debug(f"Making request to {endpoint} with params: {params}")
response = requests.get(url, headers=self.headers, params=params)
if response.status_code != 200:
logger.error(f"Error making request to {endpoint}:")
logger.error(f"Status Code: {response.status_code}")
logger.error(f"Response Text: {response.text}")
return None
logger.debug(f"Successful response from {endpoint}")
return response.json()
def _make_post_request(self, endpoint: str, json: Optional[Dict] = None) -> Optional[Dict]:
"""
Make a POST request to the Spotify API
"""
url = f'{self.base_url}/{endpoint}'
logger.debug(f"Making POST request to {endpoint} with json: {json}")
response = requests.post(url, headers=self.headers, json=json)
if response.status_code not in [200, 201]:
logger.error(f"Error making POST request to {endpoint}:")
logger.error(f"Status Code: {response.status_code}")
logger.error(f"Response Text: {response.text}")
return None
logger.debug(f"Successful response from {endpoint}")
return response.json()
def _paginate_request(self, endpoint: str, params: Optional[Dict] = None, limit: Optional[int] = None) -> List[Dict]:
"""
Handle pagination for Spotify API requests
"""
logger.debug(f"Starting paginated request to {endpoint} with limit {limit}")
items = []
url = f'{self.base_url}/{endpoint}'
while url and (limit is None or len(items) < limit):
logger.debug(f"Fetching page from {url}")
response = requests.get(url, headers=self.headers, params=params)
if response.status_code != 200:
logger.error(f"Error in pagination for {endpoint}:")
logger.error(f"Status Code: {response.status_code}")
return items
data = response.json()
# Handle different response structures
if 'items' in data:
items.extend(data['items'])
elif endpoint.startswith('me/following') and 'artists' in data:
items.extend(data['artists']['items'])
# Update URL for next page
url = data.get('next') if 'next' in data else data.get('artists', {}).get('next')
params = None # Clear params for subsequent requests
logger.debug(f"Collected {len(items)} items so far")
if limit and len(items) >= limit:
items = items[:limit]
break
logger.info(f"Completed paginated request to {endpoint}, collected {len(items)} items")
return items
def get_user_profile_raw(self) -> Dict:
"""Get raw API response for user's profile"""
logger.info("Getting user profile")
try:
response = self._make_request('me')
logger.info(f"Retrieved profile for user: {response.get('display_name', 'Unknown')}")
return response
except Exception as e:
logger.error(f"Failed to get user profile: {str(e)}")
return {}
def get_top_items_raw(self, time_range: str, item_type: str) -> Optional[Dict]:
"""Get raw API response for user's top artists or tracks"""
logger.info(f"Getting top {item_type} for time range {time_range}")
return self._make_request(f'me/top/{item_type}', {'time_range': time_range})
def get_followed_artists_raw(self) -> List[Dict]:
"""Get raw API response for user's followed artists"""
logger.info("Getting user's followed artists")
artists = self._paginate_request('me/following', {'type': 'artist', 'limit': 50})
logger.info(f"Retrieved {len(artists)} followed artists")
return artists
def get_user_playlists_raw(self, limit: int = 100) -> List[Dict]:
"""Get raw API response for user's playlists"""
logger.info(f"Getting user's playlists (limit: {limit})")
playlists = self._paginate_request('me/playlists', {'limit': 50}, limit)
logger.info(f"Retrieved {len(playlists)} playlists")
return playlists
def get_saved_podcasts_raw(self) -> List[Dict]:
"""Get raw API response for user's saved shows"""
logger.info("Getting user's saved podcasts")
podcasts = self._paginate_request('me/shows', {'limit': 50})
logger.info(f"Retrieved {len(podcasts)} saved podcasts")
return podcasts
def get_recently_played_tracks_raw(self) -> List[Dict]:
"""Get raw API response for user's recently played tracks"""
logger.info("Getting user's recently played tracks")
tracks = self._paginate_request('me/player/recently-played', {'limit': 50})
logger.info(f"Retrieved {len(tracks)} recently played tracks")
return tracks
def search_item_raw(self, query: str, search_type: str, filters: Optional[Dict] = None) -> Optional[Dict]:
"""Get raw API response for search query"""
logger.info(f"Searching for {search_type} with query: {query}")
if filters:
logger.debug(f"Applied filters: {filters}")
query_parts = [query]
if filters:
query_parts.extend(
f'{key}:"{value}"' if isinstance(value, str) else f'{key}:{value}'
for key, value in filters.items()
if value
)
params = {
'q': ' '.join(query_parts),
'type': search_type,
'limit': 10
}
result = self._make_request('search', params)
if result:
logger.info(f"Search completed successfully")
return result
def create_playlist_raw(self, name: str, public: bool = True,
collaborative: bool = False, description: str = None) -> Optional[Dict]:
"""Create a new playlist for the authenticated user"""
payload = {
'name': name,
'public': public,
'collaborative': collaborative
}
if description:
payload['description'] = description
return self._make_post_request('me/playlists', json=payload)
def add_songs_to_playlist_raw(self, playlist_id: str, uris: List[str], position: Optional[int] = None) -> Optional[Dict]:
"""Add items to a playlist"""
logger.info(f"Adding {len(uris)} items to playlist {playlist_id}")
# Construct payload
payload = {'uris': uris}
if position is not None:
payload['position'] = position
endpoint = f'playlists/{playlist_id}/tracks'
return self._make_post_request(endpoint, json=payload)
def remove_playlist_items_raw(self, playlist_id: str, uris: List[str], snapshot_id: Optional[str] = None) -> Optional[Dict]:
"""Remove items from a playlist."""
url = f'playlists/{playlist_id}/tracks'
payload = {'tracks': [{'uri': uri} for uri in uris]}
if snapshot_id:
payload['snapshot_id'] = snapshot_id
logger.info(f"Removing {len(uris)} items from playlist {playlist_id}")
response = requests.delete(f'{self.base_url}/{url}', headers=self.headers, json=payload)
if response.status_code != 200:
logger.error(f"Failed to remove items from playlist {playlist_id}. Status: {response.status_code}, Response: {response.text}")
return None
return response.json()
def update_playlist_details_raw(self, playlist_id: str, payload: Dict) -> Optional[Dict]:
"""
Update playlist details with raw Spotify API call.
Args:
playlist_id (str): Spotify playlist ID.
payload (Dict): JSON payload with the details to update.
Returns:
Optional[Dict]: API response.
"""
url = f'{self.base_url}/playlists/{playlist_id}'
response = requests.put(url, headers=self.headers, json=payload)
if response.status_code != 200:
logger.error(f"Failed to update playlist details. Status: {response.status_code}, Response: {response.text}")
return None
return {"status": "success"}
def get_saved_audiobooks_raw(self, limit: int = 20, offset: int = 0) -> Optional[Dict]:
"""
Get raw API response for user's saved audiobooks.
"""
logger.info(f"Getting user's saved audiobooks with limit {limit} and offset {offset}")
return self._make_request('me/audiobooks', {'limit': limit, 'offset': offset})
def get_saved_tracks_raw(self, limit: int = 50, offset: int = 0) -> Optional[Dict]:
"""
Get raw API response for user's saved tracks.
"""
params = {'limit': limit, 'offset': offset}
return self._make_request('me/tracks', params)