mirror of
https://github.com/beetbox/beets.git
synced 2026-01-30 12:02:41 +01:00
Migrate from isort/Black/Flake8 to Ruff for Code Formatting and Linting (#5424)
#### Context I noticed one of recently merged PRs used `unittest` assertions that we are migrating from. Thus, I thought it would be a good idea to configure our linting to catch such issues in the future. Since I was here, I replaced our multiple linting and formatting tools with a single tool, Ruff, to simplify and speed up our development workflow. #### Summary This PR migrates the codebase from using isort, Black and Flake8 to Ruff for code formatting and linting. The changes include updates to the GitHub Actions workflow, pre-commit configuration, and documentation to reflect the new tooling. #### Changes 1. **GitHub Actions Workflow** - Updated `.github/workflows/lint.yml` to use `poetry install --only=lint` and `poe lint --output-format=github`. 2. **Pre-commit Configuration** - Replaced Black and Isort with Ruff in `.pre-commit-config.yaml`. 3. **Documentation** - Updated `CONTRIBUTING.rst` to reflect the use of Ruff for formatting and linting. - Modified instructions for running tests and handling external API requests. 4. **Poetry Configuration** - Removed Black, Isort, Flake8, and related dependencies from `poetry.lock` and `pyproject.toml`. - Added Ruff as the new linter and formatter in `pyproject.toml`. 5. **Setup Configuration** - Removed Flake8 configuration from `setup.cfg`. 6. **Git blame** - Introduced `.git-blame-ignore-revs` file to keep git blame clean from formatting changes. Configure your local `beets` repository to use this file by running: ```fish $ git config --local blame.ignoreRevsFile .git-blame-ignore-revs ``` #### Benefits - **Performance**: Ruff is known for its speed and efficiency, which should improve the developer experience. - **Consolidation**: Using a single tool for both formatting and linting simplifies the development workflow. #### How to Test 1. **Linting and Formatting** - Run `poe check-format` to check for formatting issues. - Run `poe format` to format the codebase. - Run `poe lint` to check for linting issues. 2. **Pre-commit Hooks** - Ensure pre-commit hooks are working correctly by running `pre-commit run --all-files`. 3. **CI Pipeline** - Verify that the GitHub Actions workflow completes successfully. #### Notes - Contributions migrating existing tests from `unittest` to `pytest` are welcome. - External API requests should be mocked using `requests_mock` and tested weekly in the integration test suite. #### References - [Ruff Documentation](https://docs.astral.sh/ruff/) - [Pre-commit Hooks](https://pre-commit.com/hooks.html) --- This PR aims to streamline our development process by adopting Ruff, a modern and efficient tool for Python code formatting and linting.
This commit is contained in:
commit
88d3f040e1
121 changed files with 554 additions and 794 deletions
45
.git-blame-ignore-revs
Normal file
45
.git-blame-ignore-revs
Normal file
|
|
@ -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
|
||||
7
.github/workflows/lint.yml
vendored
7
.github/workflows/lint.yml
vendored
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
--------------
|
||||
|
|
@ -345,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``.
|
||||
|
|
@ -379,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.
|
||||
|
||||
In order to add such a test, mark your test with the ``integration_test`` marker
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@unittest.skipUnless(
|
||||
os.environ.get('INTEGRATION_TEST', '0') == '1',
|
||||
'integration testing not enabled')
|
||||
@pytest.mark.integration_test
|
||||
def test_external_api_call():
|
||||
...
|
||||
|
||||
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.
|
||||
|
||||
- **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
|
||||
|
|
@ -410,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/
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
`python -m beets`.
|
||||
"""
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
from .ui import main
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
music and items' embedded album art.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
|
|
|
|||
|
|
@ -12,23 +12,38 @@
|
|||
# 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
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ class Distance:
|
|||
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 = {}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@
|
|||
releases and tracks.
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import re
|
||||
from enum import IntEnum
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
|
|
@ -48,7 +48,6 @@ from beets.autotag import (
|
|||
)
|
||||
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
|
||||
|
|
@ -63,7 +62,7 @@ log = logging.getLogger("beets")
|
|||
# Recommendation enumeration.
|
||||
|
||||
|
||||
class Recommendation(OrderedEnum):
|
||||
class Recommendation(IntEnum):
|
||||
"""Indicates a qualitative suggestion to the user about what should
|
||||
be done with a given match.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -54,7 +54,7 @@ FIELDS_TO_MB_KEYS = {
|
|||
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.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -32,4 +32,18 @@ from .queryparse import (
|
|||
)
|
||||
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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1224,9 +1224,7 @@ class Database:
|
|||
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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ REIMPORT_FRESH_FIELDS_ITEM = list(REIMPORT_FRESH_FIELDS_ALBUM)
|
|||
log = logging.getLogger("beets")
|
||||
|
||||
|
||||
class ImportAbort(Exception):
|
||||
class ImportAbortError(Exception):
|
||||
"""Raised when the user aborts the tagging operation."""
|
||||
|
||||
pass
|
||||
|
|
@ -360,7 +360,7 @@ class ImportSession:
|
|||
pl.run_parallel(QUEUE_SIZE)
|
||||
else:
|
||||
pl.run_sequential()
|
||||
except ImportAbort:
|
||||
except ImportAbortError:
|
||||
# User aborted operation. Silently stop.
|
||||
pass
|
||||
|
||||
|
|
@ -627,8 +627,7 @@ class ImportTask(BaseImportTask):
|
|||
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()
|
||||
|
||||
|
|
@ -947,7 +946,7 @@ class ImportTask(BaseImportTask):
|
|||
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()),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,10 +20,34 @@ use {}-style formatting and can interpolate keywords arguments to the logging
|
|||
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):
|
||||
|
|
@ -46,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.
|
||||
|
||||
|
|
@ -96,12 +120,12 @@ class StrFormatLogger(logging.Logger):
|
|||
)
|
||||
|
||||
|
||||
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
|
||||
|
|
@ -128,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
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
"""Support for beets plugins."""
|
||||
|
||||
|
||||
import abc
|
||||
import inspect
|
||||
import re
|
||||
|
|
@ -36,7 +35,7 @@ LASTFM_KEY = "2dc3914abf35f0d9c92d97d8f8e42b43"
|
|||
log = logging.getLogger("beets")
|
||||
|
||||
|
||||
class PluginConflictException(Exception):
|
||||
class PluginConflictError(Exception):
|
||||
"""Indicates that the services provided by one plugin conflict with
|
||||
those of another.
|
||||
|
||||
|
|
@ -343,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)
|
||||
|
|
@ -447,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."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -171,7 +171,7 @@ class Assertions:
|
|||
# Mock I/O.
|
||||
|
||||
|
||||
class InputException(Exception):
|
||||
class InputError(Exception):
|
||||
def __init__(self, output=None):
|
||||
self.output = output
|
||||
|
||||
|
|
@ -218,9 +218,9 @@ class DummyIn:
|
|||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ interface. To invoke the CLI, just call beets.ui.main(). The actual
|
|||
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 @@ class SubcommandsOptionParser(CommonOptionsParser):
|
|||
"""
|
||||
# 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
|
||||
|
|
@ -1867,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:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
interface.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
from collections import Counter
|
||||
|
|
@ -1027,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):
|
||||
|
|
@ -1057,7 +1056,7 @@ class TerminalImportSession(importer.ImportSession):
|
|||
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."
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ PathLike = Union[BytesOrStr, Path]
|
|||
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 @@ class HumanReadableException(Exception):
|
|||
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.
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 run(root_coro):
|
|||
"""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 run(root_coro):
|
|||
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 run(root_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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -26,7 +26,6 @@ This is sort of like a tiny, horrible degeneration of a real templating
|
|||
engine like Jinja2 or Mustache.
|
||||
"""
|
||||
|
||||
|
||||
import ast
|
||||
import dis
|
||||
import functools
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ To do so, pass an iterable of coroutines to the Pipeline constructor
|
|||
in place of any single coroutine.
|
||||
"""
|
||||
|
||||
|
||||
import queue
|
||||
import sys
|
||||
from threading import Lock, Thread
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
"""A namespace package for beets plugins."""
|
||||
|
||||
|
||||
# Make this a namespace package.
|
||||
from pkgutil import extend_path
|
||||
|
||||
|
|
|
|||
|
|
@ -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 @@ only files which would be processed",
|
|||
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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
"""An AURA server using Flask."""
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -30,7 +28,7 @@ from beets.ui import Subcommand
|
|||
from beets.util import displayable_path, par_map
|
||||
|
||||
|
||||
class CheckerCommandException(Exception):
|
||||
class CheckerCommandError(Exception):
|
||||
"""Raised when running a checker failed.
|
||||
|
||||
Attributes:
|
||||
|
|
@ -70,7 +68,7 @@ class BadFiles(BeetsPlugin):
|
|||
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]
|
||||
|
||||
|
|
@ -128,7 +126,7 @@ class BadFiles(BeetsPlugin):
|
|||
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: {}",
|
||||
|
|
@ -200,7 +198,7 @@ class BadFiles(BeetsPlugin):
|
|||
elif sel == "c":
|
||||
return None
|
||||
elif sel == "b":
|
||||
raise importer.ImportAbort()
|
||||
raise importer.ImportAbortError()
|
||||
else:
|
||||
raise Exception(f"Unexpected selection: {sel}")
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
"""Provides a bare-ASCII matching query."""
|
||||
|
||||
|
||||
from unidecode import unidecode
|
||||
|
||||
from beets import ui
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ Beets library. Attempts to implement a compatible protocol to allow
|
|||
use of the wide range of MPD clients.
|
||||
"""
|
||||
|
||||
|
||||
import inspect
|
||||
import math
|
||||
import random
|
||||
|
|
@ -168,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.
|
||||
"""
|
||||
|
|
@ -349,7 +348,7 @@ class BaseServer:
|
|||
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."""
|
||||
|
|
@ -357,7 +356,7 @@ class BaseServer:
|
|||
|
||||
def cmd_close(self, conn):
|
||||
"""Closes the connection."""
|
||||
raise BPDClose()
|
||||
raise BPDCloseError()
|
||||
|
||||
def cmd_password(self, conn, password):
|
||||
"""Attempts password authentication."""
|
||||
|
|
@ -739,13 +738,13 @@ class BaseServer:
|
|||
|
||||
# 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:
|
||||
|
|
@ -773,8 +772,8 @@ class Connection:
|
|||
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)
|
||||
|
|
@ -853,8 +852,8 @@ class MPDConnection(Connection):
|
|||
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.
|
||||
|
|
@ -888,12 +887,12 @@ class MPDConnection(Connection):
|
|||
# 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"
|
||||
|
|
@ -922,8 +921,8 @@ class ControlConnection(Connection):
|
|||
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)
|
||||
|
|
@ -1046,12 +1045,12 @@ class Command:
|
|||
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:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
music player.
|
||||
"""
|
||||
|
||||
|
||||
import _thread
|
||||
import copy
|
||||
import os
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
"""Determine BPM by pressing a key to the rhythm."""
|
||||
|
||||
|
||||
import time
|
||||
|
||||
from beets import ui
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin):
|
|||
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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -304,7 +303,9 @@ class DuplicatesPlugin(BeetsPlugin):
|
|||
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":
|
||||
|
||||
|
|
@ -317,9 +318,13 @@ class DuplicatesPlugin(BeetsPlugin):
|
|||
)
|
||||
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ by default but can be added via the `-e` / `--extravalues` flag. For example:
|
|||
`beet fish -e genre -e albumartist`
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
from operator import attrgetter
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ and has been edited to remove some questionable entries.
|
|||
The scraper script used is available here:
|
||||
https://gist.github.com/1241307
|
||||
"""
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import traceback
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1063,7 +1061,7 @@ class LyricsPlugin(plugins.BeetsPlugin):
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 @@ class MetaSyncPlugin(BeetsPlugin):
|
|||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
and work composition date
|
||||
"""
|
||||
|
||||
|
||||
import musicbrainzngs
|
||||
|
||||
from beets import ui
|
||||
|
|
|
|||
|
|
@ -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 @@ class PlayPlugin(BeetsPlugin):
|
|||
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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -334,9 +334,7 @@ class FfmpegBackend(Backend):
|
|||
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
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
automatically whenever tags are written.
|
||||
"""
|
||||
|
||||
|
||||
import mediafile
|
||||
import mutagen
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
"""Moves patterns in path formats (suitable for moving articles)."""
|
||||
|
||||
|
||||
import re
|
||||
from typing import List
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ This plugin is POSIX-only.
|
|||
Spec: standards.freedesktop.org/thumbnail-spec/latest/index.html
|
||||
"""
|
||||
|
||||
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import os
|
||||
|
|
@ -280,8 +279,7 @@ class GioURI(URIGetter):
|
|||
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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
166
poetry.lock
generated
166
poetry.lock
generated
|
|
@ -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"
|
||||
|
|
@ -678,22 +632,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"
|
||||
|
|
@ -940,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"
|
||||
|
|
@ -1398,17 +1319,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"
|
||||
|
|
@ -1714,31 +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 = "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 +1924,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 +2003,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 +2527,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 +3132,4 @@ web = ["flask", "flask-cors"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.8,<4"
|
||||
content-hash = "09b64b25f3ff363a3c31d44fd99e4c07309b27dda65523f9ec3d1279ca3a3143"
|
||||
content-hash = "b3eb66c6852cb14afcbe817619ea9ecea699e838885d8e9719f2809eb708bcc3"
|
||||
|
|
|
|||
|
|
@ -84,13 +84,8 @@ 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]
|
||||
flake8 = "*"
|
||||
pep8-naming = "*"
|
||||
ruff = ">=0.6.4"
|
||||
|
||||
[tool.poetry.group.typing.dependencies]
|
||||
mypy = "*"
|
||||
|
|
@ -154,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 flake8 and
|
||||
# mypy do: they act on the entire codebase (flake8 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 <some-path>`
|
||||
# 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"
|
||||
|
|
@ -187,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."
|
||||
|
|
@ -200,13 +176,11 @@ 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 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."
|
||||
|
|
@ -249,13 +223,41 @@ done
|
|||
"""
|
||||
interpreter = "zsh"
|
||||
|
||||
[tool.black]
|
||||
[tool.ruff]
|
||||
target-version = "py38"
|
||||
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.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
|
||||
parametrize-names-type = "csv"
|
||||
|
||||
[tool.ruff.lint.flake8-unused-arguments]
|
||||
ignore-variadic-names = true
|
||||
|
||||
[tool.ruff.lint.pep8-naming]
|
||||
classmethod-decorators = ["cached_classproperty"]
|
||||
extend-ignore-names = ["assert*", "cached_classproperty"]
|
||||
|
|
|
|||
37
setup.cfg
37
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
|
||||
|
|
@ -27,41 +29,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
|
||||
|
|
|
|||
12
test/conftest.py
Normal file
12
test/conftest.py
Normal file
|
|
@ -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}")
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
"""Tests for the 'albumtypes' plugin."""
|
||||
|
||||
|
||||
from typing import Sequence, Tuple
|
||||
|
||||
from beets.autotag.mb import VARIOUS_ARTISTS_ID
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
"""Tests for the album art fetchers."""
|
||||
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from unittest.mock import patch
|
||||
|
|
@ -866,7 +865,7 @@ class ArtForAlbumTest(UseThePlugin):
|
|||
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
|
||||
|
||||
|
|
@ -897,42 +896,42 @@ class ArtForAlbumTest(UseThePlugin):
|
|||
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()
|
||||
|
|
@ -949,7 +948,7 @@ class ArtForAlbumTest(UseThePlugin):
|
|||
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()
|
||||
|
|
@ -995,7 +994,7 @@ class DeprecatedConfigTest(BeetsTestCase):
|
|||
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):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
"""Tests for the 'bareasc' plugin."""
|
||||
|
||||
|
||||
from beets import logging
|
||||
from beets.test.helper import PluginTestCase, capture_stdout
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
"""Tests for the 'bucket' plugin."""
|
||||
|
||||
|
||||
import pytest
|
||||
|
||||
from beets import config, ui
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class ConvertMixin:
|
|||
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 @@ class ConvertMixin:
|
|||
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`.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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 @@ class EditCommandTest(EditMixin, BeetsTestCase):
|
|||
{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,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import os.path
|
|||
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 import _common
|
|||
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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
"""Tests for the 'ftintitle' plugin."""
|
||||
|
||||
|
||||
import unittest
|
||||
|
||||
from beets.test.helper import PluginTestCase
|
||||
|
|
@ -83,14 +82,14 @@ class FtInTitlePluginFunctional(PluginTestCase):
|
|||
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):
|
||||
|
|
|
|||
|
|
@ -74,11 +74,11 @@ class ImportAddedTest(PluginMixin, ImportTestCase):
|
|||
"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
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
"""Tests for the 'limit' plugin."""
|
||||
|
||||
|
||||
from beets.test.helper import PluginTestCase
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
"""Tests for the 'lyrics' plugin."""
|
||||
|
||||
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
|
|
@ -22,6 +21,7 @@ import unittest
|
|||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import confuse
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from beets import logging
|
||||
|
|
@ -336,10 +336,7 @@ class LyricsPluginSourcesTest(LyricsGoogleBaseTest, LyricsAssertions):
|
|||
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.
|
||||
|
|
@ -352,10 +349,7 @@ class LyricsPluginSourcesTest(LyricsGoogleBaseTest, LyricsAssertions):
|
|||
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.
|
||||
|
|
@ -509,14 +503,14 @@ class GeniusFetchTest(GeniusBaseTest):
|
|||
{
|
||||
"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",
|
||||
}
|
||||
},
|
||||
|
|
@ -650,19 +644,13 @@ class TekstowoIntegrationTest(TekstowoBaseTest, LyricsAssertions):
|
|||
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"""
|
||||
|
|
@ -737,28 +725,19 @@ class LRCLibIntegrationTest(LyricsAssertions):
|
|||
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
|
||||
|
|
@ -786,10 +765,10 @@ class SlugTests(unittest.TestCase):
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -14,11 +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
|
||||
|
|
@ -85,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",
|
||||
|
|
@ -107,10 +103,6 @@ class ParentWorkIntegrationTest(PluginTestCase):
|
|||
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(
|
||||
|
|
@ -128,10 +120,6 @@ class ParentWorkIntegrationTest(PluginTestCase):
|
|||
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(
|
||||
|
|
@ -153,10 +141,6 @@ class ParentWorkIntegrationTest(PluginTestCase):
|
|||
# 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 (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
"""Tests for the 'permissions' plugin.
|
||||
"""
|
||||
"""Tests for the 'permissions' plugin."""
|
||||
|
||||
import os
|
||||
import platform
|
||||
|
|
@ -63,7 +62,7 @@ class PermissionsPluginTest(AsIsImporterMixin, PluginMixin, ImportTestCase):
|
|||
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], "=="),
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
"""Tests for the play plugin"""
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ gstplayer = importlib.util.module_from_spec(
|
|||
)
|
||||
|
||||
|
||||
def _gstplayer_play(*_): # noqa: 42
|
||||
def _gstplayer_play(*_):
|
||||
bpd.gstplayer._GstPlayer.playing = True
|
||||
return mock.DEFAULT
|
||||
|
||||
|
|
@ -242,7 +242,7 @@ class MPCClient:
|
|||
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 implements(commands, expectedFailure=False): # noqa: N803
|
|||
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 @@ class BPDTest(BPDTestHelper):
|
|||
|
||||
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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ from beetsplug.thumbnails import (
|
|||
|
||||
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 @@ class ThumbnailsTest(BeetsTestCase):
|
|||
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 @@ class ThumbnailsTest(BeetsTestCase):
|
|||
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 @@ class ThumbnailsTest(BeetsTestCase):
|
|||
|
||||
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 @@ class ThumbnailsTest(BeetsTestCase):
|
|||
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()
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue