diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py index 0a7923e..30393e2 100644 --- a/supysonic/api/__init__.py +++ b/supysonic/api/__init__.py @@ -28,6 +28,7 @@ from xml.dom import minidom from xml.etree import ElementTree from ..managers.user import UserManager +from ..py23 import strtype from builtins import dict @@ -191,7 +192,7 @@ class ResponseHelper: """ if not isinstance(dictionary, dict): raise TypeError('Expecting a dict') - if not all(map(lambda x: isinstance(x, basestring), dictionary)): + if not all(map(lambda x: isinstance(x, strtype), dictionary)): raise TypeError('Dictionary keys must be strings') for name, value in dictionary.items(): @@ -214,7 +215,7 @@ class ResponseHelper: def value_tostring(value): if value is None: return None - if isinstance(value, basestring): + if isinstance(value, strtype): return value if isinstance(value, bool): return str(value).lower() diff --git a/supysonic/db.py b/supysonic/db.py index 0ce5915..c723ee4 100644 --- a/supysonic/db.py +++ b/supysonic/db.py @@ -28,6 +28,8 @@ from pony.orm import ObjectNotFound from pony.orm import min, max, avg, sum from uuid import UUID, uuid4 +from .py23 import strtype + from builtins import dict try: from urllib.parse import urlparse @@ -420,7 +422,7 @@ class Playlist(db.Entity): tid = track elif isinstance(track, Track): tid = track.id - elif isinstance(track, basestring): + elif isinstance(track, strtype): tid = UUID(track) if self.tracks and len(self.tracks) > 0: @@ -438,7 +440,7 @@ class Playlist(db.Entity): self.tracks = ','.join(t for t in tracks if t) def parse_uri(database_uri): - if not isinstance(database_uri, basestring): + if not isinstance(database_uri, strtype): raise TypeError('Expecting a string') uri = urlparse(database_uri) diff --git a/supysonic/lastfm.py b/supysonic/lastfm.py index e09ae1b..e0918c4 100644 --- a/supysonic/lastfm.py +++ b/supysonic/lastfm.py @@ -20,6 +20,8 @@ import requests, hashlib +from .py23 import strtype + class LastFm: def __init__(self, config, user, logger): self.__api_key = config['api_key'] @@ -71,9 +73,9 @@ class LastFm: kwargs['api_key'] = self.__api_key - sig_str = '' + sig_str = b'' for k, v in sorted(kwargs.items()): - if type(v) is unicode: + if isinstance(v, strtype): sig_str += k + v.encode('utf-8') else: sig_str += k + str(v) diff --git a/supysonic/managers/folder.py b/supysonic/managers/folder.py index f5f04e6..d4027ba 100644 --- a/supysonic/managers/folder.py +++ b/supysonic/managers/folder.py @@ -3,7 +3,7 @@ # This file is part of Supysonic. # # Supysonic is a Python implementation of the Subsonic server API. -# Copyright (C) 2013-2017 Alban 'spl0k' Féron +# Copyright (C) 2013-2018 Alban 'spl0k' Féron # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -25,6 +25,7 @@ from pony.orm import db_session, select from pony.orm import ObjectNotFound from ..db import Folder, Artist, Album, Track, StarredFolder, RatingFolder +from ..py23 import strtype from ..scanner import Scanner class FolderManager: @@ -39,12 +40,12 @@ class FolderManager: @staticmethod @db_session def get(uid): - if isinstance(uid, basestring): + if isinstance(uid, strtype): try: uid = uuid.UUID(uid) except: return FolderManager.INVALID_ID, None - elif type(uid) is uuid.UUID: + elif isinstance(uid, uuid.UUID): pass else: return FolderManager.INVALID_ID, None @@ -61,7 +62,7 @@ class FolderManager: if Folder.get(name = name, root = True) is not None: return FolderManager.NAME_EXISTS - path = unicode(os.path.abspath(path)) + path = os.path.abspath(path) if not os.path.isdir(path): return FolderManager.INVALID_PATH if Folder.get(path = path) is not None: diff --git a/supysonic/managers/user.py b/supysonic/managers/user.py index d36d5c0..ebda914 100644 --- a/supysonic/managers/user.py +++ b/supysonic/managers/user.py @@ -4,7 +4,7 @@ # This file is part of Supysonic. # Supysonic is a Python implementation of the Subsonic server API. # -# Copyright (C) 2013-2017 Alban 'spl0k' Féron +# Copyright (C) 2013-2018 Alban 'spl0k' Féron # 2017 Óscar García Amor # # Distributed under terms of the GNU AGPLv3 license. @@ -20,6 +20,7 @@ from pony.orm import ObjectNotFound from ..db import User, ChatMessage, Playlist from ..db import StarredFolder, StarredArtist, StarredAlbum, StarredTrack from ..db import RatingFolder, RatingTrack +from ..py23 import strtype class UserManager: SUCCESS = 0 @@ -31,12 +32,12 @@ class UserManager: @staticmethod @db_session def get(uid): - if type(uid) in (str, unicode): + if isinstance(uid, strtype): try: uid = uuid.UUID(uid) except: return UserManager.INVALID_ID, None - elif type(uid) is uuid.UUID: + elif isinstance(uid, uuid.UUID): pass else: return UserManager.INVALID_ID, None diff --git a/supysonic/py23.py b/supysonic/py23.py new file mode 100644 index 0000000..f03de2b --- /dev/null +++ b/supysonic/py23.py @@ -0,0 +1,16 @@ +# coding: utf-8 +# +# This file is part of Supysonic. +# Supysonic is a Python implementation of the Subsonic server API. +# +# Copyright (C) 2018 Alban 'spl0k' Féron +# +# Distributed under terms of the GNU AGPLv3 license. + +try: + # Python 2 + strtype = basestring +except NameError: + # Python 3 + strtype = str + diff --git a/supysonic/scanner.py b/supysonic/scanner.py index 5ec590c..147b1bf 100644 --- a/supysonic/scanner.py +++ b/supysonic/scanner.py @@ -3,7 +3,7 @@ # This file is part of Supysonic. # # Supysonic is a Python implementation of the Subsonic server API. -# Copyright (C) 2013-2017 Alban 'spl0k' Féron +# Copyright (C) 2013-2018 Alban 'spl0k' Féron # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -28,6 +28,7 @@ from pony.orm import db_session from .db import Folder, Artist, Album, Track, User from .db import StarredFolder, StarredArtist, StarredAlbum, StarredTrack from .db import RatingFolder, RatingTrack +from .py23 import strtype class Scanner: def __init__(self, force = False, extensions = None): @@ -119,7 +120,7 @@ class Scanner: @db_session def scan_file(self, path): - if not isinstance(path, basestring): + if not isinstance(path, strtype): raise TypeError('Expecting string, got ' + str(type(path))) tr = Track.get(path = path) @@ -181,7 +182,7 @@ class Scanner: @db_session def remove_file(self, path): - if not isinstance(path, basestring): + if not isinstance(path, strtype): raise TypeError('Expecting string, got ' + str(type(path))) tr = Track.get(path = path) @@ -196,9 +197,9 @@ class Scanner: @db_session def move_file(self, src_path, dst_path): - if not isinstance(src_path, basestring): + if not isinstance(src_path, strtype): raise TypeError('Expecting string, got ' + str(type(src_path))) - if not isinstance(dst_path, basestring): + if not isinstance(dst_path, strtype): raise TypeError('Expecting string, got ' + str(type(dst_path))) if src_path == dst_path: diff --git a/tests/api/apitestbase.py b/tests/api/apitestbase.py index cc3d540..053c802 100644 --- a/tests/api/apitestbase.py +++ b/tests/api/apitestbase.py @@ -4,7 +4,7 @@ # This file is part of Supysonic. # Supysonic is a Python implementation of the Subsonic server API. # -# Copyright (C) 2017 Alban 'spl0k' Féron +# Copyright (C) 2017-2018 Alban 'spl0k' Féron # # Distributed under terms of the GNU AGPLv3 license. @@ -12,6 +12,7 @@ import re from lxml import etree from supysonic.managers.user import UserManager +from supysonic.py23 import strtype from ..testbase import TestBase @@ -65,7 +66,7 @@ class ApiTestBase(TestBase): if not isinstance(args, dict): raise TypeError("'args', expecting a dict, got " + type(args).__name__) - if tag and not isinstance(tag, basestring): + if tag and not isinstance(tag, strtype): raise TypeError("'tag', expecting a str, got " + type(tag).__name__) args.update({ 'c': 'tests', 'v': '1.8.0' }) diff --git a/tests/api/test_response_helper.py b/tests/api/test_response_helper.py index f20c51f..4cee2ba 100644 --- a/tests/api/test_response_helper.py +++ b/tests/api/test_response_helper.py @@ -14,6 +14,8 @@ import unittest import simplejson from xml.etree import ElementTree +from supysonic.py23 import strtype + from ..testbase import TestBase class ResponseHelperBaseCase(TestBase): @@ -26,108 +28,108 @@ class ResponseHelperBaseCase(TestBase): class ResponseHelperJsonTestCase(ResponseHelperBaseCase): def serialize_and_deserialize(self, d, error = False): if not isinstance(d, dict): - raise TypeError(u'Invalid tested value, expecting a dict') + raise TypeError('Invalid tested value, expecting a dict') json = self.helper.responsize_json(d, error) return simplejson.loads(json) def process_and_extract(self, d, error = False): # Basically returns d with additional version and status - return self.serialize_and_deserialize(d, error)[u'subsonic-response'] + return self.serialize_and_deserialize(d, error)['subsonic-response'] def test_basic(self): empty = self.serialize_and_deserialize({}) self.assertEqual(len(empty), 1) - self.assertIn(u'subsonic-response', empty) - self.assertIsInstance(empty[u'subsonic-response'], dict) + self.assertIn('subsonic-response', empty) + self.assertIsInstance(empty['subsonic-response'], dict) - resp = empty[u'subsonic-response'] + resp = empty['subsonic-response'] self.assertEqual(len(resp), 2) - self.assertIn(u'status', resp) - self.assertIn(u'version', resp) - self.assertEqual(resp[u'status'], u'ok') + self.assertIn('status', resp) + self.assertIn('version', resp) + self.assertEqual(resp['status'], 'ok') resp = self.process_and_extract({}, True) - self.assertEqual(resp[u'status'], u'failed') + self.assertEqual(resp['status'], 'failed') some_dict = { - u'intValue': 2, - u'someString': u'Hello world!' + 'intValue': 2, + 'someString': 'Hello world!' } resp = self.process_and_extract(some_dict) - self.assertIn(u'intValue', resp) - self.assertIn(u'someString', resp) + self.assertIn('intValue', resp) + self.assertIn('someString', resp) def test_lists(self): resp = self.process_and_extract({ - u'someList': [ 2, 4, 8, 16 ], - u'emptyList': [] + 'someList': [ 2, 4, 8, 16 ], + 'emptyList': [] }) - self.assertIn(u'someList', resp) - self.assertNotIn(u'emptyList', resp) - self.assertListEqual(resp[u'someList'], [ 2, 4, 8, 16 ]) + self.assertIn('someList', resp) + self.assertNotIn('emptyList', resp) + self.assertListEqual(resp['someList'], [ 2, 4, 8, 16 ]) def test_dicts(self): resp = self.process_and_extract({ - u'dict': { u's': u'Blah', u'i': 20 }, - u'empty': {} + 'dict': { 's': 'Blah', 'i': 20 }, + 'empty': {} }) - self.assertIn(u'dict', resp) - self.assertIn(u'empty', resp) - self.assertDictEqual(resp[u'dict'], { u's': u'Blah', u'i': 20 }) - self.assertDictEqual(resp[u'empty'], {}) + self.assertIn('dict', resp) + self.assertIn('empty', resp) + self.assertDictEqual(resp['dict'], { 's': 'Blah', 'i': 20 }) + self.assertDictEqual(resp['empty'], {}) def test_nesting(self): resp = self.process_and_extract({ - u'dict': { - u'value': u'hey look! a string', - u'list': [ 1, 2, 3 ], - u'emptyList': [], - u'subdict': { u'a': u'A' } + 'dict': { + 'value': 'hey look! a string', + 'list': [ 1, 2, 3 ], + 'emptyList': [], + 'subdict': { 'a': 'A' } }, - u'list': [ - { u'b': u'B' }, - { u'c': u'C' }, + 'list': [ + { 'b': 'B' }, + { 'c': 'C' }, [ 4, 5, 6 ], - u'final string' + 'final string' ] }) self.assertEqual(len(resp), 4) # dict, list, status and version - self.assertIn(u'dict', resp) - self.assertIn(u'list', resp) + self.assertIn('dict', resp) + self.assertIn('list', resp) - d = resp[u'dict'] - l = resp[u'list'] + d = resp['dict'] + l = resp['list'] - self.assertIn(u'value', d) - self.assertIn(u'list', d) + self.assertIn('value', d) + self.assertIn('list', d) self.assertNotIn('emptyList', d) - self.assertIn(u'subdict', d) - self.assertIsInstance(d[u'value'], basestring) - self.assertIsInstance(d[u'list'], list) - self.assertIsInstance(d[u'subdict'], dict) + self.assertIn('subdict', d) + self.assertIsInstance(d['value'], strtype) + self.assertIsInstance(d['list'], list) + self.assertIsInstance(d['subdict'], dict) self.assertEqual(l, [ - { u'b': u'B' }, - { u'c': u'C' }, + { 'b': 'B' }, + { 'c': 'C' }, [ 4, 5, 6 ], - u'final string' + 'final string' ]) class ResponseHelperJsonpTestCase(ResponseHelperBaseCase): def test_basic(self): - result = self.helper.responsize_jsonp({}, u'callback') - self.assertTrue(result.startswith(u'callback({')) - self.assertTrue(result.endswith(u'})')) + result = self.helper.responsize_jsonp({}, 'callback') + self.assertTrue(result.startswith('callback({')) + self.assertTrue(result.endswith('})')) json = simplejson.loads(result[9:-1]) - self.assertIn(u'subsonic-response', json) + self.assertIn('subsonic-response', json) class ResponseHelperXMLTestCase(ResponseHelperBaseCase): def serialize_and_deserialize(self, d, error = False): xml = self.helper.responsize_xml(d, error) - xml = xml.replace(u'xmlns="http://subsonic.org/restapi"', u'') + xml = xml.replace('xmlns="http://subsonic.org/restapi"', '') root = ElementTree.fromstring(xml) return root @@ -137,80 +139,80 @@ class ResponseHelperXMLTestCase(ResponseHelperBaseCase): def test_root(self): xml = self.helper.responsize_xml({ 'tag': {}}) - self.assertIn(u'')) + self.assertIn('')) def test_basic(self): empty = self.serialize_and_deserialize({}) - self.assertIsNotNone(empty.find(u'.[@version]')) - self.assertIsNotNone(empty.find(u".[@status='ok']")) + self.assertIsNotNone(empty.find('.[@version]')) + self.assertIsNotNone(empty.find(".[@status='ok']")) resp = self.serialize_and_deserialize({}, True) - self.assertIsNotNone(resp.find(u".[@status='failed']")) + self.assertIsNotNone(resp.find(".[@status='failed']")) some_dict = { - u'intValue': 2, - u'someString': u'Hello world!' + 'intValue': 2, + 'someString': 'Hello world!' } resp = self.serialize_and_deserialize(some_dict) - self.assertIsNotNone(resp.find(u'.[@intValue]')) - self.assertIsNotNone(resp.find(u'.[@someString]')) + self.assertIsNotNone(resp.find('.[@intValue]')) + self.assertIsNotNone(resp.find('.[@someString]')) def test_lists(self): resp = self.serialize_and_deserialize({ - u'someList': [ 2, 4, 8, 16 ], - u'emptyList': [] + 'someList': [ 2, 4, 8, 16 ], + 'emptyList': [] }) - elems = resp.findall(u'./someList') + elems = resp.findall('./someList') self.assertEqual(len(elems), 4) - self.assertIsNone(resp.find(u'./emptyList')) + self.assertIsNone(resp.find('./emptyList')) for e, i in zip(elems, [ 2, 4, 8, 16 ]): self.assertEqual(int(e.text), i) def test_dicts(self): resp = self.serialize_and_deserialize({ - u'dict': { u's': u'Blah', u'i': 20 }, - u'empty': {} + 'dict': { 's': 'Blah', 'i': 20 }, + 'empty': {} }) - d = resp.find(u'./dict') + d = resp.find('./dict') self.assertIsNotNone(d) - self.assertIsNotNone(resp.find(u'./empty')) - self.assertAttributesMatchDict(d, { u's': u'Blah', u'i': 20 }) + self.assertIsNotNone(resp.find('./empty')) + self.assertAttributesMatchDict(d, { 's': 'Blah', 'i': 20 }) def test_nesting(self): resp = self.serialize_and_deserialize({ - u'dict': { - u'value': u'hey look! a string', - u'list': [ 1, 2, 3 ], - u'emptyList': [], - u'subdict': { u'a': u'A' } + 'dict': { + 'value': 'hey look! a string', + 'list': [ 1, 2, 3 ], + 'emptyList': [], + 'subdict': { 'a': 'A' } }, - u'list': [ - { u'b': u'B' }, - { u'c': u'C' }, - u'final string' + 'list': [ + { 'b': 'B' }, + { 'c': 'C' }, + 'final string' ] }) self.assertEqual(len(resp), 4) # 'dict' and 3 'list's - d = resp.find(u'./dict') - lists = resp.findall(u'./list') + d = resp.find('./dict') + lists = resp.findall('./list') self.assertIsNotNone(d) - self.assertAttributesMatchDict(d, { u'value': u'hey look! a string' }) - self.assertEqual(len(d.findall(u'./list')), 3) - self.assertEqual(len(d.findall(u'./emptyList')), 0) - self.assertIsNotNone(d.find(u'./subdict')) + self.assertAttributesMatchDict(d, { 'value': 'hey look! a string' }) + self.assertEqual(len(d.findall('./list')), 3) + self.assertEqual(len(d.findall('./emptyList')), 0) + self.assertIsNotNone(d.find('./subdict')) self.assertEqual(len(lists), 3) - self.assertAttributesMatchDict(lists[0], { u'b': u'B' }) - self.assertAttributesMatchDict(lists[1], { u'c': u'C' }) - self.assertEqual(lists[2].text, u'final string') + self.assertAttributesMatchDict(lists[0], { 'b': 'B' }) + self.assertAttributesMatchDict(lists[1], { 'c': 'C' }) + self.assertEqual(lists[2].text, 'final string') def suite(): suite = unittest.TestSuite() diff --git a/tests/base/test_config.py b/tests/base/test_config.py index c357630..c1fff82 100644 --- a/tests/base/test_config.py +++ b/tests/base/test_config.py @@ -11,6 +11,7 @@ import unittest from supysonic.config import IniConfig +from supysonic.py23 import strtype class ConfigTestCase(unittest.TestCase): def test_sections(self): @@ -24,7 +25,7 @@ class ConfigTestCase(unittest.TestCase): self.assertIsInstance(conf.TYPES['float'], float) self.assertIsInstance(conf.TYPES['int'], int) - self.assertIsInstance(conf.TYPES['string'], basestring) + self.assertIsInstance(conf.TYPES['string'], strtype) for t in ('bool', 'switch', 'yn'): self.assertIsInstance(conf.BOOLEANS[t + '_false'], bool) diff --git a/tests/managers/test_manager_folder.py b/tests/managers/test_manager_folder.py index f1a5c5f..96dd4b9 100644 --- a/tests/managers/test_manager_folder.py +++ b/tests/managers/test_manager_folder.py @@ -5,13 +5,14 @@ # This file is part of Supysonic. # Supysonic is a Python implementation of the Subsonic server API. # -# Copyright (C) 2013-2017 Alban 'spl0k' Féron +# Copyright (C) 2013-2018 Alban 'spl0k' Féron # 2017 Óscar García Amor # # Distributed under terms of the GNU AGPLv3 license. from supysonic import db from supysonic.managers.folder import FolderManager +from supysonic.py23 import strtype import os import io @@ -153,7 +154,7 @@ class FolderManagerTestCase(unittest.TestCase): FolderManager.INVALID_PATH, FolderManager.PATH_EXISTS, FolderManager.NO_SUCH_FOLDER, FolderManager.SUBPATH_EXISTS, 1594826, 'string', uuid.uuid4() ] for value in values: - self.assertIsInstance(FolderManager.error_str(value), basestring) + self.assertIsInstance(FolderManager.error_str(value), strtype) if __name__ == '__main__': unittest.main() diff --git a/tests/managers/test_manager_user.py b/tests/managers/test_manager_user.py index 632dc22..1aa56ea 100644 --- a/tests/managers/test_manager_user.py +++ b/tests/managers/test_manager_user.py @@ -5,13 +5,14 @@ # This file is part of Supysonic. # Supysonic is a Python implementation of the Subsonic server API. # -# Copyright (C) 2013-2017 Alban 'spl0k' Féron +# Copyright (C) 2013-2018 Alban 'spl0k' Féron # 2017 Óscar García Amor # # Distributed under terms of the GNU AGPLv3 license. from supysonic import db from supysonic.managers.user import UserManager +from supysonic.py23 import strtype import io import unittest @@ -180,7 +181,7 @@ class UserManagerTestCase(unittest.TestCase): values = [ UserManager.SUCCESS, UserManager.INVALID_ID, UserManager.NO_SUCH_USER, UserManager.NAME_EXISTS, UserManager.WRONG_PASS, 1594826, 'string', uuid.uuid4() ] for value in values: - self.assertIsInstance(UserManager.error_str(value), basestring) + self.assertIsInstance(UserManager.error_str(value), strtype) if __name__ == '__main__': unittest.main()