diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f22746..d20dbb9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,22 +12,19 @@ jobs: fail-fast: false matrix: include: - - name: "Test: Python 3.9" - python: "3.9" - tox: py39 - - name: "Test: Python 3.10" - python: "3.10" - tox: py310 - name: "Test: Python 3.11" python: "3.11" tox: py311 + - name: "Test: Python 3.12" + python: "3.12" + tox: py312 coverage: true - - name: "Lint: check-manifest" - python: "3.11" - tox: check-manifest - - name: "Lint: flake8" - python: "3.11" - tox: flake8 + - name: "Lint: ruff lint" + python: "3.12" + tox: ruff-lint + - name: "Lint: ruff format" + python: "3.12" + tox: ruff-format name: ${{ matrix.name }} runs-on: ubuntu-22.04 diff --git a/mopidy_mpd/__init__.py b/mopidy_mpd/__init__.py index f2441dd..df05d23 100644 --- a/mopidy_mpd/__init__.py +++ b/mopidy_mpd/__init__.py @@ -1,7 +1,6 @@ import pathlib import pkg_resources - from mopidy import config, ext __version__ = pkg_resources.get_distribution("Mopidy-MPD").version diff --git a/mopidy_mpd/actor.py b/mopidy_mpd/actor.py index 882a187..25e6cd8 100644 --- a/mopidy_mpd/actor.py +++ b/mopidy_mpd/actor.py @@ -1,9 +1,9 @@ import logging import pykka - from mopidy import exceptions, listener, zeroconf from mopidy.core import CoreListener + from mopidy_mpd import network, session, uri_mapper logger = logging.getLogger(__name__) @@ -54,18 +54,14 @@ def _setup_server(self, config, core): timeout=config["mpd"]["connection_timeout"], ) except OSError as exc: - raise exceptions.FrontendError(f"MPD server startup failed: {exc}") + raise exceptions.FrontendError(f"MPD server startup failed: {exc}") from exc - logger.info( - f"MPD server running at {network.format_address(server.address)}" - ) + logger.info(f"MPD server running at {network.format_address(server.address)}") return server def on_start(self): - if self.zeroconf_name and not network.is_unix_socket( - self.server.server_socket - ): + if self.zeroconf_name and not network.is_unix_socket(self.server.server_socket): self.zeroconf_service = zeroconf.Zeroconf( name=self.zeroconf_name, stype="_mpd._tcp", port=self.port ) @@ -83,9 +79,7 @@ def on_stop(self): def on_event(self, event, **kwargs): if event not in _CORE_EVENTS_TO_IDLE_SUBSYSTEMS: - logger.warning( - "Got unexpected event: %s(%s)", event, ", ".join(kwargs) - ) + logger.warning("Got unexpected event: %s(%s)", event, ", ".join(kwargs)) else: self.send_idle(_CORE_EVENTS_TO_IDLE_SUBSYSTEMS[event]) diff --git a/mopidy_mpd/dispatcher.py b/mopidy_mpd/dispatcher.py index 25e3220..52bbea3 100644 --- a/mopidy_mpd/dispatcher.py +++ b/mopidy_mpd/dispatcher.py @@ -49,9 +49,7 @@ def handle_idle(self, subsystem): # TODO: validate against mopidy_mpd/protocol/status.SUBSYSTEMS self.context.events.add(subsystem) - subsystems = self.context.subscriptions.intersection( - self.context.events - ) + subsystems = self.context.subscriptions.intersection(self.context.events) if not subsystems: return @@ -67,10 +65,9 @@ def _call_next_filter(self, request, response, filter_chain): if filter_chain: next_filter = filter_chain.pop(0) return next_filter(request, response, filter_chain) - else: - return response + return response - # Filter: catch MPD ACK errors + # --- Filter: catch MPD ACK errors def _catch_mpd_ack_errors_filter(self, request, response, filter_chain): try: @@ -80,53 +77,55 @@ def _catch_mpd_ack_errors_filter(self, request, response, filter_chain): mpd_ack_error.index = self.command_list_index return [mpd_ack_error.get_mpd_ack()] - # Filter: authenticate + # --- Filter: authenticate def _authenticate_filter(self, request, response, filter_chain): if self.authenticated: return self._call_next_filter(request, response, filter_chain) - elif self.config["mpd"]["password"] is None: + + if self.config["mpd"]["password"] is None: self.authenticated = True return self._call_next_filter(request, response, filter_chain) - else: - command_name = request.split(" ")[0] - command = protocol.commands.handlers.get(command_name) - if command and not command.auth_required: - return self._call_next_filter(request, response, filter_chain) - else: - raise exceptions.MpdPermissionError(command=command_name) - # Filter: command list + command_name = request.split(" ")[0] + command = protocol.commands.handlers.get(command_name) + + if command and not command.auth_required: + return self._call_next_filter(request, response, filter_chain) + + raise exceptions.MpdPermissionError(command=command_name) + + # --- Filter: command list def _command_list_filter(self, request, response, filter_chain): if self._is_receiving_command_list(request): self.command_list.append(request) return [] - else: - response = self._call_next_filter(request, response, filter_chain) - if self._is_receiving_command_list( - request - ) or self._is_processing_command_list(request): - if response and response[-1] == "OK": - response = response[:-1] - return response + + response = self._call_next_filter(request, response, filter_chain) + if ( + ( + self._is_receiving_command_list(request) + or self._is_processing_command_list(request) + ) + and response + and response[-1] == "OK" + ): + response = response[:-1] + return response def _is_receiving_command_list(self, request): return self.command_list_receiving and request != "command_list_end" def _is_processing_command_list(self, request): - return ( - self.command_list_index is not None - and request != "command_list_end" - ) + return self.command_list_index is not None and request != "command_list_end" - # Filter: idle + # --- Filter: idle def _idle_filter(self, request, response, filter_chain): if self._is_currently_idle() and not self._noidle.match(request): logger.debug( - "Client sent us %s, only %s is allowed while in " - "the idle state", + "Client sent us %s, only %s is allowed while in " "the idle state", repr(request), repr("noidle"), ) @@ -140,13 +139,13 @@ def _idle_filter(self, request, response, filter_chain): if self._is_currently_idle(): return [] - else: - return response + + return response def _is_currently_idle(self): return bool(self.context.subscriptions) - # Filter: add OK + # --- Filter: add OK def _add_ok_filter(self, request, response, filter_chain): response = self._call_next_filter(request, response, filter_chain) @@ -157,15 +156,15 @@ def _add_ok_filter(self, request, response, filter_chain): def _has_error(self, response): return response and response[-1].startswith("ACK") - # Filter: call handler + # --- Filter: call handler def _call_handler_filter(self, request, response, filter_chain): try: response = self._format_response(self._call_handler(request)) return self._call_next_filter(request, response, filter_chain) - except pykka.ActorDeadError as e: + except pykka.ActorDeadError as exc: logger.warning("Tried to communicate with dead actor.") - raise exceptions.MpdSystemError(e) + raise exceptions.MpdSystemError(exc) from exc def _call_handler(self, request): tokens = tokenize.split(request) @@ -173,7 +172,7 @@ def _call_handler(self, request): blacklist = self.config["mpd"].get("command_blacklist", []) if tokens and tokens[0] in blacklist: logger.warning("MPD client used blacklisted command: %s", tokens[0]) - raise exceptions.MpdDisabled(command=tokens[0]) + raise exceptions.MpdDisabledError(command=tokens[0]) try: return protocol.commands.call(tokens, context=self.context) except exceptions.MpdAckError as exc: @@ -241,9 +240,7 @@ class MpdContext: _uri_map = None - def __init__( - self, dispatcher, session=None, config=None, core=None, uri_map=None - ): + def __init__(self, dispatcher, session=None, config=None, core=None, uri_map=None): # noqa: PLR0913 self.dispatcher = dispatcher self.session = session if config is not None: @@ -265,7 +262,7 @@ def lookup_playlist_name_from_uri(self, uri): """ return self._uri_map.playlist_name_from_uri(uri) - def browse(self, path, recursive=True, lookup=True): + def browse(self, path, *, recursive=True, lookup=True): # noqa: C901, PLR0912 """ Browse the contents of a given directory path. @@ -285,7 +282,7 @@ def browse(self, path, recursive=True, lookup=True): """ path_parts = re.findall(r"[^/]+", path or "") - root_path = "/".join([""] + path_parts) + root_path = "/".join(["", *path_parts]) uri = self._uri_map.uri_from_name(root_path) if uri is None: diff --git a/mopidy_mpd/exceptions.py b/mopidy_mpd/exceptions.py index c778224..10eac86 100644 --- a/mopidy_mpd/exceptions.py +++ b/mopidy_mpd/exceptions.py @@ -59,7 +59,7 @@ class MpdUnknownError(MpdAckError): error_code = MpdAckError.ACK_ERROR_UNKNOWN -class MpdUnknownCommand(MpdUnknownError): +class MpdUnknownCommandError(MpdUnknownError): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) assert self.command is not None, "command must be given explicitly" @@ -67,7 +67,7 @@ def __init__(self, *args, **kwargs): self.command = "" -class MpdNoCommand(MpdUnknownCommand): +class MpdNoCommandError(MpdUnknownCommandError): def __init__(self, *args, **kwargs): kwargs["command"] = "" super().__init__(*args, **kwargs) @@ -86,7 +86,7 @@ class MpdSystemError(MpdAckError): error_code = MpdAckError.ACK_ERROR_SYSTEM -class MpdInvalidPlaylistName(MpdAckError): +class MpdInvalidPlaylistNameError(MpdAckError): error_code = MpdAckError.ACK_ERROR_ARG def __init__(self, *args, **kwargs): @@ -97,7 +97,7 @@ def __init__(self, *args, **kwargs): ) -class MpdNotImplemented(MpdAckError): +class MpdNotImplementedError(MpdAckError): error_code = 0 def __init__(self, *args, **kwargs): @@ -105,7 +105,7 @@ def __init__(self, *args, **kwargs): self.message = "Not implemented" -class MpdInvalidTrackForPlaylist(MpdAckError): +class MpdInvalidTrackForPlaylistError(MpdAckError): # NOTE: This is a custom error for Mopidy that does not exist in MPD. error_code = 0 @@ -117,18 +117,16 @@ def __init__(self, playlist_scheme, track_scheme, *args, **kwargs): ) -class MpdFailedToSavePlaylist(MpdAckError): +class MpdFailedToSavePlaylistError(MpdAckError): # NOTE: This is a custom error for Mopidy that does not exist in MPD. error_code = 0 def __init__(self, backend_scheme, *args, **kwargs): super().__init__(*args, **kwargs) - self.message = ( - f'Backend with scheme "{backend_scheme}" failed to save playlist' - ) + self.message = f'Backend with scheme "{backend_scheme}" failed to save playlist' -class MpdDisabled(MpdAckError): +class MpdDisabledError(MpdAckError): # NOTE: This is a custom error for Mopidy that does not exist in MPD. error_code = 0 diff --git a/mopidy_mpd/formatting.py b/mopidy_mpd/formatting.py index 2236246..bd0cdd5 100644 --- a/mopidy_mpd/formatting.py +++ b/mopidy_mpd/formatting.py @@ -1,4 +1,4 @@ -def indent(string, places=4, linebreak="\n", singles=False): +def indent(string, *, places=4, linebreak="\n", singles=False): lines = string.split(linebreak) if not singles and len(lines) == 1: return string diff --git a/mopidy_mpd/network.py b/mopidy_mpd/network.py index 2e9d63b..54afacb 100644 --- a/mopidy_mpd/network.py +++ b/mopidy_mpd/network.py @@ -1,3 +1,4 @@ +import contextlib import errno import logging import os @@ -42,8 +43,7 @@ def get_socket_address(host, port): unix_socket_path = get_unix_socket_path(host) if unix_socket_path is not None: return (unix_socket_path, None) - else: - return (host, port) + return (host, port) class ShouldRetrySocketCallError(Exception): @@ -57,13 +57,13 @@ def try_ipv6_socket(): return False try: socket.socket(socket.AF_INET6).close() - return True except OSError as exc: logger.debug( - f"Platform supports IPv6, but socket creation failed, " - f"disabling: {exc}" + f"Platform supports IPv6, but socket creation failed, " f"disabling: {exc}" ) - return False + return False + else: + return True #: Boolean value that indicates if creating an IPv6 socket will succeed. @@ -97,8 +97,7 @@ def format_address(address): host, port = address[:2] if port is not None: return f"[{host}]:{port}" - else: - return f"[{host}]" + return f"[{host}]" def format_hostname(hostname): @@ -112,7 +111,7 @@ class Server: """Setup listener and register it with GLib's event loop.""" - def __init__( + def __init__( # noqa: PLR0913 self, host, port, @@ -146,7 +145,7 @@ def create_server_socket(self, host, port): sock = create_tcp_socket() sock.bind((host, port)) - sock.setblocking(False) + sock.setblocking(False) # noqa: FBT003 sock.listen(1) return sock @@ -162,12 +161,12 @@ def stop(self): # clean up the socket file if unix_socket_path is not None: - os.unlink(unix_socket_path) + os.unlink(unix_socket_path) # noqa: PTH108 def register_server_socket(self, fileno): return GLib.io_add_watch(fileno, GLib.IO_IN, self.handle_connection) - def handle_connection(self, fd, flags): + def handle_connection(self, _fd, _flags): try: sock, addr = self.accept_connection() except ShouldRetrySocketCallError: @@ -184,11 +183,12 @@ def accept_connection(self): sock, addr = self.server_socket.accept() if is_unix_socket(sock): addr = (sock.getsockname(), None) - return sock, addr except OSError as e: if e.errno in (errno.EAGAIN, errno.EINTR): - raise ShouldRetrySocketCallError + raise ShouldRetrySocketCallError from None raise + else: + return sock, addr def maximum_connections_exceeded(self): return ( @@ -202,15 +202,11 @@ def number_of_connections(self): def reject_connection(self, sock, addr): # FIXME provide more context in logging? logger.warning("Rejected connection from %s", format_address(addr)) - try: + with contextlib.suppress(OSError): sock.close() - except OSError: - pass def init_connection(self, sock, addr): - Connection( - self.protocol, self.protocol_kwargs, sock, addr, self.timeout - ) + Connection(self.protocol, self.protocol_kwargs, sock, addr, self.timeout) class Connection: @@ -222,8 +218,8 @@ class Connection: # false return value would only tell us that what we thought was registered # is already gone, there is really nothing more we can do. - def __init__(self, protocol, protocol_kwargs, sock, addr, timeout): - sock.setblocking(False) + def __init__(self, protocol, protocol_kwargs, sock, addr, timeout): # noqa: PLR0913 + sock.setblocking(False) # noqa: FBT003 self.host, self.port = addr[:2] # IPv6 has larger addr @@ -250,28 +246,24 @@ def stop(self, reason, level=logging.DEBUG): if self.stopping: logger.log(level, f"Already stopping: {reason}") return - else: - self.stopping = True + + self.stopping = True logger.log(level, reason) - try: + with contextlib.suppress(pykka.ActorDeadError): self.actor_ref.stop(block=False) - except pykka.ActorDeadError: - pass self.disable_timeout() self.disable_recv() self.disable_send() - try: + with contextlib.suppress(OSError): self._sock.close() - except OSError: - pass def queue_send(self, data): """Try to send data to client exactly as is and queue rest.""" - self.send_lock.acquire(True) + self.send_lock.acquire(blocking=True) self.send_buffer = self.send(self.send_buffer + data) self.send_lock.release() if self.send_buffer: @@ -294,9 +286,7 @@ def enable_timeout(self): return self.disable_timeout() - self.timeout_id = GLib.timeout_add_seconds( - self.timeout, self.timeout_callback - ) + self.timeout_id = GLib.timeout_add_seconds(self.timeout, self.timeout_callback) def disable_timeout(self): """Deactivate timeout mechanism.""" @@ -344,7 +334,7 @@ def disable_send(self): GLib.source_remove(self.send_id) self.send_id = None - def recv_callback(self, fd, flags): + def recv_callback(self, fd, flags): # noqa: ARG002 if flags & (GLib.IO_ERR | GLib.IO_HUP): self.stop(f"Bad client flags: {flags}") return True @@ -368,14 +358,14 @@ def recv_callback(self, fd, flags): return True - def send_callback(self, fd, flags): + def send_callback(self, fd, flags): # noqa: ARG002 if flags & (GLib.IO_ERR | GLib.IO_HUP): self.stop(f"Bad client flags: {flags}") return True # If with can't get the lock, simply try again next time socket is # ready for sending. - if not self.send_lock.acquire(False): + if not self.send_lock.acquire(blocking=False): return True try: @@ -454,14 +444,14 @@ def on_receive(self, message): self.recv_buffer += message["received"] for line in self.parse_lines(): - line = self.decode(line) - if line is not None: - self.on_line_received(line) + decoded_line = self.decode(line) + if decoded_line is not None: + self.on_line_received(decoded_line) if not self.prevent_timeout: self.connection.enable_timeout() - def on_failure(self, exception_type, exception_value, traceback): + def on_failure(self, exception_type, exception_value, traceback): # noqa: ARG002 """Clean up connection resouces when actor fails.""" self.connection.stop("Actor failed.") diff --git a/mopidy_mpd/protocol/__init__.py b/mopidy_mpd/protocol/__init__.py index daf5390..683a854 100644 --- a/mopidy_mpd/protocol/__init__.py +++ b/mopidy_mpd/protocol/__init__.py @@ -29,7 +29,7 @@ def load_protocol_modules(): The protocol modules must be imported to get them registered in :attr:`commands`. """ - from . import ( # noqa + from . import ( # noqa: F401 audio_output, channels, command_list, @@ -121,7 +121,7 @@ def __init__(self): # TODO: consider removing auth_required and list_command in favour of # additional command instances to register in? - def add(self, name, auth_required=True, list_command=True, **validators): + def add(self, name, *, auth_required=True, list_command=True, **validators): # noqa: C901 """Create a decorator that registers a handler and validation rules. Additional keyword arguments are treated as converters/validators to @@ -142,22 +142,24 @@ def add(self, name, auth_required=True, list_command=True, **validators): :param bool list_command: If command should be listed in reflection. """ - def wrapper(func): + def wrapper(func): # noqa: C901 if name in self.handlers: raise ValueError(f"{name} already registered") spec = inspect.getfullargspec(func) defaults = dict( - zip(spec.args[-len(spec.defaults or []) :], spec.defaults or []) + zip( + spec.args[-len(spec.defaults or []) :], + spec.defaults or [], + strict=False, + ) ) if not spec.args and not spec.varargs: raise TypeError("Handler must accept at least one argument.") if len(spec.args) > 1 and spec.varargs: - raise TypeError( - "*args may not be combined with regular arguments" - ) + raise TypeError("*args may not be combined with regular arguments") if not set(validators.keys()).issubset(spec.args): raise TypeError("Validator for non-existent arg passed") @@ -173,18 +175,18 @@ def validate(*args, **kwargs): ba = inspect.signature(func).bind(*args, **kwargs) ba.apply_defaults() callargs = ba.arguments - except TypeError: + except TypeError as exc: raise exceptions.MpdArgError( f'wrong number of arguments for "{name}"' - ) + ) from exc for key, value in callargs.items(): default = defaults.get(key, object()) if key in validators and value != default: try: callargs[key] = validators[key](value) - except ValueError: - raise exceptions.MpdArgError("incorrect arguments") + except ValueError as exc: + raise exceptions.MpdArgError("incorrect arguments") from exc return func(**callargs) @@ -206,9 +208,9 @@ def call(self, tokens, context=None): :type context: :class:`~mopidy_mpd.dispatcher.MpdContext` """ if not tokens: - raise exceptions.MpdNoCommand() + raise exceptions.MpdNoCommandError if tokens[0] not in self.handlers: - raise exceptions.MpdUnknownCommand(command=tokens[0]) + raise exceptions.MpdUnknownCommandError(command=tokens[0]) return self.handlers[tokens[0]](context, *tokens[1:]) diff --git a/mopidy_mpd/protocol/channels.py b/mopidy_mpd/protocol/channels.py index c6f618a..fe65675 100644 --- a/mopidy_mpd/protocol/channels.py +++ b/mopidy_mpd/protocol/channels.py @@ -13,7 +13,7 @@ def subscribe(context, channel): underscore, dash, dot and colon. """ # TODO: match channel against [A-Za-z0-9:._-]+ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("unsubscribe") @@ -26,7 +26,7 @@ def unsubscribe(context, channel): Unsubscribe from a channel. """ # TODO: match channel against [A-Za-z0-9:._-]+ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("channels") @@ -39,7 +39,7 @@ def channels(context): Obtain a list of all channels. The response is a list of "channel:" lines. """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("readmessages") @@ -52,7 +52,7 @@ def readmessages(context): Reads messages for this client. The response is a list of "channel:" and "message:" lines. """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("sendmessage") @@ -65,4 +65,4 @@ def sendmessage(context, channel, text): Send a message to the specified channel. """ # TODO: match channel against [A-Za-z0-9:._-]+ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO diff --git a/mopidy_mpd/protocol/command_list.py b/mopidy_mpd/protocol/command_list.py index 4974d1a..854a5d9 100644 --- a/mopidy_mpd/protocol/command_list.py +++ b/mopidy_mpd/protocol/command_list.py @@ -28,7 +28,7 @@ def command_list_end(context): """See :meth:`command_list_begin()`.""" # TODO: batch consecutive add commands if not context.dispatcher.command_list_receiving: - raise exceptions.MpdUnknownCommand(command="command_list_end") + raise exceptions.MpdUnknownCommandError(command="command_list_end") context.dispatcher.command_list_receiving = False (command_list, context.dispatcher.command_list) = ( context.dispatcher.command_list, @@ -44,9 +44,7 @@ def command_list_end(context): command, current_command_list_index=index ) command_list_response.extend(response) - if command_list_response and command_list_response[-1].startswith( - "ACK" - ): + if command_list_response and command_list_response[-1].startswith("ACK"): return command_list_response if command_list_ok: command_list_response.append("list_OK") diff --git a/mopidy_mpd/protocol/connection.py b/mopidy_mpd/protocol/connection.py index 41fdf9d..73b3838 100644 --- a/mopidy_mpd/protocol/connection.py +++ b/mopidy_mpd/protocol/connection.py @@ -51,7 +51,6 @@ def ping(context): Does nothing but return ``OK``. """ - pass @protocol.commands.add("tagtypes") @@ -82,19 +81,20 @@ def tagtypes(context, *parameters): parameters = list(parameters) if parameters: subcommand = parameters.pop(0).lower() - if subcommand not in ("all", "clear", "disable", "enable"): - raise exceptions.MpdArgError("Unknown sub command") - elif subcommand == "all": - context.session.tagtypes.update(tagtype_list.TAGTYPE_LIST) - elif subcommand == "clear": - context.session.tagtypes.clear() - elif subcommand == "disable": - _validate_tagtypes(parameters) - context.session.tagtypes.difference_update(parameters) - elif subcommand == "enable": - _validate_tagtypes(parameters) - context.session.tagtypes.update(parameters) - return + match subcommand: + case "all": + context.session.tagtypes.update(tagtype_list.TAGTYPE_LIST) + case "clear": + context.session.tagtypes.clear() + case "disable": + _validate_tagtypes(parameters) + context.session.tagtypes.difference_update(parameters) + case "enable": + _validate_tagtypes(parameters) + context.session.tagtypes.update(parameters) + case _: + raise exceptions.MpdArgError("Unknown sub command") + return None return [("tagtype", tagtype) for tagtype in context.session.tagtypes] diff --git a/mopidy_mpd/protocol/current_playlist.py b/mopidy_mpd/protocol/current_playlist.py index 687f1e1..a3ebad7 100644 --- a/mopidy_mpd/protocol/current_playlist.py +++ b/mopidy_mpd/protocol/current_playlist.py @@ -1,4 +1,4 @@ -import urllib +from urllib.parse import urlparse from mopidy_mpd import exceptions, protocol, translator @@ -22,9 +22,8 @@ def add(context, uri): # If we have an URI just try and add it directly without bothering with # jumping through browse... - if urllib.parse.urlparse(uri).scheme != "": - if context.core.tracklist.add(uris=[uri]).get(): - return + if urlparse(uri).scheme != "" and context.core.tracklist.add(uris=[uri]).get(): + return try: uris = [] @@ -32,9 +31,7 @@ def add(context, uri): if ref: uris.append(ref.uri) except exceptions.MpdNoExistError as exc: - exc.message = ( # noqa B306: Our own exception - "directory or file not found" - ) + exc.message = "directory or file not found" raise if not uris: @@ -68,9 +65,7 @@ def addid(context, uri, songpos=None): if songpos is not None and songpos > length.get(): raise exceptions.MpdArgError("Bad song index") - tl_tracks = context.core.tracklist.add( - uris=[uri], at_position=songpos - ).get() + tl_tracks = context.core.tracklist.add(uris=[uri], at_position=songpos).get() if not tl_tracks: raise exceptions.MpdNoExistError("No such song") @@ -191,7 +186,7 @@ def playlistfind(context, tag, needle): return translator.track_to_mpd_format( tl_tracks[0], context.session.tagtypes, position=position ) - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("playlistid", tlid=protocol.UINT) @@ -212,11 +207,11 @@ def playlistid(context, tlid=None): return translator.track_to_mpd_format( tl_tracks[0], context.session.tagtypes, position=position ) - else: - return translator.tracks_to_mpd_format( - context.core.tracklist.get_tl_tracks().get(), - context.session.tagtypes, - ) + + return translator.tracks_to_mpd_format( + context.core.tracklist.get_tl_tracks().get(), + context.session.tagtypes, + ) @protocol.commands.add("playlistinfo") @@ -247,7 +242,7 @@ def playlistinfo(context, parameter=None): if end and end > len(tl_tracks): end = None return translator.tracks_to_mpd_format( - tl_tracks, context.session.tagtypes, start, end + tl_tracks, context.session.tagtypes, start=start, end=end ) @@ -265,7 +260,7 @@ def playlistsearch(context, tag, needle): - uses ``filename`` and ``any`` as tags """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("plchanges", version=protocol.INT) @@ -286,12 +281,14 @@ def plchanges(context, version): """ # XXX Naive implementation that returns all tracks as changed tracklist_version = context.core.tracklist.get_version().get() + if version < tracklist_version: return translator.tracks_to_mpd_format( context.core.tracklist.get_tl_tracks().get(), context.session.tagtypes, ) - elif version == tracklist_version: + + if version == tracklist_version: # A version match could indicate this is just a metadata update, so # check for a stream ref and let the client know about the change. stream_title = context.core.playback.get_stream_title().get() @@ -307,6 +304,8 @@ def plchanges(context, version): stream_title=stream_title, ) + return None + @protocol.commands.add("plchangesposid", version=protocol.INT) def plchangesposid(context, version): @@ -331,6 +330,7 @@ def plchangesposid(context, version): result.append(("cpos", position)) result.append(("Id", tlid)) return result + return None @protocol.commands.add("prio", priority=protocol.UINT, position=protocol.RANGE) @@ -346,7 +346,7 @@ def prio(context, priority, position): A priority is an integer between 0 and 255. The default priority of new songs is 0. """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("prioid") @@ -358,7 +358,7 @@ def prioid(context, *args): Same as prio, but address the songs with their id. """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("rangeid", tlid=protocol.UINT, songrange=protocol.RANGE) @@ -377,7 +377,7 @@ def rangeid(context, tlid, songrange): .. versionadded:: 0.19 New in MPD protocol version 0.19 """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("shuffle", songrange=protocol.RANGE) @@ -445,7 +445,7 @@ def addtagid(context, tlid, tag, value): .. versionadded:: 0.19 New in MPD protocol version 0.19 """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("cleartagid", tlid=protocol.UINT) @@ -462,4 +462,4 @@ def cleartagid(context, tlid, tag): .. versionadded:: 0.19 New in MPD protocol version 0.19 """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO diff --git a/mopidy_mpd/protocol/mount.py b/mopidy_mpd/protocol/mount.py index e0d61f0..f75eb99 100644 --- a/mopidy_mpd/protocol/mount.py +++ b/mopidy_mpd/protocol/mount.py @@ -15,7 +15,7 @@ def mount(context, path, uri): .. versionadded:: 0.19 New in MPD protocol version 0.19 """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("unmount") @@ -32,7 +32,7 @@ def unmount(context, path): .. versionadded:: 0.19 New in MPD protocol version 0.19 """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("listmounts") @@ -55,7 +55,7 @@ def listmounts(context): .. versionadded:: 0.19 New in MPD protocol version 0.19 """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("listneighbors") @@ -77,4 +77,4 @@ def listneighbors(context): .. versionadded:: 0.19 New in MPD protocol version 0.19 """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO diff --git a/mopidy_mpd/protocol/music_db.py b/mopidy_mpd/protocol/music_db.py index 5c90d61..6abd539 100644 --- a/mopidy_mpd/protocol/music_db.py +++ b/mopidy_mpd/protocol/music_db.py @@ -2,6 +2,7 @@ import itertools from mopidy.models import Track + from mopidy_mpd import exceptions, protocol, translator _LIST_MAPPING = { @@ -41,7 +42,7 @@ "uri": "file", } -_SEARCH_MAPPING = dict(_LIST_MAPPING, **{"any": "any"}) +_SEARCH_MAPPING = dict(_LIST_MAPPING, any="any") def _query_from_mpd_search_parameters(parameters, mapping): @@ -80,9 +81,7 @@ def _album_as_track(album): def _artist_as_track(artist): - return Track( - uri=artist.uri, name="Artist: " + artist.name, artists=[artist] - ) + return Track(uri=artist.uri, name="Artist: " + artist.name, artists=[artist]) @protocol.commands.add("count") @@ -101,8 +100,8 @@ def count(context, *args): """ try: query = _query_from_mpd_search_parameters(args, _SEARCH_MAPPING) - except ValueError: - raise exceptions.MpdArgError("incorrect arguments") + except ValueError as exc: + raise exceptions.MpdArgError("incorrect arguments") from exc results = context.core.library.search(query=query, exact=True).get() result_tracks = _get_tracks(results) total_length = sum(t.length for t in result_tracks if t.length) @@ -141,7 +140,7 @@ def find(context, *args): try: query = _query_from_mpd_search_parameters(args, _SEARCH_MAPPING) except ValueError: - return + return None results = context.core.library.search(query=query, exact=True).get() result_tracks = [] @@ -155,9 +154,7 @@ def find(context, *args): if "album" not in query: result_tracks += [_album_as_track(a) for a in _get_albums(results)] result_tracks += _get_tracks(results) - return translator.tracks_to_mpd_format( - result_tracks, context.session.tagtypes - ) + return translator.tracks_to_mpd_format(result_tracks, context.session.tagtypes) @protocol.commands.add("findadd") @@ -177,9 +174,7 @@ def findadd(context, *args): results = context.core.library.search(query=query, exact=True).get() - context.core.tracklist.add( - uris=[track.uri for track in _get_tracks(results)] - ).get() + context.core.tracklist.add(uris=[track.uri for track in _get_tracks(results)]).get() @protocol.commands.add("list") @@ -279,10 +274,10 @@ def list_(context, *args): try: query = _query_from_mpd_search_parameters(params, _SEARCH_MAPPING) except exceptions.MpdArgError as exc: - exc.message = "Unknown filter type" # noqa B306: Our own exception + exc.message = "Unknown filter type" # B306: Our own exception raise except ValueError: - return + return None name = _LIST_NAME_MAPPING[field] result = context.core.library.get_distinct(field, query) @@ -342,9 +337,7 @@ def listallinfo(context, uri=None): for tracks in lookup_future.get().values(): for track in tracks: result.extend( - translator.track_to_mpd_format( - track, context.session.tagtypes - ) + translator.track_to_mpd_format(track, context.session.tagtypes) ) return result @@ -370,7 +363,7 @@ def listfiles(context, uri=None): .. versionadded:: 0.19 New in MPD protocol version 0.19 """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("lsinfo") @@ -450,7 +443,7 @@ def search(context, *args): try: query = _query_from_mpd_search_parameters(args, _SEARCH_MAPPING) except ValueError: - return + return None results = context.core.library.search(query).get() artists = [_artist_as_track(a) for a in _get_artists(results)] albums = [_album_as_track(a) for a in _get_albums(results)] @@ -480,9 +473,7 @@ def searchadd(context, *args): results = context.core.library.search(query).get() - context.core.tracklist.add( - uris=[track.uri for track in _get_tracks(results)] - ).get() + context.core.tracklist.add(uris=[track.uri for track in _get_tracks(results)]).get() @protocol.commands.add("searchaddpl") @@ -560,4 +551,3 @@ def readcomments(context, uri): The meaning of these depends on the codec, and not all decoder plugins support it. For example, on Ogg files, this lists the Vorbis comments. """ - pass diff --git a/mopidy_mpd/protocol/playback.py b/mopidy_mpd/protocol/playback.py index dae801f..dc3e4dc 100644 --- a/mopidy_mpd/protocol/playback.py +++ b/mopidy_mpd/protocol/playback.py @@ -1,4 +1,5 @@ from mopidy.core import PlaybackState + from mopidy_mpd import exceptions, protocol @@ -25,7 +26,7 @@ def crossfade(context, seconds): Sets crossfading between songs. """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("mixrampdb") @@ -42,7 +43,7 @@ def mixrampdb(context, decibels): tags crossfading will be used. See https://sourceforge.net/projects/mixramp/ """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("mixrampdelay", seconds=protocol.UINT) @@ -56,7 +57,7 @@ def mixrampdelay(context, seconds): value of "nan" disables MixRamp overlapping and falls back to crossfading. """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("next") @@ -170,21 +171,22 @@ def play(context, songpos=None): """ if songpos is None: return context.core.playback.play().get() - elif songpos == -1: + + if songpos == -1: return _play_minus_one(context) try: tl_track = context.core.tracklist.slice(songpos, songpos + 1).get()[0] return context.core.playback.play(tl_track).get() - except IndexError: - raise exceptions.MpdArgError("Bad song index") + except IndexError as exc: + raise exceptions.MpdArgError("Bad song index") from exc def _play_minus_one(context): playback_state = context.core.playback.get_state().get() if playback_state == PlaybackState.PLAYING: - return # Nothing to do - elif playback_state == PlaybackState.PAUSED: + return None # Nothing to do + if playback_state == PlaybackState.PAUSED: return context.core.playback.resume().get() current_tl_track = context.core.playback.get_current_tl_track().get() @@ -195,7 +197,7 @@ def _play_minus_one(context): if tl_tracks: return context.core.playback.play(tl_tracks[0]).get() - return # Fail silently + return None # Fail silently @protocol.commands.add("playid", tlid=protocol.INT) @@ -309,7 +311,7 @@ def replay_gain_mode(context, mode): This command triggers the options idle event. """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("replay_gain_status") @@ -436,14 +438,19 @@ def volume(context, change): Note: ``volume`` is deprecated, use ``setvol`` instead. """ - if change < -100 or change > 100: + min_volume_change = -100 + max_volume_change = 100 + min_volume = 0 + max_volume = 100 + + if change < min_volume_change or change > max_volume_change: raise exceptions.MpdArgError("Invalid volume value") old_volume = context.core.mixer.get_volume().get() if old_volume is None: raise exceptions.MpdSystemError("problems setting volume") - new_volume = min(max(0, old_volume + change), 100) + new_volume = min(max(min_volume, old_volume + change), max_volume) success = context.core.mixer.set_volume(new_volume).get() if not success: raise exceptions.MpdSystemError("problems setting volume") diff --git a/mopidy_mpd/protocol/reflection.py b/mopidy_mpd/protocol/reflection.py index 9feb5d3..96a370c 100644 --- a/mopidy_mpd/protocol/reflection.py +++ b/mopidy_mpd/protocol/reflection.py @@ -90,6 +90,5 @@ def urlhandlers(context): Gets a list of available URL handlers. """ return [ - ("handler", uri_scheme) - for uri_scheme in context.core.get_uri_schemes().get() + ("handler", uri_scheme) for uri_scheme in context.core.get_uri_schemes().get() ] diff --git a/mopidy_mpd/protocol/status.py b/mopidy_mpd/protocol/status.py index 54512ce..c88f949 100644 --- a/mopidy_mpd/protocol/status.py +++ b/mopidy_mpd/protocol/status.py @@ -1,6 +1,6 @@ import pykka - from mopidy.core import PlaybackState + from mopidy_mpd import exceptions, protocol, translator #: Subsystems that can be registered with idle command. @@ -26,7 +26,7 @@ def clearerror(context): Clears the current error message in status (this is also accomplished by any command that starts playback). """ - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO @protocol.commands.add("currentsong") @@ -49,6 +49,7 @@ def currentsong(context): stream_title=stream_title, tagtypes=context.session.tagtypes, ) + return None @protocol.commands.add("idle") @@ -96,7 +97,7 @@ def idle(context, *subsystems): active = context.subscriptions.intersection(context.events) if not active: context.session.prevent_timeout = True - return + return None response = [] context.events = set() @@ -196,9 +197,7 @@ def status(context): "playback.current_tl_track": tl_track, "tracklist.index": context.core.tracklist.index(tl_track.get()), "tracklist.next_tlid": next_tlid, - "tracklist.next_index": context.core.tracklist.index( - tlid=next_tlid.get() - ), + "tracklist.next_index": context.core.tracklist.index(tlid=next_tlid.get()), "playback.time_position": context.core.playback.get_time_position(), } pykka.get_all(futures.values()) @@ -239,10 +238,7 @@ def _status_bitrate(futures): def _status_consume(futures): - if futures["tracklist.consume"].get(): - return 1 - else: - return 0 + return int(futures["tracklist.consume"].get()) def _status_playlist_length(futures): @@ -269,8 +265,7 @@ def _status_songid(futures): current_tl_track = futures["playback.current_tl_track"].get() if current_tl_track is not None: return current_tl_track.tlid - else: - return _status_songpos(futures) + return _status_songpos(futures) def _status_songpos(futures): @@ -286,13 +281,15 @@ def _status_nextsongpos(futures): def _status_state(futures): - state = futures["playback.state"].get() - if state == PlaybackState.PLAYING: - return "play" - elif state == PlaybackState.STOPPED: - return "stop" - elif state == PlaybackState.PAUSED: - return "pause" + match futures["playback.state"].get(): + case PlaybackState.PLAYING: + return "play" + case PlaybackState.STOPPED: + return "stop" + case PlaybackState.PAUSED: + return "pause" + case _: + return None def _status_time(futures): @@ -308,20 +305,16 @@ def _status_time_elapsed(futures): def _status_time_total(futures): current_tl_track = futures["playback.current_tl_track"].get() - if current_tl_track is None: - return 0 - elif current_tl_track.track.length is None: + if current_tl_track is None or current_tl_track.track.length is None: return 0 - else: - return current_tl_track.track.length + return current_tl_track.track.length def _status_volume(futures): volume = futures["mixer.volume"].get() - if volume is not None: - return volume - else: + if volume is None: return -1 + return volume def _status_xfade(futures): diff --git a/mopidy_mpd/protocol/stickers.py b/mopidy_mpd/protocol/stickers.py index 82d95ad..b67ecf6 100644 --- a/mopidy_mpd/protocol/stickers.py +++ b/mopidy_mpd/protocol/stickers.py @@ -2,7 +2,7 @@ @protocol.commands.add("sticker", list_command=False) -def sticker(context, action, field, uri, name=None, value=None): +def sticker(context, action, field, uri, name=None, value=None): # noqa: PLR0913 """ *musicpd.org, sticker section:* @@ -33,4 +33,4 @@ def sticker(context, action, field, uri, name=None, value=None): """ # TODO: check that action in ('list', 'find', 'get', 'set', 'delete') # TODO: check name/value matches with action - raise exceptions.MpdNotImplemented # TODO + raise exceptions.MpdNotImplementedError # TODO diff --git a/mopidy_mpd/protocol/stored_playlists.py b/mopidy_mpd/protocol/stored_playlists.py index ba3ea12..009f78d 100644 --- a/mopidy_mpd/protocol/stored_playlists.py +++ b/mopidy_mpd/protocol/stored_playlists.py @@ -10,10 +10,10 @@ def _check_playlist_name(name): if re.search("[/\n\r]", name): - raise exceptions.MpdInvalidPlaylistName() + raise exceptions.MpdInvalidPlaylistNameError -def _get_playlist(context, name, must_exist=True): +def _get_playlist(context, name, *, must_exist=True): playlist = None uri = context.lookup_playlist_uri_from_name(name) if uri: @@ -112,11 +112,10 @@ def _get_last_modified(last_modified=None): """ if last_modified is None: # If unknown, assume the playlist is modified - dt = datetime.datetime.utcnow() + dt = datetime.datetime.now(tz=datetime.UTC) else: - dt = datetime.datetime.utcfromtimestamp(last_modified / 1000.0) - dt = dt.replace(microsecond=0) - return f"{dt.isoformat()}Z" + dt = datetime.datetime.fromtimestamp(last_modified / 1000.0, tz=datetime.UTC) + return dt.isoformat(timespec="seconds").replace("+00:00", "Z") DEFAULT_PLAYLIST_SLICE = slice(0, None) @@ -165,9 +164,7 @@ def playlistadd(context, name, track_uri): if not old_playlist: # Create new playlist with this single track lookup_res = context.core.library.lookup(uris=[track_uri]).get() - tracks = [ - track for uri_tracks in lookup_res.values() for track in uri_tracks - ] + tracks = [track for uri_tracks in lookup_res.values() for track in uri_tracks] _create_playlist(context, name, tracks) else: # Add track to existing playlist @@ -182,7 +179,7 @@ def playlistadd(context, name, track_uri): if saved_playlist is None: playlist_scheme = urllib.parse.urlparse(old_playlist.uri).scheme uri_scheme = urllib.parse.urlparse(track_uri).scheme - raise exceptions.MpdInvalidTrackForPlaylist( + raise exceptions.MpdInvalidTrackForPlaylistError( playlist_scheme, uri_scheme ) @@ -201,20 +198,19 @@ def _create_playlist(context, name, tracks): saved_playlist = context.core.playlists.save(new_playlist).get() if saved_playlist is not None: return # Created and saved - else: - continue # Failed to save using this backend + continue # Failed to save using this backend # Can't use backend appropriate for passed URI schemes, use default one default_scheme = context.dispatcher.config["mpd"]["default_playlist_scheme"] new_playlist = context.core.playlists.create(name, default_scheme).get() if new_playlist is None: # If even MPD's default backend can't save playlist, everything is lost logger.warning("MPD's default backend can't create playlists") - raise exceptions.MpdFailedToSavePlaylist(default_scheme) + raise exceptions.MpdFailedToSavePlaylistError(default_scheme) new_playlist = new_playlist.replace(tracks=tracks) saved_playlist = context.core.playlists.save(new_playlist).get() if saved_playlist is None: uri_scheme = urllib.parse.urlparse(new_playlist.uri).scheme - raise exceptions.MpdFailedToSavePlaylist(uri_scheme) + raise exceptions.MpdFailedToSavePlaylistError(uri_scheme) @protocol.commands.add("playlistclear") @@ -236,7 +232,7 @@ def playlistclear(context, name): # Just replace tracks with empty list and save playlist = playlist.replace(tracks=[]) if context.core.playlists.save(playlist).get() is None: - raise exceptions.MpdFailedToSavePlaylist( + raise exceptions.MpdFailedToSavePlaylistError( urllib.parse.urlparse(playlist.uri).scheme ) @@ -257,21 +253,19 @@ def playlistdelete(context, name, songpos): # Convert tracks to list and remove requested tracks = list(playlist.tracks) tracks.pop(songpos) - except IndexError: - raise exceptions.MpdArgError("Bad song index") + except IndexError as exc: + raise exceptions.MpdArgError("Bad song index") from exc # Replace tracks and save playlist playlist = playlist.replace(tracks=tracks) saved_playlist = context.core.playlists.save(playlist).get() if saved_playlist is None: - raise exceptions.MpdFailedToSavePlaylist( + raise exceptions.MpdFailedToSavePlaylistError( urllib.parse.urlparse(playlist.uri).scheme ) -@protocol.commands.add( - "playlistmove", from_pos=protocol.UINT, to_pos=protocol.UINT -) +@protocol.commands.add("playlistmove", from_pos=protocol.UINT, to_pos=protocol.UINT) def playlistmove(context, name, from_pos, to_pos): """ *musicpd.org, stored playlists section:* @@ -300,14 +294,14 @@ def playlistmove(context, name, from_pos, to_pos): tracks = list(playlist.tracks) track = tracks.pop(from_pos) tracks.insert(to_pos, track) - except IndexError: - raise exceptions.MpdArgError("Bad song index") + except IndexError as exc: + raise exceptions.MpdArgError("Bad song index") from exc # Replace tracks and save playlist playlist = playlist.replace(tracks=tracks) saved_playlist = context.core.playlists.save(playlist).get() if saved_playlist is None: - raise exceptions.MpdFailedToSavePlaylist( + raise exceptions.MpdFailedToSavePlaylistError( urllib.parse.urlparse(playlist.uri).scheme ) @@ -337,7 +331,7 @@ def rename(context, old_name, new_name): saved_playlist = context.core.playlists.save(new_playlist).get() if saved_playlist is None: - raise exceptions.MpdFailedToSavePlaylist(uri_scheme) + raise exceptions.MpdFailedToSavePlaylistError(uri_scheme) context.core.playlists.delete(old_playlist.uri).get() @@ -378,6 +372,6 @@ def save(context, name): new_playlist = playlist.replace(tracks=tracks) saved_playlist = context.core.playlists.save(new_playlist).get() if saved_playlist is None: - raise exceptions.MpdFailedToSavePlaylist( + raise exceptions.MpdFailedToSavePlaylistError( urllib.parse.urlparse(playlist.uri).scheme ) diff --git a/mopidy_mpd/tokenize.py b/mopidy_mpd/tokenize.py index d80e562..84fe81d 100644 --- a/mopidy_mpd/tokenize.py +++ b/mopidy_mpd/tokenize.py @@ -18,14 +18,13 @@ r""" ^ # Leading whitespace is not allowed (?: - ([^%(unprintable)s"']+) # ord(char) < 0x20, not ", not ' + ([^{unprintable}"']+) # ord(char) < 0x20, not ", not ' | # or "([^"\\]*(?:\\.[^"\\]*)*)" # anything surrounded by quotes ) (?:\s+|$) # trailing whitespace or EOS (.*) # Possibly a remainder to be parsed - """ - % {"unprintable": "".join(map(chr, range(0x21)))}, + """.format(unprintable="".join(map(chr, range(0x21)))), re.VERBOSE, ) @@ -64,7 +63,7 @@ def split(line): For examples see the tests for this function. """ if not line.strip(): - raise exceptions.MpdNoCommand("No command given") + raise exceptions.MpdNoCommandError("No command given") match = WORD_RE.match(line) if not match: raise exceptions.MpdUnknownError("Invalid word character") @@ -87,9 +86,8 @@ def _determine_error_message(remainder): """Helper to emulate MPD errors.""" # Following checks are simply to match MPD error messages: match = BAD_QUOTED_PARAM_RE.match(remainder) - if match: - if match.group(1): - return "Space expected after closing '\"'" - else: - return "Missing closing '\"'" - return "Invalid unquoted character" + if not match: + return "Invalid unquoted character" + if match.group(1): + return "Space expected after closing '\"'" + return "Missing closing '\"'" diff --git a/mopidy_mpd/translator.py b/mopidy_mpd/translator.py index bc0176f..52aeece 100644 --- a/mopidy_mpd/translator.py +++ b/mopidy_mpd/translator.py @@ -1,24 +1,14 @@ import datetime import logging -import re from mopidy.models import TlTrack + from mopidy_mpd.protocol import tagtype_list logger = logging.getLogger(__name__) -# TODO: special handling of local:// uri scheme -normalize_path_re = re.compile(r"[^/]+") - - -def normalize_path(path, relative=False): - parts = normalize_path_re.findall(path or "") - if not relative: - parts.insert(0, "") - return "/".join(parts) - -def track_to_mpd_format(track, tagtypes, position=None, stream_title=None): +def track_to_mpd_format(track, tagtypes, *, position=None, stream_title=None): # noqa: C901, PLR0912 """ Format track for output to MPD client. @@ -33,7 +23,7 @@ def track_to_mpd_format(track, tagtypes, position=None, stream_title=None): if isinstance(track, TlTrack): (tlid, track) = track else: - (tlid, track) = (None, track) + (tlid, track) = (None, track) # noqa: PLW0127 if not track.uri: logger.warning("Ignoring track without uri") @@ -57,9 +47,7 @@ def track_to_mpd_format(track, tagtypes, position=None, stream_title=None): result.append(("Date", track.date)) if track.album is not None and track.album.num_tracks is not None: - result.append( - ("Track", f"{track.track_no or 0}/{track.album.num_tracks}") - ) + result.append(("Track", f"{track.track_no or 0}/{track.album.num_tracks}")) else: result.append(("Track", track.track_no or 0)) if position is not None and tlid is not None: @@ -71,9 +59,7 @@ def track_to_mpd_format(track, tagtypes, position=None, stream_title=None): if track.album is not None and track.album.artists: result += multi_tag_list(track.album.artists, "name", "AlbumArtist") - musicbrainz_ids = concat_multi_values( - track.album.artists, "musicbrainz_id" - ) + musicbrainz_ids = concat_multi_values(track.album.artists, "musicbrainz_id") if musicbrainz_ids: result.append(("MUSICBRAINZ_ALBUMARTISTID", musicbrainz_ids)) @@ -95,10 +81,10 @@ def track_to_mpd_format(track, tagtypes, position=None, stream_title=None): result.append(("Disc", track.disc_no)) if track.last_modified: - datestring = datetime.datetime.utcfromtimestamp( - track.last_modified // 1000 - ).isoformat() - result.append(("Last-Modified", datestring + "Z")) + datestring = datetime.datetime.fromtimestamp( + track.last_modified // 1000, tz=datetime.UTC + ).isoformat(timespec="seconds") + result.append(("Last-Modified", datestring.replace("+00:00", "Z"))) if track.musicbrainz_id is not None: result.append(("MUSICBRAINZ_TRACKID", track.musicbrainz_id)) @@ -122,10 +108,9 @@ def _has_value(tagtypes, tagtype, value): :rtype: bool """ if tagtype in tagtype_list.TAGTYPE_LIST: - if tagtype in tagtypes: - return bool(value) - else: + if tagtype not in tagtypes: return False + return bool(value) return True @@ -144,9 +129,7 @@ def concat_multi_values(models, attribute): # strict alphabetical). If we just use them in the order in which they come # in then the musicbrainz ids have a higher chance of staying in sync return ";".join( - getattr(m, attribute) - for m in models - if getattr(m, attribute, None) is not None + getattr(m, attribute) for m in models if getattr(m, attribute, None) is not None ) @@ -172,7 +155,7 @@ def multi_tag_list(objects, attribute, tag): ] -def tracks_to_mpd_format(tracks, tagtypes, start=0, end=None): +def tracks_to_mpd_format(tracks, tagtypes, *, start=0, end=None): """ Format list of tracks for output to MPD client. @@ -193,17 +176,17 @@ def tracks_to_mpd_format(tracks, tagtypes, start=0, end=None): positions = range(start, end) assert len(tracks) == len(positions) result = [] - for track, position in zip(tracks, positions): - formatted_track = track_to_mpd_format(track, tagtypes, position) + for track, position in zip(tracks, positions, strict=False): + formatted_track = track_to_mpd_format(track, tagtypes, position=position) if formatted_track: result.append(formatted_track) return result -def playlist_to_mpd_format(playlist, tagtypes, *args, **kwargs): +def playlist_to_mpd_format(playlist, tagtypes, *, start=0, end=None): """ Format playlist for output to MPD client. Arguments as for :func:`tracks_to_mpd_format`, except the first one. """ - return tracks_to_mpd_format(playlist.tracks, tagtypes, *args, **kwargs) + return tracks_to_mpd_format(playlist.tracks, tagtypes, start=start, end=end) diff --git a/mopidy_mpd/uri_mapper.py b/mopidy_mpd/uri_mapper.py index 5896614..407cdc9 100644 --- a/mopidy_mpd/uri_mapper.py +++ b/mopidy_mpd/uri_mapper.py @@ -33,7 +33,7 @@ def _create_unique_name(self, name, uri): i += 1 return name - def insert(self, name, uri, playlist=False): + def insert(self, name, uri, *, playlist=False): """ Create a unique and MPD compatible name that maps to the given URI. """ diff --git a/pyproject.toml b/pyproject.toml index 04a6524..676792f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,17 +1,106 @@ [build-system] -requires = ["setuptools >= 30.3.0", "wheel"] +requires = ["setuptools >= 66", "setuptools-scm >= 7.1"] +build-backend = "setuptools.build_meta" -[tool.black] -target-version = ["py39", "py310", "py311"] -line-length = 80 +[project] +name = "Mopidy-MPD" +version = "4.0.0a1" +description = "Mopidy extension for controlling Mopidy from MPD clients" +readme = "README.rst" +requires-python = ">= 3.11" +license = { text = "Apache-2.0" } +authors = [{ name = "Stein Magnus Jodal", email = "stein.magnus@jodal.no" }] +classifiers = [ + "Environment :: No Input/Output (Daemon)", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Multimedia :: Sound/Audio :: Players", +] +dependencies = ["mopidy >= 3.3.0", "pykka >= 4.0", "setuptools >= 66"] +[project.optional-dependencies] +lint = ["ruff"] +test = ["pytest", "pytest-cov"] +typing = ["pyright"] +dev = ["mopidy-mpd[lint,test,typing]", "tox"] -[tool.isort] -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true -line_length = 88 -known_tests = "tests" -sections = "FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,TESTS,LOCALFOLDER" +[project.urls] +Source = "https://github.com/mopidy/mopidy-mpd" +Issues = "https://github.com/mopidy/mopidy-mpd/issues" + +[project.entry-points."mopidy.ext"] +mpd = "mopidy_mpd:Extension" + + +[tool.ruff] +target-version = "py311" + +[tool.ruff.lint] +select = [ + "A", # flake8-builtins + "ANN", # flake8-annotations + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "C90", # mccabe + "D", # pydocstyle + "DTZ", # flake8-datetimez + "E", # pycodestyle + "ERA", # eradicate + "F", # pyflakes + "FBT", # flake8-boolean-trap + "I", # isort + "INP", # flake8-no-pep420 + "ISC", # flake8-implicit-str-concat + "N", # pep8-naming + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PLC", # pylint convention + "PLE", # pylint error + "PLR", # pylint refactor + "PLW", # pylint warning + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "Q", # flake8-quotes + "RET", # flake8-return + "RSE", # flake8-raise + "RUF", # ruff + "SIM", # flake8-simplify + "SLF", # flake8-self + "T20", # flake8-print + "TCH", # flake8-type-checking + "TID", # flake8-tidy-imports + "TRY", # tryceratops + "UP", # pyupgrade + "W", # pycodestyle +] +ignore = [ + "ANN", # flake8-annotations + "D", # pydocstyle + "ISC001", # single-line-implicit-string-concatenation + "TRY003", # raise-vanilla-args +] + +[tool.ruff.lint.per-file-ignores] +"mopidy_mpd/protocol/*" = [ + "ARG001", # unused-function-argument +] +"tests/*" = [ + "ANN", # flake8-annotations + "ARG", # flake8-unused-arguments + "D", # pydocstyle + "FBT", # flake8-boolean-trap + "PLR0913", # too-many-arguments + "PLR2004", # magic-value-comparison + "PT027", # pytest-unittest-raises-assertion + "SLF001", # private-member-access + "TRY002", # raise-vanilla-class +] + + +[tool.setuptools_scm] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 05b6f3e..0000000 --- a/setup.cfg +++ /dev/null @@ -1,83 +0,0 @@ -[metadata] -name = Mopidy-MPD -version = 3.3.0 -url = https://github.com/mopidy/mopidy-mpd -author = Stein Magnus Jodal -author_email = stein.magnus@jodal.no -license = Apache License, Version 2.0 -license_file = LICENSE -description = Mopidy extension for controlling Mopidy from MPD clients -long_description = file: README.rst -classifiers = - Environment :: No Input/Output (Daemon) - Intended Audience :: End Users/Desktop - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Topic :: Multimedia :: Sound/Audio :: Players - - -[options] -zip_safe = False -include_package_data = True -packages = find: -python_requires = >= 3.9 -install_requires = - Mopidy >= 3.3.0 - Pykka >= 2.0.1 - setuptools - - -[options.extras_require] -lint = - black - check-manifest - flake8 - flake8-black - flake8-bugbear - flake8-import-order - isort[pyproject] -test = - pytest - pytest-cov -dev = - %(lint)s - %(test)s - - -[options.packages.find] -exclude = - tests - tests.* - - -[options.entry_points] -mopidy.ext = - mpd = mopidy_mpd:Extension - - -[flake8] -application-import-names = mopidy_mpd, tests -max-line-length = 80 -exclude = .git, .tox, build -select = - # Regular flake8 rules - C, E, F, W - # flake8-bugbear rules - B - # B950: line too long (soft speed limit) - B950 - # pep8-naming rules - N -ignore = - # E203: whitespace before ':' (not PEP8 compliant) - E203 - # E501: line too long (replaced by B950) - E501 - # W503: line break before binary operator (not PEP8 compliant) - W503 - # B305: .next() is not a thing on Python 3 (used by playback controller) - B305 diff --git a/setup.py b/setup.py deleted file mode 100644 index 6068493..0000000 --- a/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -from setuptools import setup - -setup() diff --git a/tests/__init__.py b/tests/__init__.py index f807495..f03d5f9 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -6,7 +6,7 @@ def __eq__(self, rhs): try: return isinstance(rhs, self.klass) except TypeError: - return type(rhs) == type(self.klass) # noqa + return type(rhs) == type(self.klass) # noqa: E721 def __ne__(self, rhs): return not self.__eq__(rhs) diff --git a/tests/dummy_audio.py b/tests/dummy_audio.py index 14acbbd..d5be9d1 100644 --- a/tests/dummy_audio.py +++ b/tests/dummy_audio.py @@ -6,7 +6,6 @@ import pykka - from mopidy import audio diff --git a/tests/dummy_backend.py b/tests/dummy_backend.py index 0ee3d2d..d4c9b6d 100644 --- a/tests/dummy_backend.py +++ b/tests/dummy_backend.py @@ -6,7 +6,6 @@ import pykka - from mopidy import backend from mopidy.models import Playlist, Ref, SearchResult @@ -109,14 +108,12 @@ def set_allow_save(self, enabled): self._allow_save = enabled def as_list(self): - return [ - Ref.playlist(uri=pl.uri, name=pl.name) for pl in self._playlists - ] + return [Ref.playlist(uri=pl.uri, name=pl.name) for pl in self._playlists] def get_items(self, uri): playlist = self.lookup(uri) if playlist is None: - return + return None return [Ref.track(uri=t.uri, name=t.name) for t in playlist.tracks] def lookup(self, uri): @@ -124,6 +121,7 @@ def lookup(self, uri): for playlist in self._playlists: if playlist.uri == uri: return playlist + return None def refresh(self): pass diff --git a/tests/dummy_mixer.py b/tests/dummy_mixer.py index 3133412..caadf24 100644 --- a/tests/dummy_mixer.py +++ b/tests/dummy_mixer.py @@ -1,5 +1,4 @@ import pykka - from mopidy import mixer diff --git a/tests/network/test_connection.py b/tests/network/test_connection.py index 05de530..5903a8d 100644 --- a/tests/network/test_connection.py +++ b/tests/network/test_connection.py @@ -8,12 +8,11 @@ from gi.repository import GLib from mopidy_mpd import network - from tests import any_int, any_unicode class ConnectionTest(unittest.TestCase): - def setUp(self): # noqa: N802 + def setUp(self): self.mock = Mock(spec=network.Connection) def test_init_ensure_nonblocking_io(self): @@ -145,8 +144,8 @@ def test_stop_does_not_proceed_when_already_stopping(self): self.mock._sock = Mock(spec=socket.SocketType) network.Connection.stop(self.mock, sentinel.reason) - assert 0 == self.mock.actor_ref.stop.call_count - assert 0 == self.mock._sock.close.call_count + assert self.mock.actor_ref.stop.call_count == 0 + assert self.mock._sock.close.call_count == 0 @patch.object(network.logger, "log", new=Mock()) def test_stop_logs_reason(self): @@ -155,9 +154,7 @@ def test_stop_logs_reason(self): self.mock._sock = Mock(spec=socket.SocketType) network.Connection.stop(self.mock, sentinel.reason) - network.logger.log.assert_called_once_with( - logging.DEBUG, sentinel.reason - ) + network.logger.log.assert_called_once_with(logging.DEBUG, sentinel.reason) @patch.object(network.logger, "log", new=Mock()) def test_stop_logs_reason_with_level(self): @@ -165,12 +162,8 @@ def test_stop_logs_reason_with_level(self): self.mock.actor_ref = Mock() self.mock._sock = Mock(spec=socket.SocketType) - network.Connection.stop( - self.mock, sentinel.reason, level=sentinel.level - ) - network.logger.log.assert_called_once_with( - sentinel.level, sentinel.reason - ) + network.Connection.stop(self.mock, sentinel.reason, level=sentinel.level) + network.logger.log.assert_called_once_with(sentinel.level, sentinel.reason) @patch.object(network.logger, "log", new=Mock()) def test_stop_logs_that_it_is_calling_itself(self): @@ -202,7 +195,7 @@ def test_enable_recv_already_registered(self): self.mock.recv_id = sentinel.tag network.Connection.enable_recv(self.mock) - assert 0 == GLib.io_add_watch.call_count + assert GLib.io_add_watch.call_count == 0 def test_enable_recv_does_not_change_tag(self): self.mock.recv_id = sentinel.tag @@ -224,13 +217,13 @@ def test_disable_recv_already_deregistered(self): self.mock.recv_id = None network.Connection.disable_recv(self.mock) - assert 0 == GLib.source_remove.call_count + assert GLib.source_remove.call_count == 0 assert self.mock.recv_id is None def test_enable_recv_on_closed_socket(self): self.mock.recv_id = None self.mock._sock = Mock(spec=socket.SocketType) - self.mock._sock.fileno.side_effect = socket.error(errno.EBADF, "") + self.mock._sock.fileno.side_effect = OSError(errno.EBADF, "") network.Connection.enable_recv(self.mock) self.mock.stop.assert_called_once_with(any_unicode) @@ -257,7 +250,7 @@ def test_enable_send_already_registered(self): self.mock.send_id = sentinel.tag network.Connection.enable_send(self.mock) - assert 0 == GLib.io_add_watch.call_count + assert GLib.io_add_watch.call_count == 0 def test_enable_send_does_not_change_tag(self): self.mock.send_id = sentinel.tag @@ -279,13 +272,13 @@ def test_disable_send_already_deregistered(self): self.mock.send_id = None network.Connection.disable_send(self.mock) - assert 0 == GLib.source_remove.call_count + assert GLib.source_remove.call_count == 0 assert self.mock.send_id is None def test_enable_send_on_closed_socket(self): self.mock.send_id = None self.mock._sock = Mock(spec=socket.SocketType) - self.mock._sock.fileno.side_effect = socket.error(errno.EBADF, "") + self.mock._sock.fileno.side_effect = OSError(errno.EBADF, "") network.Connection.enable_send(self.mock) assert self.mock.send_id is None @@ -303,37 +296,35 @@ def test_enable_timeout_add_glib_timeout(self): GLib.timeout_add_seconds.return_value = sentinel.tag network.Connection.enable_timeout(self.mock) - GLib.timeout_add_seconds.assert_called_once_with( - 10, self.mock.timeout_callback - ) + GLib.timeout_add_seconds.assert_called_once_with(10, self.mock.timeout_callback) assert sentinel.tag == self.mock.timeout_id @patch.object(GLib, "timeout_add_seconds", new=Mock()) def test_enable_timeout_does_not_add_timeout(self): self.mock.timeout = 0 network.Connection.enable_timeout(self.mock) - assert 0 == GLib.timeout_add_seconds.call_count + assert GLib.timeout_add_seconds.call_count == 0 self.mock.timeout = -1 network.Connection.enable_timeout(self.mock) - assert 0 == GLib.timeout_add_seconds.call_count + assert GLib.timeout_add_seconds.call_count == 0 self.mock.timeout = None network.Connection.enable_timeout(self.mock) - assert 0 == GLib.timeout_add_seconds.call_count + assert GLib.timeout_add_seconds.call_count == 0 def test_enable_timeout_does_not_call_disable_for_invalid_timeout(self): self.mock.timeout = 0 network.Connection.enable_timeout(self.mock) - assert 0 == self.mock.disable_timeout.call_count + assert self.mock.disable_timeout.call_count == 0 self.mock.timeout = -1 network.Connection.enable_timeout(self.mock) - assert 0 == self.mock.disable_timeout.call_count + assert self.mock.disable_timeout.call_count == 0 self.mock.timeout = None network.Connection.enable_timeout(self.mock) - assert 0 == self.mock.disable_timeout.call_count + assert self.mock.disable_timeout.call_count == 0 @patch.object(GLib, "source_remove", new=Mock()) def test_disable_timeout_deregisters(self): @@ -348,7 +339,7 @@ def test_disable_timeout_already_deregistered(self): self.mock.timeout_id = None network.Connection.disable_timeout(self.mock) - assert 0 == GLib.source_remove.call_count + assert GLib.source_remove.call_count == 0 assert self.mock.timeout_id is None def test_queue_send_acquires_and_releases_lock(self): @@ -356,7 +347,7 @@ def test_queue_send_acquires_and_releases_lock(self): self.mock.send_buffer = b"" network.Connection.queue_send(self.mock, b"data") - self.mock.send_lock.acquire.assert_called_once_with(True) + self.mock.send_lock.acquire.assert_called_once_with(blocking=True) self.mock.send_lock.release.assert_called_once_with() def test_queue_send_calls_send(self): @@ -366,8 +357,8 @@ def test_queue_send_calls_send(self): network.Connection.queue_send(self.mock, b"data") self.mock.send.assert_called_once_with(b"data") - assert 0 == self.mock.enable_send.call_count - assert b"" == self.mock.send_buffer + assert self.mock.enable_send.call_count == 0 + assert self.mock.send_buffer == b"" def test_queue_send_calls_enable_send_for_partial_send(self): self.mock.send_buffer = b"" @@ -377,7 +368,7 @@ def test_queue_send_calls_enable_send_for_partial_send(self): network.Connection.queue_send(self.mock, b"data") self.mock.send.assert_called_once_with(b"data") self.mock.enable_send.assert_called_once_with() - assert b"ta" == self.mock.send_buffer + assert self.mock.send_buffer == b"ta" def test_queue_send_calls_send_with_existing_buffer(self): self.mock.send_buffer = b"foo" @@ -386,8 +377,8 @@ def test_queue_send_calls_send_with_existing_buffer(self): network.Connection.queue_send(self.mock, b"bar") self.mock.send.assert_called_once_with(b"foobar") - assert 0 == self.mock.enable_send.call_count - assert b"" == self.mock.send_buffer + assert self.mock.enable_send.call_count == 0 + assert self.mock.send_buffer == b"" def test_recv_callback_respects_io_err(self): self.mock._sock = Mock(spec=socket.SocketType) @@ -421,9 +412,7 @@ def test_recv_callback_sends_data_to_actor(self): self.mock._sock.recv.return_value = b"data" self.mock.actor_ref = Mock() - assert network.Connection.recv_callback( - self.mock, sentinel.fd, GLib.IO_IN - ) + assert network.Connection.recv_callback(self.mock, sentinel.fd, GLib.IO_IN) self.mock.actor_ref.tell.assert_called_once_with({"received": b"data"}) def test_recv_callback_handles_dead_actors(self): @@ -432,9 +421,7 @@ def test_recv_callback_handles_dead_actors(self): self.mock.actor_ref = Mock() self.mock.actor_ref.tell.side_effect = pykka.ActorDeadError() - assert network.Connection.recv_callback( - self.mock, sentinel.fd, GLib.IO_IN - ) + assert network.Connection.recv_callback(self.mock, sentinel.fd, GLib.IO_IN) self.mock.stop.assert_called_once_with(any_unicode) def test_recv_callback_gets_no_data(self): @@ -442,9 +429,7 @@ def test_recv_callback_gets_no_data(self): self.mock._sock.recv.return_value = b"" self.mock.actor_ref = Mock() - assert network.Connection.recv_callback( - self.mock, sentinel.fd, GLib.IO_IN - ) + assert network.Connection.recv_callback(self.mock, sentinel.fd, GLib.IO_IN) assert self.mock.mock_calls == [ call._sock.recv(any_int), call.disable_recv(), @@ -455,19 +440,15 @@ def test_recv_callback_recoverable_error(self): self.mock._sock = Mock(spec=socket.SocketType) for error in (errno.EWOULDBLOCK, errno.EINTR): - self.mock._sock.recv.side_effect = socket.error(error, "") - assert network.Connection.recv_callback( - self.mock, sentinel.fd, GLib.IO_IN - ) - assert 0 == self.mock.stop.call_count + self.mock._sock.recv.side_effect = OSError(error, "") + assert network.Connection.recv_callback(self.mock, sentinel.fd, GLib.IO_IN) + assert self.mock.stop.call_count == 0 def test_recv_callback_unrecoverable_error(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.recv.side_effect = socket.error - assert network.Connection.recv_callback( - self.mock, sentinel.fd, GLib.IO_IN - ) + assert network.Connection.recv_callback(self.mock, sentinel.fd, GLib.IO_IN) self.mock.stop.assert_called_once_with(any_unicode) def test_send_callback_respects_io_err(self): @@ -513,10 +494,8 @@ def test_send_callback_acquires_and_releases_lock(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.send.return_value = 0 - assert network.Connection.send_callback( - self.mock, sentinel.fd, GLib.IO_IN - ) - self.mock.send_lock.acquire.assert_called_once_with(False) + assert network.Connection.send_callback(self.mock, sentinel.fd, GLib.IO_IN) + self.mock.send_lock.acquire.assert_called_once_with(blocking=False) self.mock.send_lock.release.assert_called_once_with() def test_send_callback_fails_to_acquire_lock(self): @@ -526,11 +505,9 @@ def test_send_callback_fails_to_acquire_lock(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.send.return_value = 0 - assert network.Connection.send_callback( - self.mock, sentinel.fd, GLib.IO_IN - ) - self.mock.send_lock.acquire.assert_called_once_with(False) - assert 0 == self.mock._sock.send.call_count + assert network.Connection.send_callback(self.mock, sentinel.fd, GLib.IO_IN) + self.mock.send_lock.acquire.assert_called_once_with(blocking=False) + assert self.mock._sock.send.call_count == 0 def test_send_callback_sends_all_data(self): self.mock.send_lock = Mock() @@ -538,12 +515,10 @@ def test_send_callback_sends_all_data(self): self.mock.send_buffer = b"data" self.mock.send.return_value = b"" - assert network.Connection.send_callback( - self.mock, sentinel.fd, GLib.IO_IN - ) + assert network.Connection.send_callback(self.mock, sentinel.fd, GLib.IO_IN) self.mock.disable_send.assert_called_once_with() self.mock.send.assert_called_once_with(b"data") - assert b"" == self.mock.send_buffer + assert self.mock.send_buffer == b"" def test_send_callback_sends_partial_data(self): self.mock.send_lock = Mock() @@ -551,40 +526,38 @@ def test_send_callback_sends_partial_data(self): self.mock.send_buffer = b"data" self.mock.send.return_value = b"ta" - assert network.Connection.send_callback( - self.mock, sentinel.fd, GLib.IO_IN - ) + assert network.Connection.send_callback(self.mock, sentinel.fd, GLib.IO_IN) self.mock.send.assert_called_once_with(b"data") - assert b"ta" == self.mock.send_buffer + assert self.mock.send_buffer == b"ta" def test_send_recoverable_error(self): self.mock._sock = Mock(spec=socket.SocketType) for error in (errno.EWOULDBLOCK, errno.EINTR): - self.mock._sock.send.side_effect = socket.error(error, "") + self.mock._sock.send.side_effect = OSError(error, "") network.Connection.send(self.mock, b"data") - assert 0 == self.mock.stop.call_count + assert self.mock.stop.call_count == 0 def test_send_calls_socket_send(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.send.return_value = 4 - assert b"" == network.Connection.send(self.mock, b"data") + assert network.Connection.send(self.mock, b"data") == b"" self.mock._sock.send.assert_called_once_with(b"data") def test_send_calls_socket_send_partial_send(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.send.return_value = 2 - assert b"ta" == network.Connection.send(self.mock, b"data") + assert network.Connection.send(self.mock, b"data") == b"ta" self.mock._sock.send.assert_called_once_with(b"data") def test_send_unrecoverable_error(self): self.mock._sock = Mock(spec=socket.SocketType) self.mock._sock.send.side_effect = socket.error - assert b"" == network.Connection.send(self.mock, b"data") + assert network.Connection.send(self.mock, b"data") == b"" self.mock.stop.assert_called_once_with(any_unicode) def test_timeout_callback(self): @@ -597,10 +570,10 @@ def test_str(self): self.mock.host = "foo" self.mock.port = 999 - assert "[foo]:999" == network.Connection.__str__(self.mock) + assert network.Connection.__str__(self.mock) == "[foo]:999" def test_str_without_port(self): self.mock.host = "foo" self.mock.port = None - assert "[foo]" == network.Connection.__str__(self.mock) + assert network.Connection.__str__(self.mock) == "[foo]" diff --git a/tests/network/test_lineprotocol.py b/tests/network/test_lineprotocol.py index fca4040..9ee7ab1 100644 --- a/tests/network/test_lineprotocol.py +++ b/tests/network/test_lineprotocol.py @@ -3,12 +3,11 @@ from unittest.mock import Mock, sentinel from mopidy_mpd import network - from tests import any_unicode class LineProtocolTest(unittest.TestCase): - def setUp(self): # noqa: N802 + def setUp(self): self.mock = Mock(spec=network.LineProtocol) self.mock.terminator = network.LineProtocol.terminator @@ -25,7 +24,7 @@ def test_init_stores_values_in_attributes(self): delimiter = re.compile(network.LineProtocol.terminator) network.LineProtocol.__init__(self.mock, sentinel.connection) assert sentinel.connection == self.mock.connection - assert b"" == self.mock.recv_buffer + assert self.mock.recv_buffer == b"" assert delimiter == self.mock.delimiter assert not self.mock.prevent_timeout @@ -46,9 +45,9 @@ def test_on_receive_no_new_lines_adds_to_recv_buffer(self): self.prepare_on_receive_test() network.LineProtocol.on_receive(self.mock, {"received": b"data"}) - assert b"data" == self.mock.recv_buffer + assert self.mock.recv_buffer == b"data" self.mock.parse_lines.assert_called_once_with() - assert 0 == self.mock.on_line_received.call_count + assert self.mock.on_line_received.call_count == 0 def test_on_receive_toggles_timeout(self): self.prepare_on_receive_test() @@ -63,14 +62,14 @@ def test_on_receive_toggles_unless_prevent_timeout_is_set(self): network.LineProtocol.on_receive(self.mock, {"received": b"data"}) self.mock.connection.disable_timeout.assert_called_once_with() - assert 0 == self.mock.connection.enable_timeout.call_count + assert self.mock.connection.enable_timeout.call_count == 0 def test_on_receive_no_new_lines_calls_parse_lines(self): self.prepare_on_receive_test() network.LineProtocol.on_receive(self.mock, {"received": b"data"}) self.mock.parse_lines.assert_called_once_with() - assert 0 == self.mock.on_line_received.call_count + assert self.mock.on_line_received.call_count == 0 def test_on_receive_with_new_line_calls_decode(self): self.prepare_on_receive_test([sentinel.line]) @@ -91,16 +90,14 @@ def test_on_receive_with_new_line_with_failed_decode(self): self.mock.decode.return_value = None network.LineProtocol.on_receive(self.mock, {"received": b"data\n"}) - assert 0 == self.mock.on_line_received.call_count + assert self.mock.on_line_received.call_count == 0 def test_on_receive_with_new_lines_calls_on_recieve(self): self.prepare_on_receive_test(["line1", "line2"]) self.mock.decode.return_value = sentinel.decoded - network.LineProtocol.on_receive( - self.mock, {"received": b"line1\nline2\n"} - ) - assert 2 == self.mock.on_line_received.call_count + network.LineProtocol.on_receive(self.mock, {"received": b"line1\nline2\n"}) + assert self.mock.on_line_received.call_count == 2 def test_on_failure_calls_stop(self): self.mock.connection = Mock(spec=network.Connection) @@ -131,38 +128,38 @@ def test_parse_lines_terminator(self): self.prepare_parse_lines_test("data\n") lines = network.LineProtocol.parse_lines(self.mock) - assert b"data" == next(lines) + assert next(lines) == b"data" with self.assertRaises(StopIteration): next(lines) - assert b"" == self.mock.recv_buffer + assert self.mock.recv_buffer == b"" def test_parse_lines_terminator_with_carriage_return(self): self.prepare_parse_lines_test("data\r\n") self.mock.delimiter = re.compile(rb"\r?\n") lines = network.LineProtocol.parse_lines(self.mock) - assert b"data" == next(lines) + assert next(lines) == b"data" with self.assertRaises(StopIteration): next(lines) - assert b"" == self.mock.recv_buffer + assert self.mock.recv_buffer == b"" def test_parse_lines_no_data_before_terminator(self): self.prepare_parse_lines_test("\n") lines = network.LineProtocol.parse_lines(self.mock) - assert b"" == next(lines) + assert next(lines) == b"" with self.assertRaises(StopIteration): next(lines) - assert b"" == self.mock.recv_buffer + assert self.mock.recv_buffer == b"" def test_parse_lines_extra_data_after_terminator(self): self.prepare_parse_lines_test("data1\ndata2") lines = network.LineProtocol.parse_lines(self.mock) - assert b"data1" == next(lines) + assert next(lines) == b"data1" with self.assertRaises(StopIteration): next(lines) - assert b"data2" == self.mock.recv_buffer + assert self.mock.recv_buffer == b"data2" def test_parse_lines_non_ascii(self): self.prepare_parse_lines_test("æøå\n") @@ -171,18 +168,18 @@ def test_parse_lines_non_ascii(self): assert "æøå".encode() == next(lines) with self.assertRaises(StopIteration): next(lines) - assert b"" == self.mock.recv_buffer + assert self.mock.recv_buffer == b"" def test_parse_lines_multiple_lines(self): self.prepare_parse_lines_test("abc\ndef\nghi\njkl") lines = network.LineProtocol.parse_lines(self.mock) - assert b"abc" == next(lines) - assert b"def" == next(lines) - assert b"ghi" == next(lines) + assert next(lines) == b"abc" + assert next(lines) == b"def" + assert next(lines) == b"ghi" with self.assertRaises(StopIteration): next(lines) - assert b"jkl" == self.mock.recv_buffer + assert self.mock.recv_buffer == b"jkl" def test_parse_lines_multiple_calls(self): self.prepare_parse_lines_test("data1") @@ -190,22 +187,22 @@ def test_parse_lines_multiple_calls(self): lines = network.LineProtocol.parse_lines(self.mock) with self.assertRaises(StopIteration): next(lines) - assert b"data1" == self.mock.recv_buffer + assert self.mock.recv_buffer == b"data1" self.mock.recv_buffer += b"\ndata2" lines = network.LineProtocol.parse_lines(self.mock) - assert b"data1" == next(lines) + assert next(lines) == b"data1" with self.assertRaises(StopIteration): next(lines) - assert b"data2" == self.mock.recv_buffer + assert self.mock.recv_buffer == b"data2" def test_send_lines_called_with_no_lines(self): self.mock.connection = Mock(spec=network.Connection) network.LineProtocol.send_lines(self.mock, []) - assert 0 == self.mock.encode.call_count - assert 0 == self.mock.connection.queue_send.call_count + assert self.mock.encode.call_count == 0 + assert self.mock.connection.queue_send.call_count == 0 def test_send_lines_calls_join_lines(self): self.mock.connection = Mock(spec=network.Connection) @@ -230,13 +227,11 @@ def test_send_lines_sends_encoded_string(self): self.mock.connection.queue_send.assert_called_once_with(sentinel.data) def test_join_lines_returns_empty_string_for_no_lines(self): - assert "" == network.LineProtocol.join_lines(self.mock, []) + assert network.LineProtocol.join_lines(self.mock, []) == "" def test_join_lines_returns_joined_lines(self): self.mock.decode.return_value = "\n" - assert "1\n2\n" == network.LineProtocol.join_lines( - self.mock, ["1", "2"] - ) + assert network.LineProtocol.join_lines(self.mock, ["1", "2"]) == "1\n2\n" def test_decode_calls_decode_on_string(self): string = Mock() @@ -246,12 +241,12 @@ def test_decode_calls_decode_on_string(self): def test_decode_plain_ascii(self): result = network.LineProtocol.decode(self.mock, b"abc") - assert "abc" == result + assert result == "abc" assert str == type(result) def test_decode_utf8(self): result = network.LineProtocol.decode(self.mock, "æøå".encode()) - assert "æøå" == result + assert result == "æøå" assert str == type(result) def test_decode_invalid_data(self): @@ -269,7 +264,7 @@ def test_encode_calls_encode_on_string(self): def test_encode_plain_ascii(self): result = network.LineProtocol.encode(self.mock, "abc") - assert b"abc" == result + assert result == b"abc" assert bytes == type(result) def test_encode_utf8(self): diff --git a/tests/network/test_server.py b/tests/network/test_server.py index 112f485..c1fda3d 100644 --- a/tests/network/test_server.py +++ b/tests/network/test_server.py @@ -7,12 +7,11 @@ from gi.repository import GLib from mopidy_mpd import network - from tests import any_int class ServerTest(unittest.TestCase): - def setUp(self): # noqa: N802 + def setUp(self): self.mock = Mock(spec=network.Server) @patch.object(network, "get_socket_address", new=Mock()) @@ -31,9 +30,7 @@ def test_init_calls_get_socket_address(self): self.mock, sentinel.host, sentinel.port, sentinel.protocol ) self.mock.create_server_socket.return_value = None - network.get_socket_address.assert_called_once_with( - sentinel.host, sentinel.port - ) + network.get_socket_address.assert_called_once_with(sentinel.host, sentinel.port) self.mock.stop() @patch.object(network, "get_socket_address", new=Mock()) @@ -45,9 +42,7 @@ def test_init_calls_register_server(self): network.Server.__init__( self.mock, sentinel.host, sentinel.port, sentinel.protocol ) - self.mock.register_server_socket.assert_called_once_with( - sentinel.fileno - ) + self.mock.register_server_socket.assert_called_once_with(sentinel.fileno) @patch.object(network, "get_socket_address", new=Mock()) def test_init_fails_on_fileno_call(self): @@ -81,9 +76,7 @@ def test_init_stores_values_in_attributes(self): def test_create_server_socket_no_port(self): with self.assertRaises(TypeError): - network.Server.create_server_socket( - self.mock, str(sentinel.host), None - ) + network.Server.create_server_socket(self.mock, str(sentinel.host), None) def test_create_server_socket_invalid_port(self): with self.assertRaises(TypeError): @@ -102,9 +95,7 @@ def test_create_server_socket_sets_up_listener(self, create_tcp_socket): create_tcp_socket.assert_called_once() @patch.object(network, "create_unix_socket", spec=socket.socket) - def test_create_server_socket_sets_up_listener_unix( - self, create_unix_socket - ): + def test_create_server_socket_sets_up_listener_unix(self, create_unix_socket): sock = create_unix_socket.return_value network.Server.create_server_socket( @@ -119,9 +110,7 @@ def test_create_server_socket_sets_up_listener_unix( def test_create_server_socket_fails(self): network.create_tcp_socket.side_effect = socket.error with self.assertRaises(socket.error): - network.Server.create_server_socket( - self.mock, str(sentinel.host), 1234 - ) + network.Server.create_server_socket(self.mock, str(sentinel.host), 1234) @patch.object(network, "create_unix_socket", new=Mock()) def test_create_server_socket_fails_unix(self): @@ -137,9 +126,7 @@ def test_create_server_bind_fails(self): sock.bind.side_effect = socket.error with self.assertRaises(socket.error): - network.Server.create_server_socket( - self.mock, str(sentinel.host), 1234 - ) + network.Server.create_server_socket(self.mock, str(sentinel.host), 1234) @patch.object(network, "create_unix_socket", new=Mock()) def test_create_server_bind_fails_unix(self): @@ -157,9 +144,7 @@ def test_create_server_listen_fails(self): sock.listen.side_effect = socket.error with self.assertRaises(socket.error): - network.Server.create_server_socket( - self.mock, str(sentinel.host), 1234 - ) + network.Server.create_server_socket(self.mock, str(sentinel.host), 1234) @patch.object(network, "create_unix_socket", new=Mock()) def test_create_server_listen_fails_unix(self): @@ -195,15 +180,11 @@ def test_handle_connection(self): ) self.mock.maximum_connections_exceeded.return_value = False - assert network.Server.handle_connection( - self.mock, sentinel.fileno, GLib.IO_IN - ) + assert network.Server.handle_connection(self.mock, sentinel.fileno, GLib.IO_IN) self.mock.accept_connection.assert_called_once_with() self.mock.maximum_connections_exceeded.assert_called_once_with() - self.mock.init_connection.assert_called_once_with( - sentinel.sock, sentinel.addr - ) - assert 0 == self.mock.reject_connection.call_count + self.mock.init_connection.assert_called_once_with(sentinel.sock, sentinel.addr) + assert self.mock.reject_connection.call_count == 0 def test_handle_connection_exceeded_connections(self): self.mock.accept_connection.return_value = ( @@ -212,15 +193,13 @@ def test_handle_connection_exceeded_connections(self): ) self.mock.maximum_connections_exceeded.return_value = True - assert network.Server.handle_connection( - self.mock, sentinel.fileno, GLib.IO_IN - ) + assert network.Server.handle_connection(self.mock, sentinel.fileno, GLib.IO_IN) self.mock.accept_connection.assert_called_once_with() self.mock.maximum_connections_exceeded.assert_called_once_with() self.mock.reject_connection.assert_called_once_with( sentinel.sock, sentinel.addr ) - assert 0 == self.mock.init_connection.call_count + assert self.mock.init_connection.call_count == 0 def test_accept_connection(self): sock = Mock(spec=socket.socket) @@ -249,7 +228,7 @@ def test_accept_connection_recoverable_error(self): self.mock.server_socket = sock for error in (errno.EAGAIN, errno.EINTR): - sock.accept.side_effect = socket.error(error, "") + sock.accept.side_effect = OSError(error, "") with self.assertRaises(network.ShouldRetrySocketCallError): network.Server.accept_connection(self.mock) @@ -278,10 +257,10 @@ def test_number_of_connections(self, get_by_class): self.mock.protocol = sentinel.protocol get_by_class.return_value = [1, 2, 3] - assert 3 == network.Server.number_of_connections(self.mock) + assert network.Server.number_of_connections(self.mock) == 3 get_by_class.return_value = [] - assert 0 == network.Server.number_of_connections(self.mock) + assert network.Server.number_of_connections(self.mock) == 0 @patch.object(network, "Connection", new=Mock()) def test_init_connection(self): @@ -315,9 +294,7 @@ def test_reject_connection_message(self): network.Server.reject_connection( self.mock, sock, (sentinel.host, sentinel.port) ) - network.format_address.assert_called_once_with( - (sentinel.host, sentinel.port) - ) + network.format_address.assert_called_once_with((sentinel.host, sentinel.port)) network.logger.warning.assert_called_once_with( "Rejected connection from %s", sentinel.formatted ) diff --git a/tests/network/test_utils.py b/tests/network/test_utils.py index db65ff0..6903c96 100644 --- a/tests/network/test_utils.py +++ b/tests/network/test_utils.py @@ -21,17 +21,11 @@ def test_format_hostname_does_nothing_when_only_ipv4_available(self): class FormatAddressTest(unittest.TestCase): def test_format_address_ipv4(self): address = (sentinel.host, sentinel.port) - assert ( - network.format_address(address) - == f"[{sentinel.host}]:{sentinel.port}" - ) + assert network.format_address(address) == f"[{sentinel.host}]:{sentinel.port}" def test_format_address_ipv6(self): address = (sentinel.host, sentinel.port, sentinel.flow, sentinel.scope) - assert ( - network.format_address(address) - == f"[{sentinel.host}]:{sentinel.port}" - ) + assert network.format_address(address) == f"[{sentinel.host}]:{sentinel.port}" def test_format_address_unix(self): address = (sentinel.path, None) @@ -61,7 +55,7 @@ def test_system_that_claims_no_ipv6_support(self): @patch("socket.has_ipv6", True) @patch("socket.socket") def test_system_with_broken_ipv6(self, socket_mock): - socket_mock.side_effect = IOError() + socket_mock.side_effect = OSError() assert not network.try_ipv6_socket() @patch("socket.has_ipv6", True) diff --git a/tests/path_utils.py b/tests/path_utils.py index 3d69b98..4113a25 100644 --- a/tests/path_utils.py +++ b/tests/path_utils.py @@ -9,7 +9,7 @@ def __init__(self): def __call__(self, path): if self.fake is not None: return self.fake - return int(os.stat(path).st_mtime) + return int(os.stat(path).st_mtime) # noqa: PTH116 def set_fake_time(self, time): self.fake = time diff --git a/tests/protocol/__init__.py b/tests/protocol/__init__.py index e83036d..793d8ec 100644 --- a/tests/protocol/__init__.py +++ b/tests/protocol/__init__.py @@ -2,10 +2,9 @@ from unittest import mock import pykka - from mopidy import core -from mopidy_mpd import session, uri_mapper +from mopidy_mpd import session, uri_mapper from tests import dummy_audio, dummy_backend, dummy_mixer @@ -31,7 +30,7 @@ def get_config(self): "mpd": {"password": None, "default_playlist_scheme": "dummy"}, } - def setUp(self): # noqa: N802 + def setUp(self): if self.enable_mixer: self.mixer = dummy_mixer.create_proxy() else: @@ -57,7 +56,7 @@ def setUp(self): # noqa: N802 self.dispatcher = self.session.dispatcher self.context = self.dispatcher.context - def tearDown(self): # noqa: N802 + def tearDown(self): pykka.ActorRegistry.stop_all() def send_request(self, request): @@ -77,7 +76,7 @@ def assertInResponse(self, value): # noqa: N802 def assertOnceInResponse(self, value): # noqa: N802 matched = len([r for r in self.connection.response if r == value]) assert ( - 1 == matched + matched == 1 ), f"Expected to find {value!r} once in {self.connection.response!r}" def assertNotInResponse(self, value): # noqa: N802 @@ -86,7 +85,7 @@ def assertNotInResponse(self, value): # noqa: N802 ), f"Found {value!r} in {self.connection.response!r}" def assertEqualResponse(self, value): # noqa: N802 - assert 1 == len(self.connection.response) + assert len(self.connection.response) == 1 assert value == self.connection.response[0] def assertResponseLength(self, value): # noqa: N802 diff --git a/tests/protocol/test_audio_output.py b/tests/protocol/test_audio_output.py index 2af6de4..9469af4 100644 --- a/tests/protocol/test_audio_output.py +++ b/tests/protocol/test_audio_output.py @@ -88,9 +88,7 @@ def test_enableoutput(self): assert self.core.mixer.get_mute().get() is None self.send_request('enableoutput "0"') - self.assertInResponse( - "ACK [52@0] {enableoutput} problems enabling output" - ) + self.assertInResponse("ACK [52@0] {enableoutput} problems enabling output") assert self.core.mixer.get_mute().get() is None @@ -98,9 +96,7 @@ def test_disableoutput(self): assert self.core.mixer.get_mute().get() is None self.send_request('disableoutput "0"') - self.assertInResponse( - "ACK [52@0] {disableoutput} problems disabling output" - ) + self.assertInResponse("ACK [52@0] {disableoutput} problems disabling output") assert self.core.mixer.get_mute().get() is None diff --git a/tests/protocol/test_authentication.py b/tests/protocol/test_authentication.py index 826e053..4e879fc 100644 --- a/tests/protocol/test_authentication.py +++ b/tests/protocol/test_authentication.py @@ -27,9 +27,7 @@ def test_authentication_without_password_fails(self): def test_anything_when_not_authenticated_should_fail(self): self.send_request("any request at all") assert not self.dispatcher.authenticated - self.assertEqualResponse( - 'ACK [4@0] {any} you don\'t have permission for "any"' - ) + self.assertEqualResponse('ACK [4@0] {any} you don\'t have permission for "any"') def test_close_is_allowed_without_authentication(self): self.send_request("close") diff --git a/tests/protocol/test_command_list.py b/tests/protocol/test_command_list.py index 7cb852a..e9abcbe 100644 --- a/tests/protocol/test_command_list.py +++ b/tests/protocol/test_command_list.py @@ -13,9 +13,7 @@ def test_command_list_end(self): def test_command_list_end_without_start_first_is_an_unknown_command(self): self.send_request("command_list_end") - self.assertEqualResponse( - 'ACK [5@0] {} unknown command "command_list_end"' - ) + self.assertEqualResponse('ACK [5@0] {} unknown command "command_list_end"') def test_command_list_with_ping(self): self.send_request("command_list_begin") diff --git a/tests/protocol/test_connection.py b/tests/protocol/test_connection.py index fa822bc..a418ff9 100644 --- a/tests/protocol/test_connection.py +++ b/tests/protocol/test_connection.py @@ -1,7 +1,7 @@ from unittest.mock import patch -from tests import protocol from mopidy_mpd.protocol import tagtype_list +from tests import protocol class ConnectionHandlerTest(protocol.BaseTestCase): diff --git a/tests/protocol/test_current_playlist.py b/tests/protocol/test_current_playlist.py index ef08245..cbacdf3 100644 --- a/tests/protocol/test_current_playlist.py +++ b/tests/protocol/test_current_playlist.py @@ -1,10 +1,11 @@ +import pytest from mopidy.models import Ref, Track from tests import protocol class AddCommandsTest(protocol.BaseTestCase): - def setUp(self): # noqa: N802 + def setUp(self): super().setUp() self.tracks = [ @@ -43,9 +44,7 @@ def test_add_with_uri_not_found_in_library_should_ack(self): self.assertEqualResponse("ACK [50@0] {add} directory or file not found") def test_add_with_empty_uri_should_not_add_anything_and_ok(self): - self.backend.library.dummy_browse_result = { - "dummy:/": [self.refs["/a"]] - } + self.backend.library.dummy_browse_result = {"dummy:/": [self.refs["/a"]]} self.send_request('add ""') assert len(self.core.tracklist.get_tracks().get()) == 0 @@ -62,9 +61,7 @@ def test_add_with_library_should_recurse(self): self.assertInResponse("OK") def test_add_root_should_not_add_anything_and_ok(self): - self.backend.library.dummy_browse_result = { - "dummy:/": [self.refs["/a"]] - } + self.backend.library.dummy_browse_result = {"dummy:/": [self.refs["/a"]]} self.send_request('add "/"') assert len(self.core.tracklist.get_tracks().get()) == 0 @@ -105,7 +102,7 @@ def test_addid_with_uri_not_found_in_library_should_ack(self): class BasePopulatedTracklistTestCase(protocol.BaseTestCase): - def setUp(self): # noqa: N802 + def setUp(self): super().setUp() tracks = [Track(uri=f"dummy:/{x}", name=x) for x in "abcdeǂ"] self.backend.library.dummy_library = tracks @@ -135,11 +132,11 @@ def test_delete_open_range(self): assert len(self.core.tracklist.get_tracks().get()) == 1 self.assertInResponse("OK") - # TODO: check how this should work. - # def test_delete_open_upper_range(self): - # self.send_request('delete ":8"') - # self.assertEqual(len(self.core.tracklist.get_tracks().get()), 0) - # self.assertInResponse('OK') + @pytest.mark.skip("Check how this should work") + def test_delete_open_upper_range(self): + self.send_request('delete ":8"') + assert len(self.core.tracklist.get_tracks().get()) == 0 + self.assertInResponse("OK") def test_delete_closed_range(self): self.send_request('delete "1:3"') diff --git a/tests/protocol/test_idle.py b/tests/protocol/test_idle.py index 89d7a0a..efbaeb9 100644 --- a/tests/protocol/test_idle.py +++ b/tests/protocol/test_idle.py @@ -1,7 +1,6 @@ from unittest.mock import patch from mopidy_mpd.protocol.status import SUBSYSTEMS - from tests import protocol diff --git a/tests/protocol/test_music_db.py b/tests/protocol/test_music_db.py index 4e518f1..169cd06 100644 --- a/tests/protocol/test_music_db.py +++ b/tests/protocol/test_music_db.py @@ -2,8 +2,8 @@ from unittest import mock from mopidy.models import Album, Artist, Playlist, Ref, SearchResult, Track -from mopidy_mpd.protocol import music_db, stored_playlists +from mopidy_mpd.protocol import music_db, stored_playlists from tests import protocol # TODO: split into more modules for faster parallel tests? @@ -104,9 +104,7 @@ def test_count_with_track_length_none(self): def test_findadd(self): track = Track(uri="dummy:a", name="A") self.backend.library.dummy_library = [track] - self.backend.library.dummy_find_exact_result = SearchResult( - tracks=[track] - ) + self.backend.library.dummy_find_exact_result = SearchResult(tracks=[track]) assert self.core.tracklist.get_length().get() == 0 self.send_request('findadd "title" "A"') @@ -358,26 +356,18 @@ def test_listfiles(self): self.assertEqualResponse("ACK [0@0] {listfiles} Not implemented") @mock.patch.object(stored_playlists, "_get_last_modified") - def test_lsinfo_without_path_returns_same_as_for_root( - self, last_modified_mock - ): + def test_lsinfo_without_path_returns_same_as_for_root(self, last_modified_mock): last_modified_mock.return_value = "2015-08-05T22:51:06Z" - self.backend.playlists.set_dummy_playlists( - [Playlist(name="a", uri="dummy:/a")] - ) + self.backend.playlists.set_dummy_playlists([Playlist(name="a", uri="dummy:/a")]) response1 = self.send_request("lsinfo") response2 = self.send_request('lsinfo "/"') assert response1 == response2 @mock.patch.object(stored_playlists, "_get_last_modified") - def test_lsinfo_with_empty_path_returns_same_as_for_root( - self, last_modified_mock - ): + def test_lsinfo_with_empty_path_returns_same_as_for_root(self, last_modified_mock): last_modified_mock.return_value = "2015-08-05T22:51:06Z" - self.backend.playlists.set_dummy_playlists( - [Playlist(name="a", uri="dummy:/a")] - ) + self.backend.playlists.set_dummy_playlists([Playlist(name="a", uri="dummy:/a")]) response1 = self.send_request('lsinfo ""') response2 = self.send_request('lsinfo "/"') @@ -386,9 +376,7 @@ def test_lsinfo_with_empty_path_returns_same_as_for_root( @mock.patch.object(stored_playlists, "_get_last_modified") def test_lsinfo_for_root_includes_playlists(self, last_modified_mock): last_modified_mock.return_value = "2015-08-05T22:51:06Z" - self.backend.playlists.set_dummy_playlists( - [Playlist(name="a", uri="dummy:/a")] - ) + self.backend.playlists.set_dummy_playlists([Playlist(name="a", uri="dummy:/a")]) self.send_request('lsinfo "/"') self.assertInResponse("playlist: a") @@ -497,14 +485,10 @@ def test_lsinfo_for_root_returns_browse_result_before_playlists(self): Ref.directory(uri="dummy:/foo", name="foo"), ] } - self.backend.playlists.set_dummy_playlists( - [Playlist(name="a", uri="dummy:/a")] - ) + self.backend.playlists.set_dummy_playlists([Playlist(name="a", uri="dummy:/a")]) response = self.send_request('lsinfo "/"') - assert response.index("directory: dummy") < response.index( - "playlist: a" - ) + assert response.index("directory: dummy") < response.index("playlist: a") def test_lsinfo_duplicate(self): self.backend.library.dummy_browse_result = { @@ -735,9 +719,7 @@ def test_find_without_filter_value(self): class MusicDatabaseListTest(protocol.BaseTestCase): def test_list(self): - self.backend.library.dummy_get_distinct_result = { - "artist": {"A Artist"} - } + self.backend.library.dummy_get_distinct_result = {"artist": {"A Artist"}} self.send_request('list "artist" "artist" "foo"') self.assertInResponse("Artist: A Artist") @@ -749,9 +731,7 @@ def test_list_foo_returns_ack(self): def test_list_without_type_returns_ack(self): self.send_request("list") - self.assertEqualResponse( - 'ACK [2@0] {list} too few arguments for "list"' - ) + self.assertEqualResponse('ACK [2@0] {list} too few arguments for "list"') def test_list_all_supported_fields(self): # Be nice to use pytest's parametrize here. @@ -803,9 +783,7 @@ def test_list_artist_without_quotes_and_capitalized(self): def test_list_artist_with_query_of_one_token(self): self.send_request('list "artist" "anartist"') - self.assertEqualResponse( - 'ACK [2@0] {list} should be "Album" for 3 arguments' - ) + self.assertEqualResponse('ACK [2@0] {list} should be "Album" for 3 arguments') def test_list_artist_with_unknown_field_in_query_returns_ack(self): self.send_request('list "artist" "foo" "bar"') @@ -864,9 +842,7 @@ def test_list_albumartist_without_quotes_and_capitalized(self): def test_list_albumartist_with_query_of_one_token(self): self.send_request('list "albumartist" "anartist"') - self.assertEqualResponse( - 'ACK [2@0] {list} should be "Album" for 3 arguments' - ) + self.assertEqualResponse('ACK [2@0] {list} should be "Album" for 3 arguments') def test_list_albumartist_with_unknown_field_in_query_returns_ack(self): self.send_request('list "albumartist" "foo" "bar"') @@ -893,9 +869,7 @@ def test_list_albumartist_by_genre(self): self.assertInResponse("OK") def test_list_albumartist_by_artist_and_album(self): - self.send_request( - 'list "albumartist" "artist" "anartist" "album" "analbum"' - ) + self.send_request('list "albumartist" "artist" "anartist" "album" "analbum"') self.assertInResponse("OK") def test_list_albumartist_without_filter_value(self): @@ -930,9 +904,7 @@ def test_list_composer_without_quotes_and_capitalized(self): def test_list_composer_with_query_of_one_token(self): self.send_request('list "composer" "anartist"') - self.assertEqualResponse( - 'ACK [2@0] {list} should be "Album" for 3 arguments' - ) + self.assertEqualResponse('ACK [2@0] {list} should be "Album" for 3 arguments') def test_list_composer_with_unknown_field_in_query_returns_ack(self): self.send_request('list "composer" "foo" "bar"') @@ -959,9 +931,7 @@ def test_list_composer_by_genre(self): self.assertInResponse("OK") def test_list_composer_by_artist_and_album(self): - self.send_request( - 'list "composer" "artist" "anartist" "album" "analbum"' - ) + self.send_request('list "composer" "artist" "anartist" "album" "analbum"') self.assertInResponse("OK") def test_list_composer_without_filter_value(self): @@ -996,9 +966,7 @@ def test_list_performer_without_quotes_and_capitalized(self): def test_list_performer_with_query_of_one_token(self): self.send_request('list "performer" "anartist"') - self.assertEqualResponse( - 'ACK [2@0] {list} should be "Album" for 3 arguments' - ) + self.assertEqualResponse('ACK [2@0] {list} should be "Album" for 3 arguments') def test_list_performer_with_unknown_field_in_query_returns_ack(self): self.send_request('list "performer" "foo" "bar"') @@ -1025,9 +993,7 @@ def test_list_performer_by_genre(self): self.assertInResponse("OK") def test_list_performer_by_artist_and_album(self): - self.send_request( - 'list "performer" "artist" "anartist" "album" "analbum"' - ) + self.send_request('list "performer" "artist" "anartist" "album" "analbum"') self.assertInResponse("OK") def test_list_performer_without_filter_value(self): @@ -1136,9 +1102,7 @@ def test_list_date_without_quotes_and_capitalized(self): def test_list_date_with_query_of_one_token(self): self.send_request('list "date" "anartist"') - self.assertEqualResponse( - 'ACK [2@0] {list} should be "Album" for 3 arguments' - ) + self.assertEqualResponse('ACK [2@0] {list} should be "Album" for 3 arguments') def test_list_date_by_artist(self): self.send_request('list "date" "artist" "anartist"') @@ -1193,9 +1157,7 @@ def test_list_genre_without_quotes_and_capitalized(self): def test_list_genre_with_query_of_one_token(self): self.send_request('list "genre" "anartist"') - self.assertEqualResponse( - 'ACK [2@0] {list} should be "Album" for 3 arguments' - ) + self.assertEqualResponse('ACK [2@0] {list} should be "Album" for 3 arguments') def test_list_genre_by_artist(self): self.send_request('list "genre" "artist" "anartist"') diff --git a/tests/protocol/test_playback.py b/tests/protocol/test_playback.py index 59b5e41..610cb2c 100644 --- a/tests/protocol/test_playback.py +++ b/tests/protocol/test_playback.py @@ -134,7 +134,7 @@ def test_replay_gain_status_album(self): class PlaybackControlHandlerTest(protocol.BaseTestCase): - def setUp(self): # noqa: N802 + def setUp(self): super().setUp() self.tracks = [ Track(uri="dummy:a", length=40000), @@ -152,55 +152,55 @@ def test_pause_off(self): self.send_request('play "0"') self.send_request('pause "1"') self.send_request('pause "0"') - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING self.assertInResponse("OK") def test_pause_on(self): self.send_request('play "0"') self.send_request('pause "1"') - assert PAUSED == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PAUSED self.assertInResponse("OK") def test_pause_toggle(self): self.send_request('play "0"') - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING self.assertInResponse("OK") # Deprecated version self.send_request("pause") - assert PAUSED == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PAUSED self.assertInResponse("OK") self.send_request("pause") - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING self.assertInResponse("OK") def test_play_without_pos(self): self.send_request("play") - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING self.assertInResponse("OK") def test_play_with_pos(self): self.send_request('play "0"') - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING self.assertInResponse("OK") def test_play_with_pos_without_quotes(self): self.send_request("play 0") - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING self.assertInResponse("OK") def test_play_with_pos_out_of_bounds(self): self.core.tracklist.clear().get() self.send_request('play "0"') - assert STOPPED == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == STOPPED self.assertInResponse("ACK [2@0] {play} Bad song index") def test_play_minus_one_plays_first_in_playlist_if_no_current_track(self): assert self.core.playback.get_current_track().get() is None self.send_request('play "-1"') - assert PLAYING == self.core.playback.get_state().get() - assert "dummy:a" == self.core.playback.get_current_track().get().uri + assert self.core.playback.get_state().get() == PLAYING + assert self.core.playback.get_current_track().get().uri == "dummy:a" self.assertInResponse("OK") def test_play_minus_one_plays_current_track_if_current_track_is_set(self): @@ -211,15 +211,15 @@ def test_play_minus_one_plays_current_track_if_current_track_is_set(self): assert self.core.playback.get_current_track().get() is not None self.send_request('play "-1"') - assert PLAYING == self.core.playback.get_state().get() - assert "dummy:b" == self.core.playback.get_current_track().get().uri + assert self.core.playback.get_state().get() == PLAYING + assert self.core.playback.get_current_track().get().uri == "dummy:b" self.assertInResponse("OK") def test_play_minus_one_on_empty_playlist_does_not_ack(self): self.core.tracklist.clear() self.send_request('play "-1"') - assert STOPPED == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == STOPPED assert self.core.playback.get_current_track().get() is None self.assertInResponse("OK") @@ -227,10 +227,10 @@ def test_play_minus_is_ignored_if_playing(self): self.core.playback.play().get() self.core.playback.seek(30000) assert self.core.playback.get_time_position().get() >= 30000 - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING self.send_request('play "-1"') - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING assert self.core.playback.get_time_position().get() >= 30000 self.assertInResponse("OK") @@ -238,31 +238,31 @@ def test_play_minus_one_resumes_if_paused(self): self.core.playback.play().get() self.core.playback.seek(30000) assert self.core.playback.get_time_position().get() >= 30000 - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING self.core.playback.pause() - assert PAUSED == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PAUSED self.send_request('play "-1"') - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING assert self.core.playback.get_time_position().get() >= 30000 self.assertInResponse("OK") def test_playid(self): self.send_request('playid "1"') - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING self.assertInResponse("OK") def test_playid_without_quotes(self): self.send_request("playid 1") - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING self.assertInResponse("OK") def test_playid_minus_1_plays_first_in_playlist_if_no_current_track(self): assert self.core.playback.get_current_track().get() is None self.send_request('playid "-1"') - assert PLAYING == self.core.playback.get_state().get() - assert "dummy:a" == self.core.playback.get_current_track().get().uri + assert self.core.playback.get_state().get() == PLAYING + assert self.core.playback.get_current_track().get().uri == "dummy:a" self.assertInResponse("OK") def test_playid_minus_1_plays_current_track_if_current_track_is_set(self): @@ -273,15 +273,15 @@ def test_playid_minus_1_plays_current_track_if_current_track_is_set(self): assert self.core.playback.get_current_track().get() is not None self.send_request('playid "-1"') - assert PLAYING == self.core.playback.get_state().get() - assert "dummy:b" == self.core.playback.get_current_track().get().uri + assert self.core.playback.get_state().get() == PLAYING + assert self.core.playback.get_current_track().get().uri == "dummy:b" self.assertInResponse("OK") def test_playid_minus_one_on_empty_playlist_does_not_ack(self): self.core.tracklist.clear() self.send_request('playid "-1"') - assert STOPPED == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == STOPPED assert self.core.playback.get_current_track().get() is None self.assertInResponse("OK") @@ -289,10 +289,10 @@ def test_playid_minus_is_ignored_if_playing(self): self.core.playback.play().get() self.core.playback.seek(30000) assert self.core.playback.get_time_position().get() >= 30000 - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING self.send_request('playid "-1"') - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING assert self.core.playback.get_time_position().get() >= 30000 self.assertInResponse("OK") @@ -300,12 +300,12 @@ def test_playid_minus_one_resumes_if_paused(self): self.core.playback.play().get() self.core.playback.seek(30000) assert self.core.playback.get_time_position().get() >= 30000 - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING self.core.playback.pause() - assert PAUSED == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PAUSED self.send_request('playid "-1"') - assert PLAYING == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == PLAYING assert self.core.playback.get_time_position().get() >= 30000 self.assertInResponse("OK") @@ -432,44 +432,44 @@ def test_seekcur_negative_float(self): def test_stop(self): self.core.tracklist.clear().get() self.send_request("stop") - assert STOPPED == self.core.playback.get_state().get() + assert self.core.playback.get_state().get() == STOPPED self.assertInResponse("OK") class VolumeTest(protocol.BaseTestCase): def test_setvol_below_min(self): self.send_request('setvol "-10"') - assert 0 == self.core.mixer.get_volume().get() + assert self.core.mixer.get_volume().get() == 0 self.assertInResponse("OK") def test_setvol_min(self): self.send_request('setvol "0"') - assert 0 == self.core.mixer.get_volume().get() + assert self.core.mixer.get_volume().get() == 0 self.assertInResponse("OK") def test_setvol_middle(self): self.send_request('setvol "50"') - assert 50 == self.core.mixer.get_volume().get() + assert self.core.mixer.get_volume().get() == 50 self.assertInResponse("OK") def test_setvol_max(self): self.send_request('setvol "100"') - assert 100 == self.core.mixer.get_volume().get() + assert self.core.mixer.get_volume().get() == 100 self.assertInResponse("OK") def test_setvol_above_max(self): self.send_request('setvol "110"') - assert 100 == self.core.mixer.get_volume().get() + assert self.core.mixer.get_volume().get() == 100 self.assertInResponse("OK") def test_setvol_plus_is_ignored(self): self.send_request('setvol "+10"') - assert 10 == self.core.mixer.get_volume().get() + assert self.core.mixer.get_volume().get() == 10 self.assertInResponse("OK") def test_setvol_without_quotes(self): self.send_request("setvol 50") - assert 50 == self.core.mixer.get_volume().get() + assert self.core.mixer.get_volume().get() == 50 self.assertInResponse("OK") def test_volume_plus(self): @@ -477,7 +477,7 @@ def test_volume_plus(self): self.send_request("volume +20") - assert 70 == self.core.mixer.get_volume().get() + assert self.core.mixer.get_volume().get() == 70 self.assertInResponse("OK") def test_volume_minus(self): @@ -485,7 +485,7 @@ def test_volume_minus(self): self.send_request("volume -20") - assert 30 == self.core.mixer.get_volume().get() + assert self.core.mixer.get_volume().get() == 30 self.assertInResponse("OK") def test_volume_less_than_minus_100(self): @@ -493,7 +493,7 @@ def test_volume_less_than_minus_100(self): self.send_request("volume -110") - assert 50 == self.core.mixer.get_volume().get() + assert self.core.mixer.get_volume().get() == 50 self.assertInResponse("ACK [2@0] {volume} Invalid volume value") def test_volume_more_than_plus_100(self): @@ -501,7 +501,7 @@ def test_volume_more_than_plus_100(self): self.send_request("volume +110") - assert 50 == self.core.mixer.get_volume().get() + assert self.core.mixer.get_volume().get() == 50 self.assertInResponse("ACK [2@0] {volume} Invalid volume value") diff --git a/tests/protocol/test_reflection.py b/tests/protocol/test_reflection.py index 434bc39..a60268e 100644 --- a/tests/protocol/test_reflection.py +++ b/tests/protocol/test_reflection.py @@ -32,7 +32,7 @@ def test_decoders(self): def test_notcommands_returns_only_config_and_kill_and_ok(self): response = self.send_request("notcommands") - assert 3 == len(response) + assert len(response) == 3 self.assertInResponse("command: config") self.assertInResponse("command: kill") self.assertInResponse("OK") diff --git a/tests/protocol/test_regression.py b/tests/protocol/test_regression.py index cee5f52..7f7d319 100644 --- a/tests/protocol/test_regression.py +++ b/tests/protocol/test_regression.py @@ -2,8 +2,8 @@ from unittest import mock from mopidy.models import Playlist, Ref, Track -from mopidy_mpd.protocol import stored_playlists +from mopidy_mpd.protocol import stored_playlists from tests import protocol @@ -40,17 +40,17 @@ def test(self): # Playlist order: abcfde self.send_request("play") - assert "dummy:a" == self.core.playback.get_current_track().get().uri + assert self.core.playback.get_current_track().get().uri == "dummy:a" self.send_request('random "1"') self.send_request("next") - assert "dummy:b" == self.core.playback.get_current_track().get().uri + assert self.core.playback.get_current_track().get().uri == "dummy:b" self.send_request("next") # Should now be at track 'c', but playback fails and it skips ahead - assert "dummy:f" == self.core.playback.get_current_track().get().uri + assert self.core.playback.get_current_track().get().uri == "dummy:f" self.send_request("next") - assert "dummy:d" == self.core.playback.get_current_track().get().uri + assert self.core.playback.get_current_track().get().uri == "dummy:d" self.send_request("next") - assert "dummy:e" == self.core.playback.get_current_track().get().uri + assert self.core.playback.get_current_track().get().uri == "dummy:e" class IssueGH18RegressionTest(protocol.BaseTestCase): @@ -184,9 +184,7 @@ def test(self): self.core.playlists.create(r"all lart spotify:track:\w\{22\} pastes") self.send_request('lsinfo "/"') - self.assertInResponse( - r"playlist: all lart spotify:track:\w\{22\} pastes" - ) + self.assertInResponse(r"playlist: all lart spotify:track:\w\{22\} pastes") self.send_request( r'listplaylistinfo "all lart spotify:track:\\w\\{22\\} pastes"' diff --git a/tests/protocol/test_stickers.py b/tests/protocol/test_stickers.py index 582395f..e5ff5fe 100644 --- a/tests/protocol/test_stickers.py +++ b/tests/protocol/test_stickers.py @@ -7,15 +7,11 @@ def test_sticker_get(self): self.assertEqualResponse("ACK [0@0] {sticker} Not implemented") def test_sticker_set(self): - self.send_request( - 'sticker set "song" "file:///dev/urandom" "a_name" "a_value"' - ) + self.send_request('sticker set "song" "file:///dev/urandom" "a_name" "a_value"') self.assertEqualResponse("ACK [0@0] {sticker} Not implemented") def test_sticker_delete_with_name(self): - self.send_request( - 'sticker delete "song" "file:///dev/urandom" "a_name"' - ) + self.send_request('sticker delete "song" "file:///dev/urandom" "a_name"') self.assertEqualResponse("ACK [0@0] {sticker} Not implemented") def test_sticker_delete_without_name(self): diff --git a/tests/protocol/test_stored_playlists.py b/tests/protocol/test_stored_playlists.py index 442d4a4..75d04eb 100644 --- a/tests/protocol/test_stored_playlists.py +++ b/tests/protocol/test_stored_playlists.py @@ -1,19 +1,15 @@ from unittest import mock from mopidy.models import Playlist, Track -from mopidy_mpd.protocol import stored_playlists +from mopidy_mpd.protocol import stored_playlists from tests import protocol class PlaylistsHandlerTest(protocol.BaseTestCase): def test_listplaylist(self): self.backend.playlists.set_dummy_playlists( - [ - Playlist( - name="name", uri="dummy:name", tracks=[Track(uri="dummy:a")] - ) - ] + [Playlist(name="name", uri="dummy:name", tracks=[Track(uri="dummy:a")])] ) self.send_request('listplaylist "name"') @@ -23,11 +19,7 @@ def test_listplaylist(self): def test_listplaylist_unicode(self): self.backend.playlists.set_dummy_playlists( - [ - Playlist( - name="nàmé", uri="dummy:nàmé", tracks=[Track(uri="dummy:à")] - ) - ] + [Playlist(name="nàmé", uri="dummy:nàmé", tracks=[Track(uri="dummy:à")])] ) self.send_request('listplaylist "nàmé"') @@ -37,11 +29,7 @@ def test_listplaylist_unicode(self): def test_listplaylist_without_quotes(self): self.backend.playlists.set_dummy_playlists( - [ - Playlist( - name="name", uri="dummy:name", tracks=[Track(uri="dummy:a")] - ) - ] + [Playlist(name="name", uri="dummy:name", tracks=[Track(uri="dummy:a")])] ) self.send_request("listplaylist name") @@ -104,9 +92,7 @@ def test_listplaylistinfo_without_quotes(self): def test_listplaylistinfo_fails_if_no_playlist_is_found(self): self.send_request('listplaylistinfo "name"') - self.assertEqualResponse( - "ACK [50@0] {listplaylistinfo} No such playlist" - ) + self.assertEqualResponse("ACK [50@0] {listplaylistinfo} No such playlist") def test_listplaylistinfo_duplicate(self): tracks = [ @@ -166,9 +152,7 @@ def test_listplaylists_ignores_playlists_without_name(self): self.assertInResponse("OK") def test_listplaylists_replaces_newline_with_space(self): - self.backend.playlists.set_dummy_playlists( - [Playlist(name="a\n", uri="dummy:")] - ) + self.backend.playlists.set_dummy_playlists([Playlist(name="a\n", uri="dummy:")]) self.send_request("listplaylists") @@ -177,9 +161,7 @@ def test_listplaylists_replaces_newline_with_space(self): self.assertInResponse("OK") def test_listplaylists_replaces_carriage_return_with_space(self): - self.backend.playlists.set_dummy_playlists( - [Playlist(name="a\r", uri="dummy:")] - ) + self.backend.playlists.set_dummy_playlists([Playlist(name="a\r", uri="dummy:")]) self.send_request("listplaylists") @@ -188,9 +170,7 @@ def test_listplaylists_replaces_carriage_return_with_space(self): self.assertInResponse("OK") def test_listplaylists_replaces_forward_slash_with_pipe(self): - self.backend.playlists.set_dummy_playlists( - [Playlist(name="a/b", uri="dummy:")] - ) + self.backend.playlists.set_dummy_playlists([Playlist(name="a/b", uri="dummy:")]) self.send_request("listplaylists") @@ -217,12 +197,12 @@ def test_load_appends_to_tracklist(self): self.send_request('load "A-list"') tracks = self.core.tracklist.get_tracks().get() - assert 5 == len(tracks) - assert "dummy:a" == tracks[0].uri - assert "dummy:b" == tracks[1].uri - assert "dummy:c" == tracks[2].uri - assert "dummy:d" == tracks[3].uri - assert "dummy:ǫ" == tracks[4].uri + assert len(tracks) == 5 + assert tracks[0].uri == "dummy:a" + assert tracks[1].uri == "dummy:b" + assert tracks[2].uri == "dummy:c" + assert tracks[3].uri == "dummy:d" + assert tracks[4].uri == "dummy:ǫ" self.assertInResponse("OK") def test_load_with_range_loads_part_of_playlist(self): @@ -244,10 +224,10 @@ def test_load_with_range_loads_part_of_playlist(self): self.send_request('load "A-list" "1:2"') tracks = self.core.tracklist.get_tracks().get() - assert 3 == len(tracks) - assert "dummy:a" == tracks[0].uri - assert "dummy:b" == tracks[1].uri - assert "dummy:d" == tracks[2].uri + assert len(tracks) == 3 + assert tracks[0].uri == "dummy:a" + assert tracks[1].uri == "dummy:b" + assert tracks[2].uri == "dummy:d" self.assertInResponse("OK") def test_load_with_range_without_end_loads_rest_of_playlist(self): @@ -269,17 +249,17 @@ def test_load_with_range_without_end_loads_rest_of_playlist(self): self.send_request('load "A-list" "1:"') tracks = self.core.tracklist.get_tracks().get() - assert 4 == len(tracks) - assert "dummy:a" == tracks[0].uri - assert "dummy:b" == tracks[1].uri - assert "dummy:d" == tracks[2].uri - assert "dummy:e" == tracks[3].uri + assert len(tracks) == 4 + assert tracks[0].uri == "dummy:a" + assert tracks[1].uri == "dummy:b" + assert tracks[2].uri == "dummy:d" + assert tracks[3].uri == "dummy:e" self.assertInResponse("OK") def test_load_unknown_playlist_acks(self): self.send_request('load "unknown playlist"') - assert 0 == len(self.core.tracklist.get_tracks().get()) + assert len(self.core.tracklist.get_tracks().get()) == 0 self.assertEqualResponse("ACK [50@0] {load} No such playlist") # No invalid name check for load. @@ -292,11 +272,7 @@ def test_load_full_track_metadata(self): ] self.backend.library.dummy_library = tracks self.backend.playlists.set_dummy_playlists( - [ - Playlist( - name="A-list", uri="dummy:a1", tracks=[Track(uri="dummy:a")] - ) - ] + [Playlist(name="A-list", uri="dummy:a1", tracks=[Track(uri="dummy:a")])] ) self.send_request('load "A-list"') @@ -322,7 +298,7 @@ def test_playlistadd(self): self.send_request('playlistadd "name" "dummy:b"') self.assertInResponse("OK") - assert 2 == len(self.backend.playlists.get_items("dummy:a1").get()) + assert len(self.backend.playlists.get_items("dummy:a1").get()) == 2 def test_playlistadd_creates_playlist(self): tracks = [ @@ -351,7 +327,7 @@ def test_playlistclear(self): self.send_request('playlistclear "namé"') self.assertInResponse("OK") - assert 0 == len(self.backend.playlists.get_items("dummy:a1").get()) + assert len(self.backend.playlists.get_items("dummy:a1").get()) == 0 def test_playlistclear_creates_playlist(self): self.send_request('playlistclear "name"') @@ -389,7 +365,7 @@ def test_playlistdelete(self): self.send_request('playlistdelete "namé" "2"') self.assertInResponse("OK") - assert 2 == len(self.backend.playlists.get_items("dummy:a1").get()) + assert len(self.backend.playlists.get_items("dummy:a1").get()) == 2 def test_playlistdelete_save_fails(self): tracks = [ @@ -439,10 +415,7 @@ def test_playlistmove(self): self.send_request('playlistmove "namé" "2" "0"') self.assertInResponse("OK") - assert ( - "dummy:c" - == self.backend.playlists.get_items("dummy:a1").get()[0].uri - ) + assert self.backend.playlists.get_items("dummy:a1").get()[0].uri == "dummy:c" def test_playlistmove_save_fails(self): tracks = [ @@ -519,8 +492,7 @@ def test_rename_save_fails(self): self.send_request('rename "old_name" "new_name"') self.assertInResponse( - "ACK [0@0] {rename} Backend with " - 'scheme "dummy" failed to save playlist' + "ACK [0@0] {rename} Backend with " 'scheme "dummy" failed to save playlist' ) def test_rename_unknown_playlist_acks(self): @@ -583,8 +555,7 @@ def test_save_fails(self): self.send_request('save "name"') self.assertInResponse( - "ACK [0@0] {save} Backend with " - 'scheme "dummy" failed to save playlist' + "ACK [0@0] {save} Backend with " 'scheme "dummy" failed to save playlist' ) def test_save_invalid_name_acks(self): diff --git a/tests/test_actor.py b/tests/test_actor.py index cb85fad..c76dde8 100644 --- a/tests/test_actor.py +++ b/tests/test_actor.py @@ -8,7 +8,7 @@ @pytest.mark.parametrize( - "event,expected", + ("event", "expected"), [ (["track_playback_paused", "tl_track", "time_position"], None), (["track_playback_resumed", "tl_track", "time_position"], None), diff --git a/tests/test_commands.py b/tests/test_commands.py index 1c47307..e9a7cdb 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -5,16 +5,16 @@ class TestConverts(unittest.TestCase): def test_integer(self): - assert 123 == protocol.INT("123") - assert (-123) == protocol.INT("-123") - assert 123 == protocol.INT("+123") + assert protocol.INT("123") == 123 + assert protocol.INT("-123") == (-123) + assert protocol.INT("+123") == 123 self.assertRaises(ValueError, protocol.INT, "3.14") self.assertRaises(ValueError, protocol.INT, "") self.assertRaises(ValueError, protocol.INT, "abc") self.assertRaises(ValueError, protocol.INT, "12 34") def test_unsigned_integer(self): - assert 123 == protocol.UINT("123") + assert protocol.UINT("123") == 123 self.assertRaises(ValueError, protocol.UINT, "-123") self.assertRaises(ValueError, protocol.UINT, "+123") self.assertRaises(ValueError, protocol.UINT, "3.14") @@ -51,7 +51,7 @@ def test_range(self): class TestCommands(unittest.TestCase): - def setUp(self): # noqa: N802 + def setUp(self): self.commands = protocol.Commands() def test_add_as_a_decorator(self): @@ -97,7 +97,7 @@ def test_function_has_varargs_succeeds(self): sentinel, args = object(), [] self.commands.add("bar")(lambda context, *args: sentinel) for _ in range(10): - assert sentinel == self.commands.call((["bar"] + args)) + assert sentinel == self.commands.call(["bar", *args]) args.append("test") def test_function_has_only_varags_succeeds(self): @@ -140,7 +140,7 @@ def test_call_chooses_correct_handler(self): assert sentinel3 == self.commands.call(["baz"]) def test_call_with_nonexistent_handler(self): - with self.assertRaises(exceptions.MpdUnknownCommand): + with self.assertRaises(exceptions.MpdUnknownCommandError): self.commands.call(["bar"]) def test_call_passes_context(self): @@ -149,26 +149,26 @@ def test_call_passes_context(self): assert sentinel == self.commands.call(["foo"], context=sentinel) def test_call_without_args_fails(self): - with self.assertRaises(exceptions.MpdNoCommand): + with self.assertRaises(exceptions.MpdNoCommandError): self.commands.call([]) def test_call_passes_required_argument(self): self.commands.add("foo")(lambda context, required: required) - assert "test123" == self.commands.call(["foo", "test123"]) + assert self.commands.call(["foo", "test123"]) == "test123" def test_call_passes_optional_argument(self): sentinel = object() self.commands.add("foo")(lambda context, optional=sentinel: optional) assert sentinel == self.commands.call(["foo"]) - assert "test" == self.commands.call(["foo", "test"]) + assert self.commands.call(["foo", "test"]) == "test" def test_call_passes_required_and_optional_argument(self): def func(context, required, optional=None): return (required, optional) self.commands.add("foo")(func) - assert ("arg", None) == self.commands.call(["foo", "arg"]) - assert ("arg", "kwarg") == self.commands.call(["foo", "arg", "kwarg"]) + assert self.commands.call(["foo", "arg"]) == ("arg", None) + assert self.commands.call(["foo", "arg", "kwarg"]) == ("arg", "kwarg") def test_call_passes_varargs(self): self.commands.add("foo")(lambda context, *args: args) diff --git a/tests/test_dispatcher.py b/tests/test_dispatcher.py index 13445e8..5496093 100644 --- a/tests/test_dispatcher.py +++ b/tests/test_dispatcher.py @@ -2,25 +2,24 @@ import pykka import pytest - from mopidy import core from mopidy.models import Ref + from mopidy_mpd.dispatcher import MpdContext, MpdDispatcher from mopidy_mpd.exceptions import MpdAckError from mopidy_mpd.uri_mapper import MpdUriMapper - from tests import dummy_backend class MpdDispatcherTest(unittest.TestCase): - def setUp(self): # noqa: N802 + def setUp(self): config = {"mpd": {"password": None, "command_blacklist": ["disabled"]}} self.backend = dummy_backend.create_proxy() self.dispatcher = MpdDispatcher(config=config) - self.core = core.Core.start(backends=[self.backend]).proxy() + self.core = core.Core.start(config=None, backends=[self.backend]).proxy() - def tearDown(self): # noqa: N802 + def tearDown(self): pykka.ActorRegistry.stop_all() def test_call_handler_for_unknown_command_raises_exception(self): @@ -44,17 +43,17 @@ def test_handling_blacklisted_command(self): ) -@pytest.fixture +@pytest.fixture() def a_track(): return Ref.track(uri="dummy:/a", name="a") -@pytest.fixture +@pytest.fixture() def b_track(): return Ref.track(uri="dummy:/foo/b", name="b") -@pytest.fixture +@pytest.fixture() def backend_to_browse(a_track, b_track): backend = dummy_backend.create_proxy() backend.library.dummy_browse_result = { @@ -64,9 +63,9 @@ def backend_to_browse(a_track, b_track): return backend -@pytest.fixture +@pytest.fixture() def mpd_context(backend_to_browse): - mopidy_core = core.Core.start(backends=[backend_to_browse]).proxy() + mopidy_core = core.Core.start(config=None, backends=[backend_to_browse]).proxy() uri_map = MpdUriMapper(mopidy_core) return MpdContext(None, core=mopidy_core, uri_map=uri_map) diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index b5d145a..a7b3dae 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,65 +1,65 @@ import unittest +import pytest + from mopidy_mpd.exceptions import ( MpdAckError, - MpdNoCommand, + MpdNoCommandError, MpdNoExistError, - MpdNotImplemented, + MpdNotImplementedError, MpdPermissionError, MpdSystemError, - MpdUnknownCommand, + MpdUnknownCommandError, ) class MpdExceptionsTest(unittest.TestCase): def test_mpd_not_implemented_is_a_mpd_ack_error(self): - try: - raise MpdNotImplemented - except MpdAckError as exc: - assert ( - exc.message # noqa: B306: Our own exception - == "Not implemented" - ) + with pytest.raises(MpdAckError) as exc_info: + raise MpdNotImplementedError + + assert exc_info.value.message == "Not implemented" def test_get_mpd_ack_with_default_values(self): e = MpdAckError("A description") + assert e.get_mpd_ack() == "ACK [0@0] {None} A description" def test_get_mpd_ack_with_values(self): - try: + with pytest.raises(MpdAckError) as exc_info: raise MpdAckError("A description", index=7, command="foo") - except MpdAckError as e: - assert e.get_mpd_ack() == "ACK [0@7] {foo} A description" + + assert exc_info.value.get_mpd_ack() == "ACK [0@7] {foo} A description" def test_mpd_unknown_command(self): - try: - raise MpdUnknownCommand(command="play") - except MpdAckError as e: - assert e.get_mpd_ack() == 'ACK [5@0] {} unknown command "play"' + with pytest.raises(MpdAckError) as exc_info: + raise MpdUnknownCommandError(command="play") + + assert exc_info.value.get_mpd_ack() == 'ACK [5@0] {} unknown command "play"' def test_mpd_no_command(self): - try: - raise MpdNoCommand - except MpdAckError as e: - assert e.get_mpd_ack() == "ACK [5@0] {} No command given" + with pytest.raises(MpdAckError) as exc_info: + raise MpdNoCommandError + + assert exc_info.value.get_mpd_ack() == "ACK [5@0] {} No command given" def test_mpd_system_error(self): - try: + with pytest.raises(MpdSystemError) as exc_info: raise MpdSystemError("foo") - except MpdSystemError as e: - assert e.get_mpd_ack() == "ACK [52@0] {None} foo" + + assert exc_info.value.get_mpd_ack() == "ACK [52@0] {None} foo" def test_mpd_permission_error(self): - try: + with pytest.raises(MpdPermissionError) as exc_info: raise MpdPermissionError(command="foo") - except MpdPermissionError as e: - assert ( - e.get_mpd_ack() - == 'ACK [4@0] {foo} you don\'t have permission for "foo"' - ) + + assert ( + exc_info.value.get_mpd_ack() + == 'ACK [4@0] {foo} you don\'t have permission for "foo"' + ) def test_mpd_noexist_error(self): - try: + with pytest.raises(MpdNoExistError) as exc_info: raise MpdNoExistError(command="foo") - except MpdNoExistError as e: - assert e.get_mpd_ack() == "ACK [50@0] {foo} " + + assert exc_info.value.get_mpd_ack() == "ACK [50@0] {foo} " diff --git a/tests/test_path_utils.py b/tests/test_path_utils.py index a3721c9..fef81b8 100644 --- a/tests/test_path_utils.py +++ b/tests/test_path_utils.py @@ -6,11 +6,11 @@ # TODO: kill this in favour of just os.path.getmtime + mocks class MtimeTest(unittest.TestCase): - def tearDown(self): # noqa: N802 + def tearDown(self): path_utils.mtime.undo_fake() def test_mtime_of_current_dir(self): - mtime_dir = int(os.stat(".").st_mtime) + mtime_dir = int(os.stat(".").st_mtime) # noqa: PTH116 assert mtime_dir == path_utils.mtime(".") def test_fake_time_is_returned(self): diff --git a/tests/test_status.py b/tests/test_status.py index 5a0adaa..428f340 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -1,13 +1,12 @@ import unittest import pykka - from mopidy import core from mopidy.core import PlaybackState from mopidy.models import Track + from mopidy_mpd import dispatcher from mopidy_mpd.protocol import status - from tests import dummy_audio, dummy_backend, dummy_mixer PAUSED = PlaybackState.PAUSED @@ -19,7 +18,7 @@ class StatusHandlerTest(unittest.TestCase): - def setUp(self): # noqa: N802 + def setUp(self): config = {"core": {"max_tracklist_length": 10000}} self.audio = dummy_audio.create_proxy() @@ -36,7 +35,7 @@ def setUp(self): # noqa: N802 self.dispatcher = dispatcher.MpdDispatcher(core=self.core) self.context = self.dispatcher.context - def tearDown(self): # noqa: N802 + def tearDown(self): pykka.ActorRegistry.stop_all() def set_tracklist(self, tracks): diff --git a/tests/test_tokenizer.py b/tests/test_tokenizer.py index ac4f794..0f22a80 100644 --- a/tests/test_tokenizer.py +++ b/tests/test_tokenizer.py @@ -13,7 +13,7 @@ def assertTokenizeRaises(self, exception, message, line): # noqa: N802 assert cm.exception.message == message def test_empty_string(self): - ex = exceptions.MpdNoCommand + ex = exceptions.MpdNoCommandError msg = "No command given" self.assertTokenizeRaises(ex, msg, "") self.assertTokenizeRaises(ex, msg, " ") diff --git a/tests/test_translator.py b/tests/test_translator.py index 9d0c76b..b88686e 100644 --- a/tests/test_translator.py +++ b/tests/test_translator.py @@ -1,9 +1,9 @@ import unittest from mopidy.models import Album, Artist, Playlist, TlTrack, Track + from mopidy_mpd import translator from mopidy_mpd.protocol import tagtype_list - from tests import path_utils @@ -34,11 +34,11 @@ class TrackMpdFormatTest(unittest.TestCase): length=137000, ) - def setUp(self): # noqa: N802 + def setUp(self): self.media_dir = "/dir/subdir" path_utils.mtime.set_fake_time(1234567) - def tearDown(self): # noqa: N802 + def tearDown(self): path_utils.mtime.undo_fake() def test_track_to_mpd_format_for_empty_track(self): @@ -103,49 +103,37 @@ def test_track_to_mpd_format_for_nonempty_track(self): def test_track_to_mpd_format_with_last_modified(self): track = self.track.replace(last_modified=995303899000) - result = translator.track_to_mpd_format( - track, tagtype_list.TAGTYPE_LIST - ) + result = translator.track_to_mpd_format(track, tagtype_list.TAGTYPE_LIST) assert ("Last-Modified", "2001-07-16T17:18:19Z") in result def test_track_to_mpd_format_with_last_modified_of_zero(self): track = self.track.replace(last_modified=0) - result = translator.track_to_mpd_format( - track, tagtype_list.TAGTYPE_LIST - ) + result = translator.track_to_mpd_format(track, tagtype_list.TAGTYPE_LIST) keys = [k for k, v in result] assert "Last-Modified" not in keys def test_track_to_mpd_format_musicbrainz_trackid(self): track = self.track.replace(musicbrainz_id="foo") - result = translator.track_to_mpd_format( - track, tagtype_list.TAGTYPE_LIST - ) + result = translator.track_to_mpd_format(track, tagtype_list.TAGTYPE_LIST) assert ("MUSICBRAINZ_TRACKID", "foo") in result def test_track_to_mpd_format_musicbrainz_albumid(self): album = self.track.album.replace(musicbrainz_id="foo") track = self.track.replace(album=album) - result = translator.track_to_mpd_format( - track, tagtype_list.TAGTYPE_LIST - ) + result = translator.track_to_mpd_format(track, tagtype_list.TAGTYPE_LIST) assert ("MUSICBRAINZ_ALBUMID", "foo") in result def test_track_to_mpd_format_musicbrainz_albumartistid(self): - artist = list(self.track.artists)[0].replace(musicbrainz_id="foo") + artist = next(iter(self.track.artists)).replace(musicbrainz_id="foo") album = self.track.album.replace(artists=[artist]) track = self.track.replace(album=album) - result = translator.track_to_mpd_format( - track, tagtype_list.TAGTYPE_LIST - ) + result = translator.track_to_mpd_format(track, tagtype_list.TAGTYPE_LIST) assert ("MUSICBRAINZ_ALBUMARTISTID", "foo") in result def test_track_to_mpd_format_musicbrainz_artistid(self): - artist = list(self.track.artists)[0].replace(musicbrainz_id="foo") + artist = next(iter(self.track.artists)).replace(musicbrainz_id="foo") track = self.track.replace(artists=[artist]) - result = translator.track_to_mpd_format( - track, tagtype_list.TAGTYPE_LIST - ) + result = translator.track_to_mpd_format(track, tagtype_list.TAGTYPE_LIST) assert ("MUSICBRAINZ_ARTISTID", "foo") in result def test_concat_multi_values(self): @@ -219,9 +207,7 @@ def test_mpd_format(self): Track(uri="baz", track_no=3), ] ) - result = translator.playlist_to_mpd_format( - playlist, tagtype_list.TAGTYPE_LIST - ) + result = translator.playlist_to_mpd_format(playlist, tagtype_list.TAGTYPE_LIST) assert len(result) == 3 def test_mpd_format_with_range(self): @@ -233,7 +219,7 @@ def test_mpd_format_with_range(self): ] ) result = translator.playlist_to_mpd_format( - playlist, tagtype_list.TAGTYPE_LIST, 1, 2 + playlist, tagtype_list.TAGTYPE_LIST, start=1, end=2 ) assert len(result) == 1 assert dict(result[0])["Track"] == 2 diff --git a/tox.ini b/tox.ini index e6a3663..f3e9390 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py39, py310, py311, check-manifest, flake8 +envlist = py311, py312, check-manifest, flake8 [testenv] sitepackages = true @@ -10,10 +10,11 @@ commands = --cov=mopidy_mpd --cov-report=term-missing \ {posargs} -[testenv:check-manifest] + +[testenv:ruff-lint] deps = .[lint] -commands = python -m check_manifest +commands = python -m ruff check . -[testenv:flake8] +[testenv:ruff-format] deps = .[lint] -commands = python -m flake8 --show-source --statistics +commands = python -m ruff format .