diff --git a/.copier-answers.yml b/.copier-answers.yml index a8257e3..1338d2f 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,4 +1,4 @@ -_commit: v2.1.3 +_commit: v2.2.0 _src_path: gh:mopidy/mopidy-ext-template author_email: stein.magnus@jodal.no author_full_name: Stein Magnus Jodal diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ec228e..aa44ad9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,13 +5,14 @@ on: push: branches: - main + workflow_dispatch: jobs: build: name: Build runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: hynek/build-and-inspect-python-package@v2 main: @@ -19,24 +20,21 @@ jobs: fail-fast: false matrix: include: - - name: "pytest (3.11)" - python: "3.11" - tox: "3.11" - - name: "pytest (3.12)" - python: "3.12" - tox: "3.12" - name: "pytest (3.13)" python: "3.13" tox: "3.13" + - name: "pytest (3.14)" + python: "3.14" + tox: "3.14" coverage: true - name: "pyright" - python: "3.13" + python: "3.14" tox: "pyright" - name: "ruff check" - python: "3.13" + python: "3.14" tox: "ruff-check" - name: "ruff format" - python: "3.13" + python: "3.14" tox: "ruff-format" name: ${{ matrix.name }} @@ -44,10 +42,10 @@ jobs: container: ghcr.io/mopidy/ci:latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Fix home dir permissions to enable pip caching run: chown -R root /github/home - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python }} cache: pip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b0ae30b..89fdad2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: hynek/build-and-inspect-python-package@v2 id: build - uses: actions/download-artifact@v4 diff --git a/pyproject.toml b/pyproject.toml index 3399120..9f9ab43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "mopidy-mpd" description = "Mopidy extension for controlling Mopidy from MPD clients" readme = "README.md" -requires-python = ">= 3.11" +requires-python = ">= 3.13" license = { text = "Apache-2.0" } authors = [{ name = "Stein Magnus Jodal", email = "stein.magnus@jodal.no" }] classifiers = [ @@ -13,7 +13,11 @@ classifiers = [ "Topic :: Multimedia :: Sound/Audio :: Players", ] dynamic = ["version"] -dependencies = ["mopidy >= 4.0.0a4", "pygobject >= 3.42", "pykka >= 4"] +dependencies = [ + "mopidy >= 4.0.0a7", + "pygobject >= 3.50", + "pykka >= 4.1", +] [project.urls] Homepage = "https://github.com/mopidy/mopidy-mpd" @@ -23,7 +27,7 @@ mpd = "mopidy_mpd:Extension" [build-system] -requires = ["setuptools >= 66", "setuptools-scm >= 7.1"] +requires = ["setuptools >= 78", "setuptools-scm >= 8.2"] build-backend = "setuptools.build_meta" @@ -50,7 +54,7 @@ show_missing = true [tool.pyright] -pythonVersion = "3.11" +pythonVersion = "3.13" typeCheckingMode = "standard" # Not all dependencies have type hints: reportMissingTypeStubs = false @@ -68,7 +72,7 @@ filterwarnings = [ [tool.ruff] -target-version = "py311" +target-version = "py313" [tool.ruff.lint] select = ["ALL"] @@ -122,7 +126,13 @@ ignore = [ [tool.tox] -env_list = ["3.11", "3.12", "3.13", "pyright", "ruff-check", "ruff-format"] +env_list = [ + "3.13", + "3.14", + "pyright", + "ruff-check", + "ruff-format", +] [tool.tox.env_run_base] package = "wheel" diff --git a/src/mopidy_mpd/__init__.py b/src/mopidy_mpd/__init__.py index a4c672f..98a246b 100644 --- a/src/mopidy_mpd/__init__.py +++ b/src/mopidy_mpd/__init__.py @@ -27,6 +27,6 @@ def get_config_schema(self) -> config.ConfigSchema: return schema def setup(self, registry: ext.Registry) -> None: - from .actor import MpdFrontend + from .actor import MpdFrontend # noqa: PLC0415 registry.add("frontend", MpdFrontend) diff --git a/src/mopidy_mpd/context.py b/src/mopidy_mpd/context.py index 317e32c..b32f756 100644 --- a/src/mopidy_mpd/context.py +++ b/src/mopidy_mpd/context.py @@ -65,14 +65,12 @@ def __init__( @overload def browse( self, path: str | None, *, recursive: bool, lookup: Literal[True] - ) -> Generator[ - tuple[str, pykka.Future[dict[Uri, list[Track]]] | None], Any, None - ]: ... + ) -> Generator[tuple[str, pykka.Future[dict[Uri, list[Track]]] | None], Any]: ... @overload def browse( self, path: str | None, *, recursive: bool, lookup: Literal[False] - ) -> Generator[tuple[str, Ref | None], Any, None]: ... + ) -> Generator[tuple[str, Ref | None], Any]: ... def browse( # noqa: C901, PLR0912 self, @@ -80,7 +78,7 @@ def browse( # noqa: C901, PLR0912 *, recursive: bool = True, lookup: bool = True, - ) -> Generator[Any, Any, None]: + ) -> Generator[Any, Any]: """ Browse the contents of a given directory path. diff --git a/src/mopidy_mpd/dispatcher.py b/src/mopidy_mpd/dispatcher.py index 698e8ac..57d77f4 100644 --- a/src/mopidy_mpd/dispatcher.py +++ b/src/mopidy_mpd/dispatcher.py @@ -5,7 +5,6 @@ from typing import ( TYPE_CHECKING, NewType, - TypeAlias, TypeVar, ) @@ -25,9 +24,9 @@ protocol.load_protocol_modules() T = TypeVar("T") -Request: TypeAlias = str +type Request = str Response = NewType("Response", list[str]) -Filter: TypeAlias = Callable[[Request, Response, list["Filter"]], Response] +type Filter = Callable[[Request, Response, list["Filter"]], Response] class MpdDispatcher: diff --git a/src/mopidy_mpd/network.py b/src/mopidy_mpd/network.py index ba55364..0573c29 100644 --- a/src/mopidy_mpd/network.py +++ b/src/mopidy_mpd/network.py @@ -20,6 +20,7 @@ from types import TracebackType from mopidy.core import CoreProxy + from mopidy_mpd import types from mopidy_mpd.session import MpdSession, MpdSessionKwargs from mopidy_mpd.types import SocketAddress @@ -502,7 +503,7 @@ def on_stop(self) -> None: """Clean up connection resouces when actor stops.""" self.connection.stop("Actor is shutting down.") - def parse_lines(self) -> Generator[bytes, Any, None]: + def parse_lines(self) -> Generator[bytes, Any]: """Consume new data and yield any lines found.""" while re.search(self.terminator, self.recv_buffer): line, self.recv_buffer = self.delimiter.split(self.recv_buffer, 1) diff --git a/src/mopidy_mpd/protocol/__init__.py b/src/mopidy_mpd/protocol/__init__.py index 87ba052..be23773 100644 --- a/src/mopidy_mpd/protocol/__init__.py +++ b/src/mopidy_mpd/protocol/__init__.py @@ -14,7 +14,7 @@ import inspect from collections.abc import Callable -from typing import TYPE_CHECKING, Any, TypeAlias +from typing import TYPE_CHECKING, Any from mopidy_mpd import exceptions @@ -31,12 +31,12 @@ VERSION = "0.19.0" -ResultValue: TypeAlias = str | int -ResultDict: TypeAlias = dict[str, ResultValue] -ResultTuple: TypeAlias = tuple[str, ResultValue] -ResultList: TypeAlias = list[ResultTuple | ResultDict] -Result: TypeAlias = None | ResultDict | ResultTuple | ResultList -HandlerFunc: TypeAlias = Callable[..., Result] +type ResultValue = str | int +type ResultDict = dict[str, ResultValue] +type ResultTuple = tuple[str, ResultValue] +type ResultList = list[ResultTuple | ResultDict] +type Result = None | ResultDict | ResultTuple | ResultList +type HandlerFunc = Callable[..., Result] def load_protocol_modules() -> None: @@ -44,7 +44,7 @@ def load_protocol_modules() -> None: The protocol modules must be imported to get them registered in :attr:`commands`. """ - from . import ( # noqa: F401 + from . import ( # noqa: F401, PLC0415 audio_output, channels, command_list, diff --git a/src/mopidy_mpd/protocol/music_db.py b/src/mopidy_mpd/protocol/music_db.py index e3356d2..3c28a3d 100644 --- a/src/mopidy_mpd/protocol/music_db.py +++ b/src/mopidy_mpd/protocol/music_db.py @@ -4,13 +4,15 @@ from typing import TYPE_CHECKING, cast from mopidy.models import Album, Artist, SearchResult, Track -from mopidy.types import DistinctField, Query, SearchField, Uri + from mopidy_mpd import exceptions, protocol, translator from mopidy_mpd.protocol import stored_playlists if TYPE_CHECKING: from collections.abc import Iterable, Sequence + from mopidy.types import DistinctField, Query, SearchField, Uri + from mopidy_mpd.context import MpdContext @@ -67,7 +69,7 @@ def _query_for_search(parameters: Sequence[str]) -> Query[SearchField]: value = parameters.pop(0) if value.strip(): query.setdefault(field, []).append(value) - return cast(Query[SearchField], query) + return cast("Query[SearchField]", query) def _get_albums(search_results: Iterable[SearchResult]) -> list[Album]: @@ -418,7 +420,7 @@ def lsinfo(context: MpdContext, uri: str | None = None) -> protocol.Result: # `protocol.Result``, but this information disappears because of the # typing of the `protocol.commands.add()`` decorator. cast( - protocol.ResultList, + "protocol.ResultList", stored_playlists.listplaylists(context), ) ) diff --git a/src/mopidy_mpd/protocol/playback.py b/src/mopidy_mpd/protocol/playback.py index 06bb71d..da54a4e 100644 --- a/src/mopidy_mpd/protocol/playback.py +++ b/src/mopidy_mpd/protocol/playback.py @@ -127,7 +127,7 @@ def next_(context: MpdContext) -> None: @protocol.commands.add("pause", state=protocol.BOOL) -def pause(context: MpdContext, state: bool | None = None) -> None: +def pause(context: MpdContext, state: bool | None = None) -> None: # noqa: FBT001 """ *musicpd.org, playback section:* diff --git a/src/mopidy_mpd/protocol/stored_playlists.py b/src/mopidy_mpd/protocol/stored_playlists.py index dcd4e45..168fc91 100644 --- a/src/mopidy_mpd/protocol/stored_playlists.py +++ b/src/mopidy_mpd/protocol/stored_playlists.py @@ -6,14 +6,15 @@ from typing import TYPE_CHECKING, Literal, cast, overload from urllib.parse import urlparse -from mopidy.models import Track from mopidy.types import Uri, UriScheme + from mopidy_mpd import exceptions, protocol, translator if TYPE_CHECKING: from collections.abc import Iterable - from mopidy.models import Playlist + from mopidy.models import Playlist, Track + from mopidy_mpd.context import MpdContext logger = logging.getLogger(__name__) @@ -176,7 +177,7 @@ def load( """ playlist = _get_playlist(context, name, must_exist=True) tracks = cast( # TODO(type): Improve typing of models to avoid cast. - tuple[Track], + "tuple[Track]", playlist.tracks[playlist_slice], # pyright: ignore[reportIndexIssue] ) track_uris = [track.uri for track in tracks] diff --git a/src/mopidy_mpd/session.py b/src/mopidy_mpd/session.py index 515947c..5614ece 100644 --- a/src/mopidy_mpd/session.py +++ b/src/mopidy_mpd/session.py @@ -8,6 +8,7 @@ if TYPE_CHECKING: from mopidy.core import CoreProxy + from mopidy_mpd.uri_mapper import MpdUriMapper diff --git a/src/mopidy_mpd/types.py b/src/mopidy_mpd/types.py index 95c969f..cd84f14 100644 --- a/src/mopidy_mpd/types.py +++ b/src/mopidy_mpd/types.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeAlias, TypedDict +from typing import TYPE_CHECKING, TypedDict from mopidy.config import Config as MopidyConfig @@ -23,4 +23,4 @@ class MpdConfig(TypedDict): default_playlist_scheme: UriScheme -SocketAddress: TypeAlias = tuple[str, int | None] +type SocketAddress = tuple[str, int | None] diff --git a/tests/__init__.py b/tests/__init__.py index f03d5f9..09882ae 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ -class IsA: +class IsA: # noqa: PLW1641 def __init__(self, klass): self.klass = klass diff --git a/tests/protocol/__init__.py b/tests/protocol/__init__.py index bd54101..d0e3ad4 100644 --- a/tests/protocol/__init__.py +++ b/tests/protocol/__init__.py @@ -44,7 +44,7 @@ def setUp(self): self.backend = dummy_backend.create_proxy(audio=self.audio) self.core = cast( - core.CoreProxy, + "core.CoreProxy", core.Core.start( self.get_config(), audio=self.audio, diff --git a/tests/test_actor.py b/tests/test_actor.py index c76dde8..09626bf 100644 --- a/tests/test_actor.py +++ b/tests/test_actor.py @@ -41,7 +41,7 @@ def test_idle_hooked_up_correctly(event, expected): frontend = actor.MpdFrontend(core=mock.Mock(), config=config) with mock.patch("mopidy.listener.send") as send_mock: - frontend.on_event(event[0], **{e: None for e in event[1:]}) + frontend.on_event(event[0], **dict.fromkeys(event[1:])) if expected is None: assert not send_mock.call_args diff --git a/tests/test_context.py b/tests/test_context.py index b4ad72e..5dfcaf5 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -23,7 +23,7 @@ def b_track() -> Ref: @pytest.fixture def backend_to_browse(a_track: Ref, b_track: Ref) -> BackendProxy: - backend = cast(BackendProxy, dummy_backend.create_proxy()) + backend = cast("BackendProxy", dummy_backend.create_proxy()) backend.library.dummy_browse_result = { "dummy:/": [ a_track, @@ -39,7 +39,7 @@ def backend_to_browse(a_track: Ref, b_track: Ref) -> BackendProxy: @pytest.fixture def mpd_context(backend_to_browse: BackendProxy) -> MpdContext: core = cast( - CoreProxy, + "CoreProxy", Core.start(config=None, backends=[backend_to_browse]).proxy(), ) return MpdContext( diff --git a/tests/test_dispatcher.py b/tests/test_dispatcher.py index c4cc7dd..fa91211 100644 --- a/tests/test_dispatcher.py +++ b/tests/test_dispatcher.py @@ -15,7 +15,7 @@ def setUp(self): config = {"mpd": {"password": None, "command_blacklist": ["disabled"]}} self.backend = dummy_backend.create_proxy() self.core = cast( - CoreProxy, Core.start(config=None, backends=[self.backend]).proxy() + "CoreProxy", Core.start(config=None, backends=[self.backend]).proxy() ) self.dispatcher = MpdDispatcher( config=config, diff --git a/tests/test_status.py b/tests/test_status.py index eeb3c4a..23833ec 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -29,7 +29,7 @@ def setUp(self): self.backend = dummy_backend.create_proxy(audio=self.audio) self.core = cast( - core.CoreProxy, + "core.CoreProxy", core.Core.start( config, audio=self.audio,