From 95f77cc170ab9a66cc359eb787ae300180de8f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alban=20F=C3=A9ron?= Date: Sun, 18 Dec 2022 18:03:51 +0100 Subject: [PATCH] Porting supysonic.api.browse --- supysonic/api/__init__.py | 24 +++-- supysonic/api/browse.py | 17 ++-- supysonic/db.py | 2 +- tests/api/test_browse.py | 186 ++++++++++++++++++-------------------- 4 files changed, 112 insertions(+), 117 deletions(-) diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py index 29ae047..0d612aa 100644 --- a/supysonic/api/__init__.py +++ b/supysonic/api/__init__.py @@ -96,8 +96,7 @@ def get_entity(cls, param="id"): eid = int(eid) else: eid = uuid.UUID(eid) - entity = cls[eid] - return entity + return cls[eid] def get_entity_id(cls, eid): @@ -107,12 +106,12 @@ def get_entity_id(cls, eid): raise GenericError("Invalid ID") try: return int(eid) - except ValueError: - raise GenericError("Invalid ID") + except ValueError as e: + raise GenericError("Invalid ID") from e try: return uuid.UUID(eid) - except (AttributeError, ValueError): - raise GenericError("Invalid ID") + except (AttributeError, ValueError) as e: + raise GenericError("Invalid ID") from e def get_root_folder(id): @@ -121,14 +120,13 @@ def get_root_folder(id): try: fid = int(id) - except ValueError: - raise ValueError("Invalid folder ID") + except ValueError as e: + raise ValueError("Invalid folder ID") from e - folder = Folder.get(id=fid, root=True) - if folder is None: - raise NotFound("Folder") - - return folder + try: + return Folder.get(id=fid, root=True) + except Folder.DoesNotExist as e: + raise NotFound("Folder") from e from .errors import * diff --git a/supysonic/api/browse.py b/supysonic/api/browse.py index f1c11c7..2bd13a0 100644 --- a/supysonic/api/browse.py +++ b/supysonic/api/browse.py @@ -9,6 +9,7 @@ import re import string from flask import current_app, request +from peewee import fn from ..db import Folder, Artist, Album, Track @@ -22,7 +23,7 @@ def list_folders(): { "musicFolder": [ {"id": str(f.id), "name": f.name} - for f in Folder.select(lambda f: f.root).order_by(Folder.name) + for f in Folder.select().where(Folder.root).order_by(Folder.name) ] }, ) @@ -77,7 +78,7 @@ def list_indexes(): ifModifiedSince = int(ifModifiedSince) / 1000 if musicFolderId is None: - folders = Folder.select(lambda f: f.root)[:] + folders = Folder.select().where(Folder.root)[:] else: folders = [get_root_folder(musicFolderId)] @@ -95,8 +96,8 @@ def list_indexes(): artists = [] children = [] for f in folders: - artists += f.children.select()[:] - children += f.tracks.select()[:] + artists += f.children[:] + children += f.tracks[:] indexes = build_indexes(artists) return request.formatter( @@ -137,9 +138,11 @@ def list_genres(): { "genre": [ {"value": genre, "songCount": sc, "albumCount": ac} - for genre, sc, ac in select( - (t.genre, count(), count(t.album)) for t in Track if t.genre + for genre, sc, ac in Track.select( + Track.genre, fn.count(), fn.count(Track.album.distinct()) ) + .group_by(Track.genre) + .tuples() ] }, ) @@ -152,7 +155,7 @@ def list_artists(): query = Artist.select() if mfid is not None: folder = get_root_folder(mfid) - query = Artist.select(lambda a: folder in a.tracks.root_folder) + query = Artist.select().join(Track).where(Track.root_folder == folder) indexes = build_indexes(query) return request.formatter( diff --git a/supysonic/db.py b/supysonic/db.py index e23f858..6a8d9e6 100644 --- a/supysonic/db.py +++ b/supysonic/db.py @@ -150,7 +150,7 @@ class Folder(PathMixin, db.Model): "name": self.name, "child": [ f.as_subsonic_child(user) - for f in self.children.order_by(lambda c: c.name.lower()) + for f in self.children.order_by(fn.lower(Folder.name)) ] + [ t.as_subsonic_child(user, client) diff --git a/tests/api/test_browse.py b/tests/api/test_browse.py index bacb5bb..73579e3 100644 --- a/tests/api/test_browse.py +++ b/tests/api/test_browse.py @@ -9,8 +9,6 @@ import time import unittest import uuid -from pony.orm import db_session - from supysonic.db import Folder, Artist, Album, Track from .apitestbase import ApiTestBase @@ -20,51 +18,52 @@ class BrowseTestCase(ApiTestBase): def setUp(self): super().setUp() - with db_session: - self.empty_root = Folder(root=True, name="Empty root", path="/tmp") - self.root = Folder(root=True, name="Root folder", path="tests/assets") + self.empty_root = Folder.create(root=True, name="Empty root", path="/tmp") + self.root = Folder.create(root=True, name="Root folder", path="tests/assets") - for letter in "ABC": - folder = Folder( - name=letter + "rtist", - path="tests/assets/{}rtist".format(letter), - parent=self.root, + for letter in "ABC": + folder = Folder.create( + name=letter + "rtist", + path="tests/assets/{}rtist".format(letter), + root=False, + parent=self.root, + ) + + artist = Artist.create(name=letter + "rtist") + + for lether in "AB": + afolder = Folder.create( + name=letter + lether + "lbum", + path="tests/assets/{0}rtist/{0}{1}lbum".format(letter, lether), + root=False, + parent=folder, ) - artist = Artist(name=letter + "rtist") + album = Album.create(name=letter + lether + "lbum", artist=artist) - for lether in "AB": - afolder = Folder( - name=letter + lether + "lbum", - path="tests/assets/{0}rtist/{0}{1}lbum".format(letter, lether), - parent=folder, + for num, song in enumerate(["One", "Two", "Three"]): + Track.create( + disc=1, + number=num, + title=song, + duration=2, + album=album, + artist=artist, + genre="Music!", + bitrate=320, + path="tests/assets/{0}rtist/{0}{1}lbum/{2}".format( + letter, lether, song + ), + last_modification=0, + root_folder=self.root, + folder=afolder, ) - album = Album(name=letter + lether + "lbum", artist=artist) - - for num, song in enumerate(["One", "Two", "Three"]): - Track( - disc=1, - number=num, - title=song, - duration=2, - album=album, - artist=artist, - genre="Music!", - bitrate=320, - path="tests/assets/{0}rtist/{0}{1}lbum/{2}".format( - letter, lether, song - ), - last_modification=0, - root_folder=self.root, - folder=afolder, - ) - - self.assertEqual(Folder.select().count(), 11) - self.assertEqual(Folder.select(lambda f: f.root).count(), 2) - self.assertEqual(Artist.select().count(), 3) - self.assertEqual(Album.select().count(), 6) - self.assertEqual(Track.select().count(), 18) + self.assertEqual(Folder.select().count(), 11) + self.assertEqual(Folder.select().where(Folder.root).count(), 2) + self.assertEqual(Artist.select().count(), 3) + self.assertEqual(Album.select().count(), 6) + self.assertEqual(Track.select().count(), 18) def test_get_music_folders(self): rv, child = self._make_request("getMusicFolders", tag="musicFolders") @@ -86,8 +85,7 @@ class BrowseTestCase(ApiTestBase): ) self.assertEqual(len(child), 0) - with db_session: - fid = Folder.get(name="Empty root").id + fid = Folder.get(name="Empty root").id rv, child = self._make_request( "getIndexes", {"musicFolderId": str(fid)}, tag="indexes" ) @@ -106,27 +104,26 @@ class BrowseTestCase(ApiTestBase): self._make_request("getMusicDirectory", {"id": 1234567890}, error=70) # should test with folders with both children folders and tracks. this code would break in that case - with db_session: - for f in Folder.select(): - rv, child = self._make_request( - "getMusicDirectory", {"id": str(f.id)}, tag="directory" - ) - self.assertEqual(child.get("id"), str(f.id)) - self.assertEqual(child.get("name"), f.name) - self.assertEqual(len(child), f.children.count() + f.tracks.count()) - for dbc, xmlc in zip( - sorted(f.children, key=lambda c: c.name), - sorted(child, key=lambda c: c.get("title")), - ): - self.assertEqual(dbc.name, xmlc.get("title")) - self.assertEqual(xmlc.get("artist"), f.name) - self.assertEqual(xmlc.get("parent"), str(f.id)) - for t, xmlc in zip( - sorted(f.tracks, key=lambda t: t.title), - sorted(child, key=lambda c: c.get("title")), - ): - self.assertEqual(t.title, xmlc.get("title")) - self.assertEqual(xmlc.get("parent"), str(f.id)) + for f in Folder.select(): + rv, child = self._make_request( + "getMusicDirectory", {"id": str(f.id)}, tag="directory" + ) + self.assertEqual(child.get("id"), str(f.id)) + self.assertEqual(child.get("name"), f.name) + self.assertEqual(len(child), f.children.count() + f.tracks.count()) + for dbc, xmlc in zip( + sorted(f.children, key=lambda c: c.name), + sorted(child, key=lambda c: c.get("title")), + ): + self.assertEqual(dbc.name, xmlc.get("title")) + self.assertEqual(xmlc.get("artist"), f.name) + self.assertEqual(xmlc.get("parent"), str(f.id)) + for t, xmlc in zip( + sorted(f.tracks, key=lambda t: t.title), + sorted(child, key=lambda c: c.get("title")), + ): + self.assertEqual(t.title, xmlc.get("title")) + self.assertEqual(xmlc.get("parent"), str(f.id)) def test_get_artists(self): # same as getIndexes standard case @@ -158,51 +155,48 @@ class BrowseTestCase(ApiTestBase): self._make_request("getArtist", {"id": "artist"}, error=0) self._make_request("getArtist", {"id": str(uuid.uuid4())}, error=70) - with db_session: - for ar in Artist.select(): - rv, child = self._make_request( - "getArtist", {"id": str(ar.id)}, tag="artist" - ) - self.assertEqual(child.get("id"), str(ar.id)) - self.assertEqual(child.get("albumCount"), str(len(child))) - self.assertEqual(len(child), ar.albums.count()) - for dal, xal in zip( - sorted(ar.albums, key=lambda a: a.name), - sorted(child, key=lambda c: c.get("name")), - ): - self.assertEqual(dal.name, xal.get("name")) - self.assertEqual( - xal.get("artist"), ar.name - ) # could break with a better dataset - self.assertEqual(xal.get("artistId"), str(ar.id)) # see above + for ar in Artist.select(): + rv, child = self._make_request( + "getArtist", {"id": str(ar.id)}, tag="artist" + ) + self.assertEqual(child.get("id"), str(ar.id)) + self.assertEqual(child.get("albumCount"), str(len(child))) + self.assertEqual(len(child), ar.albums.count()) + for dal, xal in zip( + sorted(ar.albums, key=lambda a: a.name), + sorted(child, key=lambda c: c.get("name")), + ): + self.assertEqual(dal.name, xal.get("name")) + self.assertEqual( + xal.get("artist"), ar.name + ) # could break with a better dataset + self.assertEqual(xal.get("artistId"), str(ar.id)) # see above def test_get_album(self): self._make_request("getAlbum", error=10) self._make_request("getAlbum", {"id": "nastynasty"}, error=0) self._make_request("getAlbum", {"id": str(uuid.uuid4())}, error=70) - with db_session: - a = Album.select().first() - rv, child = self._make_request("getAlbum", {"id": str(a.id)}, tag="album") - self.assertEqual(child.get("id"), str(a.id)) - self.assertEqual(child.get("songCount"), str(len(child))) + a = Album.select().first() + rv, child = self._make_request("getAlbum", {"id": str(a.id)}, tag="album") + self.assertEqual(child.get("id"), str(a.id)) + self.assertEqual(child.get("songCount"), str(len(child))) - self.assertEqual(len(child), a.tracks.count()) - for dal, xal in zip( - sorted(a.tracks, key=lambda t: t.title), - sorted(child, key=lambda c: c.get("title")), - ): - self.assertEqual(dal.title, xal.get("title")) - self.assertEqual(xal.get("album"), a.name) - self.assertEqual(xal.get("albumId"), str(a.id)) + self.assertEqual(len(child), a.tracks.count()) + for dal, xal in zip( + sorted(a.tracks, key=lambda t: t.title), + sorted(child, key=lambda c: c.get("title")), + ): + self.assertEqual(dal.title, xal.get("title")) + self.assertEqual(xal.get("album"), a.name) + self.assertEqual(xal.get("albumId"), str(a.id)) def test_get_song(self): self._make_request("getSong", error=10) self._make_request("getSong", {"id": "nastynasty"}, error=0) self._make_request("getSong", {"id": str(uuid.uuid4())}, error=70) - with db_session: - s = Track.select().first() + s = Track.select().first() self._make_request("getSong", {"id": str(s.id)}, tag="song") def test_get_videos(self):