diff --git a/.copier-answers.yml b/.copier-answers.yml
new file mode 100644
index 0000000..0f3e2ce
--- /dev/null
+++ b/.copier-answers.yml
@@ -0,0 +1,8 @@
+_commit: v2.2.0
+_src_path: gh:mopidy/mopidy-ext-template
+author_email: git@matthewgamble.net
+author_full_name: Matthew Gamble
+dist_name: mopidy-api-explorer
+ext_name: api_explorer
+github_username: mopidy
+short_description: Mopidy extension which lets you explore the JSON-RPC API
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index af3eaa5..4f0b333 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,37 +5,57 @@ on:
push:
branches:
- main
+ workflow_dispatch:
jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v6
+ - uses: hynek/build-and-inspect-python-package@v2
+
main:
strategy:
fail-fast: false
matrix:
include:
- - name: "Lint: check-manifest"
- python: "3.11"
- tox: check-manifest
- - name: "Lint: flake8"
- python: "3.11"
- tox: flake8
+ # - 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.14"
+ tox: "pyright"
+ - name: "ruff check"
+ python: "3.14"
+ tox: "ruff-check"
+ - name: "ruff format"
+ python: "3.14"
+ tox: "ruff-format"
name: ${{ matrix.name }}
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
container: ghcr.io/mopidy/ci:latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v6
- name: Fix home dir permissions to enable pip caching
run: chown -R root /github/home
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python }}
cache: pip
- cache-dependency-path: setup.cfg
- - run: python -m pip install pygobject tox
+ allow-prereleases: true
+ - run: python -m pip install tox
- run: python -m tox -e ${{ matrix.tox }}
if: ${{ ! matrix.coverage }}
- run: python -m tox -e ${{ matrix.tox }} -- --cov-report=xml
if: ${{ matrix.coverage }}
- - uses: codecov/codecov-action@v3
+ - uses: codecov/codecov-action@v5
if: ${{ matrix.coverage }}
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7badb07..766356a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -6,18 +6,18 @@ on:
jobs:
release:
- runs-on: ubuntu-20.04
-
+ runs-on: ubuntu-24.04
+ environment:
+ name: pypi
+ url: https://pypi.org/project/mopidy-api-explorer/
+ permissions:
+ id-token: write
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-python@v2
- with:
- python-version: '3.9'
- - name: "Install dependencies"
- run: python3 -m pip install build
- - name: "Build package"
- run: python3 -m build
- - uses: pypa/gh-action-pypi-publish@v1.13.0
- with:
- user: __token__
- password: ${{ secrets.PYPI_TOKEN }}
+ - uses: actions/checkout@v6
+ - uses: hynek/build-and-inspect-python-package@v2
+ id: build
+ - uses: actions/download-artifact@v4
+ with:
+ name: ${{ steps.build.outputs.artifact-name }}
+ path: dist
+ - uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.gitignore b/.gitignore
index 03640c9..bd2e3f6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,9 @@
-*.pyc
+*.egg-info/
+/*.lock
+/.*_cache/
/.coverage
-/.mypy_cache/
-/.pytest_cache/
/.tox/
-/*.egg-info
+/.venv/
/build/
/dist/
-/MANIFEST
+__pycache__/
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index dd9fc68..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,13 +0,0 @@
-include *.py
-include *.rst
-include .mailmap
-include LICENSE
-include MANIFEST.in
-include pyproject.toml
-include tox.ini
-
-recursive-include .github *
-
-include mopidy_api_explorer/ext.conf
-
-recursive-include mopidy_api_explorer/static *
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..755a4de
--- /dev/null
+++ b/README.md
@@ -0,0 +1,105 @@
+# mopidy-api-explorer
+
+[](https://pypi.org/p/mopidy-api-explorer)
+[](https://github.com/mopidy/mopidy-api-explorer/actions/workflows/ci.yml)
+[](https://codecov.io/gh/mopidy/mopidy-api-explorer)
+
+[Mopidy](https://mopidy.com/) extension which lets you explore the JSON-RPC API.
+
+
+## Installation
+
+Install by running:
+
+```sh
+python3 -m pip install mopidy-api-explorer
+```
+
+
+## Usage
+
+Once the extension is installed, Mopidy must be restarted to detect the extension.
+After restarting Mopidy, visit the following URL to explore the Mopidy JSON-RPC API:
+
+ http://localhost:6680/api_explorer/
+
+
+## Project resources
+
+- [Source code](https://github.com/mopidy/mopidy-api-explorer)
+- [Issues](https://github.com/mopidy/mopidy-api-explorer/issues)
+- [Releases](https://github.com/mopidy/mopidy-api-explorer/releases)
+
+
+## Development
+
+### Set up development environment
+
+Clone the repo using, e.g. using [gh](https://cli.github.com/):
+
+```sh
+gh repo clone mopidy/mopidy-api-explorer
+```
+
+Enter the directory, and install dependencies using [uv](https://docs.astral.sh/uv/):
+
+```sh
+cd mopidy-api-explorer/
+uv sync
+```
+
+### Running tests
+
+To run all tests and linters in isolated environments, use
+[tox](https://tox.wiki/):
+
+```sh
+tox
+```
+
+To only run tests, use [pytest](https://pytest.org/):
+
+```sh
+pytest
+```
+
+To format the code, use [ruff](https://docs.astral.sh/ruff/):
+
+```sh
+ruff format .
+```
+
+To check for lints with ruff, run:
+
+```sh
+ruff check .
+```
+
+To check for type errors, use [pyright](https://microsoft.github.io/pyright/):
+
+```sh
+pyright .
+```
+
+### Making a release
+
+To make a release to PyPI, go to the project's [GitHub releases
+page](https://github.com/mopidy/mopidy-api-explorer/releases)
+and click the "Draft a new release" button.
+
+In the "choose a tag" dropdown, select the tag you want to release or create a
+new tag, e.g. `v0.1.0`. Add a title, e.g. `v0.1.0`, and a description of the changes.
+
+Decide if the release is a pre-release (alpha, beta, or release candidate) or
+should be marked as the latest release, and click "Publish release".
+
+Once the release is created, the `release.yml` GitHub Action will automatically
+build and publish the release to
+[PyPI](https://pypi.org/project/mopidy-api-explorer/).
+
+
+## Credits
+
+- Original author: [Janes Troha](https://github.com/dz0ny)
+- Current maintainer: [Matthew Gamble](https://github.com/djmattyg007)
+- [Contributors](https://github.com/mopidy/mopidy-api-explorer/graphs/contributors)
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 4a270db..0000000
--- a/README.rst
+++ /dev/null
@@ -1,46 +0,0 @@
-*******************
-Mopidy-API-Explorer
-*******************
-
-.. image:: https://img.shields.io/pypi/v/Mopidy-API-Explorer.svg
- :target: https://pypi.org/project/Mopidy-API-Explorer/
- :alt: Latest PyPI version
-
-.. image:: https://img.shields.io/github/actions/workflow/status/mopidy/mopidy-api-explorer/ci.yml?branch=main
- :target: https://github.com/mopidy/mopidy-api-explorer/actions
- :alt: CI build status
-
-`Mopidy `_ extension which lets you explore the
-JSON-RPC API.
-
-
-Installation
-============
-
-Install by running::
-
- python3 -m pip install Mopidy-API-Explorer
-
-
-Usage
-=====
-
-Once the extension is installed Mopidy must be restarted to detect the
-extension. After restarting Mopidy, visit http://localhost:6680/api_explorer/
-to explore the Mopidy JSON-RPC API.
-
-
-Project resources
-=================
-
-- `Source code `_
-- `Issue tracker `_
-- `Changelog `_
-
-
-Credits
-=======
-
-- Original author: `Janes Troha `_
-- Current maintainer: `Matthew Gamble `_
-- `Contributors `_
diff --git a/mopidy_api_explorer/__init__.py b/mopidy_api_explorer/__init__.py
deleted file mode 100644
index ee2f2ed..0000000
--- a/mopidy_api_explorer/__init__.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import pathlib
-
-import pkg_resources
-from mopidy import config, ext
-
-__version__ = pkg_resources.get_distribution("Mopidy-API-Explorer").version
-
-ext_dir = pathlib.Path(__file__).parent
-
-
-class Extension(ext.Extension):
- dist_name = "Mopidy-API-Explorer"
- ext_name = "api_explorer"
- version = __version__
-
- def get_default_config(self):
- return config.read(ext_dir / "ext.conf")
-
- def setup(self, registry):
- registry.add(
- "http:static",
- {
- "name": self.ext_name,
- "path": ext_dir / "static",
- },
- )
diff --git a/pyproject.toml b/pyproject.toml
index 04a6524..fc07100 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,17 +1,140 @@
+[project]
+name = "mopidy-api-explorer"
+description = "Mopidy extension which lets you explore the JSON-RPC API"
+readme = "README.md"
+requires-python = ">= 3.13"
+license = { text = "MIT" }
+authors = [{ name = "Matthew Gamble", email = "git@matthewgamble.net" }]
+classifiers = [
+ "Environment :: No Input/Output (Daemon)",
+ "Intended Audience :: End Users/Desktop",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ "Topic :: Multimedia :: Sound/Audio :: Players",
+]
+dynamic = ["version"]
+dependencies = [
+ "mopidy >= 4.0.0a12",
+ "pykka >= 4.1",
+]
+
+[project.urls]
+Homepage = "https://github.com/mopidy/mopidy-api-explorer"
+
+[project.entry-points."mopidy.ext"]
+api_explorer = "mopidy_api_explorer:Extension"
+
+
[build-system]
-requires = ["setuptools >= 30.3.0", "wheel"]
+requires = ["setuptools >= 78", "setuptools-scm >= 8.2"]
+build-backend = "setuptools.build_meta"
+
+
+[dependency-groups]
+dev = [
+ "tox",
+ { include-group = "ruff" },
+ { include-group = "tests" },
+ { include-group = "typing" },
+]
+ruff = ["ruff"]
+tests = ["pytest", "pytest-cov"]
+typing = ["pyright"]
+
+
+[tool.coverage.paths]
+source = ["src/", "*/site-packages/"]
+
+[tool.coverage.run]
+source_pkgs = ["mopidy_api_explorer"]
+
+[tool.coverage.report]
+show_missing = true
+
+
+[tool.pyright]
+pythonVersion = "3.13"
+typeCheckingMode = "standard"
+# Not all dependencies have type hints:
+reportMissingTypeStubs = false
+# Already covered by ruff's flake8-self checks:
+reportPrivateImportUsage = false
+
+
+[tool.pytest.ini_options]
+filterwarnings = [
+ # By default, fail tests on warnings from our own code
+ "error:::mopidy_api_explorer",
+ #
+ # Add any warnings you want to ignore here
+]
+
+
+[tool.ruff]
+target-version = "py313"
+
+[tool.ruff.lint]
+select = ["ALL"]
+ignore = [
+ # Add rules you want to ignore here
+ "D", # pydocstyle
+ "D203", # one-blank-line-before-class
+ "D213", # multi-line-summary-second-line
+ "G004", # logging-f-string
+ "TD002", # missing-todo-author
+ "TD003", # missing-todo-link
+ #
+ # Conflicting with `ruff format`
+ "COM812", # missing-trailing-comma
+ "ISC001", # single-line-implicit-string-concatenation
+]
+
+[tool.ruff.lint.per-file-ignores]
+"tests/*" = [
+ "ARG", # flake8-unused-arguments
+ "D", # pydocstyle
+ "S101", # assert
+]
+
+
+[tool.setuptools.package-data]
+"*" = ["*.conf"]
+
+
+[tool.setuptools_scm]
+# This section, even if empty, must be present for setuptools_scm to work
+
+
+[tool.tox]
+env_list = [
+ "pyright",
+ "ruff-check",
+ "ruff-format",
+]
+[tool.tox.env_run_base]
+package = "wheel"
+wheel_build_env = ".pkg"
+dependency_groups = ["tests"]
+commands = [
+ [
+ "pytest",
+ "--cov",
+ "--basetemp={envtmpdir}",
+ { replace = "posargs", extend = true },
+ ],
+]
-[tool.black]
-target-version = ["py39", "py310", "py311"]
-line-length = 80
+[tool.tox.env.pyright]
+dependency_groups = ["typing"]
+commands = [["pyright", "{posargs:src}"]]
+[tool.tox.env.ruff-check]
+skip_install = true
+dependency_groups = ["ruff"]
+commands = [["ruff", "check", "{posargs:.}"]]
-[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"
+[tool.tox.env.ruff-format]
+skip_install = true
+dependency_groups = ["ruff"]
+commands = [["ruff", "format", "--check", "--diff", "{posargs:.}"]]
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 664b449..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,72 +0,0 @@
-[metadata]
-name = Mopidy-API-Explorer
-version = 2.0.0
-url = https://github.com/mopidy/mopidy-api-explorer
-author = Matthew Gamble
-author_email = git@matthewgamble.net
-license = MIT
-license_file = LICENSE
-description = Mopidy API Explorer
-long_description = file: README.rst
-classifiers =
- Environment :: No Input/Output (Daemon)
- Intended Audience :: Developers
- License :: OSI Approved :: MIT 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.1.1
- setuptools
-
-
-[options.extras_require]
-lint =
- black
- check-manifest
- flake8
- flake8-black
- flake8-bugbear
- flake8-import-order
- isort
-dev =
- %(lint)s
-
-
-[options.entry_points]
-mopidy.ext =
- api_explorer = mopidy_api_explorer:Extension
-
-
-[flake8]
-application-import-names = mopidy_api_explorer, 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/src/mopidy_api_explorer/__init__.py b/src/mopidy_api_explorer/__init__.py
new file mode 100644
index 0000000..2f2ae96
--- /dev/null
+++ b/src/mopidy_api_explorer/__init__.py
@@ -0,0 +1,27 @@
+import pathlib
+from importlib.metadata import version
+from typing import override
+
+from mopidy import config, ext
+
+__version__ = version("mopidy-api-explorer")
+
+
+class Extension(ext.Extension):
+ dist_name = "mopidy-api-explorer"
+ ext_name = "api_explorer"
+ version = __version__
+
+ @override
+ def get_default_config(self) -> str:
+ return config.read(pathlib.Path(__file__).parent / "ext.conf")
+
+ @override
+ def setup(self, registry: ext.Registry) -> None:
+ registry.add(
+ "http:static",
+ {
+ "name": self.ext_name,
+ "path": str(pathlib.Path(__file__).parent / "static"),
+ },
+ )
diff --git a/mopidy_api_explorer/ext.conf b/src/mopidy_api_explorer/ext.conf
similarity index 50%
rename from mopidy_api_explorer/ext.conf
rename to src/mopidy_api_explorer/ext.conf
index 80f8fd0..6fb6358 100644
--- a/mopidy_api_explorer/ext.conf
+++ b/src/mopidy_api_explorer/ext.conf
@@ -1,2 +1,2 @@
[api_explorer]
-enabled = True
\ No newline at end of file
+enabled = true
diff --git a/mopidy_api_explorer/static/css/app.css b/src/mopidy_api_explorer/static/css/app.css
similarity index 100%
rename from mopidy_api_explorer/static/css/app.css
rename to src/mopidy_api_explorer/static/css/app.css
diff --git a/mopidy_api_explorer/static/css/vendor/bootstrap-v5.0.0-beta1.min.css b/src/mopidy_api_explorer/static/css/vendor/bootstrap-v5.0.0-beta1.min.css
similarity index 100%
rename from mopidy_api_explorer/static/css/vendor/bootstrap-v5.0.0-beta1.min.css
rename to src/mopidy_api_explorer/static/css/vendor/bootstrap-v5.0.0-beta1.min.css
diff --git a/mopidy_api_explorer/static/css/vendor/bootstrap-v5.0.0-beta1.min.css.map b/src/mopidy_api_explorer/static/css/vendor/bootstrap-v5.0.0-beta1.min.css.map
similarity index 100%
rename from mopidy_api_explorer/static/css/vendor/bootstrap-v5.0.0-beta1.min.css.map
rename to src/mopidy_api_explorer/static/css/vendor/bootstrap-v5.0.0-beta1.min.css.map
diff --git a/mopidy_api_explorer/static/index.html b/src/mopidy_api_explorer/static/index.html
similarity index 100%
rename from mopidy_api_explorer/static/index.html
rename to src/mopidy_api_explorer/static/index.html
diff --git a/mopidy_api_explorer/static/js/app.js b/src/mopidy_api_explorer/static/js/app.js
similarity index 100%
rename from mopidy_api_explorer/static/js/app.js
rename to src/mopidy_api_explorer/static/js/app.js
diff --git a/mopidy_api_explorer/static/js/vendor/bootstrap-v5.0.0-beta1.min.js b/src/mopidy_api_explorer/static/js/vendor/bootstrap-v5.0.0-beta1.min.js
similarity index 100%
rename from mopidy_api_explorer/static/js/vendor/bootstrap-v5.0.0-beta1.min.js
rename to src/mopidy_api_explorer/static/js/vendor/bootstrap-v5.0.0-beta1.min.js
diff --git a/mopidy_api_explorer/static/js/vendor/bootstrap-v5.0.0-beta1.min.js.map b/src/mopidy_api_explorer/static/js/vendor/bootstrap-v5.0.0-beta1.min.js.map
similarity index 100%
rename from mopidy_api_explorer/static/js/vendor/bootstrap-v5.0.0-beta1.min.js.map
rename to src/mopidy_api_explorer/static/js/vendor/bootstrap-v5.0.0-beta1.min.js.map
diff --git a/mopidy_api_explorer/static/js/vendor/handlebars-v4.7.6.min.js b/src/mopidy_api_explorer/static/js/vendor/handlebars-v4.7.6.min.js
similarity index 100%
rename from mopidy_api_explorer/static/js/vendor/handlebars-v4.7.6.min.js
rename to src/mopidy_api_explorer/static/js/vendor/handlebars-v4.7.6.min.js
diff --git a/mopidy_api_explorer/static/js/vendor/mopidy-v1.2.1.min.js b/src/mopidy_api_explorer/static/js/vendor/mopidy-v1.2.1.min.js
similarity index 100%
rename from mopidy_api_explorer/static/js/vendor/mopidy-v1.2.1.min.js
rename to src/mopidy_api_explorer/static/js/vendor/mopidy-v1.2.1.min.js
diff --git a/mopidy_api_explorer/static/js/vendor/mopidy-v1.2.1.min.js.map b/src/mopidy_api_explorer/static/js/vendor/mopidy-v1.2.1.min.js.map
similarity index 100%
rename from mopidy_api_explorer/static/js/vendor/mopidy-v1.2.1.min.js.map
rename to src/mopidy_api_explorer/static/js/vendor/mopidy-v1.2.1.min.js.map
diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index 1e64cda..0000000
--- a/tox.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[tox]
-envlist = check-manifest, flake8
-
-[testenv:check-manifest]
-deps = .[lint]
-commands = python -m check_manifest
-
-[testenv:flake8]
-deps = .[lint]
-commands = python -m flake8 --show-source --statistics