Is there a reason that get_search_history returns a HistoryItem class, and not a json or something else?
Searching in your history is not as flexible as searching in search.py. It would be nice to filter my history results.
playlist.py
All commands are f strings that are vulnerable to SQL injection attacks.
Checking if a track or playlist exists by counting ALL rows seems a bit tedious. I would recommend just using a WHERE clause and figuring out if any rows are returned.
You do not raise an exception if you attempt to remove a song from a playlist if the song does not exist. I have no idea if that's a possibility; it's better to keep yourself covered.
A playlist ID can be given but not a list of songs on the endpoints for testing.
Both issues related to count and the SQL injection weaknesses are prevalent in ratings.py, user.py, etc.
ratings.py
You do quite a few queries to check if a row exists, then if a song has a rating. Call a query for the entire song row as needed and check if it exists from there. Then you can edit the rating as you like without having to make multiple queries. It's also a bit confusing to read, but isn't all code confusing?
How are ratings done? There is a rating from 1-10 for the song overall but nothing that connects it to the features vector. If I rate one song a 7, how does that affect the features vector compatibility?
recs.py
If the recs_stmt returns an empty CursorResult, what happens then?
search.py
I don't think results needs to be returned as a single json. An array of jsons is still workable and a little easier to unpack.
Can we search by elements within the features vector? Am I allowed to do this?
All searches are turned in somewhat unordered. One of the big things about searching is easy access to info. I would suggest adding ordering features similar to the potion shop's customer table.
user.py
Another nitpick, but why is it that we only have a username, but not a password?
history.py
playlist.py
Both issues related to count and the SQL injection weaknesses are prevalent in ratings.py, user.py, etc.
ratings.py
recs.py
search.py
user.py