diff --git a/mopidy_iris/__init__.py b/mopidy_iris/__init__.py index 7f8bc1e0..7a1311d9 100755 --- a/mopidy_iris/__init__.py +++ b/mopidy_iris/__init__.py @@ -40,8 +40,8 @@ class Extension(ext.Extension): # Add web extension registry.add( - "http:app", { - "name": self.ext_name, "factory": iris_factory}) + "http:app", {"name": self.ext_name, "factory": iris_factory} + ) # Add our frontend registry.add("frontend", IrisFrontend) diff --git a/mopidy_iris/core.py b/mopidy_iris/core.py index 39af5372..8f618bc1 100755 --- a/mopidy_iris/core.py +++ b/mopidy_iris/core.py @@ -119,8 +119,9 @@ class IrisCore(pykka.ThreadingActor): # @return string ## def generateGuid(self): - return "".join(random.choices( - string.ascii_uppercase + string.digits, k=12)) + return "".join( + random.choices(string.ascii_uppercase + string.digits, k=12) + ) ## # Digest a protocol header into it's id/name parts @@ -242,8 +243,11 @@ class IrisCore(pykka.ThreadingActor): if send_to_this_connection: connection["connection"].write_message(json_encode(message)) - response = {"message": "Broadcast to " + - str(len(self.connections)) + " connections"} + response = { + "message": "Broadcast to " + + str(len(self.connections)) + + " connections" + } if callback: callback(response) else: @@ -286,8 +290,8 @@ class IrisCore(pykka.ThreadingActor): self.broadcast( data={ "method": "connection_added", - "params": { - "connection": client}} + "params": {"connection": client}, + } ) def update_connection(self, *args, **kwargs): @@ -304,11 +308,11 @@ class IrisCore(pykka.ThreadingActor): data={ "method": "connection_changed", "params": { - "connection": - self.connections[connection_id]["client"]}, - }) - response = { - "connection": self.connections[connection_id]["client"]} + "connection": self.connections[connection_id]["client"] + }, + } + ) + response = {"connection": self.connections[connection_id]["client"]} if callback: callback(response) else: @@ -350,12 +354,14 @@ class IrisCore(pykka.ThreadingActor): data={ "method": "connection_changed", "params": { - "connection": - self.connections[connection_id]["client"]}, - }) + "connection": self.connections[connection_id]["client"] + }, + } + ) response = { "connection_id": connection_id, - "username": data["username"]} + "username": data["username"], + } if callback: callback(response) else: @@ -469,15 +475,11 @@ class IrisCore(pykka.ThreadingActor): if error: self.broadcast(data={"method": "restart_error", "params": error}) elif update: - self.broadcast( - data={ - "method": "restart_updated", - "params": update}) + self.broadcast(data={"method": "restart_updated", "params": update}) else: self.broadcast( - data={ - "method": "restart_finished", - "params": response}) + data={"method": "restart_finished", "params": response} + ) ## # Run an upgrade of Iris @@ -502,15 +504,11 @@ class IrisCore(pykka.ThreadingActor): if error: self.broadcast(data={"method": "upgrade_error", "params": error}) elif update: - self.broadcast( - data={ - "method": "upgrade_updated", - "params": update}) + self.broadcast(data={"method": "upgrade_updated", "params": update}) else: self.broadcast( - data={ - "method": "upgrade_finished", - "params": response}) + data={"method": "upgrade_finished", "params": response} + ) self.restart() ## @@ -522,10 +520,7 @@ class IrisCore(pykka.ThreadingActor): ioloop = kwargs.get("ioloop", False) # Trigger the action - IrisSystemThread( - "local_scan", - ioloop, - self.local_scan_callback).start() + IrisSystemThread("local_scan", ioloop, self.local_scan_callback).start() self.broadcast(data={"method": "local_scan_started"}) @@ -537,20 +532,15 @@ class IrisCore(pykka.ThreadingActor): def local_scan_callback(self, response, error, update): if error: - self.broadcast( - data={ - "method": "local_scan_error", - "params": error}) + self.broadcast(data={"method": "local_scan_error", "params": error}) elif update: self.broadcast( - data={ - "method": "local_scan_updated", - "params": update}) + data={"method": "local_scan_updated", "params": update} + ) else: self.broadcast( - data={ - "method": "local_scan_finished", - "params": response}) + data={"method": "local_scan_finished", "params": response} + ) ## # Spotify Radio @@ -624,15 +614,15 @@ class IrisCore(pykka.ThreadingActor): self.broadcast( data={ "method": "radio_started", - "params": { - "radio": self.radio}} + "params": {"radio": self.radio}, + } ) else: self.broadcast( data={ "method": "radio_changed", - "params": { - "radio": self.radio}} + "params": {"radio": self.radio}, + } ) self.get_radio(callback=callback) @@ -691,29 +681,30 @@ class IrisCore(pykka.ThreadingActor): url = ( url + "?seed_artists=" - + (",".join(self.radio["seed_artists"]) - ).replace("spotify:artist:", "") + + (",".join(self.radio["seed_artists"])).replace( + "spotify:artist:", "" + ) ) url = ( url + "&seed_genres=" - + (",".join(self.radio["seed_genres"]) - ).replace("spotify:genre:", "") + + (",".join(self.radio["seed_genres"])).replace( + "spotify:genre:", "" + ) ) url = ( url + "&seed_tracks=" - + (",".join(self.radio["seed_tracks"]) - ).replace("spotify:track:", "") + + (",".join(self.radio["seed_tracks"])).replace( + "spotify:track:", "" + ) ) url = url + "&limit=50" http_client = AsyncHTTPClient() try: http_response = await http_client.fetch( - url, "POST", headers={ - "Authorization": "Bearer " + access_token - } + url, "POST", headers={"Authorization": "Bearer " + access_token} ) response_body = json.loads(http_response.body) @@ -730,8 +721,8 @@ class IrisCore(pykka.ThreadingActor): + error["error_description"] } logger.error( - "Could not fetch Spotify recommendations: " + - error["error_description"] + "Could not fetch Spotify recommendations: " + + error["error_description"] ) logger.debug(error) return False @@ -810,17 +801,19 @@ class IrisCore(pykka.ThreadingActor): for tlid in data["tlids"]: item = { "tlid": tlid, - "added_from": - data["added_from"] if "added_from" in data else None, - "added_by": - data["added_by"] if "added_by" in data else None, + "added_from": data["added_from"] + if "added_from" in data + else None, + "added_by": data["added_by"] if "added_by" in data else None, } self.queue_metadata["tlid_" + str(tlid)] = item self.broadcast( data={ "method": "queue_metadata_changed", - "params": {"queue_metadata": self.queue_metadata}, + "params": { + "queue_metadata": self.queue_metadata + }, } ) @@ -873,7 +866,9 @@ class IrisCore(pykka.ThreadingActor): data={ "method": "commands_changed", "params": { - "commands": self.commands}} + "commands": self.commands + }, + } ) response = {"message": "Commands saved"} @@ -890,8 +885,8 @@ class IrisCore(pykka.ThreadingActor): if str(data["id"]) not in self.commands: error = { "message": "Command failed", - "description": r'''Could not find command by ID - "' + str(data["id"]) + '"''', + "description": "Could not find command by ID " + + '"' + str(data["id"]) + '"', } else: command = self.commands[str(data["id"])] @@ -921,8 +916,7 @@ class IrisCore(pykka.ThreadingActor): d = command["additional_headers"].split("\n") lines = list(filter(lambda x: x.find(":") > 0, d)) fields = [ - (x.split(":", 1)[0].strip().lower(), - x.split(":", 1)[1].strip()) + (x.split(":", 1)[0].strip().lower(), x.split(":", 1)[1].strip()) for x in lines ] headers = dict(fields) @@ -948,7 +942,8 @@ class IrisCore(pykka.ThreadingActor): command["url"], connect_timeout=5, validate_cert=False, - headers=headers) + headers=headers, + ) # Make the request, and handle any request errors try: @@ -969,14 +964,13 @@ class IrisCore(pykka.ThreadingActor): # Perhaps it requires unicode encoding? try: command_response_body = tornado.escape.to_unicode( - command_response.body) + command_response.body + ) except BaseException: command_response_body = "" # Finally, return the result - response = { - "message": "Command run", - "response": command_response_body} + response = {"message": "Command run", "response": command_response_body} if callback: callback(response) @@ -998,8 +992,8 @@ class IrisCore(pykka.ThreadingActor): # Expired, so go get a new one if ( - not self.spotify_token or - self.spotify_token["expires_at"] <= time.time() + not self.spotify_token + or self.spotify_token["expires_at"] <= time.time() ): await self.refresh_spotify_token() @@ -1036,7 +1030,9 @@ class IrisCore(pykka.ThreadingActor): self.broadcast( data={ "method": "spotify_token_changed", - "params": {"spotify_token": self.spotify_token}, + "params": { + "spotify_token": self.spotify_token + }, } ) @@ -1049,8 +1045,9 @@ class IrisCore(pykka.ThreadingActor): except (urllib.error.HTTPError, urllib.error.URLError) as e: error = json.loads(e.read()) error = { - "message": "Could not refresh token: " + - error["error_description"]} + "message": "Could not refresh token: " + + error["error_description"] + } if callback: callback(False, error) @@ -1094,9 +1091,9 @@ class IrisCore(pykka.ThreadingActor): if connection_id not in self.connections: error = { "message": "Unauthorized request", - "description": "Connection " + - connection_id + - " not connected", + "description": "Connection " + + connection_id + + " not connected", } except Exception as e: @@ -1113,10 +1110,8 @@ class IrisCore(pykka.ThreadingActor): http_client = AsyncHTTPClient() http_response = await http_client.fetch(url) callback( - http_response.body.decode( - "utf-8", - errors="replace"), - False) + http_response.body.decode("utf-8", errors="replace"), False + ) except (urllib.error.HTTPError, urllib.error.URLError) as e: error = json.loads(e.read()) @@ -1125,8 +1120,8 @@ class IrisCore(pykka.ThreadingActor): + error["error_description"] } logger.error( - "Could not fetch Spotify recommendations: " + - error["error_description"] + "Could not fetch Spotify recommendations: " + + error["error_description"] ) logger.debug(error) return error @@ -1155,7 +1150,4 @@ class IrisCore(pykka.ThreadingActor): elif error: self.broadcast(data={"method": "test_updated", "params": update}) else: - self.broadcast( - data={ - "method": "test_finished", - "params": response}) + self.broadcast(data={"method": "test_finished", "params": response}) diff --git a/mopidy_iris/frontend.py b/mopidy_iris/frontend.py index 60fcb740..87d4a585 100755 --- a/mopidy_iris/frontend.py +++ b/mopidy_iris/frontend.py @@ -23,9 +23,7 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener): iris.stop() def track_playback_ended(self, tl_track, time_position): - iris.ioloop.add_callback( - functools.partial( - iris.check_for_radio_update)) + iris.ioloop.add_callback(functools.partial(iris.check_for_radio_update)) def tracklist_changed(self): iris.ioloop.add_callback(functools.partial(iris.clean_queue_metadata)) diff --git a/mopidy_iris/handlers.py b/mopidy_iris/handlers.py index a6519383..84f27911 100755 --- a/mopidy_iris/handlers.py +++ b/mopidy_iris/handlers.py @@ -31,7 +31,8 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler): # Get the client's IP. If it's local, then use it's proxy origin ip = self.request.remote_ip if ip == "127.0.0.1" and hasattr( - self.request.headers, "X-Forwarded-For"): + self.request.headers, "X-Forwarded-For" + ): ip = self.request.headers["X-Forwarded-For"] # Construct our initial client object, and add to our list of @@ -62,8 +63,8 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler): error={ "id": id, "code": 32602, - "message": r'''Invalid JSON-RPC request (missing - property "jsonrpc")''', + "message": r"""Invalid JSON-RPC request (missing + property "jsonrpc")""", }, ) @@ -87,12 +88,12 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler): # For async methods we need to await, but it must be # ommited for syncronous methods if asyncio.iscoroutinefunction( - getattr(iris, message["method"])): + getattr(iris, message["method"]) + ): await getattr(iris, message["method"])( ioloop=self.ioloop, data=params, - callback=lambda response, - error=False: self.handle_result( + callback=lambda response, error=False: self.handle_result( id=id, method=message["method"], response=response, @@ -100,13 +101,10 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler): ), ) else: - getattr( - iris, - message["method"])( + getattr(iris, message["method"])( ioloop=self.ioloop, data=params, - callback=lambda response, - error=False: self.handle_result( + callback=lambda response, error=False: self.handle_result( id=id, method=message["method"], response=response, @@ -121,9 +119,9 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler): error={ "id": id, "code": 32601, - "message": 'Method "' + - message["method"] + - '" does not exist', + "message": 'Method "' + + message["method"] + + '" does not exist', }, id=id, ) @@ -179,8 +177,8 @@ class HttpHandler(tornado.web.RequestHandler): self.set_header("Access-Control-Allow-Origin", "*") self.set_header( "Access-Control-Allow-Headers", - r'''Origin, X-Requested-With, Content-Type, Accept, - Authorization, Client-Security-Token, Accept-Encoding''', + r"""Origin, X-Requested-With, Content-Type, Accept, + Authorization, Client-Security-Token, Accept-Encoding""", ) def initialize(self, core, config): @@ -208,23 +206,16 @@ class HttpHandler(tornado.web.RequestHandler): await getattr(iris, slug)( ioloop=self.ioloop, request=self, - callback=lambda response, - error=False: self.handle_result( + callback=lambda response, error=False: self.handle_result( id=id, method=slug, response=response, error=error ), ) else: - getattr( - iris, - slug)( + getattr(iris, slug)( ioloop=self.ioloop, request=self, - callback=lambda response, - error=False: self.handle_result( - id=id, - method=slug, - response=response, - error=error + callback=lambda response, error=False: self.handle_result( + id=id, method=slug, response=response, error=error ), ) except Exception as e: @@ -235,9 +226,8 @@ class HttpHandler(tornado.web.RequestHandler): id=id, error={ "code": 32601, - "message": "Method " + - slug + - " does not exist"}, + "message": "Method " + slug + " does not exist", + }, ) return @@ -249,8 +239,8 @@ class HttpHandler(tornado.web.RequestHandler): params = json.loads(self.request.body.decode("utf-8")) except BaseException: self.handle_result( - id=id, error={ - "code": 32700, "message": "Missing or invalid payload"} + id=id, + error={"code": 32700, "message": "Missing or invalid payload"}, ) return @@ -261,30 +251,23 @@ class HttpHandler(tornado.web.RequestHandler): await getattr(iris, slug)( data=params, request=self.request, - callback=lambda response=False, - error=False: self.handle_result( + callback=lambda response=False, error=False: self.handle_result( id=id, method=slug, response=response, error=error ), ) else: - getattr( - iris, - slug)( + getattr(iris, slug)( data=params, request=self.request, - callback=lambda response=False, - error=False: self.handle_result( - id=id, - method=slug, - response=response, - error=error + callback=lambda response=False, error=False: self.handle_result( + id=id, method=slug, response=response, error=error ), ) except tornado.web.HTTPError: self.handle_result( - id=id, error={ - "code": 32601, "message": "Invalid JSON payload"} + id=id, + error={"code": 32601, "message": "Invalid JSON payload"}, ) return @@ -293,9 +276,8 @@ class HttpHandler(tornado.web.RequestHandler): id=id, error={ "code": 32601, - "message": "Method " + - slug + - " does not exist"}, + "message": "Method " + slug + " does not exist", + }, ) return @@ -322,10 +304,8 @@ class HttpHandler(tornado.web.RequestHandler): # Digest JSON responses into JSON content_type = response.headers.get("Content-Type") if content_type.startswith( - "application/json" - ) or content_type.startswith( - "text/json" - ): + "application/json" + ) or content_type.startswith("text/json"): body = json.loads(response.body) # Non-JSON so just copy as-is diff --git a/mopidy_iris/system.py b/mopidy_iris/system.py index f061aa3e..a3809712 100755 --- a/mopidy_iris/system.py +++ b/mopidy_iris/system.py @@ -17,9 +17,10 @@ class IrisSystemPermissionError(IrisSystemError): def __init__(self, path): message = ( - r'''Password-less access to %s was refused. - Check your /etc/sudoers file.''' % - path.as_uri()) + r"""Password-less access to %s was refused. + Check your /etc/sudoers file.""" + % path.as_uri() + ) logger.error(message) super().__init__(message) @@ -67,7 +68,8 @@ class IrisSystemThread(Thread): command = self.get_command() logger.debug("Running '%s'", os.fsdecode(b" ".join(command))) process = subprocess.Popen( - command, stdout=subprocess.PIPE, encoding="utf8") + command, stdout=subprocess.PIPE, encoding="utf8" + ) lines = "" while True: @@ -95,13 +97,14 @@ class IrisSystemThread(Thread): def can_run(self, *args, **kwargs): # Attempt an empty call to our system file command_bytes = b" ".join( - self.get_command( - "check", non_interactive=True)) + self.get_command("check", non_interactive=True) + ) process = subprocess.Popen( command_bytes, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - shell=True) + shell=True, + ) result, error = process.communicate() exitCode = process.wait() diff --git a/tests/test_handlers.py b/tests/test_handlers.py index 26a2cdbc..8afef8ab 100644 --- a/tests/test_handlers.py +++ b/tests/test_handlers.py @@ -11,7 +11,7 @@ from mopidy_iris import handlers from mopidy_iris import core from mopidy_iris.mem import iris - + def async_return_helper(result): f = Future() f.set_result(result) @@ -22,22 +22,13 @@ class HttpHandlerTest(tornado.testing.AsyncHTTPTestCase): @pytest.fixture(autouse=True) def inject_fixtures(self, caplog): self._caplog = caplog - + def get_app(self): http_handler = handlers.HttpHandler # http_handler.handle_result = mock.Mock() # self.handler_mock = http_handler.handle_result return tornado.web.Application( - [ - ( - r"/(.*)", - http_handler, - { - 'core': None, - 'config': {}, - }, - ) - ] + [(r"/(.*)", http_handler, {"core": None, "config": {},},)] ) def test_get_method(self): @@ -69,7 +60,7 @@ class HttpHandlerTest(tornado.testing.AsyncHTTPTestCase): iris_mock.foo = mock.Mock() response = self.fetch("/foo", method="GET") - + iris_mock.foo.assert_called_once() assert 200 == response.code @@ -78,14 +69,14 @@ class HttpHandlerTest(tornado.testing.AsyncHTTPTestCase): iris_mock.foo = mock.Mock(side_effect=Exception("bar")) response = self.fetch("/foo", method="GET") - + iris_mock.foo.assert_called_once() assert 200 == response.code assert "bar" in self._caplog.text @mock.patch.object(handlers.iris, "do_fetch") def test_get_method_with_fetch(self, fetch_mock): - iris.config = {"spotify" : {"client_id": 123, "client_secret": 456}} + iris.config = {"spotify": {"client_id": 123, "client_secret": 456}} result = mock.Mock(spec=HTTPResponse, body='{"expires_in":88}') fetch_mock.return_value = async_return_helper(result) diff --git a/tests/test_system.py b/tests/test_system.py index 7bd7ac81..de3607a6 100755 --- a/tests/test_system.py +++ b/tests/test_system.py @@ -5,54 +5,55 @@ from mopidy_iris.system import IrisSystemThread, IrisSystemPermissionError def test_system_sh_path(): - iris_system = IrisSystemThread('foo', None, None) + iris_system = IrisSystemThread("foo", None, None) assert iris_system.script_path.is_file() assert iris_system.script_path.name == "system.sh" def test_can_run(): - iris_system = IrisSystemThread('foo', None, None) + iris_system = IrisSystemThread("foo", None, None) iris_system._USE_SUDO = False assert iris_system.can_run() is True + @pytest.fixture def popen_mock(): patcher = mock.patch("subprocess.Popen", spec=True) yield patcher.start() patcher.stop() + @pytest.fixture def process_mock(popen_mock): mock_process = popen_mock.return_value - mock_process.communicate.return_value = ('', None) + mock_process.communicate.return_value = ("", None) mock_process.wait.return_value = 0 yield mock_process + def test_can_run_args(popen_mock, process_mock): - IrisSystemThread('foo', None, None).can_run() + IrisSystemThread("foo", None, None).can_run() popen_mock.assert_called_once_with( - mock.ANY, - shell=True, - stderr=subprocess.PIPE, - stdout=subprocess.PIPE + mock.ANY, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE ) + def test_can_run_uses_sudo_non_interactive(popen_mock, process_mock): - IrisSystemThread('foo', None, None).can_run() + IrisSystemThread("foo", None, None).can_run() popen_mock.assert_called_once() assert popen_mock.call_args[0][0].startswith(b"sudo -n ") def test_can_run_calls_script_check(popen_mock, process_mock): - IrisSystemThread('foo', None, None).can_run() + IrisSystemThread("foo", None, None).can_run() assert popen_mock.call_args[0][0].endswith(b"system.sh check") def test_can_run_sudo_refused_raises(popen_mock, process_mock, caplog): process_mock.wait.return_value = 1 - iris_system = IrisSystemThread('foo', None, None) + iris_system = IrisSystemThread("foo", None, None) with pytest.raises(IrisSystemPermissionError) as excinfo: iris_system.can_run() @@ -66,20 +67,18 @@ def test_can_run_sudo_refused_raises(popen_mock, process_mock, caplog): def test_run_args(popen_mock, process_mock): - iris_system = IrisSystemThread('foo', mock.Mock(), None) - iris_system.can_run = mock.Mock(return_value = True) + iris_system = IrisSystemThread("foo", mock.Mock(), None) + iris_system.can_run = mock.Mock(return_value=True) iris_system.run() popen_mock.assert_called_once_with( - mock.ANY, - stderr=subprocess.PIPE, - stdout=subprocess.PIPE + mock.ANY, stderr=subprocess.PIPE, stdout=subprocess.PIPE ) def test_run_uses_sudo(popen_mock, process_mock): - iris_system = IrisSystemThread('foo', mock.Mock(), None) - iris_system.can_run = mock.Mock(return_value = True) + iris_system = IrisSystemThread("foo", mock.Mock(), None) + iris_system.can_run = mock.Mock(return_value=True) iris_system.run() popen_mock.assert_called_once()