"""Interaction with Spotify API.""" import sentry_sdk from owsresponse import response as ows_response from spotipy.oauth2 import SpotifyClientCredentials from participant import config from participant.connectors import redis from participant.constants import error from participant.models.service.spotify_client import SpotifyClient _spotipy_credentials = SpotifyClientCredentials( config.SPOTIFY_CLIENT_ID, config.SPOTIFY_CLIENT_SECRET ) _spotipy_client = SpotifyClient(client_credentials_manager=_spotipy_credentials) # Spotify's hard ceiling: limit + offset must not exceed 1000. _SPOTIFY_MAX_LIMIT_OFFSET = 1000 def search_artist(query, limit, offset, localization): """Execute artists search in Spotify. Args: query (str): Search term. limit (int): Max number of matched artists. offset (int): Offset from the beginning of the result. Returns: List: List of matched artists. """ if int(limit) + int(offset) > _SPOTIFY_MAX_LIMIT_OFFSET: return ows_response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=( f'limit + offset must not exceed {_SPOTIFY_MAX_LIMIT_OFFSET} ' f'(got {int(limit) + int(offset)}).' ), ) redis_key = f"{query},{limit},{offset},{localization}" cached = redis.get_value(redis_key) if cached: return cached try: response = _spotipy_client.search( q=f'{query}*', limit=limit, offset=offset, search_type='artist', locale=localization, ) redis.set_value(redis_key, response) return response except Exception as err: sentry_sdk.capture_exception(err) return ows_response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ) def get_artist_by_id(artist_id): """Execute artist search by ID in Spotify. Args: artist_id (str): Artist ID. Returns: dict: Matched artist. """ cached = redis.get_value(artist_id) if cached: return cached try: response = _spotipy_client.artist(artist_id) redis.set_value(artist_id, response) return response except Exception as err: sentry_sdk.capture_exception(err) return ows_response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ) def get_artists_by_ids(artist_ids: list[str]) -> ows_response.Response: """Verify that artist ids exist on Spotify. Args: artist_ids (list[str]): List of Spotify artist ids. Returns: dict[str, bool]: Mapping of artist id to existence boolean. """ cached_artists = {} for artist_id in artist_ids: cached = redis.get_value(artist_id) if cached: cached_artists[artist_id] = cached try: lookup_ids = sorted(set(artist_ids) - set(cached_artists.keys())) artists = ( _spotipy_client.get_artists_by_ids(artist_ids=lookup_ids) if lookup_ids else {} ) # Cache each artist in redis for artist_id, artist in artists.items(): redis.set_value(artist_id, artist) return ows_response.Response({**cached_artists, **artists}) except Exception as err: sentry_sdk.capture_exception(err) return ows_response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, )