from spotipy.oauth2 import SpotifyClientCredentials from contributor import config from contributor.api import datasources from contributor.connectors.spotify import SpotifyArtist, SpotifyClient # Spotify's hard ceiling: limit + offset must not exceed 1000. _SPOTIFY_MAX_LIMIT_OFFSET = 1000 _spotify_client = SpotifyClient( client_credentials_manager=SpotifyClientCredentials( config.SPOTIFY_CLIENT_ID, config.SPOTIFY_CLIENT_SECRET ) ) async def artist_search( query: str, limit: int, offset: int, localization: str = "en" ) -> list[SpotifyArtist]: """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. Raises: ValueError: If limit + offset exceeds Spotify's maximum. Returns: list[SpotifyArtist]: List of matched artists. """ if int(limit) + int(offset) > _SPOTIFY_MAX_LIMIT_OFFSET: raise ValueError( f"limit + offset must not exceed {_SPOTIFY_MAX_LIMIT_OFFSET} " f"(got {int(limit) + int(offset)})." ) redis = datasources.get_redis_client() redis_key = f"{query},{limit},{offset},{localization}" cached = await redis.get(redis_key) if isinstance(cached, list): return [SpotifyArtist(**artist) for artist in cached] response = _spotify_client.search( q=f"{query}*", limit=limit, offset=offset, type="artist", locale=localization, ) await redis.set(redis_key, [artist.model_dump(mode="json") for artist in response]) return response async def get_artist_by_id(artist_id: str) -> SpotifyArtist | None: """Execute artist search by ID in Spotify. Args: artist_id (str): Artist ID. Returns: SpotifyArtist | None: Matched artist. """ redis = datasources.get_redis_client() cached = await redis.get(artist_id) if isinstance(cached, dict): return SpotifyArtist(**cached) artist: SpotifyArtist | None = _spotify_client.artist(artist_id) if artist: await redis.set(artist_id, artist.model_dump(mode="json")) return artist async def get_artists_by_ids(artist_ids: list[str]) -> dict[str, SpotifyArtist | None]: """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. """ redis = datasources.get_redis_client() cached_artists: dict[str, SpotifyArtist | None] = {} for artist_id in artist_ids: cached = await redis.get(artist_id) if isinstance(cached, dict): cached_artists[artist_id] = SpotifyArtist(**cached) lookup_ids = sorted(set(artist_ids) - set(cached_artists.keys())) artists: dict[str, SpotifyArtist | None] = ( _spotify_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(): if isinstance(artist, dict): await redis.set(artist_id, artist) return {**cached_artists, **artists}