"""HTTP client for Spotify API with automatic token refresh""" import requests from typing import Dict, Any, Optional from .spotify_auth import SpotifyAuth class SpotifyRateLimitError(Exception): """Raised when Spotify API returns 429 Too Many Requests""" def __init__(self, retry_after: int = 5): self.retry_after = retry_after super().__init__(f'Rate limit exceeded. Retry after {retry_after} seconds.') class SpotifyClient: """HTTP client for Spotify API with automatic token refresh on 401 errors""" def __init__(self, auth: SpotifyAuth): self.auth = auth self.base_url = 'https://api.spotify.com/v1' self.session = requests.Session() def _make_request( self, endpoint: str, params: Optional[Dict[str, Any]] = None, retry: bool = False ) -> requests.Response: """ Make HTTP request with auto-refresh on 401 Args: endpoint: API endpoint (e.g., '/me/top/artists') params: Query parameters retry: Whether this is a retry after token refresh Returns: Response object Raises: Exception: If API call fails """ url = f'{self.base_url}{endpoint}' headers = { 'Authorization': f'Bearer {self.auth.get_access_token()}', 'Content-Type': 'application/json', } if retry: headers['X-Retry'] = 'true' try: response = self.session.get( url, headers=headers, params=params, timeout=30 ) # Handle 401 - try to refresh token and retry once if response.status_code == 401 and not retry: self.auth.refresh_access_token() return self._make_request(endpoint, params, retry=True) # Handle 429 - rate limit exceeded if response.status_code == 429: retry_after = int(response.headers.get('Retry-After', 5)) raise SpotifyRateLimitError(retry_after) response.raise_for_status() return response except requests.RequestException as e: error_msg = str(e) if hasattr(e, 'response') and e.response is not None: try: error_data = e.response.json() error_msg = error_data.get('error', {}).get('message', error_msg) except ValueError: # The response body is not JSON; ignore this error and use the original error message pass raise Exception(f'Spotify API error: {error_msg}') def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """ GET request to Spotify API Args: endpoint: API endpoint (e.g., '/me/top/artists') params: Query parameters Returns: JSON response as dictionary """ response = self._make_request(endpoint, params) return response.json() def get_artist(self, artist_id: str) -> Dict[str, Any]: """ Fetch artist metadata from Spotify API Args: artist_id: Spotify artist ID Returns: Dict containing artist metadata (name, images, genres, etc.) """ return self.get(f'/artists/{artist_id}')