diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py index 3794c26..ddbdf9e 100644 --- a/supysonic/api/__init__.py +++ b/supysonic/api/__init__.py @@ -36,7 +36,7 @@ def api_routing(endpoint): @api.before_request def set_formatter(): """Return a function to create the response.""" - f, callback = map(request.values.get, ["f", "callback"]) + f, callback = map(request.values.get, ("f", "callback")) if f == "jsonp": request.formatter = JSONPFormatter(callback) elif f == "json": diff --git a/supysonic/api/albums_songs.py b/supysonic/api/albums_songs.py index 2fc5e58..f71295b 100644 --- a/supysonic/api/albums_songs.py +++ b/supysonic/api/albums_songs.py @@ -29,7 +29,7 @@ from .exceptions import GenericError, NotFound def rand_songs(): size = request.values.get("size", "10") genre, fromYear, toYear, musicFolderId = map( - request.values.get, ["genre", "fromYear", "toYear", "musicFolderId"] + request.values.get, ("genre", "fromYear", "toYear", "musicFolderId") ) size = int(size) if size else 10 @@ -57,12 +57,12 @@ def rand_songs(): return request.formatter( "randomSongs", - dict( - song=[ + { + "song": [ t.as_subsonic_child(request.user, request.client) for t in query.without_distinct().random(size) ] - ), + }, ) @@ -70,7 +70,7 @@ def rand_songs(): def album_list(): ltype = request.values["type"] - size, offset = map(request.values.get, ["size", "offset"]) + size, offset = map(request.values.get, ("size", "offset")) size = int(size) if size else 10 offset = int(offset) if offset else 0 @@ -78,12 +78,12 @@ def album_list(): if ltype == "random": return request.formatter( "albumList", - dict( - album=[ + { + "album": [ a.as_subsonic_child(request.user) for a in distinct(query.random(size)) ] - ), + }, ) elif ltype == "newest": query = query.sort_by(desc(Folder.created)).distinct() @@ -123,9 +123,11 @@ def album_list(): return request.formatter( "albumList", - dict( - album=[f.as_subsonic_child(request.user) for f in query.limit(size, offset)] - ), + { + "album": [ + f.as_subsonic_child(request.user) for f in query.limit(size, offset) + ] + }, ) @@ -133,7 +135,7 @@ def album_list(): def album_list_id3(): ltype = request.values["type"] - size, offset = map(request.values.get, ["size", "offset"]) + size, offset = map(request.values.get, ("size", "offset")) size = int(size) if size else 10 offset = int(offset) if offset else 0 @@ -141,7 +143,7 @@ def album_list_id3(): if ltype == "random": return request.formatter( "albumList2", - dict(album=[a.as_subsonic_album(request.user) for a in query.random(size)]), + {"album": [a.as_subsonic_album(request.user) for a in query.random(size)]}, ) elif ltype == "newest": query = query.order_by(lambda a: desc(min(a.tracks.created))) @@ -177,9 +179,11 @@ def album_list_id3(): return request.formatter( "albumList2", - dict( - album=[f.as_subsonic_album(request.user) for f in query.limit(size, offset)] - ), + { + "album": [ + f.as_subsonic_album(request.user) for f in query.limit(size, offset) + ] + }, ) @@ -187,14 +191,14 @@ def album_list_id3(): def songs_by_genre(): genre = request.values["genre"] - count, offset = map(request.values.get, ["count", "offset"]) + count, offset = map(request.values.get, ("count", "offset")) count = int(count) if count else 10 offset = int(offset) if offset else 0 query = select(t for t in Track if t.genre == genre).limit(count, offset) return request.formatter( "songsByGenre", - dict(song=[t.as_subsonic_child(request.user, request.client) for t in query]), + {"song": [t.as_subsonic_child(request.user, request.client) for t in query]}, ) @@ -207,17 +211,17 @@ def now_playing(): return request.formatter( "nowPlaying", - dict( - entry=[ - dict( - u.last_play.as_subsonic_child(request.user, request.client), - username=u.name, - minutesAgo=(now() - u.last_play_date).seconds / 60, - playerId=0, - ) + { + "entry": [ + { + **u.last_play.as_subsonic_child(request.user, request.client), + "username": u.name, + "minutesAgo": (now() - u.last_play_date).seconds / 60, + "playerId": 0, + } for u in query ] - ), + }, ) @@ -227,22 +231,22 @@ def get_starred(): return request.formatter( "starred", - dict( - artist=[ + { + "artist": [ sf.as_subsonic_artist(request.user) for sf in folders.filter(lambda f: count(f.tracks) == 0) ], - album=[ + "album": [ sf.as_subsonic_child(request.user) for sf in folders.filter(lambda f: count(f.tracks) > 0) ], - song=[ + "song": [ st.as_subsonic_child(request.user, request.client) for st in select( s.starred for s in StarredTrack if s.user.id == request.user.id ) ], - ), + }, ) @@ -250,24 +254,24 @@ def get_starred(): def get_starred_id3(): return request.formatter( "starred2", - dict( - artist=[ + { + "artist": [ sa.as_subsonic_artist(request.user) for sa in select( s.starred for s in StarredArtist if s.user.id == request.user.id ) ], - album=[ + "album": [ sa.as_subsonic_album(request.user) for sa in select( s.starred for s in StarredAlbum if s.user.id == request.user.id ) ], - song=[ + "song": [ st.as_subsonic_child(request.user, request.client) for st in select( s.starred for s in StarredTrack if s.user.id == request.user.id ) ], - ), + }, ) diff --git a/supysonic/api/annotation.py b/supysonic/api/annotation.py index 41f1549..4b90533 100644 --- a/supysonic/api/annotation.py +++ b/supysonic/api/annotation.py @@ -54,7 +54,7 @@ def unstar_single(cls, starcls, eid): def handle_star_request(func): - id, albumId, artistId = map(request.values.getlist, ["id", "albumId", "artistId"]) + id, albumId, artistId = map(request.values.getlist, ("id", "albumId", "artistId")) if not id and not albumId and not artistId: raise MissingParameter("id, albumId or artistId") @@ -174,7 +174,7 @@ def rate(): @api_routing("/scrobble") def scrobble(): res = get_entity(Track) - t, submission = map(request.values.get, ["time", "submission"]) + t, submission = map(request.values.get, ("time", "submission")) t = int(t) / 1000 if t else int(time.time()) lfm = LastFm(current_app.config["LASTFM"], request.user) diff --git a/supysonic/api/browse.py b/supysonic/api/browse.py index 6e83c27..af3b520 100644 --- a/supysonic/api/browse.py +++ b/supysonic/api/browse.py @@ -20,12 +20,12 @@ from . import get_entity, get_entity_id, api_routing def list_folders(): return request.formatter( "musicFolders", - dict( - musicFolder=[ - dict(id=str(f.id), name=f.name) + { + "musicFolder": [ + {"id": str(f.id), "name": f.name} for f in Folder.select(lambda f: f.root).order_by(Folder.name) ] - ), + }, ) @@ -66,13 +66,14 @@ def list_indexes(): folders = [folder] - last_modif = max(map(lambda f: f.last_scan, folders)) + last_modif = max(f.last_scan for f in folders) if ifModifiedSince is not None and last_modif < ifModifiedSince: return request.formatter( "indexes", - dict( - lastModified=last_modif * 1000, ignoredArticles=ignored_articles_str() - ), + { + "lastModified": last_modif * 1000, + "ignoredArticles": ignored_articles_str(), + }, ) # The XSD lies, we don't return artists but a directory structure @@ -82,7 +83,7 @@ def list_indexes(): artists += f.children.select()[:] children += f.tracks.select()[:] - indexes = dict() + indexes = {} pattern = build_ignored_articles_pattern() for artist in artists: name = artist.name @@ -101,24 +102,24 @@ def list_indexes(): return request.formatter( "indexes", - dict( - lastModified=last_modif * 1000, - ignoredArticles=ignored_articles_str(), - index=[ - dict( - name=k, - artist=[ + { + "lastModified": last_modif * 1000, + "ignoredArticles": ignored_articles_str(), + "index": [ + { + "name": k, + "artist": [ a.as_subsonic_artist(request.user) for a, _ in sorted(v, key=lambda t: t[1].lower()) ], - ) + } for k, v in sorted(indexes.items()) ], - child=[ + "child": [ c.as_subsonic_child(request.user, request.client) for c in sorted(children, key=lambda t: t.sort_key()) ], - ), + }, ) @@ -134,21 +135,21 @@ def show_directory(): def list_genres(): return request.formatter( "genres", - dict( - genre=[ - dict(value=genre, songCount=sc, albumCount=ac) + { + "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 ) ] - ), + }, ) @api_routing("/getArtists") def list_artists(): # According to the API page, there are no parameters? - indexes = dict() + indexes = {} pattern = build_ignored_articles_pattern() for artist in Artist.select(): name = artist.name or "?" @@ -167,19 +168,19 @@ def list_artists(): return request.formatter( "artists", - dict( - ignoredArticles=ignored_articles_str(), - index=[ - dict( - name=k, - artist=[ + { + "ignoredArticles": ignored_articles_str(), + "index": [ + { + "name": k, + "artist": [ a.as_subsonic_artist(request.user) for a, _ in sorted(v, key=lambda t: t[1].lower()) ], - ) + } for k, v in sorted(indexes.items()) ], - ), + }, ) diff --git a/supysonic/api/chat.py b/supysonic/api/chat.py index dbe5e24..b5a2be9 100644 --- a/supysonic/api/chat.py +++ b/supysonic/api/chat.py @@ -21,7 +21,7 @@ def get_chat(): query = query.filter(lambda m: m.time > since) return request.formatter( - "chatMessages", dict(chatMessage=[msg.responsize() for msg in query]) + "chatMessages", {"chatMessage": [msg.responsize() for msg in query]} ) diff --git a/supysonic/api/exceptions.py b/supysonic/api/exceptions.py index 6a1a80e..48e7a42 100644 --- a/supysonic/api/exceptions.py +++ b/supysonic/api/exceptions.py @@ -114,11 +114,12 @@ class AggregateException(SubsonicAPIException): codes = {exc.api_code for exc in self.exceptions} errors = [ - dict(code=exc.api_code, message=exc.message) for exc in self.exceptions + {"code": exc.api_code, "message": exc.message} for exc in self.exceptions ] rv = request.formatter( - "error", dict(code=list(codes)[0] if len(codes) == 1 else 0, error=errors) + "error", + {"code": next(iter(codes)) if len(codes) == 1 else 0, "error": errors}, ) # rv.status_code = self.code return rv diff --git a/supysonic/api/formatters.py b/supysonic/api/formatters.py index 2321b96..c9f6375 100644 --- a/supysonic/api/formatters.py +++ b/supysonic/api/formatters.py @@ -16,7 +16,7 @@ class BaseFormatter: raise NotImplementedError() def make_error(self, code, message): - return self.make_response("error", dict(code=code, message=message)) + return self.make_response("error", {"code": code, "message": message}) def make_empty(self): return self.make_response(None, None) @@ -78,7 +78,7 @@ class JSONPFormatter(JSONBaseFormatter): def make_response(self, elem, data): if not self.__callback: return jsonify( - self._subsonicify("error", dict(code=10, message="Missing callback")) + self._subsonicify("error", {"code": 10, "message": "Missing callback"}) ) rv = self._subsonicify(elem, data) @@ -100,7 +100,7 @@ class XMLFormatter(BaseFormatter): """ if not isinstance(dictionary, dict): raise TypeError("Expecting a dict") - if not all(map(lambda x: isinstance(x, str), dictionary)): + if not all(isinstance(x, str) for x in dictionary): raise TypeError("Dictionary keys must be strings") for name, value in dictionary.items(): diff --git a/supysonic/api/jukebox.py b/supysonic/api/jukebox.py index 28a2255..4dd9972 100644 --- a/supysonic/api/jukebox.py +++ b/supysonic/api/jukebox.py @@ -79,12 +79,12 @@ def jukebox_control(): except DaemonUnavailableError: raise GenericError("Jukebox unavaliable") - rv = dict( - currentIndex=status.index, - playing=status.playing, - gain=status.gain, - position=status.position, - ) + rv = { + "currentIndex": status.index, + "playing": status.playing, + "gain": status.gain, + "position": status.position, + } if action == "get": playlist = [] for path in status.playlist: diff --git a/supysonic/api/media.py b/supysonic/api/media.py index 18622a0..471cb43 100644 --- a/supysonic/api/media.py +++ b/supysonic/api/media.py @@ -73,7 +73,7 @@ def stream_media(): raise UnsupportedParameter("size") maxBitRate, request_format, estimateContentLength = map( - request.values.get, ["maxBitRate", "format", "estimateContentLength"] + request.values.get, ("maxBitRate", "format", "estimateContentLength") ) if request_format: request_format = request_format.lower() @@ -378,7 +378,7 @@ def cover_art(): def lyrics_response_for_track(track, lyrics): return request.formatter( "lyrics", - dict(artist=track.album.artist.name, title=track.title, value=lyrics), + {"artist": track.album.artist.name, "title": track.title, "value": lyrics}, ) @@ -419,7 +419,7 @@ def lyrics(): ).hexdigest() cache_key = "lyrics-{}".format(unique) - lyrics = dict() + lyrics = {} try: lyrics = json.loads( zlib.decompress(current_app.cache.get_value(cache_key)).decode("utf-8") @@ -434,11 +434,11 @@ def lyrics(): root = ElementTree.fromstring(r.content) ns = {"cl": "http://api.chartlyrics.com/"} - lyrics = dict( - artist=root.find("cl:LyricArtist", namespaces=ns).text, - title=root.find("cl:LyricSong", namespaces=ns).text, - value=root.find("cl:Lyric", namespaces=ns).text, - ) + lyrics = { + "artist": root.find("cl:LyricArtist", namespaces=ns).text, + "title": root.find("cl:LyricSong", namespaces=ns).text, + "value": root.find("cl:Lyric", namespaces=ns).text, + } current_app.cache.set( cache_key, zlib.compress(json.dumps(lyrics).encode("utf-8"), 9) diff --git a/supysonic/api/playlists.py b/supysonic/api/playlists.py index 174a785..6ae9079 100644 --- a/supysonic/api/playlists.py +++ b/supysonic/api/playlists.py @@ -36,7 +36,7 @@ def list_playlists(): return request.formatter( "playlists", - dict(playlist=[p.as_subsonic_playlist(request.user) for p in query]), + {"playlist": [p.as_subsonic_playlist(request.user) for p in query]}, ) @@ -55,7 +55,7 @@ def show_playlist(): @api_routing("/createPlaylist") def create_playlist(): - playlist_id, name = map(request.values.get, ["playlistId", "name"]) + playlist_id, name = map(request.values.get, ("playlistId", "name")) # songId actually doesn't seem to be required songs = request.values.getlist("songId") playlist_id = uuid.UUID(playlist_id) if playlist_id else None @@ -99,9 +99,9 @@ def update_playlist(): raise Forbidden() playlist = res - name, comment, public = map(request.values.get, ["name", "comment", "public"]) + name, comment, public = map(request.values.get, ("name", "comment", "public")) to_add, to_remove = map( - request.values.getlist, ["songIdToAdd", "songIndexToRemove"] + request.values.getlist, ("songIdToAdd", "songIndexToRemove") ) if name: diff --git a/supysonic/api/radio.py b/supysonic/api/radio.py index 8b5aca6..d4f2e46 100644 --- a/supysonic/api/radio.py +++ b/supysonic/api/radio.py @@ -18,7 +18,7 @@ def get_radio_stations(): query = RadioStation.select().sort_by(RadioStation.name) return request.formatter( "internetRadioStations", - dict(internetRadioStation=[p.as_subsonic_station() for p in query]), + {"internetRadioStation": [p.as_subsonic_station() for p in query]}, ) @@ -28,7 +28,7 @@ def create_radio_station(): raise Forbidden() stream_url, name, homepage_url = map( - request.values.get, ["streamUrl", "name", "homepageUrl"] + request.values.get, ("streamUrl", "name", "homepageUrl") ) if stream_url and name: @@ -47,7 +47,7 @@ def update_radio_station(): res = get_entity(RadioStation) stream_url, name, homepage_url = map( - request.values.get, ["streamUrl", "name", "homepageUrl"] + request.values.get, ("streamUrl", "name", "homepageUrl") ) if stream_url and name: res.stream_url = stream_url diff --git a/supysonic/api/scan.py b/supysonic/api/scan.py index 3e5ae95..9dc2009 100644 --- a/supysonic/api/scan.py +++ b/supysonic/api/scan.py @@ -28,10 +28,10 @@ def startScan(): raise ServerError(str(e)) return request.formatter( "scanStatus", - dict( - scanning="true" if scanned is not None else "false", - count=scanned if scanned is not None else 0, - ), + { + "scanning": scanned is not None, + "count": scanned or 0, + }, ) @@ -46,8 +46,8 @@ def getScanStatus(): raise ServerError(str(e)) return request.formatter( "scanStatus", - dict( - scanning="true" if scanned is not None else "false", - count=scanned if scanned is not None else 0, - ), + { + "scanning": scanned is not None, + "count": scanned or 0, + }, ) diff --git a/supysonic/api/search.py b/supysonic/api/search.py index 18a0444..4091e75 100644 --- a/supysonic/api/search.py +++ b/supysonic/api/search.py @@ -20,7 +20,7 @@ from .exceptions import MissingParameter def old_search(): artist, album, title, anyf, count, offset, newer_than = map( request.values.get, - ["artist", "album", "title", "any", "count", "offset", "newerThan"], + ("artist", "album", "title", "any", "count", "offset", "newerThan"), ) count = int(count) if count else 20 @@ -54,32 +54,32 @@ def old_search(): return request.formatter( "searchResult", - dict( - totalHits=folders.count() + tracks.count(), - offset=offset, - match=[ + { + "totalHits": folders.count() + tracks.count(), + "offset": offset, + "match": [ r.as_subsonic_child(request.user) if isinstance(r, Folder) else r.as_subsonic_child(request.user, request.client) for r in res ], - ), + }, ) else: raise MissingParameter("search") return request.formatter( "searchResult", - dict( - totalHits=query.count(), - offset=offset, - match=[ + { + "totalHits": query.count(), + "offset": offset, + "match": [ r.as_subsonic_child(request.user) if isinstance(r, Folder) else r.as_subsonic_child(request.user, request.client) for r in query[offset : offset + count] ], - ), + }, ) @@ -95,14 +95,14 @@ def new_search(): song_offset, ) = map( request.values.get, - [ + ( "artistCount", "artistOffset", "albumCount", "albumOffset", "songCount", "songOffset", - ], + ), ) artist_count = int(artist_count) if artist_count else 20 @@ -147,14 +147,14 @@ def search_id3(): song_offset, ) = map( request.values.get, - [ + ( "artistCount", "artistOffset", "albumCount", "albumOffset", "songCount", "songOffset", - ], + ), ) artist_count = int(artist_count) if artist_count else 20 diff --git a/supysonic/api/system.py b/supysonic/api/system.py index 8bea25d..341a43c 100644 --- a/supysonic/api/system.py +++ b/supysonic/api/system.py @@ -18,4 +18,4 @@ def ping(): @api_routing("/getLicense") def license(): - return request.formatter("license", dict(valid=True)) + return request.formatter("license", {"valid": True}) diff --git a/supysonic/api/user.py b/supysonic/api/user.py index ace7c57..df51e88 100644 --- a/supysonic/api/user.py +++ b/supysonic/api/user.py @@ -43,7 +43,7 @@ def user_info(): @admin_only def users_info(): return request.formatter( - "users", dict(user=[u.as_subsonic_user() for u in User.select()]) + "users", {"user": [u.as_subsonic_user() for u in User.select()]} ) @@ -107,7 +107,7 @@ def user_edit(): UserManager.change_password2(user, password) email, admin, jukebox = map( - request.values.get, ["email", "adminRole", "jukeboxRole"] + request.values.get, ("email", "adminRole", "jukeboxRole") ) if email is not None: user.mail = email diff --git a/supysonic/db.py b/supysonic/db.py index 3e531bc..9c8a4fb 100644 --- a/supysonic/db.py +++ b/supysonic/db.py @@ -92,13 +92,13 @@ class Folder(PathMixin, db.Entity): ratings = Set(lambda: RatingFolder) def as_subsonic_child(self, user): - info = dict( - id=str(self.id), - isDir=True, - title=self.name, - album=self.name, - created=self.created.isoformat(), - ) + info = { + "id": str(self.id), + "isDir": True, + "title": self.name, + "album": self.name, + "created": self.created.isoformat(), + } if not self.root: info["parent"] = str(self.parent.id) info["artist"] = self.parent.name @@ -129,7 +129,7 @@ class Folder(PathMixin, db.Entity): return info def as_subsonic_artist(self, user): # "Artist" type in XSD - info = dict(id=str(self.id), name=self.name) + info = {"id": str(self.id), "name": self.name} try: starred = StarredFolder[user.id, self.id] @@ -140,10 +140,10 @@ class Folder(PathMixin, db.Entity): return info def as_subsonic_directory(self, user, client): # "Directory" type in XSD - info = dict( - id=str(self.id), - name=self.name, - child=[ + info = { + "id": str(self.id), + "name": self.name, + "child": [ f.as_subsonic_child(user) for f in self.children.order_by(lambda c: c.name.lower()) ] @@ -151,7 +151,7 @@ class Folder(PathMixin, db.Entity): t.as_subsonic_child(user, client) for t in sorted(self.tracks, key=lambda t: t.sort_key()) ], - ) + } if not self.root: info["parent"] = str(self.parent.id) @@ -183,12 +183,12 @@ class Artist(db.Entity): stars = Set(lambda: StarredArtist) def as_subsonic_artist(self, user): - info = dict( - id=str(self.id), - name=self.name, + info = { + "id": str(self.id), + "name": self.name, # coverArt - albumCount=self.albums.count(), - ) + "albumCount": self.albums.count(), + } try: starred = StarredArtist[user.id, self.id] @@ -217,15 +217,15 @@ class Album(db.Entity): stars = Set(lambda: StarredAlbum) def as_subsonic_album(self, user): # "AlbumID3" type in XSD - info = dict( - id=str(self.id), - name=self.name, - artist=self.artist.name, - artistId=str(self.artist.id), - songCount=self.tracks.count(), - duration=sum(self.tracks.duration), - created=min(self.tracks.created).isoformat(), - ) + info = { + "id": str(self.id), + "name": self.name, + "artist": self.artist.name, + "artistId": str(self.artist.id), + "songCount": self.tracks.count(), + "duration": sum(self.tracks.duration), + "created": min(self.tracks.created).isoformat(), + } track_with_cover = self.tracks.select( lambda t: t.folder.cover_art is not None @@ -253,8 +253,8 @@ class Album(db.Entity): return info def sort_key(self): - year = min(map(lambda t: t.year if t.year else 9999, self.tracks)) - return "%i%s" % (year, self.name.lower()) + year = min(t.year if t.year else 9999 for t in self.tracks) + return f"{year}{self.name.lower()}" @classmethod def prune(cls): @@ -297,27 +297,27 @@ class Track(PathMixin, db.Entity): ratings = Set(lambda: RatingTrack) def as_subsonic_child(self, user, prefs): - info = dict( - id=str(self.id), - parent=str(self.folder.id), - isDir=False, - title=self.title, - album=self.album.name, - artist=self.artist.name, - track=self.number, - size=os.path.getsize(self.path) if os.path.isfile(self.path) else -1, - contentType=self.mimetype, - suffix=self.suffix(), - duration=self.duration, - bitRate=self.bitrate, - path=self.path[len(self.root_folder.path) + 1 :], - isVideo=False, - discNumber=self.disc, - created=self.created.isoformat(), - albumId=str(self.album.id), - artistId=str(self.artist.id), - type="music", - ) + info = { + "id": str(self.id), + "parent": str(self.folder.id), + "isDir": False, + "title": self.title, + "album": self.album.name, + "artist": self.artist.name, + "track": self.number, + "size": os.path.getsize(self.path) if os.path.isfile(self.path) else -1, + "contentType": self.mimetype, + "suffix": self.suffix(), + "duration": self.duration, + "bitRate": self.bitrate, + "path": self.path[len(self.root_folder.path) + 1 :], + "isVideo": False, + "discNumber": self.disc, + "created": self.created.isoformat(), + "albumId": str(self.album.id), + "artistId": str(self.artist.id), + "type": "music", + } if self.year: info["year"] = self.year @@ -362,22 +362,16 @@ class Track(PathMixin, db.Entity): return mimetypes.guess_type(self.path, False)[0] or "application/octet-stream" def duration_str(self): - ret = "%02i:%02i" % ((self.duration % 3600) / 60, self.duration % 60) + ret = "{:02}:{:02}".format((self.duration % 3600) / 60, self.duration % 60) if self.duration >= 3600: - ret = "%02i:%s" % (self.duration / 3600, ret) + ret = "{:02}:{}".format(self.duration / 3600, ret) return ret def suffix(self): return os.path.splitext(self.path)[1][1:].lower() def sort_key(self): - return ( - self.album.artist.name - + self.album.name - + ("%02i" % self.disc) - + ("%02i" % self.number) - + self.title - ).lower() + return f"{self.album.artist.name}{self.album.name}{self.disc:02}{self.number:02}{self.title}".lower() class User(db.Entity): @@ -412,22 +406,22 @@ class User(db.Entity): track_ratings = Set(lambda: RatingTrack, lazy=True) def as_subsonic_user(self): - return dict( - username=self.name, - email=self.mail, - scrobblingEnabled=self.lastfm_session is not None and self.lastfm_status, - adminRole=self.admin, - settingsRole=True, - downloadRole=True, - uploadRole=False, - playlistRole=True, - coverArtRole=False, - commentRole=False, - podcastRole=False, - streamRole=True, - jukeboxRole=self.admin or self.jukebox, - shareRole=False, - ) + return { + "username": self.name, + "email": self.mail, + "scrobblingEnabled": self.lastfm_session is not None and self.lastfm_status, + "adminRole": self.admin, + "settingsRole": True, + "downloadRole": True, + "uploadRole": False, + "playlistRole": True, + "coverArtRole": False, + "commentRole": False, + "podcastRole": False, + "streamRole": True, + "jukeboxRole": self.admin or self.jukebox, + "shareRole": False, + } class ClientPrefs(db.Entity): @@ -507,9 +501,11 @@ class ChatMessage(db.Entity): message = Required(str, 512) def responsize(self): - return dict( - username=self.user.name, time=self.time * 1000, message=self.message - ) + return { + "username": self.user.name, + "time": self.time * 1000, + "message": self.message, + } class Playlist(db.Entity): @@ -525,17 +521,17 @@ class Playlist(db.Entity): def as_subsonic_playlist(self, user): tracks = self.get_tracks() - info = dict( - id=str(self.id), - name=self.name + info = { + "id": str(self.id), + "name": self.name if self.user.id == user.id else "[{}] {}".format(self.user.name, self.name), - owner=self.user.name, - public=self.public, - songCount=len(tracks), - duration=sum(map(lambda t: t.duration, tracks)), - created=self.created.isoformat(), - ) + "owner": self.user.name, + "public": self.public, + "songCount": len(tracks), + "duration": sum(t.duration for t in tracks), + "created": self.created.isoformat(), + } if self.comment: info["comment"] = self.comment return info @@ -556,7 +552,7 @@ class Playlist(db.Entity): should_fix = True if should_fix: - self.tracks = ",".join(map(lambda t: str(t.id), tracks)) + self.tracks = ",".join(str(t.id) for t in tracks) db.commit() return tracks @@ -597,12 +593,12 @@ class RadioStation(db.Entity): created = Required(datetime, precision=0, default=now) def as_subsonic_station(self): - info = dict( - id=str(self.id), - streamUrl=self.stream_url, - name=self.name, - homePageUrl=self.homepage_url, - ) + info = { + "id": str(self.id), + "streamUrl": self.stream_url, + "name": self.name, + "homePageUrl": self.homepage_url, + } return info @@ -622,28 +618,28 @@ def parse_uri(database_uri): elif path[0] == "/": path = path[1:] - return dict(provider="sqlite", filename=path, create_db=True, **args) + return {"provider": "sqlite", "filename": path, "create_db": True, **args} elif uri.scheme in ("postgres", "postgresql"): - return dict( - provider="postgres", - user=uri.username, - password=uri.password, - host=uri.hostname, - dbname=uri.path[1:], - **args - ) + return { + "provider": "postgres", + "user": uri.username, + "password": uri.password, + "host": uri.hostname, + "dbname": uri.path[1:], + **args, + } elif uri.scheme == "mysql": args.setdefault("charset", "utf8mb4") args.setdefault("binary_prefix", True) - return dict( - provider="mysql", - user=uri.username, - passwd=uri.password, - host=uri.hostname, - db=uri.path[1:], - **args - ) - return dict() + return { + "provider": "mysql", + "user": uri.username, + "passwd": uri.password, + "host": uri.hostname, + "db": uri.path[1:], + **args, + } + return {} def execute_sql_resource_script(respath): diff --git a/supysonic/frontend/folder.py b/supysonic/frontend/folder.py index 1ab340e..3622a1f 100644 --- a/supysonic/frontend/folder.py +++ b/supysonic/frontend/folder.py @@ -43,7 +43,7 @@ def add_folder_form(): @admin_only def add_folder_post(): error = False - (name, path) = map(request.form.get, ["name", "path"]) + name, path = map(request.form.get, ("name", "path")) if name in (None, ""): flash("The name is required.") error = True @@ -59,7 +59,7 @@ def add_folder_post(): flash(str(e), "error") return render_template("addfolder.html") - flash("Folder '%s' created. You should now run a scan" % name) + flash(f"Folder '{name}' created. You should now run a scan") return redirect(url_for("frontend.folder_index")) diff --git a/supysonic/frontend/user.py b/supysonic/frontend/user.py index f215028..ce74408 100644 --- a/supysonic/frontend/user.py +++ b/supysonic/frontend/user.py @@ -73,7 +73,7 @@ def user_profile(uid, user): @frontend.route("/user/", methods=["POST"]) @me_or_uuid def update_clients(uid, user): - clients_opts = dict() + clients_opts = {} for key, value in request.form.items(): if "_" not in key: continue @@ -85,7 +85,7 @@ def update_clients(uid, user): continue if client not in clients_opts: - clients_opts[client] = dict([(opt, value)]) + clients_opts[client] = {opt: value} else: clients_opts[client][opt] = value logger.debug(clients_opts) @@ -157,9 +157,9 @@ def change_username_post(uid): if user.name != username or user.admin != admin: user.name = username user.admin = admin - flash("User '%s' updated." % username) + flash(f"User '{username}' updated.") else: - flash("No changes for '%s'." % username) + flash(f"No changes for '{username}'.") return redirect(url_for("frontend.user_profile", uid=uid)) @@ -195,7 +195,7 @@ def change_password_post(uid, user): flash("The current password is required") error = True - new, confirm = map(request.form.get, ["new", "confirm"]) + new, confirm = map(request.form.get, ("new", "confirm")) if not new: flash("The new password is required") @@ -231,7 +231,7 @@ def add_user_post(): error = False args = request.form.copy() (name, passwd, passwd_confirm) = map( - args.pop, ["user", "passwd", "passwd_confirm"], [None] * 3 + args.pop, ("user", "passwd", "passwd_confirm"), (None,) * 3 ) if not name: flash("The name is required.") @@ -246,7 +246,7 @@ def add_user_post(): if not error: try: UserManager.add(name, passwd, **args) - flash("User '%s' successfully added" % name) + flash(f"User '{name}' successfully added") return redirect(url_for("frontend.user_index")) except ValueError as e: flash(str(e), "error") @@ -302,7 +302,7 @@ def login(): if request.method == "GET": return render_template("login.html") - name, password = map(request.form.get, ["user", "password"]) + name, password = map(request.form.get, ("user", "password")) error = False if not name: flash("Missing user name") diff --git a/supysonic/lastfm.py b/supysonic/lastfm.py index acdf83b..d347a7e 100644 --- a/supysonic/lastfm.py +++ b/supysonic/lastfm.py @@ -30,7 +30,7 @@ class LastFm: if not res: return False, "Error connecting to LastFM" elif "error" in res: - return False, "Error %i: %s" % (res["error"], res["message"]) + return False, f"Error {res['error']}: {res['message']}" else: self.__user.lastfm_session = res["session"]["key"] self.__user.lastfm_status = True @@ -107,6 +107,6 @@ class LastFm: if "error" in json: if json["error"] in (9, "9"): self.__user.lastfm_status = False - logger.warning("LastFM error %i: %s" % (json["error"], json["message"])) + logger.warning("LastFM error %i: %s", json["error"], json["message"]) return json diff --git a/supysonic/scanner.py b/supysonic/scanner.py index 0630217..bde3192 100644 --- a/supysonic/scanner.py +++ b/supysonic/scanner.py @@ -400,9 +400,12 @@ class Scanner(Thread): created = datetime.fromtimestamp(os.path.getmtime(path)) children.append( - dict( - root=False, name=os.path.basename(path), path=path, created=created - ) + { + "root": False, + "name": os.path.basename(path), + "path": path, + "created": created, + } ) path = os.path.dirname(path) diff --git a/supysonic/watcher.py b/supysonic/watcher.py index 4259025..f55e461 100644 --- a/supysonic/watcher.py +++ b/supysonic/watcher.py @@ -31,9 +31,9 @@ class SupysonicWatcherEventHandler(PatternMatchingEventHandler): def __init__(self, extensions): patterns = None if extensions: - patterns = list(map(lambda e: "*." + e.lower(), extensions.split())) + list( - map(lambda e: "*" + e, covers.EXTENSIONS) - ) + patterns = ["*." + e.lower() for e in extensions.split()] + [ + "*" + e for e in covers.EXTENSIONS + ] super().__init__(patterns=patterns, ignore_directories=True) def dispatch(self, event): @@ -132,7 +132,7 @@ class ScannerProcessingQueue(Thread): self.__timeout = delay self.__cond = Condition() self.__timer = None - self.__queue = dict() + self.__queue = {} self.__running = True def run(self): diff --git a/tests/api/test_playlist.py b/tests/api/test_playlist.py index 0df4d1b..a7c2a71 100644 --- a/tests/api/test_playlist.py +++ b/tests/api/test_playlist.py @@ -172,7 +172,7 @@ class PlaylistTestCase(ApiTestBase): "createPlaylist", { "name": "songs", - "songId": list(map(lambda s: songs[s], ["Three", "One", "Two"])), + "songId": [songs[s] for s in ("Three", "One", "Two")], }, skip_post=True, )