"""Lookup endpoints.""" import logging import time import requests import spotipy from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi.responses import JSONResponse from pydantic import UUID4 from product_staging.api.auth import identity_uuid_from_scope from product_staging.api.datasources import get_spotify_client from product_staging.api.schemas.lookup import AlbumLookupResponse, ArtistLookupResponse from product_staging.logic.spotify import ( parse_spotify_album_id, parse_spotify_artist_id, ) logger = logging.getLogger(__name__) router = APIRouter(tags=["Lookup"]) @router.get( "/lookup/album", operation_id="lookup_album", response_model=AlbumLookupResponse, summary="Look up a Spotify album", description="Look up a Spotify album by URI, URL, or ID.", status_code=status.HTTP_200_OK, ) def lookup_album( query: str = Query(..., description="Spotify album URI, URL, or ID"), identity_uuid: UUID4 = Depends(identity_uuid_from_scope), spotify_client: spotipy.Spotify = Depends(get_spotify_client), ) -> AlbumLookupResponse | JSONResponse: try: album_id = parse_spotify_album_id(query) except ValueError as exc: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc), ) try: album = _call_with_retries(spotify_client.album, album_id, "album") except spotipy.SpotifyException as exc: logger.error("Spotify API error looking up album %s: %s", album_id, exc) return JSONResponse(status_code=status.HTTP_404_NOT_FOUND, content={}) if not album: return JSONResponse(status_code=status.HTTP_404_NOT_FOUND, content={}) external_url = album.get("external_urls", {}).get("spotify", "") artists = album.get("artists", []) artist_name = artists[0]["name"] if artists else "" display_name = f"{artist_name} - {album['name']}" if artist_name else album["name"] return AlbumLookupResponse( name=display_name, url=external_url, ) @router.get( "/lookup/artist", operation_id="lookup_artist", response_model=ArtistLookupResponse, summary="Look up a Spotify artist", description="Look up a Spotify artist by URI, URL, or ID.", status_code=status.HTTP_200_OK, ) def lookup_artist( query: str = Query(..., description="Spotify artist URI, URL, or ID"), identity_uuid: UUID4 = Depends(identity_uuid_from_scope), spotify_client: spotipy.Spotify = Depends(get_spotify_client), ) -> ArtistLookupResponse | JSONResponse: try: artist_id = parse_spotify_artist_id(query) except ValueError as exc: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc), ) try: artist = _call_with_retries(spotify_client.artist, artist_id, "artist") except spotipy.SpotifyException as exc: logger.error("Spotify API error looking up artist %s: %s", artist_id, exc) return JSONResponse(status_code=status.HTTP_404_NOT_FOUND, content={}) if not artist: return JSONResponse(status_code=status.HTTP_404_NOT_FOUND, content={}) external_url = artist.get("external_urls", {}).get("spotify", "") return ArtistLookupResponse( name=artist["name"], url=external_url, ) def _call_with_retries( lookup_func, lookup_id, lookup_type, max_retries=3, base_delay=1.0 ): entity = None for attempt in range(max_retries): try: entity = lookup_func(lookup_id) break except requests.exceptions.ConnectionError as exc: if attempt < max_retries - 1: delay = base_delay * (2**attempt) logger.warning( "Connection error looking up %s %s (attempt %d/%d), retrying in %.1fs: %s", lookup_type, lookup_id, attempt + 1, max_retries, delay, exc, ) time.sleep(delay) else: logger.error( "Connection error looking up %s %s after %d attempts: %s", lookup_type, lookup_id, max_retries, exc, ) raise HTTPException( status_code=status.HTTP_502_BAD_GATEWAY, detail="Failed to connect to Spotify API", ) return entity