From f8ef22348bb6665b61c98d936a2757d66bed1c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sat, 7 Sep 2024 12:22:49 +0100 Subject: [PATCH 1/9] Replace flake8 by ruff --- .github/workflows/lint.yml | 5 +-- poetry.lock | 92 ++++++++++++-------------------------- pyproject.toml | 48 +++++++++++++++++--- setup.cfg | 35 --------------- 4 files changed, 71 insertions(+), 109 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4e2a2db260..821aaa5b54 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -84,10 +84,7 @@ jobs: run: poetry install --only=lint - name: Lint code - uses: liskin/gh-problem-matcher-wrap@v3 - with: - linters: flake8 - run: poe lint ${{ needs.changed-files.outputs.changed_python_files }} + run: poe lint --output-format=github ${{ needs.changed-files.outputs.changed_python_files }} mypy: if: needs.changed-files.outputs.any_python_changed == 'true' diff --git a/poetry.lock b/poetry.lock index 94951cd495..b7e46d71eb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -678,22 +678,6 @@ files = [ [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "flake8" -version = "5.0.4" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.6.1" -files = [ - {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, - {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.9.0,<2.10.0" -pyflakes = ">=2.5.0,<2.6.0" - [[package]] name = "flask" version = "3.0.3" @@ -1398,17 +1382,6 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - [[package]] name = "mediafile" version = "0.12.0" @@ -1725,20 +1698,6 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] -[[package]] -name = "pep8-naming" -version = "0.14.1" -description = "Check PEP-8 naming conventions, plugin for flake8" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pep8-naming-0.14.1.tar.gz", hash = "sha256:1ef228ae80875557eb6c1549deafed4dabbf3261cfcafa12f773fe0db9be8a36"}, - {file = "pep8_naming-0.14.1-py3-none-any.whl", hash = "sha256:63f514fc777d715f935faf185dedd679ab99526a7f2f503abb61587877f7b1c5"}, -] - -[package.dependencies] -flake8 = ">=5.0.0" - [[package]] name = "pillow" version = "10.4.0" @@ -2039,17 +1998,6 @@ files = [ {file = "pycairo-1.26.1.tar.gz", hash = "sha256:a11b999ce55b798dbf13516ab038e0ce8b6ec299b208d7c4e767a6f7e68e8430"}, ] -[[package]] -name = "pycodestyle" -version = "2.9.1" -description = "Python style guide checker" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, - {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, -] - [[package]] name = "pycparser" version = "2.22" @@ -2129,17 +2077,6 @@ dev = ["nox", "pre-commit", "pydata-sphinx-theme[doc,test]", "pyyaml"] doc = ["ablog (>=0.11.0rc2)", "colorama", "ipykernel", "ipyleaflet", "jupyter_sphinx", "jupyterlite-sphinx", "linkify-it-py", "matplotlib", "myst-parser", "nbsphinx", "numpy", "numpydoc", "pandas", "plotly", "rich", "sphinx-autoapi (>=3.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-favicon (>=1.0.1)", "sphinx-sitemap", "sphinx-togglebutton", "sphinxcontrib-youtube (<1.4)", "sphinxext-rediraffe", "xarray"] test = ["pytest", "pytest-cov", "pytest-regressions"] -[[package]] -name = "pyflakes" -version = "2.5.0" -description = "passive checker of Python programs" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, - {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, -] - [[package]] name = "pygments" version = "2.18.0" @@ -2664,6 +2601,33 @@ urllib3 = ">=1.25.10,<3.0" [package.extras] tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"] +[[package]] +name = "ruff" +version = "0.6.6" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.6.6-py3-none-linux_armv6l.whl", hash = "sha256:f5bc5398457484fc0374425b43b030e4668ed4d2da8ee7fdda0e926c9f11ccfb"}, + {file = "ruff-0.6.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:515a698254c9c47bb84335281a170213b3ee5eb47feebe903e1be10087a167ce"}, + {file = "ruff-0.6.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6bb1b4995775f1837ab70f26698dd73852bbb82e8f70b175d2713c0354fe9182"}, + {file = "ruff-0.6.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c546f412dfae8bb9cc4f27f0e45cdd554e42fecbb34f03312b93368e1cd0a6"}, + {file = "ruff-0.6.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:59627e97364329e4eae7d86fa7980c10e2b129e2293d25c478ebcb861b3e3fd6"}, + {file = "ruff-0.6.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94c3f78c3d32190aafbb6bc5410c96cfed0a88aadb49c3f852bbc2aa9783a7d8"}, + {file = "ruff-0.6.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:704da526c1e137f38c8a067a4a975fe6834b9f8ba7dbc5fd7503d58148851b8f"}, + {file = "ruff-0.6.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:efeede5815a24104579a0f6320660536c5ffc1c91ae94f8c65659af915fb9de9"}, + {file = "ruff-0.6.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e368aef0cc02ca3593eae2fb8186b81c9c2b3f39acaaa1108eb6b4d04617e61f"}, + {file = "ruff-0.6.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2653fc3b2a9315bd809725c88dd2446550099728d077a04191febb5ea79a4f79"}, + {file = "ruff-0.6.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bb858cd9ce2d062503337c5b9784d7b583bcf9d1a43c4df6ccb5eab774fbafcb"}, + {file = "ruff-0.6.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:488f8e15c01ea9afb8c0ba35d55bd951f484d0c1b7c5fd746ce3c47ccdedce68"}, + {file = "ruff-0.6.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:aefb0bd15f1cfa4c9c227b6120573bb3d6c4ee3b29fb54a5ad58f03859bc43c6"}, + {file = "ruff-0.6.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a4c0698cc780bcb2c61496cbd56b6a3ac0ad858c966652f7dbf4ceb029252fbe"}, + {file = "ruff-0.6.6-py3-none-win32.whl", hash = "sha256:aadf81ddc8ab5b62da7aae78a91ec933cbae9f8f1663ec0325dae2c364e4ad84"}, + {file = "ruff-0.6.6-py3-none-win_amd64.whl", hash = "sha256:0adb801771bc1f1b8cf4e0a6fdc30776e7c1894810ff3b344e50da82ef50eeb1"}, + {file = "ruff-0.6.6-py3-none-win_arm64.whl", hash = "sha256:4b4d32c137bc781c298964dd4e52f07d6f7d57c03eae97a72d97856844aa510a"}, + {file = "ruff-0.6.6.tar.gz", hash = "sha256:0fc030b6fd14814d69ac0196396f6761921bd20831725c7361e1b8100b818034"}, +] + [[package]] name = "scikit-learn" version = "1.3.2" @@ -3242,4 +3206,4 @@ web = ["flask", "flask-cors"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4" -content-hash = "09b64b25f3ff363a3c31d44fd99e4c07309b27dda65523f9ec3d1279ca3a3143" +content-hash = "40aa1c95a4d1f17e50ce3514f729b104ec9a3bd61ed164a2f937613cb9ed2330" diff --git a/pyproject.toml b/pyproject.toml index 43a77e8c66..69f4bf70a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,8 +89,7 @@ isort = { version = "<5.14", extras = ["colors"] } black = ">=24.3,<25" [tool.poetry.group.lint.dependencies] -flake8 = "*" -pep8-naming = "*" +ruff = ">=0.6.4" [tool.poetry.group.typing.dependencies] mypy = "*" @@ -154,8 +153,8 @@ build-backend = "poetry.core.masonry.api" poethepoet = ">=0.26" poetry = ">=1.8" -# We use a default path '.' to make black and isort behave like flake8 and -# mypy do: they act on the entire codebase (flake8 does it by default, and +# We use a default path '.' to make black and isort behave like ruff and +# mypy do: they act on the entire codebase (ruff does it by default, and # mypy follows our configuration) by default. Positional command-line arguments # override this. Therefore, locally you can run `poe check-format ` # to quickly check a specific path. @@ -205,8 +204,8 @@ sequence = ["_black $path", "_isort $path"] args = { path = { help = "Path to format", positional = true, multiple = true, default = "." } } [tool.poe.tasks.lint] -help = "Check the code for linting issues. Accepts flake8 options." -cmd = "flake8" +help = "Check the code for linting issues. Accepts ruff options." +cmd = "ruff check" [tool.poe.tasks.update-dependencies] help = "Update dependencies to their latest versions." @@ -259,3 +258,40 @@ py_version = 38 multi_line_output = 3 line_length = 80 indent = 4 + +[tool.ruff] +target-version = "py38" +line-length = 80 + +[tool.ruff.lint] +select = [ + # "ARG", # flake8-unused-arguments + # "C4", # flake8-comprehensions + "E", # pycodestyle + "F", # pyflakes + # "B", # flake8-bugbear + "I", # isort + "N", # pep8-naming + "PT", # flake8-pytest-style + # "RUF", # ruff + # "UP", # pyupgrade + "W", # pycodestyle +] +[tool.ruff.lint.per-file-ignores] +"beets/**" = ["PT"] + +[tool.ruff.lint.isort] +split-on-trailing-comma = false + +[tool.ruff.lint.pycodestyle] +max-line-length = 88 + +[tool.ruff.lint.flake8-pytest-style] +fixture-parentheses = false +mark-parentheses = false + +[tool.ruff.lint.flake8-unused-arguments] +ignore-variadic-names = true + +[tool.ruff.lint.pep8-naming] +classmethod-decorators = ["cached_classproperty"] diff --git a/setup.cfg b/setup.cfg index b918bdb1d6..a45c49a81a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,41 +27,6 @@ exclude_lines = [coverage:html] show_contexts = true -[flake8] -min-version = 3.8 -accept-encodings = utf-8 -max-line-length = 88 -classmethod-decorators = - classmethod - cached_classproperty -# errors we ignore; see https://www.flake8rules.com/ for more info -ignore = - # pycodestyle errors - # continuation line under-indented for hanging indent - E121, - # closing bracket does not match indentation of opening bracket's line - E123, - # continuation line over-indented for hanging indent - E126, - # multiple spaces after non-arithmetic operators (for vertical alignment) - E241, - # expected 2 blank lines after end of function or class - E305, - # do not assign a lambda expression, use a def - E731, - # do not use variables name 'I', 'O', or 'l' - E741, - # pycodestyle warnings: line breaks around binary operators - W503, - W504, - # mccabe errors: function is too complex - C901, - # Exception subclasses should be named with an Error suffix - N818, - # Exclude rules for black compatibility - E203, - E704, - [mypy] files = beets,beetsplug,test,extra,docs allow_any_generics = false From 06a5ecaf809b9951576e8d2b17905ac0ef00802e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sun, 8 Sep 2024 03:02:39 +0100 Subject: [PATCH 2/9] Replace isort and black by ruff format --- .github/workflows/lint.yml | 2 +- .pre-commit-config.yaml | 12 ++---- CONTRIBUTING.rst | 7 ++-- poetry.lock | 76 +------------------------------------- pyproject.toml | 40 +------------------- 5 files changed, 10 insertions(+), 127 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 821aaa5b54..c24f42564a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -60,7 +60,7 @@ jobs: cache: poetry - name: Install dependencies - run: poetry install --only=format + run: poetry install --only=lint - name: Check code formatting # the job output will contain colored diffs with what needs adjusting diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a417588970..477cf9cc07 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,13 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - - repo: https://github.com/psf/black - rev: 24.2.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.6 hooks: - - id: black - - - repo: https://github.com/pycqa/isort - rev: 5.13.2 - hooks: - - id: isort - name: isort (python) + - id: ruff-format diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index e54076ba05..911090f17a 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -274,14 +274,13 @@ There are a few coding conventions we use in beets: Style ----- -We follow `black`_ formatting and `google's docstring format`_. +We use `ruff`_ to format and lint the codebase. -Use ``poe check-format`` and ``poe lint`` to check your code for style and +Run ``poe check-format`` and ``poe lint`` to check your code for style and linting errors. Running ``poe format`` will automatically format your code according to the specifications required by the project. -.. _black: https://black.readthedocs.io/en/stable/ -.. _google's docstring format: https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings +.. _ruff: https://docs.astral.sh/ruff/ Handling Paths -------------- diff --git a/poetry.lock b/poetry.lock index b7e46d71eb..bca5609d9e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -110,52 +110,6 @@ charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] -[[package]] -name = "black" -version = "24.8.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "blinker" version = "1.8.2" @@ -924,23 +878,6 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.dependencies] -colorama = {version = ">=0.4.6", optional = true, markers = "extra == \"colors\""} - -[package.extras] -colors = ["colorama (>=0.4.6)"] - [[package]] name = "itsdangerous" version = "2.2.0" @@ -1687,17 +1624,6 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - [[package]] name = "pillow" version = "10.4.0" @@ -3206,4 +3132,4 @@ web = ["flask", "flask-cors"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4" -content-hash = "40aa1c95a4d1f17e50ce3514f729b104ec9a3bd61ed164a2f937613cb9ed2330" +content-hash = "b3eb66c6852cb14afcbe817619ea9ecea699e838885d8e9719f2809eb708bcc3" diff --git a/pyproject.toml b/pyproject.toml index 69f4bf70a0..9f85522610 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,10 +84,6 @@ rarfile = "*" requests_oauthlib = "*" responses = ">=0.3.0" -[tool.poetry.group.format.dependencies] -isort = { version = "<5.14", extras = ["colors"] } -black = ">=24.3,<25" - [tool.poetry.group.lint.dependencies] ruff = ">=0.6.4" @@ -153,24 +149,6 @@ build-backend = "poetry.core.masonry.api" poethepoet = ">=0.26" poetry = ">=1.8" -# We use a default path '.' to make black and isort behave like ruff and -# mypy do: they act on the entire codebase (ruff does it by default, and -# mypy follows our configuration) by default. Positional command-line arguments -# override this. Therefore, locally you can run `poe check-format ` -# to quickly check a specific path. -# -# Note: both tools respect .gitignore, therefore if we see them format -# something unwanted locally, we should add these paths to .gitignore. -[tool.poe.tasks._black] -help = "Run black" -cmd = "black $OPTS $path" -args = { path = { help = "Path to blacken", positional = true, multiple = true, default = "." } } - -[tool.poe.tasks._isort] -help = "Run isort" -cmd = "isort $OPTS $path" -args = { path = { help = "Path to isort", positional = true, multiple = true, default = "." } } - [tool.poe.tasks.bump] help = "Bump project version and update relevant files" cmd = "python ./extra/release.py bump $version" @@ -186,8 +164,7 @@ cmd = "make -C docs linkcheck" [tool.poe.tasks.check-format] help = "Check the code for style issues" -ref = "format" -env.OPTS = "--check --diff --color" +cmd = "ruff format --check --diff" [tool.poe.tasks.check-types] help = "Check the code for typing issues. Accepts mypy options." @@ -199,9 +176,7 @@ cmd = "make -C docs html" [tool.poe.tasks.format] help = "Format the codebase" -ignore_fail = "return_non_zero" -sequence = ["_black $path", "_isort $path"] -args = { path = { help = "Path to format", positional = true, multiple = true, default = "." } } +cmd = "ruff format" [tool.poe.tasks.lint] help = "Check the code for linting issues. Accepts ruff options." @@ -248,17 +223,6 @@ done """ interpreter = "zsh" -[tool.black] -line-length = 80 -target-version = ["py38", "py39", "py310", "py311"] - -[tool.isort] -profile = "black" -py_version = 38 -multi_line_output = 3 -line_length = 80 -indent = 4 - [tool.ruff] target-version = "py38" line-length = 80 From 85a17ee5039628a6f3cdcb7a03d7d1bd530fbe89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Tue, 17 Sep 2024 07:35:32 +0100 Subject: [PATCH 3/9] Reformat the codebase --- beets/__main__.py | 1 - beets/art.py | 1 - beets/autotag/__init__.py | 4 ++-- beets/autotag/match.py | 1 - beets/autotag/mb.py | 4 ++-- beets/dbcore/db.py | 4 +--- beets/dbcore/types.py | 4 ++-- beets/importer.py | 3 +-- beets/logging.py | 1 - beets/plugins.py | 1 - beets/ui/__init__.py | 9 +++------ beets/ui/commands.py | 4 +--- beets/util/functemplate.py | 1 - beets/util/pipeline.py | 1 - beetsplug/__init__.py | 1 - beetsplug/absubmit.py | 10 ++++------ beetsplug/acousticbrainz.py | 3 +-- beetsplug/albumtypes.py | 1 - beetsplug/aura.py | 1 - beetsplug/badfiles.py | 4 +--- beetsplug/bareasc.py | 1 - beetsplug/beatport.py | 3 +-- beetsplug/bench.py | 4 +--- beetsplug/bpd/__init__.py | 1 - beetsplug/bpd/gstplayer.py | 1 - beetsplug/bpm.py | 1 - beetsplug/bpsync.py | 3 +-- beetsplug/bucket.py | 4 +--- beetsplug/convert.py | 4 ++-- beetsplug/deezer.py | 7 +++---- beetsplug/duplicates.py | 3 +-- beetsplug/edit.py | 3 +-- beetsplug/embyupdate.py | 12 ++++++------ beetsplug/export.py | 4 +--- beetsplug/fetchart.py | 3 +-- beetsplug/filefilter.py | 4 +--- beetsplug/fish.py | 1 - beetsplug/freedesktop.py | 4 +--- beetsplug/ftintitle.py | 3 +-- beetsplug/fuzzy.py | 4 +--- beetsplug/importfeeds.py | 1 + beetsplug/info.py | 4 +--- beetsplug/inline.py | 3 +-- beetsplug/ipfs.py | 4 +--- beetsplug/keyfinder.py | 4 +--- beetsplug/lastgenre/__init__.py | 1 + beetsplug/loadext.py | 4 +--- beetsplug/lyrics.py | 4 +--- beetsplug/mbsync.py | 3 +-- beetsplug/metasync/__init__.py | 7 ++----- beetsplug/metasync/amarok.py | 4 +--- beetsplug/metasync/itunes.py | 4 +--- beetsplug/missing.py | 3 +-- beetsplug/parentwork.py | 1 - beetsplug/play.py | 5 ++--- beetsplug/random.py | 3 +-- beetsplug/replaygain.py | 4 +--- beetsplug/scrub.py | 1 - beetsplug/smartplaylist.py | 4 +--- beetsplug/the.py | 1 - beetsplug/thumbnails.py | 4 +--- beetsplug/zero.py | 3 +-- extra/release.py | 4 ++-- test/plugins/test_acousticbrainz.py | 4 +--- test/plugins/test_advancedrewrite.py | 4 +--- test/plugins/test_albumtypes.py | 1 - test/plugins/test_art.py | 1 - test/plugins/test_bareasc.py | 1 - test/plugins/test_bucket.py | 1 - test/plugins/test_export.py | 4 +--- test/plugins/test_filefilter.py | 4 ++-- test/plugins/test_ftintitle.py | 1 - test/plugins/test_limit.py | 1 - test/plugins/test_lyrics.py | 9 ++++----- test/plugins/test_parentwork.py | 1 - test/plugins/test_permissions.py | 3 +-- test/plugins/test_play.py | 1 - test/plugins/test_plugin_mediafield.py | 3 +-- test/plugins/test_random.py | 4 +--- test/test_art_resize.py | 1 - test/test_autotag.py | 3 +-- test/test_datequery.py | 3 +-- test/test_dbcore.py | 8 ++------ test/test_hidden.py | 1 - test/test_m3ufile.py | 1 - test/test_metasync.py | 12 ++++++------ test/test_pipeline.py | 3 +-- test/test_plugins.py | 1 - test/test_query.py | 3 +-- test/test_sort.py | 4 +--- test/test_template.py | 3 +-- test/test_ui.py | 3 +-- test/test_ui_commands.py | 4 +--- test/test_ui_init.py | 3 +-- test/test_util.py | 3 +-- test/test_vfs.py | 1 - 96 files changed, 94 insertions(+), 211 deletions(-) diff --git a/beets/__main__.py b/beets/__main__.py index 81995f7af7..3473c63195 100644 --- a/beets/__main__.py +++ b/beets/__main__.py @@ -16,7 +16,6 @@ `python -m beets`. """ - import sys from .ui import main diff --git a/beets/art.py b/beets/art.py index 466d40005d..2ff58c309e 100644 --- a/beets/art.py +++ b/beets/art.py @@ -16,7 +16,6 @@ music and items' embedded album art. """ - import os from tempfile import NamedTemporaryFile diff --git a/beets/autotag/__init__.py b/beets/autotag/__init__.py index abbc07772b..a67531a69e 100644 --- a/beets/autotag/__init__.py +++ b/beets/autotag/__init__.py @@ -12,8 +12,8 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Facilities for automatically determining files' correct metadata. -""" +"""Facilities for automatically determining files' correct metadata.""" + from typing import Mapping, Sequence, Union from beets import config, logging diff --git a/beets/autotag/match.py b/beets/autotag/match.py index 23f81ce929..fe391b4cf9 100644 --- a/beets/autotag/match.py +++ b/beets/autotag/match.py @@ -16,7 +16,6 @@ releases and tracks. """ - from __future__ import annotations import datetime diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 80ac6c8ed7..2ea5f7baab 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -12,8 +12,8 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Searches for albums in the MusicBrainz database. -""" +"""Searches for albums in the MusicBrainz database.""" + from __future__ import annotations import re diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py index 566c116314..4645d4b096 100755 --- a/beets/dbcore/db.py +++ b/beets/dbcore/db.py @@ -1224,9 +1224,7 @@ def _make_attribute_table(self, flex_table: str): UNIQUE(entity_id, key) ON CONFLICT REPLACE); CREATE INDEX IF NOT EXISTS {0}_by_entity ON {0} (entity_id); - """.format( - flex_table - ) + """.format(flex_table) ) # Querying. diff --git a/beets/dbcore/types.py b/beets/dbcore/types.py index 432db2b72b..11d850b8b1 100644 --- a/beets/dbcore/types.py +++ b/beets/dbcore/types.py @@ -12,8 +12,8 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Representation of type information for DBCore model fields. -""" +"""Representation of type information for DBCore model fields.""" + import typing from abc import ABC from typing import Any, Generic, List, TypeVar, Union, cast diff --git a/beets/importer.py b/beets/importer.py index 3a290a033a..6fed3a02d7 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -627,8 +627,7 @@ def finalize(self, session): self.save_progress() if session.config["incremental"] and not ( # Should we skip recording to incremental list? - self.skip - and session.config["incremental_skip_later"] + self.skip and session.config["incremental_skip_later"] ): self.save_history() diff --git a/beets/logging.py b/beets/logging.py index faa93d59df..34fc7bafae 100644 --- a/beets/logging.py +++ b/beets/logging.py @@ -20,7 +20,6 @@ calls (`debug`, `info`, etc). """ - import logging import threading from copy import copy diff --git a/beets/plugins.py b/beets/plugins.py index 35995c3414..5df738c843 100644 --- a/beets/plugins.py +++ b/beets/plugins.py @@ -14,7 +14,6 @@ """Support for beets plugins.""" - import abc import inspect import re diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 8580bd1e8a..f84ce06d08 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -17,7 +17,6 @@ CLI commands are implemented in the ui.commands module. """ - import errno import optparse import os.path @@ -318,7 +317,7 @@ def input_options( # Wrap the query text. # Start prompt with U+279C: Heavy Round-Tipped Rightwards Arrow - prompt = colorize("action", "\u279C ") + prompt = colorize("action", "\u279c ") line_length = 0 for i, (part, length) in enumerate( zip(prompt_parts, prompt_part_lengths) @@ -387,7 +386,7 @@ def input_yn(prompt, require=False): "yes" unless `require` is `True`, in which case there is no default. """ # Start prompt with U+279C: Heavy Round-Tipped Rightwards Arrow - yesno = colorize("action", "\u279C ") + colorize( + yesno = colorize("action", "\u279c ") + colorize( "action_description", "Enter Y or N:" ) sel = input_options(("y", "n"), require, prompt, yesno) @@ -1497,9 +1496,7 @@ def __init__(self, *args, **kwargs): """ # A more helpful default usage. if "usage" not in kwargs: - kwargs[ - "usage" - ] = """ + kwargs["usage"] = """ %prog COMMAND [ARGS...] %prog help COMMAND""" kwargs["add_help_option"] = False diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 24cae1dd12..5d0ac206a6 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -16,7 +16,6 @@ interface. """ - import os import re from collections import Counter @@ -1317,8 +1316,7 @@ def import_files(lib, paths, query): loghandler = logging.FileHandler(logpath, encoding="utf-8") except OSError: raise ui.UserError( - "could not open log file for writing: " - "{}".format(displayable_path(logpath)) + f"Could not open log file for writing: {displayable_path(logpath)}" ) else: loghandler = None diff --git a/beets/util/functemplate.py b/beets/util/functemplate.py index 7d7e8f01f6..b0daefac28 100644 --- a/beets/util/functemplate.py +++ b/beets/util/functemplate.py @@ -26,7 +26,6 @@ engine like Jinja2 or Mustache. """ - import ast import dis import functools diff --git a/beets/util/pipeline.py b/beets/util/pipeline.py index c4933ff007..d23b1bd10c 100644 --- a/beets/util/pipeline.py +++ b/beets/util/pipeline.py @@ -31,7 +31,6 @@ in place of any single coroutine. """ - import queue import sys from threading import Lock, Thread diff --git a/beetsplug/__init__.py b/beetsplug/__init__.py index 763ff3a05a..ad573cdb39 100644 --- a/beetsplug/__init__.py +++ b/beetsplug/__init__.py @@ -14,7 +14,6 @@ """A namespace package for beets plugins.""" - # Make this a namespace package. from pkgutil import extend_path diff --git a/beetsplug/absubmit.py b/beetsplug/absubmit.py index fc40b85e72..bbbc14edfc 100644 --- a/beetsplug/absubmit.py +++ b/beetsplug/absubmit.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Calculate acoustic information and submit to AcousticBrainz. -""" - +"""Calculate acoustic information and submit to AcousticBrainz.""" import errno import hashlib @@ -187,9 +185,9 @@ def _get_analysis(self, item): with open(filename) as tmp_file: analysis = json.load(tmp_file) # Add the hash to the output. - analysis["metadata"]["version"][ - "essentia_build_sha" - ] = self.extractor_sha + analysis["metadata"]["version"]["essentia_build_sha"] = ( + self.extractor_sha + ) return analysis finally: try: diff --git a/beetsplug/acousticbrainz.py b/beetsplug/acousticbrainz.py index a4b153fc99..899288260f 100644 --- a/beetsplug/acousticbrainz.py +++ b/beetsplug/acousticbrainz.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Fetch various AcousticBrainz metadata using MBID. -""" +"""Fetch various AcousticBrainz metadata using MBID.""" from collections import defaultdict diff --git a/beetsplug/albumtypes.py b/beetsplug/albumtypes.py index 5200b5c6d1..b1e143a884 100644 --- a/beetsplug/albumtypes.py +++ b/beetsplug/albumtypes.py @@ -14,7 +14,6 @@ """Adds an album template field for formatted album types.""" - from beets.autotag.mb import VARIOUS_ARTISTS_ID from beets.library import Album from beets.plugins import BeetsPlugin diff --git a/beetsplug/aura.py b/beetsplug/aura.py index 09d859200b..5ddf851902 100644 --- a/beetsplug/aura.py +++ b/beetsplug/aura.py @@ -14,7 +14,6 @@ """An AURA server using Flask.""" - import os import re import sys diff --git a/beetsplug/badfiles.py b/beetsplug/badfiles.py index 056b65346c..f596189acc 100644 --- a/beetsplug/badfiles.py +++ b/beetsplug/badfiles.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Use command-line tools to check for audio file corruption. -""" - +"""Use command-line tools to check for audio file corruption.""" import errno import os diff --git a/beetsplug/bareasc.py b/beetsplug/bareasc.py index 8cdcbb113c..0a867dfe1e 100644 --- a/beetsplug/bareasc.py +++ b/beetsplug/bareasc.py @@ -18,7 +18,6 @@ """Provides a bare-ASCII matching query.""" - from unidecode import unidecode from beets import ui diff --git a/beetsplug/beatport.py b/beetsplug/beatport.py index 6108b03996..fab720c2b2 100644 --- a/beetsplug/beatport.py +++ b/beetsplug/beatport.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Adds Beatport release and track search support to the autotagger -""" +"""Adds Beatport release and track search support to the autotagger""" import json import re diff --git a/beetsplug/bench.py b/beetsplug/bench.py index 673b9b7c64..62d512ce71 100644 --- a/beetsplug/bench.py +++ b/beetsplug/bench.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Some simple performance benchmarks for beets. -""" - +"""Some simple performance benchmarks for beets.""" import cProfile import timeit diff --git a/beetsplug/bpd/__init__.py b/beetsplug/bpd/__init__.py index a4cb4d2919..c78452ee84 100644 --- a/beetsplug/bpd/__init__.py +++ b/beetsplug/bpd/__init__.py @@ -17,7 +17,6 @@ use of the wide range of MPD clients. """ - import inspect import math import random diff --git a/beetsplug/bpd/gstplayer.py b/beetsplug/bpd/gstplayer.py index 77ddc1983e..03fb179aa2 100644 --- a/beetsplug/bpd/gstplayer.py +++ b/beetsplug/bpd/gstplayer.py @@ -16,7 +16,6 @@ music player. """ - import _thread import copy import os diff --git a/beetsplug/bpm.py b/beetsplug/bpm.py index 3edcbef827..10edfbfd7c 100644 --- a/beetsplug/bpm.py +++ b/beetsplug/bpm.py @@ -14,7 +14,6 @@ """Determine BPM by pressing a key to the rhythm.""" - import time from beets import ui diff --git a/beetsplug/bpsync.py b/beetsplug/bpsync.py index 4f3e0e907f..05be94c99c 100644 --- a/beetsplug/bpsync.py +++ b/beetsplug/bpsync.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Update library's tags using Beatport. -""" +"""Update library's tags using Beatport.""" from beets import autotag, library, ui, util from beets.plugins import BeetsPlugin, apply_item_changes diff --git a/beetsplug/bucket.py b/beetsplug/bucket.py index 59ee080bb1..9246539fcf 100644 --- a/beetsplug/bucket.py +++ b/beetsplug/bucket.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Provides the %bucket{} function for path formatting. -""" - +"""Provides the %bucket{} function for path formatting.""" import re import string diff --git a/beetsplug/convert.py b/beetsplug/convert.py index f150b7c369..a1d0680338 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -12,8 +12,8 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Converts tracks or albums to external directory -""" +"""Converts tracks or albums to external directory""" + import logging import os import shlex diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 70ebe8a92a..6f05474b96 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Adds Deezer release and track search support to the autotagger -""" +"""Adds Deezer release and track search support to the autotagger""" import collections import time @@ -112,8 +111,8 @@ def album_for_id(self, album_id): day = None else: raise ui.UserError( - "Invalid `release_date` returned " - "by {} API: '{}'".format(self.data_source, release_date) + f"Invalid `release_date` returned by {self.data_source} API: " + f"{release_date!r}" ) tracks_obj = self.fetch_data(self.album_url + deezer_id + "/tracks") if tracks_obj is None: diff --git a/beetsplug/duplicates.py b/beetsplug/duplicates.py index ced96e4033..a5f2bc8150 100644 --- a/beetsplug/duplicates.py +++ b/beetsplug/duplicates.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""List duplicate tracks or albums. -""" +"""List duplicate tracks or albums.""" import os import shlex diff --git a/beetsplug/edit.py b/beetsplug/edit.py index 323dd9e417..51b36bdab4 100644 --- a/beetsplug/edit.py +++ b/beetsplug/edit.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Open metadata information in a text editor to let the user edit it. -""" +"""Open metadata information in a text editor to let the user edit it.""" import codecs import os diff --git a/beetsplug/embyupdate.py b/beetsplug/embyupdate.py index 22c889473c..2cda6af5e9 100644 --- a/beetsplug/embyupdate.py +++ b/beetsplug/embyupdate.py @@ -1,11 +1,11 @@ """Updates the Emby Library whenever the beets library is changed. - emby: - host: localhost - port: 8096 - username: user - apikey: apikey - password: password +emby: + host: localhost + port: 8096 + username: user + apikey: apikey + password: password """ import hashlib diff --git a/beetsplug/export.py b/beetsplug/export.py index ef3ba94aaf..9b8ad35809 100644 --- a/beetsplug/export.py +++ b/beetsplug/export.py @@ -11,9 +11,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Exports data from beets -""" - +"""Exports data from beets""" import codecs import csv diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index aab2143341..0da884278b 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Fetches album art. -""" +"""Fetches album art.""" import os import re diff --git a/beetsplug/filefilter.py b/beetsplug/filefilter.py index 5618c1bd15..b78a3750eb 100644 --- a/beetsplug/filefilter.py +++ b/beetsplug/filefilter.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Filter imported files using a regular expression. -""" - +"""Filter imported files using a regular expression.""" import re diff --git a/beetsplug/fish.py b/beetsplug/fish.py index 71ac857432..4cf9b60a16 100644 --- a/beetsplug/fish.py +++ b/beetsplug/fish.py @@ -22,7 +22,6 @@ `beet fish -e genre -e albumartist` """ - import os from operator import attrgetter diff --git a/beetsplug/freedesktop.py b/beetsplug/freedesktop.py index a9a25279cf..50bbf18e5e 100644 --- a/beetsplug/freedesktop.py +++ b/beetsplug/freedesktop.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Creates freedesktop.org-compliant .directory files on an album level. -""" - +"""Creates freedesktop.org-compliant .directory files on an album level.""" from beets import ui from beets.plugins import BeetsPlugin diff --git a/beetsplug/ftintitle.py b/beetsplug/ftintitle.py index 60d6c287b9..5bc018fe10 100644 --- a/beetsplug/ftintitle.py +++ b/beetsplug/ftintitle.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Moves "featured" artists to the title from the artist field. -""" +"""Moves "featured" artists to the title from the artist field.""" import re diff --git a/beetsplug/fuzzy.py b/beetsplug/fuzzy.py index 45ada8b0bd..959544ed31 100644 --- a/beetsplug/fuzzy.py +++ b/beetsplug/fuzzy.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Provides a fuzzy matching query. -""" - +"""Provides a fuzzy matching query.""" import difflib diff --git a/beetsplug/importfeeds.py b/beetsplug/importfeeds.py index 316c1c72b1..0a5a6afe46 100644 --- a/beetsplug/importfeeds.py +++ b/beetsplug/importfeeds.py @@ -17,6 +17,7 @@ music player. Also allow printing the new file locations to stdout in case one wants to manually add music to a player by its path. """ + import datetime import os import re diff --git a/beetsplug/info.py b/beetsplug/info.py index 1c3b6f542e..d759d6066f 100644 --- a/beetsplug/info.py +++ b/beetsplug/info.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Shows file metadata. -""" - +"""Shows file metadata.""" import os diff --git a/beetsplug/inline.py b/beetsplug/inline.py index 4ca676e5f4..4092c46d0a 100644 --- a/beetsplug/inline.py +++ b/beetsplug/inline.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Allows inline path template customization code in the config file. -""" +"""Allows inline path template customization code in the config file.""" import itertools import traceback diff --git a/beetsplug/ipfs.py b/beetsplug/ipfs.py index 06835ba947..29d65ab780 100644 --- a/beetsplug/ipfs.py +++ b/beetsplug/ipfs.py @@ -11,9 +11,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Adds support for ipfs. Requires go-ipfs and a running ipfs daemon -""" - +"""Adds support for ipfs. Requires go-ipfs and a running ipfs daemon""" import os import shutil diff --git a/beetsplug/keyfinder.py b/beetsplug/keyfinder.py index d6605486de..87f0cc427f 100644 --- a/beetsplug/keyfinder.py +++ b/beetsplug/keyfinder.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Uses the `KeyFinder` program to add the `initial_key` field. -""" - +"""Uses the `KeyFinder` program to add the `initial_key` field.""" import os.path import subprocess diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index 50be9d1b98..f86ac7bc19 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -21,6 +21,7 @@ The scraper script used is available here: https://gist.github.com/1241307 """ + import codecs import os import traceback diff --git a/beetsplug/loadext.py b/beetsplug/loadext.py index 5e8b59a8fa..cc673dab26 100644 --- a/beetsplug/loadext.py +++ b/beetsplug/loadext.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Load SQLite extensions. -""" - +"""Load SQLite extensions.""" import sqlite3 diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index db29c9c6c4..bf0ce96d02 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Fetches, embeds, and displays lyrics. -""" - +"""Fetches, embeds, and displays lyrics.""" import difflib import errno diff --git a/beetsplug/mbsync.py b/beetsplug/mbsync.py index 0e63a6f227..283c401865 100644 --- a/beetsplug/mbsync.py +++ b/beetsplug/mbsync.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Update library's tags using MusicBrainz. -""" +"""Update library's tags using MusicBrainz.""" import re from collections import defaultdict diff --git a/beetsplug/metasync/__init__.py b/beetsplug/metasync/__init__.py index d17071b5bc..2466efe544 100644 --- a/beetsplug/metasync/__init__.py +++ b/beetsplug/metasync/__init__.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Synchronize information from music player libraries -""" - +"""Synchronize information from music player libraries""" from abc import ABCMeta, abstractmethod from importlib import import_module @@ -126,8 +124,7 @@ def func(self, lib, opts, args): meta_source_instances[player] = cls(self.config, self._log) except (ImportError, ConfigValueError) as e: self._log.error( - "Failed to instantiate metadata source " - "'{}': {}".format(player, e) + f"Failed to instantiate metadata source {player!r}: {e}" ) # Avoid needlessly iterating over items diff --git a/beetsplug/metasync/amarok.py b/beetsplug/metasync/amarok.py index 195cd87875..f8dcbe3f35 100644 --- a/beetsplug/metasync/amarok.py +++ b/beetsplug/metasync/amarok.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Synchronize information from amarok's library via dbus -""" - +"""Synchronize information from amarok's library via dbus""" from datetime import datetime from os.path import basename diff --git a/beetsplug/metasync/itunes.py b/beetsplug/metasync/itunes.py index 15cbd7bb3d..02f592fdce 100644 --- a/beetsplug/metasync/itunes.py +++ b/beetsplug/metasync/itunes.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Synchronize information from iTunes's library -""" - +"""Synchronize information from iTunes's library""" import os import plistlib diff --git a/beetsplug/missing.py b/beetsplug/missing.py index 2e37fde788..d5e4deda1b 100644 --- a/beetsplug/missing.py +++ b/beetsplug/missing.py @@ -13,8 +13,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""List missing tracks. -""" +"""List missing tracks.""" from collections import defaultdict diff --git a/beetsplug/parentwork.py b/beetsplug/parentwork.py index 4ddef1c14e..26f8f224f2 100644 --- a/beetsplug/parentwork.py +++ b/beetsplug/parentwork.py @@ -16,7 +16,6 @@ and work composition date """ - import musicbrainzngs from beets import ui diff --git a/beetsplug/play.py b/beetsplug/play.py index 3476e58243..ddebd7d41d 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Send the results of a query to the configured music player as a playlist. -""" +"""Send the results of a query to the configured music player as a playlist.""" import shlex import subprocess @@ -197,7 +196,7 @@ def _create_tmp_playlist(self, paths_list): filename = get_temp_filename(__name__, suffix=".m3u") with open(filename, "wb") as m3u: if utf8_bom: - m3u.write(b"\xEF\xBB\xBF") + m3u.write(b"\xef\xbb\xbf") for item in paths_list: m3u.write(item + b"\n") diff --git a/beetsplug/random.py b/beetsplug/random.py index dc94a0e3a1..05f2cdf779 100644 --- a/beetsplug/random.py +++ b/beetsplug/random.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Get a random song or album from the library. -""" +"""Get a random song or album from the library.""" from beets.plugins import BeetsPlugin from beets.random import random_objs diff --git a/beetsplug/replaygain.py b/beetsplug/replaygain.py index a2753f9601..578b692d10 100644 --- a/beetsplug/replaygain.py +++ b/beetsplug/replaygain.py @@ -334,9 +334,7 @@ def compute_track_gain(self, task: AnyRgTask) -> AnyRgTask: task.target_level, task.peak_method, count_blocks=False, - )[ - 0 - ] # take only the gain, discarding number of gating blocks + )[0] # take only the gain, discarding number of gating blocks for item in task.items ] diff --git a/beetsplug/scrub.py b/beetsplug/scrub.py index d1e63ee312..630a4e6e65 100644 --- a/beetsplug/scrub.py +++ b/beetsplug/scrub.py @@ -16,7 +16,6 @@ automatically whenever tags are written. """ - import mediafile import mutagen diff --git a/beetsplug/smartplaylist.py b/beetsplug/smartplaylist.py index 9df2cca648..a3b24b5693 100644 --- a/beetsplug/smartplaylist.py +++ b/beetsplug/smartplaylist.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Generates smart playlists based on beets queries. -""" - +"""Generates smart playlists based on beets queries.""" import json import os diff --git a/beetsplug/the.py b/beetsplug/the.py index c6fb46ddf8..c820e5972f 100644 --- a/beetsplug/the.py +++ b/beetsplug/the.py @@ -14,7 +14,6 @@ """Moves patterns in path formats (suitable for moving articles).""" - import re from typing import List diff --git a/beetsplug/thumbnails.py b/beetsplug/thumbnails.py index 19c19f06cc..3f88248e00 100644 --- a/beetsplug/thumbnails.py +++ b/beetsplug/thumbnails.py @@ -18,7 +18,6 @@ Spec: standards.freedesktop.org/thumbnail-spec/latest/index.html """ - import ctypes import ctypes.util import os @@ -280,8 +279,7 @@ def uri(self, path): if not uri_ptr: self.libgio.g_free(uri_ptr) raise RuntimeError( - "No URI received from the gfile pointer for " - "{}".format(displayable_path(path)) + f"No URI received from the gfile pointer for {displayable_path(path)}" ) try: diff --git a/beetsplug/zero.py b/beetsplug/zero.py index 14c157ce8a..bda4052ab7 100644 --- a/beetsplug/zero.py +++ b/beetsplug/zero.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -""" Clears tag fields in media files.""" - +"""Clears tag fields in media files.""" import re diff --git a/extra/release.py b/extra/release.py index 2c3ffd1bf7..1e90eb97bd 100755 --- a/extra/release.py +++ b/extra/release.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -"""A utility script for automating the beets release process. -""" +"""A utility script for automating the beets release process.""" + from __future__ import annotations import re diff --git a/test/plugins/test_acousticbrainz.py b/test/plugins/test_acousticbrainz.py index 77a04dafaf..2c4f0d9d68 100644 --- a/test/plugins/test_acousticbrainz.py +++ b/test/plugins/test_acousticbrainz.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Tests for the 'acousticbrainz' plugin. -""" - +"""Tests for the 'acousticbrainz' plugin.""" import json import os.path diff --git a/test/plugins/test_advancedrewrite.py b/test/plugins/test_advancedrewrite.py index 6f4f8a59b4..756d6d5fad 100644 --- a/test/plugins/test_advancedrewrite.py +++ b/test/plugins/test_advancedrewrite.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Test the advancedrewrite plugin for various configurations. -""" - +"""Test the advancedrewrite plugin for various configurations.""" import pytest diff --git a/test/plugins/test_albumtypes.py b/test/plugins/test_albumtypes.py index f03e948195..11c6e712b8 100644 --- a/test/plugins/test_albumtypes.py +++ b/test/plugins/test_albumtypes.py @@ -14,7 +14,6 @@ """Tests for the 'albumtypes' plugin.""" - from typing import Sequence, Tuple from beets.autotag.mb import VARIOUS_ARTISTS_ID diff --git a/test/plugins/test_art.py b/test/plugins/test_art.py index ede04a300e..86da975592 100644 --- a/test/plugins/test_art.py +++ b/test/plugins/test_art.py @@ -14,7 +14,6 @@ """Tests for the album art fetchers.""" - import os import shutil from unittest.mock import patch diff --git a/test/plugins/test_bareasc.py b/test/plugins/test_bareasc.py index 624392dbfd..e699a3dcf0 100644 --- a/test/plugins/test_bareasc.py +++ b/test/plugins/test_bareasc.py @@ -3,7 +3,6 @@ """Tests for the 'bareasc' plugin.""" - from beets import logging from beets.test.helper import PluginTestCase, capture_stdout diff --git a/test/plugins/test_bucket.py b/test/plugins/test_bucket.py index bc764038e0..b075bc4f23 100644 --- a/test/plugins/test_bucket.py +++ b/test/plugins/test_bucket.py @@ -14,7 +14,6 @@ """Tests for the 'bucket' plugin.""" - import pytest from beets import config, ui diff --git a/test/plugins/test_export.py b/test/plugins/test_export.py index bd1b7458d7..f37a0d2a79 100644 --- a/test/plugins/test_export.py +++ b/test/plugins/test_export.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Test the beets.export utilities associated with the export plugin. -""" - +"""Test the beets.export utilities associated with the export plugin.""" import json import re # used to test csv format diff --git a/test/plugins/test_filefilter.py b/test/plugins/test_filefilter.py index 92d19e0294..7f9fa2d182 100644 --- a/test/plugins/test_filefilter.py +++ b/test/plugins/test_filefilter.py @@ -12,8 +12,8 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Tests for the `filefilter` plugin. -""" +"""Tests for the `filefilter` plugin.""" + from beets.test.helper import ImportTestCase, PluginMixin from beets.util import bytestring_path diff --git a/test/plugins/test_ftintitle.py b/test/plugins/test_ftintitle.py index 8788f7cf55..bd40948736 100644 --- a/test/plugins/test_ftintitle.py +++ b/test/plugins/test_ftintitle.py @@ -14,7 +14,6 @@ """Tests for the 'ftintitle' plugin.""" - import unittest from beets.test.helper import PluginTestCase diff --git a/test/plugins/test_limit.py b/test/plugins/test_limit.py index 9f372992bd..12700295e0 100644 --- a/test/plugins/test_limit.py +++ b/test/plugins/test_limit.py @@ -13,7 +13,6 @@ """Tests for the 'limit' plugin.""" - from beets.test.helper import PluginTestCase diff --git a/test/plugins/test_lyrics.py b/test/plugins/test_lyrics.py index 7cb081fc42..76c0112251 100644 --- a/test/plugins/test_lyrics.py +++ b/test/plugins/test_lyrics.py @@ -14,7 +14,6 @@ """Tests for the 'lyrics' plugin.""" - import itertools import os import re @@ -509,14 +508,14 @@ def test_json(self, mock_fetch_url, mock_scrape): { "result": { "primary_artist": { - "name": "\u200Bblackbear", + "name": "\u200bblackbear", }, "url": "blackbear_url", } }, { "result": { - "primary_artist": {"name": "El\u002Dp"}, + "primary_artist": {"name": "El\u002dp"}, "url": "El-p_url", } }, @@ -786,10 +785,10 @@ def test_slug(self): assert lyrics.slug(text) == "cafe-au-lait-boisson" text = "Multiple spaces -- and symbols! -- merged" assert lyrics.slug(text) == "multiple-spaces-and-symbols-merged" - text = "\u200Bno-width-space" + text = "\u200bno-width-space" assert lyrics.slug(text) == "no-width-space" # variations of dashes should get standardized - dashes = ["\u200D", "\u2010"] + dashes = ["\u200d", "\u2010"] for dash1, dash2 in itertools.combinations(dashes, 2): assert lyrics.slug(dash1) == lyrics.slug(dash2) diff --git a/test/plugins/test_parentwork.py b/test/plugins/test_parentwork.py index 71b9f1fede..9ce804091d 100644 --- a/test/plugins/test_parentwork.py +++ b/test/plugins/test_parentwork.py @@ -14,7 +14,6 @@ """Tests for the 'parentwork' plugin.""" - import os import unittest from unittest.mock import patch diff --git a/test/plugins/test_permissions.py b/test/plugins/test_permissions.py index 327304d821..7979cfa129 100644 --- a/test/plugins/test_permissions.py +++ b/test/plugins/test_permissions.py @@ -1,5 +1,4 @@ -"""Tests for the 'permissions' plugin. -""" +"""Tests for the 'permissions' plugin.""" import os import platform diff --git a/test/plugins/test_play.py b/test/plugins/test_play.py index 63f20aeefc..7127396335 100644 --- a/test/plugins/test_play.py +++ b/test/plugins/test_play.py @@ -14,7 +14,6 @@ """Tests for the play plugin""" - import os import sys import unittest diff --git a/test/plugins/test_plugin_mediafield.py b/test/plugins/test_plugin_mediafield.py index 39b5aa3d22..898e891ce7 100644 --- a/test/plugins/test_plugin_mediafield.py +++ b/test/plugins/test_plugin_mediafield.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Tests the facility that lets plugins add custom field to MediaFile. -""" +"""Tests the facility that lets plugins add custom field to MediaFile.""" import os import shutil diff --git a/test/plugins/test_random.py b/test/plugins/test_random.py index 626f31779e..5bff1ee5e8 100644 --- a/test/plugins/test_random.py +++ b/test/plugins/test_random.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Test the beets.random utilities associated with the random plugin. -""" - +"""Test the beets.random utilities associated with the random plugin.""" import math import unittest diff --git a/test/test_art_resize.py b/test/test_art_resize.py index fb628bca76..8dd4d0e895 100644 --- a/test/test_art_resize.py +++ b/test/test_art_resize.py @@ -14,7 +14,6 @@ """Tests for image resizing based on filesize.""" - import os import unittest from unittest.mock import patch diff --git a/test/test_autotag.py b/test/test_autotag.py index 7e6e7f43e9..6843a48986 100644 --- a/test/test_autotag.py +++ b/test/test_autotag.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Tests for autotagging functionality. -""" +"""Tests for autotagging functionality.""" import re import unittest diff --git a/test/test_datequery.py b/test/test_datequery.py index 31ec5f9da7..e5c5f2bfda 100644 --- a/test/test_datequery.py +++ b/test/test_datequery.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Test for dbcore's date-based queries. -""" +"""Test for dbcore's date-based queries.""" import time import unittest diff --git a/test/test_dbcore.py b/test/test_dbcore.py index fc4a614f77..ba2b84ad2a 100644 --- a/test/test_dbcore.py +++ b/test/test_dbcore.py @@ -239,9 +239,7 @@ def test_mutate_increase_revision(self): old_rev = self.db.revision with self.db.transaction() as tx: tx.mutate( - "INSERT INTO {} " - "(field_one) " - "VALUES (?);".format(ModelFixture1._table), + f"INSERT INTO {ModelFixture1._table} (field_one) VALUES (?);", (111,), ) assert self.db.revision > old_rev @@ -517,9 +515,7 @@ def pqp(self, part): part, {"year": dbcore.query.NumericQuery}, {":": dbcore.query.RegexpQuery}, - )[ - :-1 - ] # remove the negate flag + )[:-1] # remove the negate flag def test_one_basic_term(self): q = "test" diff --git a/test/test_hidden.py b/test/test_hidden.py index e7af321336..a7e6a1a10c 100644 --- a/test/test_hidden.py +++ b/test/test_hidden.py @@ -14,7 +14,6 @@ """Tests for the 'hidden' utility.""" - import ctypes import errno import subprocess diff --git a/test/test_m3ufile.py b/test/test_m3ufile.py index e9fbee644f..12686d8248 100644 --- a/test/test_m3ufile.py +++ b/test/test_m3ufile.py @@ -13,7 +13,6 @@ # included in all copies or substantial portions of the Software. """Testsuite for the M3UFile class.""" - import sys import unittest from os import path diff --git a/test/test_metasync.py b/test/test_metasync.py index 9e18a59ef0..13c003a1c3 100644 --- a/test/test_metasync.py +++ b/test/test_metasync.py @@ -69,12 +69,12 @@ def _set_up_data(self): items[1].album = "An Awesome Wave" if _is_windows(): - items[0].path = ( - "G:\\Music\\Alt-J\\An Awesome Wave\\03 Tessellate.mp3" - ) - items[1].path = ( - "G:\\Music\\Alt-J\\An Awesome Wave\\04 Breezeblocks.mp3" - ) + items[ + 0 + ].path = "G:\\Music\\Alt-J\\An Awesome Wave\\03 Tessellate.mp3" + items[ + 1 + ].path = "G:\\Music\\Alt-J\\An Awesome Wave\\04 Breezeblocks.mp3" else: items[0].path = "/Music/Alt-J/An Awesome Wave/03 Tessellate.mp3" items[1].path = "/Music/Alt-J/An Awesome Wave/04 Breezeblocks.mp3" diff --git a/test/test_pipeline.py b/test/test_pipeline.py index 7b909dc27c..3dd32df1ce 100644 --- a/test/test_pipeline.py +++ b/test/test_pipeline.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Test the "pipeline.py" restricted parallel programming library. -""" +"""Test the "pipeline.py" restricted parallel programming library.""" import unittest diff --git a/test/test_plugins.py b/test/test_plugins.py index cb8d8e0d5e..f3b9290b6b 100644 --- a/test/test_plugins.py +++ b/test/test_plugins.py @@ -281,7 +281,6 @@ def __init__(self): @patch("beets.plugins.find_plugins") def test_listener_params(self, mock_find_plugins): - class DummyPlugin(plugins.BeetsPlugin): def __init__(self): super().__init__() diff --git a/test/test_query.py b/test/test_query.py index 04170a1598..61237e10e8 100644 --- a/test/test_query.py +++ b/test/test_query.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Various tests for querying the library database. -""" +"""Various tests for querying the library database.""" import os import sys diff --git a/test/test_sort.py b/test/test_sort.py index 3d27f6591d..f212bd6544 100644 --- a/test/test_sort.py +++ b/test/test_sort.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Various tests for querying the library database. -""" - +"""Various tests for querying the library database.""" import beets.library from beets import config, dbcore diff --git a/test/test_template.py b/test/test_template.py index 23bf527a0e..236bee5aa0 100644 --- a/test/test_template.py +++ b/test/test_template.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Tests for template engine. -""" +"""Tests for template engine.""" import unittest diff --git a/test/test_ui.py b/test/test_ui.py index 5423083e20..c087aca7d6 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Tests for the command-line interface. -""" +"""Tests for the command-line interface.""" import os import platform diff --git a/test/test_ui_commands.py b/test/test_ui_commands.py index c8a39dc694..8daf8e31a7 100644 --- a/test/test_ui_commands.py +++ b/test/test_ui_commands.py @@ -12,9 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Test module for file ui/commands.py -""" - +"""Test module for file ui/commands.py""" import os import shutil diff --git a/test/test_ui_init.py b/test/test_ui_init.py index 29ce36a613..a6f06c494f 100644 --- a/test/test_ui_init.py +++ b/test/test_ui_init.py @@ -12,8 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Test module for file ui/__init__.py -""" +"""Test module for file ui/__init__.py""" import os import shutil diff --git a/test/test_util.py b/test/test_util.py index c719bafa61..85534949fa 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -11,8 +11,7 @@ # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Tests for base utils from the beets.util package. -""" +"""Tests for base utils from the beets.util package.""" import os import platform diff --git a/test/test_vfs.py b/test/test_vfs.py index 41ad276f5d..7f75fbd835 100644 --- a/test/test_vfs.py +++ b/test/test_vfs.py @@ -14,7 +14,6 @@ """Tests for the virtual filesystem builder..""" - from beets import vfs from beets.test import _common from beets.test.helper import BeetsTestCase From f36bc497c8c8f89004f3f6879908d3f0b25123e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Tue, 17 Sep 2024 12:57:12 +0100 Subject: [PATCH 4/9] Fix lint issues - Fix imports - Fix pytest issues - Do not assign lambda as variable - Use isinstance instead of type to check type - Rename ambiguously named variables - Name custom errors with Error suffix --- beets/autotag/__init__.py | 31 ++++++--- beets/autotag/mb.py | 2 +- beets/importer.py | 6 +- beets/plugins.py | 8 +-- beets/test/_common.py | 6 +- beets/ui/__init__.py | 2 +- beets/ui/commands.py | 4 +- beets/util/__init__.py | 4 +- beets/util/bluelet.py | 8 +-- beetsplug/badfiles.py | 8 +-- beetsplug/bpd/__init__.py | 28 ++++---- beetsplug/convert.py | 5 +- beetsplug/duplicates.py | 12 +++- beetsplug/lyrics.py | 2 +- pyproject.toml | 1 + test/plugins/lyrics_download_samples.py | 3 +- test/plugins/test_art.py | 2 +- test/plugins/test_embedart.py | 2 +- test/plugins/test_ftintitle.py | 8 +-- test/plugins/test_thumbnails.py | 20 +++--- test/test_autotag.py | 2 +- test/test_config_command.py | 8 +-- test/test_importer.py | 7 +- test/test_logging.py | 14 ++-- test/test_pipeline.py | 92 ++++++++++++++----------- test/test_plugins.py | 5 +- test/test_ui.py | 74 ++++++++++---------- test/test_ui_commands.py | 4 +- test/test_ui_importer.py | 3 +- 29 files changed, 204 insertions(+), 167 deletions(-) diff --git a/beets/autotag/__init__.py b/beets/autotag/__init__.py index a67531a69e..c6f65ee499 100644 --- a/beets/autotag/__init__.py +++ b/beets/autotag/__init__.py @@ -20,15 +20,30 @@ from beets.library import Album, Item # Parts of external interface. -from .hooks import ( # noqa - AlbumInfo, - AlbumMatch, - Distance, - TrackInfo, - TrackMatch, +from .hooks import AlbumInfo, AlbumMatch, Distance, TrackInfo, TrackMatch +from .match import ( + Proposal, + Recommendation, + current_metadata, + tag_album, + tag_item, ) -from .match import Recommendation # noqa -from .match import Proposal, current_metadata, tag_album, tag_item # noqa + +__all__ = [ + "AlbumInfo", + "AlbumMatch", + "Distance", + "TrackInfo", + "TrackMatch", + "Proposal", + "Recommendation", + "apply_album_metadata", + "apply_item_metadata", + "apply_metadata", + "current_metadata", + "tag_album", + "tag_item", +] # Global logger. log = logging.getLogger("beets") diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 2ea5f7baab..41d0fdd8aa 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -54,7 +54,7 @@ musicbrainzngs.set_useragent("beets", beets.__version__, "https://beets.io/") -class MusicBrainzAPIError(util.HumanReadableException): +class MusicBrainzAPIError(util.HumanReadableError): """An error while talking to MusicBrainz. The `query` field is the parameter to the action and may have any type. """ diff --git a/beets/importer.py b/beets/importer.py index 6fed3a02d7..ea5b1cc512 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -74,7 +74,7 @@ log = logging.getLogger("beets") -class ImportAbort(Exception): +class ImportAbortError(Exception): """Raised when the user aborts the tagging operation.""" pass @@ -360,7 +360,7 @@ def run(self): pl.run_parallel(QUEUE_SIZE) else: pl.run_sequential() - except ImportAbort: + except ImportAbortError: # User aborted operation. Silently stop. pass @@ -946,7 +946,7 @@ def remove_replaced(self, lib): dup_item.remove() log.debug( "{0} of {1} items replaced", - sum(bool(l) for l in self.replaced_items.values()), + sum(bool(v) for v in self.replaced_items.values()), len(self.imported_items()), ) diff --git a/beets/plugins.py b/beets/plugins.py index 5df738c843..0864c4b9b0 100644 --- a/beets/plugins.py +++ b/beets/plugins.py @@ -35,7 +35,7 @@ log = logging.getLogger("beets") -class PluginConflictException(Exception): +class PluginConflictError(Exception): """Indicates that the services provided by one plugin conflict with those of another. @@ -342,7 +342,7 @@ def types(model_cls): plugin_types = getattr(plugin, attr_name, {}) for field in plugin_types: if field in types and plugin_types[field] != types[field]: - raise PluginConflictException( + raise PluginConflictError( "Plugin {} defines flexible field {} " "which has already been defined with " "another type.".format(plugin.name, field) @@ -446,13 +446,13 @@ def import_stages(): def _check_conflicts_and_merge(plugin, plugin_funcs, funcs): """Check the provided template functions for conflicts and merge into funcs. - Raises a `PluginConflictException` if a plugin defines template functions + Raises a `PluginConflictError` if a plugin defines template functions for fields that another plugin has already defined template functions for. """ if plugin_funcs: if not plugin_funcs.keys().isdisjoint(funcs.keys()): conflicted_fields = ", ".join(plugin_funcs.keys() & funcs.keys()) - raise PluginConflictException( + raise PluginConflictError( f"Plugin {plugin.name} defines template functions for " f"{conflicted_fields} that conflict with another plugin." ) diff --git a/beets/test/_common.py b/beets/test/_common.py index 76456ca7f1..b1cc269361 100644 --- a/beets/test/_common.py +++ b/beets/test/_common.py @@ -171,7 +171,7 @@ def assert_equal_path(self, a, b): # Mock I/O. -class InputException(Exception): +class InputError(Exception): def __init__(self, output=None): self.output = output @@ -218,9 +218,9 @@ def close(self): def readline(self): if not self.buf: if self.out: - raise InputException(self.out.get()) + raise InputError(self.out.get()) else: - raise InputException() + raise InputError() self.reads += 1 return self.buf.pop(0) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index f84ce06d08..39ef7d2915 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -1864,7 +1864,7 @@ def main(args=None): message = exc.args[0] if exc.args else None log.error("error: {0}", message) sys.exit(1) - except util.HumanReadableException as exc: + except util.HumanReadableError as exc: exc.log(log) sys.exit(1) except library.FileOperationError as exc: diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 5d0ac206a6..9ad19ad05e 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1026,7 +1026,7 @@ def manual_id(session, task): def abort_action(session, task): """A prompt choice callback that aborts the importer.""" - raise importer.ImportAbort() + raise importer.ImportAbortError() class TerminalImportSession(importer.ImportSession): @@ -1056,7 +1056,7 @@ def choose_match(self, task): if len(actions) == 1: return actions[0] elif len(actions) > 1: - raise plugins.PluginConflictException( + raise plugins.PluginConflictError( "Only one handler for `import_task_before_choice` may return " "an action." ) diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 88e3a141b4..759026f3a5 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -67,7 +67,7 @@ Replacements: TypeAlias = "Sequence[tuple[Pattern[str], str]]" -class HumanReadableException(Exception): +class HumanReadableError(Exception): """An Exception that can include a human-readable error message to be logged without a traceback. Can preserve a traceback for debugging purposes as well. @@ -123,7 +123,7 @@ def log(self, logger): logger.error("{0}: {1}", self.error_kind, self.args[0]) -class FilesystemError(HumanReadableException): +class FilesystemError(HumanReadableError): """An error that occurred while performing a filesystem manipulation via a function in this module. The `paths` field is a sequence of pathnames involved in the operation. diff --git a/beets/util/bluelet.py b/beets/util/bluelet.py index db34486b5c..b81b389e0d 100644 --- a/beets/util/bluelet.py +++ b/beets/util/bluelet.py @@ -203,7 +203,7 @@ def _event_select(events): return ready_events -class ThreadException(Exception): +class ThreadError(Exception): def __init__(self, coro, exc_info): self.coro = coro self.exc_info = exc_info @@ -266,7 +266,7 @@ def advance_thread(coro, value, is_exc=False): """After an event is fired, run a given coroutine associated with it in the threads dict until it yields again. If the coroutine exits, then the thread is removed from the pool. If the coroutine - raises an exception, it is reraised in a ThreadException. If + raises an exception, it is reraised in a ThreadError. If is_exc is True, then the value must be an exc_info tuple and the exception is thrown into the coroutine. """ @@ -281,7 +281,7 @@ def advance_thread(coro, value, is_exc=False): except BaseException: # Thread raised some other exception. del threads[coro] - raise ThreadException(coro, sys.exc_info()) + raise ThreadError(coro, sys.exc_info()) else: if isinstance(next_event, types.GeneratorType): # Automatically invoke sub-coroutines. (Shorthand for @@ -369,7 +369,7 @@ def kill_thread(coro): else: advance_thread(event2coro[event], value) - except ThreadException as te: + except ThreadError as te: # Exception raised from inside a thread. event = ExceptionEvent(te.exc_info) if te.coro in delegators: diff --git a/beetsplug/badfiles.py b/beetsplug/badfiles.py index f596189acc..f93f03d5e5 100644 --- a/beetsplug/badfiles.py +++ b/beetsplug/badfiles.py @@ -28,7 +28,7 @@ from beets.util import displayable_path, par_map -class CheckerCommandException(Exception): +class CheckerCommandError(Exception): """Raised when running a checker failed. Attributes: @@ -68,7 +68,7 @@ def run_command(self, cmd): errors = 1 status = e.returncode except OSError as e: - raise CheckerCommandException(cmd, e) + raise CheckerCommandError(cmd, e) output = output.decode(sys.getdefaultencoding(), "replace") return status, errors, [line for line in output.split("\n") if line] @@ -126,7 +126,7 @@ def check_item(self, item): path = item.path.decode(sys.getfilesystemencoding()) try: status, errors, output = checker(path) - except CheckerCommandException as e: + except CheckerCommandError as e: if e.errno == errno.ENOENT: self._log.error( "command not found: {} when validating file: {}", @@ -198,7 +198,7 @@ def on_import_task_before_choice(self, task, session): elif sel == "c": return None elif sel == "b": - raise importer.ImportAbort() + raise importer.ImportAbortError() else: raise Exception(f"Unexpected selection: {sel}") diff --git a/beetsplug/bpd/__init__.py b/beetsplug/bpd/__init__.py index c78452ee84..cc56a27c59 100644 --- a/beetsplug/bpd/__init__.py +++ b/beetsplug/bpd/__init__.py @@ -167,13 +167,13 @@ def cast_arg(t, val): raise ArgumentTypeError() -class BPDClose(Exception): +class BPDCloseError(Exception): """Raised by a command invocation to indicate that the connection should be closed. """ -class BPDIdle(Exception): +class BPDIdleError(Exception): """Raised by a command to indicate the client wants to enter the idle state and should be notified when a relevant event happens. """ @@ -348,7 +348,7 @@ def cmd_idle(self, conn, *subsystems): for system in subsystems: if system not in SUBSYSTEMS: raise BPDError(ERROR_ARG, f"Unrecognised idle event: {system}") - raise BPDIdle(subsystems) # put the connection into idle mode + raise BPDIdleError(subsystems) # put the connection into idle mode def cmd_kill(self, conn): """Exits the server process.""" @@ -356,7 +356,7 @@ def cmd_kill(self, conn): def cmd_close(self, conn): """Closes the connection.""" - raise BPDClose() + raise BPDCloseError() def cmd_password(self, conn, password): """Attempts password authentication.""" @@ -772,8 +772,8 @@ def send(self, lines): if isinstance(lines, str): lines = [lines] out = NEWLINE.join(lines) + NEWLINE - for l in out.split(NEWLINE)[:-1]: - self.debug(l, kind=">") + for line in out.split(NEWLINE)[:-1]: + self.debug(line, kind=">") if isinstance(out, str): out = out.encode("utf-8") return self.sock.sendall(out) @@ -852,8 +852,8 @@ def run(self): self.disconnect() # Client sent a blank line. break line = line.decode("utf8") # MPD protocol uses UTF-8. - for l in line.split(NEWLINE): - self.debug(l, kind="<") + for line in line.split(NEWLINE): + self.debug(line, kind="<") if self.idle_subscriptions: # The connection is in idle mode. @@ -887,12 +887,12 @@ def run(self): # Ordinary command. try: yield bluelet.call(self.do_command(Command(line))) - except BPDClose: + except BPDCloseError: # Command indicates that the conn should close. self.sock.close() self.disconnect() # Client explicitly closed. return - except BPDIdle as e: + except BPDIdleError as e: self.idle_subscriptions = e.subsystems self.debug( "awaiting: {}".format(" ".join(e.subsystems)), kind="z" @@ -921,8 +921,8 @@ def run(self): if not line: break # Client sent a blank line. line = line.decode("utf8") # Protocol uses UTF-8. - for l in line.split(NEWLINE): - self.debug(l, kind="<") + for line in line.split(NEWLINE): + self.debug(line, kind="<") command = Command(line) try: func = command.delegate("ctrl_", self) @@ -1045,12 +1045,12 @@ def run(self, conn): e.cmd_name = self.name raise e - except BPDClose: + except BPDCloseError: # An indication that the connection should close. Send # it on the Connection. raise - except BPDIdle: + except BPDIdleError: raise except Exception: diff --git a/beetsplug/convert.py b/beetsplug/convert.py index a1d0680338..fe9d91d25a 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -95,8 +95,9 @@ def should_transcode(item, fmt): query, _ = parse_query_string(query_string, Item) if query.match(item): return False - if config["convert"]["never_convert_lossy_files"] and not ( - item.format.lower() in LOSSLESS_FORMATS + if ( + config["convert"]["never_convert_lossy_files"] + and item.format.lower() not in LOSSLESS_FORMATS ): return False maxbr = config["convert"]["max_bitrate"].get(Optional(int)) diff --git a/beetsplug/duplicates.py b/beetsplug/duplicates.py index a5f2bc8150..1e30a60a58 100644 --- a/beetsplug/duplicates.py +++ b/beetsplug/duplicates.py @@ -303,7 +303,9 @@ def _order(self, objs, tiebreak=None): kind = "items" if all(isinstance(o, Item) for o in objs) else "albums" if tiebreak and kind in tiebreak.keys(): - key = lambda x: tuple(getattr(x, k) for k in tiebreak[kind]) + + def key(x): + return tuple(getattr(x, k) for k in tiebreak[kind]) else: if kind == "items": @@ -316,9 +318,13 @@ def truthy(v): ) fields = Item.all_keys() - key = lambda x: sum(1 for f in fields if truthy(getattr(x, f))) + + def key(x): + return sum(1 for f in fields if truthy(getattr(x, f))) else: - key = lambda x: len(x.items()) + + def key(x): + return len(x.items()) return sorted(objs, key=key, reverse=True) diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index bf0ce96d02..ccc8cae75f 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -1061,7 +1061,7 @@ def fetch_item_lyrics(self, lib, item, write, force): if any(lyrics): break - lyrics = "\n\n---\n\n".join([l for l in lyrics if l]) + lyrics = "\n\n---\n\n".join(filter(None, lyrics)) if lyrics: self._log.info("fetched lyrics: {0}", item) diff --git a/pyproject.toml b/pyproject.toml index 9f85522610..70902f0092 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -253,6 +253,7 @@ max-line-length = 88 [tool.ruff.lint.flake8-pytest-style] fixture-parentheses = false mark-parentheses = false +parametrize-names-type = "csv" [tool.ruff.lint.flake8-unused-arguments] ignore-variadic-names = true diff --git a/test/plugins/lyrics_download_samples.py b/test/plugins/lyrics_download_samples.py index 9a1c86591c..4d68e7d50a 100644 --- a/test/plugins/lyrics_download_samples.py +++ b/test/plugins/lyrics_download_samples.py @@ -15,10 +15,11 @@ import os import sys -from test.plugins import test_lyrics import requests +from test.plugins import test_lyrics + def mkdir_p(path): try: diff --git a/test/plugins/test_art.py b/test/plugins/test_art.py index 86da975592..bd602c434c 100644 --- a/test/plugins/test_art.py +++ b/test/plugins/test_art.py @@ -994,7 +994,7 @@ def setUp(self): self.plugin = fetchart.FetchArtPlugin() def test_moves_filesystem_to_end(self): - assert type(self.plugin.sources[-1]) == fetchart.FileSystem + assert isinstance(self.plugin.sources[-1], fetchart.FileSystem) class EnforceRatioConfigTest(BeetsTestCase): diff --git a/test/plugins/test_embedart.py b/test/plugins/test_embedart.py index b20ff6c87e..14bfdf5225 100644 --- a/test/plugins/test_embedart.py +++ b/test/plugins/test_embedart.py @@ -17,7 +17,6 @@ import shutil import tempfile import unittest -from test.test_art_resize import DummyIMBackend from unittest.mock import MagicMock, patch import pytest @@ -28,6 +27,7 @@ from beets.test.helper import BeetsTestCase, FetchImageHelper, PluginMixin from beets.util import bytestring_path, displayable_path, syspath from beets.util.artresizer import ArtResizer +from test.test_art_resize import DummyIMBackend def require_artresizer_compare(test): diff --git a/test/plugins/test_ftintitle.py b/test/plugins/test_ftintitle.py index bd40948736..45146b42b7 100644 --- a/test/plugins/test_ftintitle.py +++ b/test/plugins/test_ftintitle.py @@ -82,14 +82,14 @@ def test_functional_keep_in_artist(self): item = self._ft_add_item("/", "Alice ft Bob", "Song 1", "Alice") self.run_command("ftintitle") item.load() - self.assertEqual(item["artist"], "Alice ft Bob") - self.assertEqual(item["title"], "Song 1 feat. Bob") + assert item["artist"] == "Alice ft Bob" + assert item["title"] == "Song 1 feat. Bob" item = self._ft_add_item("/", "Alice ft Bob", "Song 1", "Alice") self.run_command("ftintitle", "-d") item.load() - self.assertEqual(item["artist"], "Alice ft Bob") - self.assertEqual(item["title"], "Song 1") + assert item["artist"] == "Alice ft Bob" + assert item["title"] == "Song 1" class FtInTitlePluginTest(unittest.TestCase): diff --git a/test/plugins/test_thumbnails.py b/test/plugins/test_thumbnails.py index 07775995c1..3eb36cd257 100644 --- a/test/plugins/test_thumbnails.py +++ b/test/plugins/test_thumbnails.py @@ -33,9 +33,9 @@ class ThumbnailsTest(BeetsTestCase): @patch("beetsplug.thumbnails.ArtResizer") - @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok") + @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock()) @patch("beetsplug.thumbnails.os.stat") - def test_add_tags(self, mock_stat, _, mock_artresizer): + def test_add_tags(self, mock_stat, mock_artresizer): plugin = ThumbnailsPlugin() plugin.get_uri = Mock( side_effect={b"/path/to/cover": "COVER_URI"}.__getitem__ @@ -98,13 +98,13 @@ def exists(path): giouri_inst.available = False assert ThumbnailsPlugin().get_uri.__self__.__class__ == PathlibURI - @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok") + @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock()) @patch("beetsplug.thumbnails.ArtResizer") @patch("beetsplug.thumbnails.util") @patch("beetsplug.thumbnails.os") @patch("beetsplug.thumbnails.shutil") def test_make_cover_thumbnail( - self, mock_shutils, mock_os, mock_util, mock_artresizer, _ + self, mock_shutils, mock_os, mock_util, mock_artresizer ): thumbnail_dir = os.path.normpath(b"/thumbnail/dir") md5_file = os.path.join(thumbnail_dir, b"md5") @@ -166,8 +166,8 @@ def os_stat(target): plugin.make_cover_thumbnail(album, 12345, thumbnail_dir) mock_resize.assert_called_once_with(12345, path_to_art, md5_file) - @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok") - def test_make_dolphin_cover_thumbnail(self, _): + @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock()) + def test_make_dolphin_cover_thumbnail(self): plugin = ThumbnailsPlugin() tmp = bytestring_path(mkdtemp()) album = Mock(path=tmp, artpath=os.path.join(tmp, b"cover.jpg")) @@ -189,9 +189,9 @@ def test_make_dolphin_cover_thumbnail(self, _): rmtree(syspath(tmp)) - @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok") + @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock()) @patch("beetsplug.thumbnails.ArtResizer") - def test_process_album(self, mock_artresizer, _): + def test_process_album(self, mock_artresizer): get_size = mock_artresizer.shared.get_size plugin = ThumbnailsPlugin() @@ -234,9 +234,9 @@ def test_process_album(self, mock_artresizer, _): any_order=True, ) - @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok") + @patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock()) @patch("beetsplug.thumbnails.decargs") - def test_invokations(self, mock_decargs, _): + def test_invokations(self, mock_decargs): plugin = ThumbnailsPlugin() plugin.process_album = Mock() album = Mock() diff --git a/test/test_autotag.py b/test/test_autotag.py index 6843a48986..f3e4f0c471 100644 --- a/test/test_autotag.py +++ b/test/test_autotag.py @@ -47,7 +47,7 @@ def test_plurality_conflict(self): assert freq == 2 def test_plurality_empty_sequence_raises_error(self): - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="must be non-empty"): plurality([]) def test_current_metadata_finds_pluralities(self): diff --git a/test/test_config_command.py b/test/test_config_command.py index 388b649737..e30fde382b 100644 --- a/test/test_config_command.py +++ b/test/test_config_command.py @@ -112,10 +112,10 @@ def test_edit_config_with_automatic_open(self): def test_config_editor_not_found(self): msg_match = "Could not edit configuration.*here is problem" - with pytest.raises(ui.UserError, match=msg_match): - with patch("os.execlp") as execlp: - execlp.side_effect = OSError("here is problem") - self.run_command("config", "-e") + with patch( + "os.execlp", side_effect=OSError("here is problem") + ), pytest.raises(ui.UserError, match=msg_match): + self.run_command("config", "-e") def test_edit_invalid_config_file(self): with open(self.config_path, "w") as file: diff --git a/test/test_importer.py b/test/test_importer.py index b9fde2a032..116e750e78 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -771,7 +771,8 @@ def test_asis_albumartists_tag_sets_multi_albumartists(self): asserted_multi_artists_1 = True assert item.artists == ["Another Artist", "Another Artist 2"] - assert asserted_multi_artists_0 and asserted_multi_artists_1 + assert asserted_multi_artists_0 + assert asserted_multi_artists_1 class ImportExistingTest(ImportTestCase): @@ -1320,7 +1321,7 @@ def test_resume_album(self, plugins_send): # the first album in the second try. def raise_exception(event, **kwargs): if event == "album_imported": - raise importer.ImportAbort + raise importer.ImportAbortError plugins_send.side_effect = raise_exception @@ -1343,7 +1344,7 @@ def test_resume_singleton(self, plugins_send): # the first album in the second try. def raise_exception(event, **kwargs): if event == "item_imported": - raise importer.ImportAbort + raise importer.ImportAbortError plugins_send.side_effect = raise_exception diff --git a/test/test_logging.py b/test/test_logging.py index 0aa0d85d3c..d95a543879 100644 --- a/test/test_logging.py +++ b/test/test_logging.py @@ -39,14 +39,14 @@ def test_logging_management(self): assert l1 != l6 def test_str_format_logging(self): - l = blog.getLogger("baz123") + logger = blog.getLogger("baz123") stream = StringIO() handler = log.StreamHandler(stream) - l.addHandler(handler) - l.propagate = False + logger.addHandler(handler) + logger.propagate = False - l.warning("foo {0} {bar}", "oof", bar="baz") + logger.warning("foo {0} {bar}", "oof", bar="baz") handler.flush() assert stream.getvalue(), "foo oof baz" @@ -265,9 +265,9 @@ def test_root_logger_levels(self): blog.getLogger("beets").set_global_level(blog.INFO) with helper.capture_log() as logs: self.run_asis_importer() - for l in logs: - assert "import" in l - assert "album" in l + for line in logs: + assert "import" in line + assert "album" in line blog.getLogger("beets").set_global_level(blog.DEBUG) with helper.capture_log() as logs: diff --git a/test/test_pipeline.py b/test/test_pipeline.py index 3dd32df1ce..83b8d744c5 100644 --- a/test/test_pipeline.py +++ b/test/test_pipeline.py @@ -33,14 +33,14 @@ def _work(): i *= 2 -def _consume(l): +def _consume(result): while True: i = yield - l.append(i) + result.append(i) # A worker that raises an exception. -class ExceptionFixture(Exception): +class PipelineError(Exception): pass @@ -49,7 +49,7 @@ def _exc_work(num=3): while True: i = yield i if i == num: - raise ExceptionFixture() + raise PipelineError() i *= 2 @@ -74,16 +74,18 @@ def _multi_work(): class SimplePipelineTest(unittest.TestCase): def setUp(self): - self.l = [] - self.pl = pipeline.Pipeline((_produce(), _work(), _consume(self.l))) + self.result = [] + self.pl = pipeline.Pipeline( + (_produce(), _work(), _consume(self.result)) + ) def test_run_sequential(self): self.pl.run_sequential() - assert self.l == [0, 2, 4, 6, 8] + assert self.result == [0, 2, 4, 6, 8] def test_run_parallel(self): self.pl.run_parallel() - assert self.l == [0, 2, 4, 6, 8] + assert self.result == [0, 2, 4, 6, 8] def test_pull(self): pl = pipeline.Pipeline((_produce(), _work())) @@ -97,19 +99,19 @@ def test_pull_chain(self): class ParallelStageTest(unittest.TestCase): def setUp(self): - self.l = [] + self.result = [] self.pl = pipeline.Pipeline( - (_produce(), (_work(), _work()), _consume(self.l)) + (_produce(), (_work(), _work()), _consume(self.result)) ) def test_run_sequential(self): self.pl.run_sequential() - assert self.l == [0, 2, 4, 6, 8] + assert self.result == [0, 2, 4, 6, 8] def test_run_parallel(self): self.pl.run_parallel() # Order possibly not preserved; use set equality. - assert set(self.l) == {0, 2, 4, 6, 8} + assert set(self.result) == {0, 2, 4, 6, 8} def test_pull(self): pl = pipeline.Pipeline((_produce(), (_work(), _work()))) @@ -118,15 +120,17 @@ def test_pull(self): class ExceptionTest(unittest.TestCase): def setUp(self): - self.l = [] - self.pl = pipeline.Pipeline((_produce(), _exc_work(), _consume(self.l))) + self.result = [] + self.pl = pipeline.Pipeline( + (_produce(), _exc_work(), _consume(self.result)) + ) def test_run_sequential(self): - with pytest.raises(ExceptionFixture): + with pytest.raises(PipelineError): self.pl.run_sequential() def test_run_parallel(self): - with pytest.raises(ExceptionFixture): + with pytest.raises(PipelineError): self.pl.run_parallel() def test_pull(self): @@ -134,59 +138,65 @@ def test_pull(self): pull = pl.pull() for i in range(3): next(pull) - with pytest.raises(ExceptionFixture): + with pytest.raises(PipelineError): next(pull) class ParallelExceptionTest(unittest.TestCase): def setUp(self): - self.l = [] + self.result = [] self.pl = pipeline.Pipeline( - (_produce(), (_exc_work(), _exc_work()), _consume(self.l)) + (_produce(), (_exc_work(), _exc_work()), _consume(self.result)) ) def test_run_parallel(self): - with pytest.raises(ExceptionFixture): + with pytest.raises(PipelineError): self.pl.run_parallel() class ConstrainedThreadedPipelineTest(unittest.TestCase): + def setUp(self): + self.result = [] + def test_constrained(self): - l = [] # Do a "significant" amount of work... - pl = pipeline.Pipeline((_produce(1000), _work(), _consume(l))) + self.pl = pipeline.Pipeline( + (_produce(1000), _work(), _consume(self.result)) + ) # ... with only a single queue slot. - pl.run_parallel(1) - assert l == [i * 2 for i in range(1000)] + self.pl.run_parallel(1) + assert self.result == [i * 2 for i in range(1000)] def test_constrained_exception(self): # Raise an exception in a constrained pipeline. - l = [] - pl = pipeline.Pipeline((_produce(1000), _exc_work(), _consume(l))) - with pytest.raises(ExceptionFixture): - pl.run_parallel(1) + self.pl = pipeline.Pipeline( + (_produce(1000), _exc_work(), _consume(self.result)) + ) + with pytest.raises(PipelineError): + self.pl.run_parallel(1) def test_constrained_parallel(self): - l = [] - pl = pipeline.Pipeline( - (_produce(1000), (_work(), _work()), _consume(l)) + self.pl = pipeline.Pipeline( + (_produce(1000), (_work(), _work()), _consume(self.result)) ) - pl.run_parallel(1) - assert set(l) == {i * 2 for i in range(1000)} + self.pl.run_parallel(1) + assert set(self.result) == {i * 2 for i in range(1000)} class BubbleTest(unittest.TestCase): def setUp(self): - self.l = [] - self.pl = pipeline.Pipeline((_produce(), _bub_work(), _consume(self.l))) + self.result = [] + self.pl = pipeline.Pipeline( + (_produce(), _bub_work(), _consume(self.result)) + ) def test_run_sequential(self): self.pl.run_sequential() - assert self.l == [0, 2, 4, 8] + assert self.result == [0, 2, 4, 8] def test_run_parallel(self): self.pl.run_parallel() - assert self.l == [0, 2, 4, 8] + assert self.result == [0, 2, 4, 8] def test_pull(self): pl = pipeline.Pipeline((_produce(), _bub_work())) @@ -195,18 +205,18 @@ def test_pull(self): class MultiMessageTest(unittest.TestCase): def setUp(self): - self.l = [] + self.result = [] self.pl = pipeline.Pipeline( - (_produce(), _multi_work(), _consume(self.l)) + (_produce(), _multi_work(), _consume(self.result)) ) def test_run_sequential(self): self.pl.run_sequential() - assert self.l == [0, 0, 1, -1, 2, -2, 3, -3, 4, -4] + assert self.result == [0, 0, 1, -1, 2, -2, 3, -3, 4, -4] def test_run_parallel(self): self.pl.run_parallel() - assert self.l == [0, 0, 1, -1, 2, -2, 3, -3, 4, -4] + assert self.result == [0, 0, 1, -1, 2, -2, 3, -3, 4, -4] def test_pull(self): pl = pipeline.Pipeline((_produce(), _multi_work())) diff --git a/test/test_plugins.py b/test/test_plugins.py index f3b9290b6b..efa26d0841 100644 --- a/test/test_plugins.py +++ b/test/test_plugins.py @@ -32,9 +32,8 @@ from beets.library import Item from beets.plugins import MetadataSourcePlugin from beets.test import helper -from beets.test.helper import AutotagStub, ImportHelper +from beets.test.helper import AutotagStub, ImportHelper, TerminalImportMixin from beets.test.helper import PluginTestCase as BasePluginTestCase -from beets.test.helper import TerminalImportMixin from beets.util import displayable_path, syspath from beets.util.id_extractors import ( beatport_id_regex, @@ -142,7 +141,7 @@ class AdventListenerPlugin(plugins.BeetsPlugin): self.advent_listener_plugin = AdventListenerPlugin self.register_plugin(EventListenerPlugin) self.register_plugin(AdventListenerPlugin) - with pytest.raises(plugins.PluginConflictException): + with pytest.raises(plugins.PluginConflictError): plugins.types(Item) def test_match(self): diff --git a/test/test_ui.py b/test/test_ui.py index c087aca7d6..0415700679 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -1235,21 +1235,25 @@ def test_album_data_change(self): def test_item_data_change(self): self.items[0].title = "different" msg = self._show_change() - assert "different" in msg and "the title" in msg + assert "different" in msg + assert "the title" in msg def test_item_data_change_with_unicode(self): self.items[0].title = "caf\xe9" msg = self._show_change() - assert "caf\xe9" in msg and "the title" in msg + assert "caf\xe9" in msg + assert "the title" in msg def test_album_data_change_with_unicode(self): msg = self._show_change(cur_artist="caf\xe9", cur_album="another album") - assert "caf\xe9" in msg and "the artist" in msg + assert "caf\xe9" in msg + assert "the artist" in msg def test_item_data_change_title_missing(self): self.items[0].title = "" msg = re.sub(r" +", " ", self._show_change()) - assert "file.mp3" in msg and "the title" in msg + assert "file.mp3" in msg + assert "the title" in msg def test_item_data_change_title_missing_with_unicode_filename(self): self.items[0].title = "" @@ -1454,69 +1458,69 @@ def setUp(self): self.lib.add_album([self.item]) def test_base(self): - l = self.run_with_output("ls") - assert l == "the artist - the album - the title\n" + output = self.run_with_output("ls") + assert output == "the artist - the album - the title\n" - l = self.run_with_output("ls", "-a") - assert l == "the album artist - the album\n" + output = self.run_with_output("ls", "-a") + assert output == "the album artist - the album\n" def test_path_option(self): - l = self.run_with_output("ls", "-p") - assert l == "xxx/yyy\n" + output = self.run_with_output("ls", "-p") + assert output == "xxx/yyy\n" - l = self.run_with_output("ls", "-a", "-p") - assert l == "xxx\n" + output = self.run_with_output("ls", "-a", "-p") + assert output == "xxx\n" def test_format_option(self): - l = self.run_with_output("ls", "-f", "$artist") - assert l == "the artist\n" + output = self.run_with_output("ls", "-f", "$artist") + assert output == "the artist\n" - l = self.run_with_output("ls", "-a", "-f", "$albumartist") - assert l == "the album artist\n" + output = self.run_with_output("ls", "-a", "-f", "$albumartist") + assert output == "the album artist\n" def test_format_option_unicode(self): - l = self.run_with_output( + output = self.run_with_output( b"ls", b"-f", "caf\xe9".encode(util.arg_encoding()) ) - assert l == "caf\xe9\n" + assert output == "caf\xe9\n" def test_root_format_option(self): - l = self.run_with_output( + output = self.run_with_output( "--format-item", "$artist", "--format-album", "foo", "ls" ) - assert l == "the artist\n" + assert output == "the artist\n" - l = self.run_with_output( + output = self.run_with_output( "--format-item", "foo", "--format-album", "$albumartist", "ls", "-a" ) - assert l == "the album artist\n" + assert output == "the album artist\n" def test_help(self): - l = self.run_with_output("help") - assert "Usage:" in l + output = self.run_with_output("help") + assert "Usage:" in output - l = self.run_with_output("help", "list") - assert "Usage:" in l + output = self.run_with_output("help", "list") + assert "Usage:" in output with pytest.raises(ui.UserError): self.run_command("help", "this.is.not.a.real.command") def test_stats(self): - l = self.run_with_output("stats") - assert "Approximate total size:" in l + output = self.run_with_output("stats") + assert "Approximate total size:" in output # # Need to have more realistic library setup for this to work - # l = self.run_with_output('stats', '-e') - # assert 'Total size:' in l + # output = self.run_with_output('stats', '-e') + # assert 'Total size:' in output def test_version(self): - l = self.run_with_output("version") - assert "Python version" in l - assert "no plugins loaded" in l + output = self.run_with_output("version") + assert "Python version" in output + assert "no plugins loaded" in output # # Need to have plugin loaded - # l = self.run_with_output('version') - # assert 'plugins: ' in l + # output = self.run_with_output('version') + # assert 'plugins: ' in output class CommonOptionsParserTest(BeetsTestCase): diff --git a/test/test_ui_commands.py b/test/test_ui_commands.py index 8daf8e31a7..897cba8a1e 100644 --- a/test/test_ui_commands.py +++ b/test/test_ui_commands.py @@ -85,10 +85,10 @@ def tearDown(self): super().tearDown() self.io.restore() - def remove_keys(self, l, text): + def remove_keys(self, keys, text): for i in text: try: - l.remove(i) + keys.remove(i) except ValueError: pass diff --git a/test/test_ui_importer.py b/test/test_ui_importer.py index 5869badd30..5a27a5b936 100644 --- a/test/test_ui_importer.py +++ b/test/test_ui_importer.py @@ -18,9 +18,8 @@ ``TerminalImportSession``. So we test this class, too. """ -from test import test_importer - from beets.test.helper import TerminalImportMixin +from test import test_importer class NonAutotaggedImportTest( From 5f78d1b82b2292d5ce0c99623ba0ec444b80d24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Thu, 19 Sep 2024 00:49:11 +0100 Subject: [PATCH 5/9] Remove some lint exclusions and fix the issues * Replace `noqa` comments in `assert...` method definitions with a configuration option to ignore these names. * Use the `__all__` variable to specify importable items from the module, replacing `*` imports and `noqa` comments for unused imports. * Address issues with poorly named variables and methods by renaming them appropriately. --- beets/autotag/hooks.py | 2 +- beets/dbcore/__init__.py | 16 +++++++++++- beets/logging.py | 43 ++++++++++++++++++++++++-------- beets/test/_common.py | 8 +++--- beets/util/__init__.py | 2 +- beetsplug/bpd/__init__.py | 4 +-- pyproject.toml | 1 + test/plugins/test_art.py | 22 ++++++++-------- test/plugins/test_convert.py | 4 +-- test/plugins/test_edit.py | 4 +-- test/plugins/test_importadded.py | 4 +-- test/plugins/test_permissions.py | 2 +- test/plugins/test_player.py | 26 +++++++++---------- test/test_datequery.py | 6 ++--- test/test_query.py | 6 ++--- 15 files changed, 92 insertions(+), 58 deletions(-) diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py index 363bcaab4f..9e44901d87 100644 --- a/beets/autotag/hooks.py +++ b/beets/autotag/hooks.py @@ -371,7 +371,7 @@ def __init__(self): self.tracks: Dict[TrackInfo, Distance] = {} @cached_classproperty - def _weights(cls) -> Dict[str, float]: # noqa: N805 + def _weights(cls) -> Dict[str, float]: """A dictionary from keys to floating-point weights.""" weights_view = config["match"]["distance_weights"] weights = {} diff --git a/beets/dbcore/__init__.py b/beets/dbcore/__init__.py index 06d0b3dc95..fa20eb00db 100644 --- a/beets/dbcore/__init__.py +++ b/beets/dbcore/__init__.py @@ -32,4 +32,18 @@ ) from .types import Type -# flake8: noqa +__all__ = [ + "AndQuery", + "Database", + "FieldQuery", + "InvalidQueryError", + "MatchQuery", + "Model", + "OrQuery", + "Query", + "Results", + "Type", + "parse_sorted_query", + "query_from_strings", + "sort_from_strings", +] diff --git a/beets/logging.py b/beets/logging.py index 34fc7bafae..fd8b1962ff 100644 --- a/beets/logging.py +++ b/beets/logging.py @@ -20,9 +20,34 @@ calls (`debug`, `info`, etc). """ -import logging import threading from copy import copy +from logging import ( + DEBUG, + INFO, + NOTSET, + WARNING, + FileHandler, + Filter, + Handler, + Logger, + NullHandler, + StreamHandler, +) + +__all__ = [ + "DEBUG", + "INFO", + "NOTSET", + "WARNING", + "FileHandler", + "Filter", + "Handler", + "Logger", + "NullHandler", + "StreamHandler", + "getLogger", +] def logsafe(val): @@ -45,7 +70,7 @@ def logsafe(val): return val -class StrFormatLogger(logging.Logger): +class StrFormatLogger(Logger): """A version of `Logger` that uses `str.format`-style formatting instead of %-style formatting and supports keyword arguments. @@ -95,12 +120,12 @@ def _log( ) -class ThreadLocalLevelLogger(logging.Logger): +class ThreadLocalLevelLogger(Logger): """A version of `Logger` whose level is thread-local instead of shared.""" - def __init__(self, name, level=logging.NOTSET): + def __init__(self, name, level=NOTSET): self._thread_level = threading.local() - self.default_level = logging.NOTSET + self.default_level = NOTSET super().__init__(name, level) @property @@ -127,17 +152,13 @@ class BeetsLogger(ThreadLocalLevelLogger, StrFormatLogger): pass -my_manager = copy(logging.Logger.manager) +my_manager = copy(Logger.manager) my_manager.loggerClass = BeetsLogger -# Act like the stdlib logging module by re-exporting its namespace. -from logging import * # noqa - - # Override the `getLogger` to use our machinery. def getLogger(name=None): # noqa if name: return my_manager.getLogger(name) else: - return logging.Logger.root + return Logger.root diff --git a/beets/test/_common.py b/beets/test/_common.py index b1cc269361..b6e7c5dccf 100644 --- a/beets/test/_common.py +++ b/beets/test/_common.py @@ -143,19 +143,19 @@ def import_session(lib=None, loghandler=None, paths=[], query=[], cli=False): class Assertions: """A mixin with additional unit test assertions.""" - def assertExists(self, path): # noqa + def assertExists(self, path): assert os.path.exists(syspath(path)), f"file does not exist: {path!r}" - def assertNotExists(self, path): # noqa + def assertNotExists(self, path): assert not os.path.exists(syspath(path)), f"file exists: {path!r}" - def assertIsFile(self, path): # noqa + def assertIsFile(self, path): self.assertExists(path) assert os.path.isfile( syspath(path) ), "path exists, but is not a regular file: {!r}".format(path) - def assertIsDir(self, path): # noqa + def assertIsDir(self, path): self.assertExists(path) assert os.path.isdir( syspath(path) diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 759026f3a5..e45f92fb72 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -1069,7 +1069,7 @@ def par_map(transform: Callable[[T], Any], items: Sequence[T]) -> None: pool.join() -class cached_classproperty: # noqa: N801 +class cached_classproperty: """A decorator implementing a read-only property that is *lazy* in the sense that the getter is only invoked once. Subsequent accesses through *any* instance use the cached result. diff --git a/beetsplug/bpd/__init__.py b/beetsplug/bpd/__init__.py index cc56a27c59..bd5ef1633c 100644 --- a/beetsplug/bpd/__init__.py +++ b/beetsplug/bpd/__init__.py @@ -738,13 +738,13 @@ def cmd_seekid(self, conn, track_id, pos): # Additions to the MPD protocol. - def cmd_crash_TypeError(self, conn): # noqa: N802 + def cmd_crash(self, conn): """Deliberately trigger a TypeError for testing purposes. We want to test that the server properly responds with ERROR_SYSTEM without crashing, and that this is not treated as ERROR_ARG (since it is caused by a programming error, not a protocol error). """ - "a" + 2 + raise TypeError class Connection: diff --git a/pyproject.toml b/pyproject.toml index 70902f0092..ff28faebce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -260,3 +260,4 @@ ignore-variadic-names = true [tool.ruff.lint.pep8-naming] classmethod-decorators = ["cached_classproperty"] +extend-ignore-names = ["assert*", "cached_classproperty"] diff --git a/test/plugins/test_art.py b/test/plugins/test_art.py index bd602c434c..acb7123548 100644 --- a/test/plugins/test_art.py +++ b/test/plugins/test_art.py @@ -865,7 +865,7 @@ def tearDown(self): fetchart.FileSystem.get = self.old_fs_source_get super().tearDown() - def _assertImageIsValidArt(self, image_file, should_exist): # noqa + def assertImageIsValidArt(self, image_file, should_exist): self.assertExists(image_file) self.image_file = image_file @@ -896,42 +896,42 @@ def _require_backend(self): def test_respect_minwidth(self): self._require_backend() self.plugin.minwidth = 300 - self._assertImageIsValidArt(self.IMG_225x225, False) - self._assertImageIsValidArt(self.IMG_348x348, True) + self.assertImageIsValidArt(self.IMG_225x225, False) + self.assertImageIsValidArt(self.IMG_348x348, True) def test_respect_enforce_ratio_yes(self): self._require_backend() self.plugin.enforce_ratio = True - self._assertImageIsValidArt(self.IMG_500x490, False) - self._assertImageIsValidArt(self.IMG_225x225, True) + self.assertImageIsValidArt(self.IMG_500x490, False) + self.assertImageIsValidArt(self.IMG_225x225, True) def test_respect_enforce_ratio_no(self): self.plugin.enforce_ratio = False - self._assertImageIsValidArt(self.IMG_500x490, True) + self.assertImageIsValidArt(self.IMG_500x490, True) def test_respect_enforce_ratio_px_above(self): self._require_backend() self.plugin.enforce_ratio = True self.plugin.margin_px = 5 - self._assertImageIsValidArt(self.IMG_500x490, False) + self.assertImageIsValidArt(self.IMG_500x490, False) def test_respect_enforce_ratio_px_below(self): self._require_backend() self.plugin.enforce_ratio = True self.plugin.margin_px = 15 - self._assertImageIsValidArt(self.IMG_500x490, True) + self.assertImageIsValidArt(self.IMG_500x490, True) def test_respect_enforce_ratio_percent_above(self): self._require_backend() self.plugin.enforce_ratio = True self.plugin.margin_percent = (500 - 490) / 500 * 0.5 - self._assertImageIsValidArt(self.IMG_500x490, False) + self.assertImageIsValidArt(self.IMG_500x490, False) def test_respect_enforce_ratio_percent_below(self): self._require_backend() self.plugin.enforce_ratio = True self.plugin.margin_percent = (500 - 490) / 500 * 1.5 - self._assertImageIsValidArt(self.IMG_500x490, True) + self.assertImageIsValidArt(self.IMG_500x490, True) def test_resize_if_necessary(self): self._require_backend() @@ -948,7 +948,7 @@ def test_fileresize_if_necessary(self): self._require_backend() self.plugin.max_filesize = self.IMG_225x225_SIZE self._assert_image_operated(self.IMG_225x225, self.RESIZE_OP, False) - self._assertImageIsValidArt(self.IMG_225x225, True) + self.assertImageIsValidArt(self.IMG_225x225, True) def test_fileresize_no_scale(self): self._require_backend() diff --git a/test/plugins/test_convert.py b/test/plugins/test_convert.py index 67f6fcd04e..725318eacf 100644 --- a/test/plugins/test_convert.py +++ b/test/plugins/test_convert.py @@ -55,7 +55,7 @@ def tagged_copy_cmd(self, tag): shell_quote(sys.executable), shell_quote(stub), tag ) - def assertFileTag(self, path, tag): # noqa + def assertFileTag(self, path, tag): """Assert that the path is a file and the files content ends with `tag`. """ @@ -68,7 +68,7 @@ def assertFileTag(self, path, tag): # noqa f.read() == tag ), f"{displayable_path(path)} is not tagged with {display_tag}" - def assertNoFileTag(self, path, tag): # noqa + def assertNoFileTag(self, path, tag): """Assert that the path is a file and the files content does not end with `tag`. """ diff --git a/test/plugins/test_edit.py b/test/plugins/test_edit.py index beeb649a35..2d557d6239 100644 --- a/test/plugins/test_edit.py +++ b/test/plugins/test_edit.py @@ -77,7 +77,7 @@ class EditMixin(PluginMixin): plugin = "edit" - def assertItemFieldsModified( # noqa + def assertItemFieldsModified( self, library_items, items, fields=[], allowed=["path"] ): """Assert that items in the library (`lib_items`) have different values @@ -134,7 +134,7 @@ def setUp(self): {f: item[f] for f in item._fields} for item in self.album.items() ] - def assertCounts( # noqa + def assertCounts( self, mock_write, album_count=ALBUM_COUNT, diff --git a/test/plugins/test_importadded.py b/test/plugins/test_importadded.py index 6af4b0a63c..d48ec6c469 100644 --- a/test/plugins/test_importadded.py +++ b/test/plugins/test_importadded.py @@ -74,11 +74,11 @@ def find_media_file(self, item): "No MediaFile found for Item " + displayable_path(item.path) ) - def assertEqualTimes(self, first, second, msg=None): # noqa + def assertEqualTimes(self, first, second, msg=None): """For comparing file modification times at a sufficient precision""" assert first == pytest.approx(second, rel=1e-4), msg - def assertAlbumImport(self): # noqa + def assertAlbumImport(self): self.importer.run() album = self.lib.albums().get() assert album.added == self.min_mtime diff --git a/test/plugins/test_permissions.py b/test/plugins/test_permissions.py index 7979cfa129..274cd92ace 100644 --- a/test/plugins/test_permissions.py +++ b/test/plugins/test_permissions.py @@ -62,7 +62,7 @@ def get_stat(v): for path in dirs_in_library(self.lib.directory, item.path): self.assertPerms(path, "dir", expect_success) - def assertPerms(self, path, typ, expect_success): # noqa + def assertPerms(self, path, typ, expect_success): for x in [ (True, self.exp_perms[expect_success][typ], "!="), (False, self.exp_perms[not expect_success][typ], "=="), diff --git a/test/plugins/test_player.py b/test/plugins/test_player.py index bf466e1b5b..b17a78c177 100644 --- a/test/plugins/test_player.py +++ b/test/plugins/test_player.py @@ -41,7 +41,7 @@ ) -def _gstplayer_play(*_): # noqa: 42 +def _gstplayer_play(*_): bpd.gstplayer._GstPlayer.playing = True return mock.DEFAULT @@ -242,7 +242,7 @@ def readline(self, terminator=b"\n", bufsize=1024): return line -def implements(commands, expectedFailure=False): # noqa: N803 +def implements(commands, fail=False): def _test(self): with self.run_bpd() as client: response = client.send_command("commands") @@ -250,7 +250,7 @@ def _test(self): implemented = response.data["command"] assert commands.intersection(implemented) == commands - return unittest.expectedFailure(_test) if expectedFailure else _test + return unittest.expectedFailure(_test) if fail else _test bluelet_listener = bluelet.Listener @@ -437,7 +437,7 @@ def test_missing_argument(self): def test_system_error(self): with self.run_bpd() as client: - response = client.send_command("crash_TypeError") + response = client.send_command("crash") self._assert_failed(response, bpd.ERROR_SYSTEM) def test_empty_request(self): @@ -763,7 +763,7 @@ class BPDControlTest(BPDTestHelper): "seekid", "seekcur", }, - expectedFailure=True, + fail=True, ) def test_cmd_play(self): @@ -873,7 +873,7 @@ class BPDQueueTest(BPDTestHelper): "addtagid", "cleartagid", }, - expectedFailure=True, + fail=True, ) METADATA = {"Pos", "Time", "Id", "file", "duration"} @@ -990,7 +990,7 @@ class BPDDatabaseTest(BPDTestHelper): "update", "rescan", }, - expectedFailure=True, + fail=True, ) def test_cmd_search(self): @@ -1050,7 +1050,7 @@ class BPDMountsTest(BPDTestHelper): "listmounts", "listneighbors", }, - expectedFailure=True, + fail=True, ) @@ -1059,7 +1059,7 @@ class BPDStickerTest(BPDTestHelper): { "sticker", }, - expectedFailure=True, + fail=True, ) @@ -1142,7 +1142,7 @@ class BPDPartitionTest(BPDTestHelper): "listpartitions", "newpartition", }, - expectedFailure=True, + fail=True, ) @@ -1154,7 +1154,7 @@ class BPDDeviceTest(BPDTestHelper): "toggleoutput", "outputs", }, - expectedFailure=True, + fail=True, ) @@ -1166,7 +1166,7 @@ class BPDReflectionTest(BPDTestHelper): "notcommands", "urlhandlers", }, - expectedFailure=True, + fail=True, ) def test_cmd_decoders(self): @@ -1187,5 +1187,5 @@ class BPDPeersTest(BPDTestHelper): "readmessages", "sendmessage", }, - expectedFailure=True, + fail=True, ) diff --git a/test/test_datequery.py b/test/test_datequery.py index e5c5f2bfda..9c968e9983 100644 --- a/test/test_datequery.py +++ b/test/test_datequery.py @@ -133,16 +133,14 @@ def test_unbounded_endpoints(self): self.assertContains("..", date=datetime.min) self.assertContains("..", "1000-01-01T00:00:00") - def assertContains( # noqa - self, interval_pattern, date_pattern=None, date=None - ): + def assertContains(self, interval_pattern, date_pattern=None, date=None): if date is None: date = _date(date_pattern) (start, end) = _parse_periods(interval_pattern) interval = DateInterval.from_periods(start, end) assert interval.contains(date) - def assertExcludes(self, interval_pattern, date_pattern): # noqa + def assertExcludes(self, interval_pattern, date_pattern): date = _date(date_pattern) (start, end) = _parse_periods(interval_pattern) interval = DateInterval.from_periods(start, end) diff --git a/test/test_query.py b/test/test_query.py index 61237e10e8..d17bce0e67 100644 --- a/test/test_query.py +++ b/test/test_query.py @@ -47,11 +47,11 @@ def assert_items_matched(self, results, titles): def assert_albums_matched(self, results, albums): assert {a.album for a in results} == set(albums) - def assertInResult(self, item, results): # noqa + def assertInResult(self, item, results): result_ids = [i.id for i in results] assert item.id in result_ids - def assertNotInResult(self, item, results): # noqa + def assertNotInResult(self, item, results): result_ids = [i.id for i in results] assert item.id not in result_ids @@ -927,7 +927,7 @@ class NotQueryTest(DummyDataTestCase): - `test_get_yyy`: tests on query strings (similar to `GetTest`) """ - def assertNegationProperties(self, q): # noqa + def assertNegationProperties(self, q): """Given a Query `q`, assert that: - q OR not(q) == all items - q AND not(q) == 0 From fe4d4921c100d308cc75364f147b7c34786f7b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Thu, 19 Sep 2024 00:52:01 +0100 Subject: [PATCH 6/9] Replace OrderedEnum with a builtin IntEnum The variable in `test_ordered_enum` was flagged for naming issues, and I noticed that `OrderedEnum` is essentially `enum.IntEnum`. I guess `OrderedEnum` exists because it was created before `enum.IntEnum` was made available in Python 3.4. We do not need it anymore though, so it's now gone. --- beets/autotag/match.py | 4 ++-- beets/util/enumeration.py | 42 --------------------------------------- test/test_autotag.py | 17 ---------------- 3 files changed, 2 insertions(+), 61 deletions(-) delete mode 100644 beets/util/enumeration.py diff --git a/beets/autotag/match.py b/beets/autotag/match.py index fe391b4cf9..331a57596e 100644 --- a/beets/autotag/match.py +++ b/beets/autotag/match.py @@ -20,6 +20,7 @@ import datetime import re +from enum import IntEnum from typing import ( Any, Dict, @@ -47,7 +48,6 @@ ) from beets.library import Item from beets.util import plurality -from beets.util.enumeration import OrderedEnum # Artist signals that indicate "various artists". These are used at the # album level to determine whether a given release is likely a VA @@ -62,7 +62,7 @@ # Recommendation enumeration. -class Recommendation(OrderedEnum): +class Recommendation(IntEnum): """Indicates a qualitative suggestion to the user about what should be done with a given match. """ diff --git a/beets/util/enumeration.py b/beets/util/enumeration.py deleted file mode 100644 index 33a6be58fb..0000000000 --- a/beets/util/enumeration.py +++ /dev/null @@ -1,42 +0,0 @@ -# This file is part of beets. -# Copyright 2016, Adrian Sampson. -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. - - -from enum import Enum - - -class OrderedEnum(Enum): - """ - An Enum subclass that allows comparison of members. - """ - - def __ge__(self, other): - if self.__class__ is other.__class__: - return self.value >= other.value - return NotImplemented - - def __gt__(self, other): - if self.__class__ is other.__class__: - return self.value > other.value - return NotImplemented - - def __le__(self, other): - if self.__class__ is other.__class__: - return self.value <= other.value - return NotImplemented - - def __lt__(self, other): - if self.__class__ is other.__class__: - return self.value < other.value - return NotImplemented diff --git a/test/test_autotag.py b/test/test_autotag.py index f3e4f0c471..6e362f2f4b 100644 --- a/test/test_autotag.py +++ b/test/test_autotag.py @@ -1040,20 +1040,3 @@ def test_ampersand_expansion(self): def test_accented_characters(self): dist = string_dist("\xe9\xe1\xf1", "ean") assert dist == 0.0 - - -class EnumTest(BeetsTestCase): - """ - Test Enum Subclasses defined in beets.util.enumeration - """ - - def test_ordered_enum(self): - OrderedEnumClass = match.OrderedEnum( # noqa - "OrderedEnumTest", ["a", "b", "c"] - ) - assert OrderedEnumClass.a < OrderedEnumClass.b - assert OrderedEnumClass.a < OrderedEnumClass.c - assert OrderedEnumClass.b < OrderedEnumClass.c - assert OrderedEnumClass.b > OrderedEnumClass.a - assert OrderedEnumClass.c > OrderedEnumClass.a - assert OrderedEnumClass.c > OrderedEnumClass.b From 962c128510229fb7cf6961bcfd6fdc2b12cc1457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Tue, 17 Sep 2024 07:45:06 +0100 Subject: [PATCH 7/9] Add .git-blame-ignore-revs file to ignore specific commits in git blame This commit introduces a `.git-blame-ignore-revs` file to the repository. The purpose of this file is to list specific commit hashes that should be ignored when using the `git blame` command. This is useful for ignoring commits that involve large-scale formatting changes, refactoring, or other non-functional changes that would otherwise clutter the blame history. I added a couple of previous commit hashes which reformatted the code. Configure this repository to use this file: git config --local blame.ignoreRevsFile .git-blame-ignore-revs --- .git-blame-ignore-revs | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..12d5ffd94f --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,45 @@ +# 2014 +# flake8-cleanliness in missing +e21c04e9125a28ae0452374acf03d93315eb4381 + +# 2016 +# Removed unicode_literals from library, logging and mediafile +43572f50b0eb3522239d94149d91223e67d9a009 +# Removed unicode_literals from plugins +53d2c8d9db87be4d4750ad879bf46176537be73f +# reformat flake8 errors +1db46dfeb6607c164afb247d8da82443677795c1 + +# 2021 +# pyupgrade root +e26276658052947e9464d9726b703335304c7c13 +# pyupgrade beets dir +6d1316f463cb7c9390f85bf35b220e250a35004a +# pyupgrade autotag dir +f8b8938fd8bbe91898d0982552bc75d35703d3ef +# pyupgrade dbcore dir +d288f872903c79a7ee7c5a7c9cc690809441196e +# pyupgrade ui directory +432fa557258d9ff01e23ed750f9a86a96239599e +# pyupgrade util dir +af102c3e2f1c7a49e99839e2825906fe01780eec +# fix unused import and flake8 +910354a6c617ed5aa643cff666205b43e1557373 +# pyupgrade beetsplug and tests +1ec87a3bdd737abe46c6e614051bf9e314db4619 + +# 2022 +# Reformat flake8 config comments +abc3dfbf429b179fac25bd1dff72d577cd4d04c7 + +# 2023 +# Apply formatting tools to all files +a6e5201ff3fad4c69bf24d17bace2ef744b9f51b + +# 2024 +# Reformat the codebase +85a17ee5039628a6f3cdcb7a03d7d1bd530fbe89 +# Fix lint issues +f36bc497c8c8f89004f3f6879908d3f0b25123e1 +# Remove some lint exclusions and fix the issues +5f78d1b82b2292d5ce0c99623ba0ec444b80d24c From edeb4308426b55f322291a348c3ccddf0995d24d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sun, 8 Sep 2024 03:03:09 +0100 Subject: [PATCH 8/9] Fix coverage task docs --- CONTRIBUTING.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 911090f17a..0185049f70 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -344,10 +344,10 @@ environment variable ``SKIP_SLOW_TESTS``, for example:: Coverage ^^^^^^^^ -Coverage is measured automatically when running the tests. If you find it takes -a while to calculate, disable it:: +The ``test`` command does not include coverage as it slows down testing. In +order to measure it, use the ``test-with-coverage`` task - $ poe test --no-cov + $ poe test-with-coverage [pytest options] You are welcome to explore coverage by opening the HTML report in ``.reports/html/index.html``. From 11fa6c7b3f9cb033ff3a2b25356fdf38bc5794a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Fri, 20 Sep 2024 15:45:56 +0100 Subject: [PATCH 9/9] Introduce integration_test marker and update testing docs --- CONTRIBUTING.rst | 32 +++++++++++++---------------- setup.cfg | 2 ++ test/conftest.py | 12 +++++++++++ test/plugins/test_lyrics.py | 36 ++++++++------------------------- test/plugins/test_parentwork.py | 21 +++---------------- 5 files changed, 39 insertions(+), 64 deletions(-) create mode 100644 test/conftest.py diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 0185049f70..b9593741c5 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -378,28 +378,24 @@ Writing Tests Writing tests is done by adding or modifying files in folder `test`_. Take a look at `https://github.com/beetbox/beets/blob/master/test/test_template.py#L224`_ -to get a basic view on how tests are written. We currently allow writing -tests with either `unittest`_ or `pytest`_. +to get a basic view on how tests are written. Since we are currently migrating +the tests from `unittest`_ to `pytest`_, new tests should be written using +`pytest`_. Contributions migrating existing tests are welcome! -Any tests that involve sending out network traffic e.g. an external API -call, should be skipped normally and run under our weekly `integration -test`_ suite. These tests can be useful in detecting external changes -that would affect ``beets``. In order to do this, simply add the -following snippet before the applicable test case: +External API requests under test should be mocked with `requests_mock`_, +However, we still want to know whether external APIs are up and that they +return expected responses, therefore we test them weekly with our `integration +test`_ suite. -.. code-block:: python +In order to add such a test, mark your test with the ``integration_test`` marker - @unittest.skipUnless( - os.environ.get('INTEGRATION_TEST', '0') == '1', - 'integration testing not enabled') +.. code-block:: python -If you do this, it is also advised to create a similar test that 'mocks' -the network call and can be run under normal circumstances by our CI and -others. See `unittest.mock`_ for more info. + @pytest.mark.integration_test + def test_external_api_call(): + ... -- **AVOID** using the ``start()`` and ``stop()`` methods of - ``mock.patch``, as they require manual cleanup. Use the annotation or - context manager forms instead. +This way, the test will be run only in the integration test suite. .. _Codecov: https://codecov.io/github/beetbox/beets .. _pytest-random: https://github.com/klrmn/pytest-random @@ -409,6 +405,6 @@ others. See `unittest.mock`_ for more info. .. _`https://github.com/beetbox/beets/blob/master/test/test_template.py#L224`: https://github.com/beetbox/beets/blob/master/test/test_template.py#L224 .. _unittest: https://docs.python.org/3/library/unittest.html .. _integration test: https://github.com/beetbox/beets/actions?query=workflow%3A%22integration+tests%22 -.. _unittest.mock: https://docs.python.org/3/library/unittest.mock.html +.. _requests-mock: https://requests-mock.readthedocs.io/en/latest/response.html .. _documentation: https://beets.readthedocs.io/en/stable/ .. _vim: https://www.vim.org/ diff --git a/setup.cfg b/setup.cfg index a45c49a81a..f694906307 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,6 +7,8 @@ addopts = # show all skipped/failed/xfailed tests in the summary except passed -ra --strict-config +markers = + integration_test: mark a test as an integration test [coverage:run] data_file = .reports/coverage/data diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000000..8b29946ae2 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,12 @@ +import os + +import pytest + + +def pytest_runtest_setup(item: pytest.Item): + """Skip integration tests if INTEGRATION_TEST environment variable is not set.""" + if os.environ.get("INTEGRATION_TEST"): + return + + if next(item.iter_markers(name="integration_test"), None): + pytest.skip(f"INTEGRATION_TEST=1 required: {item.nodeid}") diff --git a/test/plugins/test_lyrics.py b/test/plugins/test_lyrics.py index 76c0112251..937e0a3cb1 100644 --- a/test/plugins/test_lyrics.py +++ b/test/plugins/test_lyrics.py @@ -21,6 +21,7 @@ from unittest.mock import MagicMock, patch import confuse +import pytest import requests from beets import logging @@ -335,10 +336,7 @@ def setUp(self): LyricsGoogleBaseTest.setUp(self) self.plugin = lyrics.LyricsPlugin() - @unittest.skipUnless( - os.environ.get("INTEGRATION_TEST", "0") == "1", - "integration testing not enabled", - ) + @pytest.mark.integration_test def test_backend_sources_ok(self): """Test default backends with songs known to exist in respective databases. @@ -351,10 +349,7 @@ def test_backend_sources_ok(self): res = backend.fetch(s["artist"], s["title"]) self.assertLyricsContentOk(s["title"], res) - @unittest.skipUnless( - os.environ.get("INTEGRATION_TEST", "0") == "1", - "integration testing not enabled", - ) + @pytest.mark.integration_test def test_google_sources_ok(self): """Test if lyrics present on websites registered in beets google custom search engine are correctly scraped. @@ -649,19 +644,13 @@ def setUp(self): self.plugin = lyrics.LyricsPlugin() tekstowo.config = self.plugin.config - @unittest.skipUnless( - os.environ.get("INTEGRATION_TEST", "0") == "1", - "integration testing not enabled", - ) + @pytest.mark.integration_test def test_normal(self): """Ensure we can fetch a song's lyrics in the ordinary case""" lyrics = tekstowo.fetch("Boy in Space", "u n eye") self.assertLyricsContentOk("u n eye", lyrics) - @unittest.skipUnless( - os.environ.get("INTEGRATION_TEST", "0") == "1", - "integration testing not enabled", - ) + @pytest.mark.integration_test def test_no_matching_results(self): """Ensure we fetch nothing if there are search results returned but no matches""" @@ -736,28 +725,19 @@ def setUp(self): self.plugin = lyrics.LyricsPlugin() lrclib.config = self.plugin.config - @unittest.skipUnless( - os.environ.get("INTEGRATION_TEST", "0") == "1", - "integration testing not enabled", - ) + @pytest.mark.integration_test def test_track_with_lyrics(self): lyrics = lrclib.fetch("Boy in Space", "u n eye", "Live EP", 160) self.assertLyricsContentOk("u n eye", lyrics) - @unittest.skipUnless( - os.environ.get("INTEGRATION_TEST", "0") == "1", - "integration testing not enabled", - ) + @pytest.mark.integration_test def test_instrumental_track(self): lyrics = lrclib.fetch( "Kelly Bailey", "Black Mesa Inbound", "Half Life 2 Soundtrack", 134 ) assert lyrics is None - @unittest.skipUnless( - os.environ.get("INTEGRATION_TEST", "0") == "1", - "integration testing not enabled", - ) + @pytest.mark.integration_test def test_nonexistent_track(self): lyrics = lrclib.fetch("blah", "blah", "blah", 999) assert lyrics is None diff --git a/test/plugins/test_parentwork.py b/test/plugins/test_parentwork.py index 9ce804091d..99267f6ffa 100644 --- a/test/plugins/test_parentwork.py +++ b/test/plugins/test_parentwork.py @@ -14,10 +14,10 @@ """Tests for the 'parentwork' plugin.""" -import os -import unittest from unittest.mock import patch +import pytest + from beets.library import Item from beets.test.helper import PluginTestCase from beetsplug import parentwork @@ -84,14 +84,11 @@ def mock_workid_response(mbid, includes): return p_work +@pytest.mark.integration_test class ParentWorkIntegrationTest(PluginTestCase): plugin = "parentwork" # test how it works with real musicbrainz data - @unittest.skipUnless( - os.environ.get("INTEGRATION_TEST", "0") == "1", - "integration testing not enabled", - ) def test_normal_case_real(self): item = Item( path="/file", @@ -106,10 +103,6 @@ def test_normal_case_real(self): item.load() assert item["mb_parentworkid"] == "32c8943f-1b27-3a23-8660-4567f4847c94" - @unittest.skipUnless( - os.environ.get("INTEGRATION_TEST", "0") == "1", - "integration testing not enabled", - ) def test_force_real(self): self.config["parentwork"]["force"] = True item = Item( @@ -127,10 +120,6 @@ def test_force_real(self): item.load() assert item["mb_parentworkid"] == "32c8943f-1b27-3a23-8660-4567f4847c94" - @unittest.skipUnless( - os.environ.get("INTEGRATION_TEST", "0") == "1", - "integration testing not enabled", - ) def test_no_force_real(self): self.config["parentwork"]["force"] = False item = Item( @@ -152,10 +141,6 @@ def test_no_force_real(self): # test different cases, still with Matthew Passion Ouverture or Mozart # requiem - @unittest.skipUnless( - os.environ.get("INTEGRATION_TEST", "0") == "1", - "integration testing not enabled", - ) def test_direct_parent_work_real(self): mb_workid = "2e4a3668-458d-3b2a-8be2-0b08e0d8243a" assert (