retire lastfm endpoints/minor formatting

This commit is contained in:
2025-09-23 13:44:54 -04:00
parent 19afb287cd
commit d6658512d8
9 changed files with 4 additions and 753 deletions

View File

@@ -94,7 +94,6 @@ routes: dict = {
"lyrics": importlib.import_module("endpoints.lyric_search").LyricSearch(
app, util, constants
),
"lastfm": importlib.import_module("endpoints.lastfm").LastFM(app, util, constants),
"yt": importlib.import_module("endpoints.yt").YT(app, util, constants),
"radio": importlib.import_module("endpoints.radio").Radio(
app, util, constants, loop

View File

@@ -4,84 +4,6 @@ from pydantic import BaseModel
Station = Literal["main", "rock", "rap", "electronic", "pop"]
"""
LastFM
"""
class LastFMException(Exception):
pass
class ValidArtistSearchRequest(BaseModel):
"""
Request model for searching an artist by name.
Attributes:
- **a** (str): Artist name.
"""
a: str
model_config = {
"json_schema_extra": {
"examples": [
{
"a": "eminem",
}
]
}
}
class ValidAlbumDetailRequest(BaseModel):
"""
Request model for album details.
Attributes:
- **a** (str): Artist name.
- **release** (str): Album/release name.
"""
a: str
release: str
model_config = {
"json_schema_extra": {
"examples": [
{
"a": "eminem",
"release": "houdini",
}
]
}
}
class ValidTrackInfoRequest(BaseModel):
"""
Request model for track info.
Attributes:
- **a** (str): Artist name.
- **t** (str): Track name.
"""
a: str
t: str
model_config = {
"json_schema_extra": {
"examples": [
{
"a": "eminem",
"t": "rap god",
}
]
}
}
"""
Rand Msg
"""

View File

@@ -1,255 +0,0 @@
import importlib
import logging
import traceback
from typing import Optional, Union
from fastapi import FastAPI, Depends
from fastapi_throttle import RateLimiter
from fastapi.responses import JSONResponse
from .constructors import (
ValidArtistSearchRequest,
ValidAlbumDetailRequest,
ValidTrackInfoRequest,
LastFMException,
)
class LastFM(FastAPI):
"""Last.FM Endpoints"""
def __init__(self, app: FastAPI, util, constants) -> None:
"""Initialize LastFM endpoints."""
self.app: FastAPI = app
self.util = util
self.constants = constants
self.lastfm = importlib.import_module("utils.lastfm_wrapper").LastFM()
self.endpoints: dict = {
"lastfm/get_artist_by_name": self.artist_by_name_handler,
"lastfm/get_artist_albums": self.artist_album_handler,
"lastfm/get_release": self.release_detail_handler,
"lastfm/get_release_tracklist": self.release_tracklist_handler,
"lastfm/get_track_info": self.track_info_handler,
# tbd
}
for endpoint, handler in self.endpoints.items():
app.add_api_route(
f"/{endpoint}",
handler,
methods=["POST"],
include_in_schema=True,
dependencies=[Depends(RateLimiter(times=2, seconds=2))],
)
async def artist_by_name_handler(
self, data: ValidArtistSearchRequest
) -> JSONResponse:
"""
Get artist information by name.
Parameters:
- **data** (ValidArtistSearchRequest): Request containing artist name.
Returns:
- **JSONResponse**: Contains artist information or an error message.
"""
artist: Optional[str] = data.a.strip()
if not artist:
return JSONResponse(
content={
"err": True,
"errorText": "No artist specified",
}
)
artist_result = await self.lastfm.search_artist(artist=artist)
if (
not artist_result
or not artist_result.get("bio")
or "err" in artist_result.keys()
):
return JSONResponse(
status_code=500,
content={
"err": True,
"errorText": "Search failed (no results?)",
},
)
return JSONResponse(
content={
"success": True,
"result": artist_result,
}
)
async def artist_album_handler(
self, data: ValidArtistSearchRequest
) -> JSONResponse:
"""
Get artist's albums/releases.
Parameters:
- **data** (ValidArtistSearchRequest): Request containing artist name.
Returns:
- **JSONResponse**: Contains a list of albums or an error message.
"""
artist: str = data.a.strip()
if not artist:
return JSONResponse(
status_code=500,
content={
"err": True,
"errorText": "Invalid request: No artist specified",
},
)
album_result: Union[dict, list[dict]] = await self.lastfm.get_artist_albums(
artist=artist
)
if isinstance(album_result, dict):
return JSONResponse(
status_code=500,
content={
"err": True,
"errorText": "General failure.",
},
)
album_result_out: list = []
seen_release_titles: list = []
for release in album_result:
release_title: str = release.get("title", "Unknown")
if release_title.lower() in seen_release_titles:
continue
seen_release_titles.append(release_title.lower())
album_result_out.append(release)
return JSONResponse(content={"success": True, "result": album_result_out})
async def release_detail_handler(
self, data: ValidAlbumDetailRequest
) -> JSONResponse:
"""
Get details of a particular release by an artist.
Parameters:
- **data** (ValidAlbumDetailRequest): Request containing artist and release name.
Returns:
- **JSONResponse**: Release details or error.
"""
artist: str = data.a.strip()
release: str = data.release.strip()
if not artist or not release:
return JSONResponse(
status_code=500,
content={
"err": True,
"errorText": "Invalid request",
},
)
release_result = await self.lastfm.get_release(artist=artist, album=release)
ret_obj = {
"id": release_result.get("id"),
"artists": release_result.get("artists"),
"title": release_result.get("title"),
"summary": release_result.get("summary"),
"tracks": release_result.get("tracks"),
}
return JSONResponse(
content={
"success": True,
"result": ret_obj,
}
)
async def release_tracklist_handler(
self, data: ValidAlbumDetailRequest
) -> JSONResponse:
"""
Get track list for a particular release by an artist.
Parameters:
- **data** (ValidAlbumDetailRequest): Request containing artist and release name.
Returns:
- **JSONResponse**: Track list or error.
"""
artist: str = data.a.strip()
release: str = data.release.strip()
if not artist or not release:
return JSONResponse(
status_code=500,
content={
"err": True,
"errorText": "Invalid request",
},
)
tracklist_result: dict = await self.lastfm.get_album_tracklist(
artist=artist, album=release
)
return JSONResponse(
content={
"success": True,
"id": tracklist_result.get("id"),
"artists": tracklist_result.get("artists"),
"title": tracklist_result.get("title"),
"summary": tracklist_result.get("summary"),
"tracks": tracklist_result.get("tracks"),
}
)
async def track_info_handler(self, data: ValidTrackInfoRequest) -> JSONResponse:
"""
Get track info from Last.FM given an artist/track.
Parameters:
- **data** (ValidTrackInfoRequest): Request containing artist and track name.
Returns:
- **JSONResponse**: Track info or error.
"""
try:
artist: str = data.a
track: str = data.t
if not artist or not track:
return JSONResponse(
status_code=500,
content={"err": True, "errorText": "Invalid request"},
)
track_info_result: Optional[dict] = await self.lastfm.get_track_info(
artist=artist, track=track
)
if not track_info_result:
return JSONResponse(
status_code=200,
content={
"err": True,
"errorText": "Not found.",
},
)
if "err" in track_info_result:
raise LastFMException(
"Unknown error occurred: %s",
track_info_result.get("errorText", "??"),
)
return JSONResponse(content={"success": True, "result": track_info_result})
except Exception as e:
logging.debug("Exception: %s", str(e))
traceback.print_exc()
return JSONResponse(
status_code=500,
content={
"err": True,
"errorText": "General error",
},
)

View File

@@ -81,6 +81,8 @@ class LyricSearch(FastAPI):
)
for endpoint, handler in self.endpoints.items():
times: int = 20
seconds: int = 2
rate_limit: tuple[int, int] = (2, 3) # Default; (Times, Seconds)
_schema_include = endpoint in ["lyric/search"]

View File

@@ -5,7 +5,7 @@ from fastapi.responses import JSONResponse
from utils.sr_wrapper import SRUtil
from auth.deps import get_current_user
from redis import Redis
from rq import Queue, Retry
from rq import Queue
from rq.job import Job
from rq.job import JobStatus
from rq.registry import (

View File

@@ -1,32 +0,0 @@
import asyncio
import logging
import sys
sys.path.insert(0, "..")
from utils.sr_wrapper import SRUtil
# logging.getLogger("sr_wrapper").propagate = False
logger = logging.getLogger()
logger.setLevel(logging.CRITICAL)
async def main():
sr = SRUtil()
artist_search = await sr.get_artists_by_name("Ren")
# logging.critical("Artist search: %s", artist_search)
res = [
dict(x)
for x in artist_search
if x.get("popularity", 0) and x.get("artist").lower() == "ren"
]
logging.critical("Results: %s", res)
# search_res = await sr.get_album_by_name(artist[:8], album)
# logging.critical("Search result: %s", search_res)
# album = search_res
# _cover = await sr.get_cover_by_album_id(album.get('id'), 640)
# # cover = sr._get_tidal_cover_url(album.get('cover'), 640)
# logging.critical("Result: %s, Cover: %s", album, _cover)
return
asyncio.run(main())

View File

@@ -2,7 +2,7 @@
import logging
from typing import Optional
from fastapi import FastAPI, Response, HTTPException
from fastapi import FastAPI, HTTPException
from fastapi.responses import RedirectResponse

View File

@@ -1,7 +0,0 @@
"""
LastFM
"""
class InvalidLastFMResponseException(Exception):
pass

View File

@@ -1,378 +0,0 @@
import traceback
import logging
from typing import Optional, Union
import regex
from aiohttp import ClientSession, ClientTimeout
from constants import Constants
from .constructors import InvalidLastFMResponseException
class LastFM:
"""LastFM Endpoints"""
def __init__(self, noInit: Optional[bool] = False) -> None:
self.creds = Constants().LFM_CREDS
self.api_base_url: str = "https://ws.audioscrobbler.com/2.0"
async def search_artist(self, artist: Optional[str] = None) -> dict:
"""Search LastFM for an artist"""
try:
if not artist:
return {
"err": "No artist specified.",
}
request_params: list[tuple] = [
("method", "artist.getInfo"),
("artist", artist),
("api_key", self.creds.get("key")),
("autocorrect", "1"),
("format", "json"),
]
async with ClientSession() as session:
async with await session.get(
self.api_base_url,
params=request_params,
timeout=ClientTimeout(connect=3, sock_read=8),
) as request:
request.raise_for_status()
data: dict = await request.json()
data = data.get("artist", "N/A")
ret_obj: dict = {
"id": data.get("mbid"),
"touring": data.get("ontour"),
"name": data.get("name"),
"bio": data.get("bio", None)
.get("summary")
.strip()
.split("<a href")[0],
}
return ret_obj
except Exception as e:
logging.debug("Exception: %s", str(e))
traceback.print_exc()
return {
"err": "Failed",
}
async def get_track_info(
self, artist: Optional[str] = None, track: Optional[str] = None
) -> Optional[dict]:
"""
Get Track Info from LastFM
Args:
artist (Optional[str])
track (Optional[str])
Returns:
dict
"""
try:
if not artist or not track:
logging.info("inv request")
return {
"err": "Invalid/No artist or track specified",
}
request_params: list[tuple] = [
("method", "track.getInfo"),
("api_key", self.creds.get("key")),
("autocorrect", "1"),
("artist", artist),
("track", track),
("format", "json"),
]
async with ClientSession() as session:
async with await session.get(
self.api_base_url,
params=request_params,
timeout=ClientTimeout(connect=3, sock_read=8),
) as request:
request.raise_for_status()
data: dict = await request.json()
if not data:
return None
data = data.get("track", None)
if not isinstance(data.get("artist"), dict):
return None
artist_mbid: int = data.get("artist", None).get("mbid")
album: str = data.get("album", None)
if not isinstance(album, dict):
return None
album = album.get("title")
ret_obj: dict = {
"artist_mbid": artist_mbid,
"album": album,
}
return ret_obj
except Exception as e:
traceback.print_exc()
logging.debug("Exception: %s", str(e))
return {
"err": "General Failure",
}
async def get_album_tracklist(
self, artist: Optional[str] = None, album: Optional[str] = None
) -> dict:
"""
Get Album Tracklist
Args:
artist (str)
album (str)
Returns:
dict
"""
try:
if not artist or not album:
return {
"err": "No artist or album specified",
}
tracks: dict = await self.get_release(artist=artist, album=album)
tracks = tracks.get("tracks", None)
ret_obj: dict = {
"tracks": tracks,
}
return ret_obj
except Exception as e:
traceback.print_exc()
logging.debug("Exception: %s", str(e))
return {
"err": "General Failure",
}
async def get_artist_albums(
self, artist: Optional[str] = None
) -> Union[dict, list[dict]]:
"""
Get Artists Albums from LastFM
Args:
artist (Optional[str])
Returns:
Union[dict, list[dict]]
"""
try:
if not artist:
return {
"err": "No artist specified.",
}
request_params: list[tuple] = [
("method", "artist.gettopalbums"),
("artist", artist),
("api_key", self.creds.get("key")),
("autocorrect", "1"),
("format", "json"),
]
async with ClientSession() as session:
async with await session.get(
self.api_base_url,
params=request_params,
timeout=ClientTimeout(connect=3, sock_read=8),
) as request:
request.raise_for_status()
json_data: dict = await request.json()
data: dict = json_data.get("topalbums", None).get("album")
ret_obj: list = [
{"title": item.get("name")}
for item in data
if not (item.get("name").lower() == "(null)")
and int(item.get("playcount")) >= 50
]
return ret_obj
except Exception as e:
logging.debug("Exception: %s", str(e))
traceback.print_exc()
return {
"err": "Failed",
}
async def get_artist_id(self, artist: Optional[str] = None) -> int:
"""
Get Artist ID from LastFM
Args:
artist (Optional[str])
Returns:
int
"""
try:
if not artist:
return -1
artist_search: dict = await self.search_artist(artist=artist)
if not artist_search:
logging.debug("[get_artist_id] Throwing no result error")
return -1
artist_id: int = int(artist_search[0].get("id", 0))
return artist_id
except Exception as e:
logging.debug("Exception: %s", str(e))
traceback.print_exc()
return -1
async def get_artist_info_by_id(self, artist_id: Optional[int] = None) -> dict:
"""
Get Artist info by ID from LastFM
Args:
artist_id (Optional[int])
Returns:
dict
"""
try:
if not artist_id or not str(artist_id).isnumeric():
return {
"err": "Invalid/no artist_id specified.",
}
req_url: str = f"{self.api_base_url}/artists/{artist_id}"
request_params: list[tuple] = [
("key", self.creds.get("key")),
("secret", self.creds.get("secret")),
]
async with ClientSession() as session:
async with await session.get(
req_url,
params=request_params,
timeout=ClientTimeout(connect=3, sock_read=8),
) as request:
request.raise_for_status()
data: dict = await request.json()
if not data.get("profile"):
raise InvalidLastFMResponseException(
"Data did not contain 'profile' key."
)
_id: int = data.get("id", None)
name: str = data.get("name", None)
profile: str = data.get("profile", "")
profile = regex.sub(r"(\[(\/{0,})(u|b|i)])", "", profile)
members: list = data.get("members", None)
ret_obj: dict = {
"id": _id,
"name": name,
"profile": profile,
"members": members,
}
return ret_obj
except Exception as e:
logging.debug("Exception: %s", str(e))
traceback.print_exc()
return {
"err": "Failed",
}
async def get_artist_info(self, artist: Optional[str] = None) -> dict:
"""
Get Artist Info from LastFM
Args:
artist (Optional[str])
Returns:
dict
"""
try:
if not artist:
return {
"err": "No artist specified.",
}
artist_id: Optional[int] = await self.get_artist_id(artist=artist)
if not artist_id:
return {
"err": "Failed",
}
artist_info: Optional[dict] = await self.get_artist_info_by_id(
artist_id=artist_id
)
if not artist_info:
return {
"err": "Failed",
}
return artist_info
except Exception as e:
logging.debug("Exception: %s", str(e))
traceback.print_exc()
return {
"err": "Failed",
}
async def get_release(
self, artist: Optional[str] = None, album: Optional[str] = None
) -> dict:
"""
Get Release info from LastFM
Args:
artist (Optional[str])
album (Optional[str])
Returns:
dict
"""
try:
if not artist or not album:
return {
"err": "Invalid artist/album pair",
}
request_params: list[tuple] = [
("method", "album.getinfo"),
("artist", artist),
("album", album),
("api_key", self.creds.get("key")),
("autocorrect", "1"),
("format", "json"),
]
async with ClientSession() as session:
async with await session.get(
self.api_base_url,
params=request_params,
timeout=ClientTimeout(connect=3, sock_read=8),
) as request:
request.raise_for_status()
json_data: dict = await request.json()
data: dict = json_data.get("album", None)
ret_obj: dict = {
"id": data.get("mbid"),
"artists": data.get("artist"),
"tags": data.get("tags"),
"title": data.get("name"),
"summary": (
data.get("wiki", None).get("summary").split("<a href")[0]
if "wiki" in data.keys()
else "No summary available for this release."
),
}
try:
track_key: list = data.get("tracks", None).get("track")
except Exception as e:
logging.debug("Exception: %s", str(e))
track_key = []
if isinstance(track_key, list):
ret_obj["tracks"] = [
{
"duration": item.get("duration", "N/A"),
"title": item.get("name"),
}
for item in track_key
]
else:
ret_obj["tracks"] = [
{
"duration": data.get("tracks")
.get("track")
.get("duration"),
"title": data.get("tracks").get("track").get("name"),
}
]
return ret_obj
except Exception as e:
logging.debug("Exception: %s", str(e))
traceback.print_exc()
return {
"err": "Failed",
}