mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
Merge remote-tracking branch 'upstream/master' into dereference-symlinks-while-hardlinking
This commit is contained in:
commit
29a5b06f67
45 changed files with 1676 additions and 1494 deletions
|
|
@ -78,4 +78,6 @@ d93ddf8dd43e4f9ed072a03829e287c78d2570a2
|
||||||
# Moved ui.commands._utils into ui.commands.utils
|
# Moved ui.commands._utils into ui.commands.utils
|
||||||
25ae330044abf04045e3f378f72bbaed739fb30d
|
25ae330044abf04045e3f378f72bbaed739fb30d
|
||||||
# Refactor test_ui_command.py into multiple modules
|
# Refactor test_ui_command.py into multiple modules
|
||||||
a59e41a88365e414db3282658d2aa456e0b3468a
|
a59e41a88365e414db3282658d2aa456e0b3468a
|
||||||
|
# pyupgrade Python 3.10
|
||||||
|
301637a1609831947cb5dd90270ed46c24b1ab1b
|
||||||
|
|
|
||||||
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
|
|
@ -20,10 +20,10 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ubuntu-latest, windows-latest]
|
platform: [ubuntu-latest, windows-latest]
|
||||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
env:
|
env:
|
||||||
IS_MAIN_PYTHON: ${{ matrix.python-version == '3.9' && matrix.platform == 'ubuntu-latest' }}
|
IS_MAIN_PYTHON: ${{ matrix.python-version == '3.10' && matrix.platform == 'ubuntu-latest' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
- name: Install Python tools
|
- name: Install Python tools
|
||||||
|
|
|
||||||
6
.github/workflows/integration_test.yaml
vendored
6
.github/workflows/integration_test.yaml
vendored
|
|
@ -3,6 +3,10 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * SUN" # run every Sunday at midnight
|
- cron: "0 0 * * SUN" # run every Sunday at midnight
|
||||||
|
|
||||||
|
env:
|
||||||
|
PYTHON_VERSION: "3.10"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test_integration:
|
test_integration:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -12,7 +16,7 @@ jobs:
|
||||||
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
uses: BrandonLWhite/pipx-install-action@v1.0.3
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
cache: poetry
|
cache: poetry
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ concurrency:
|
||||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHON_VERSION: 3.9
|
PYTHON_VERSION: "3.10"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
changed-files:
|
changed-files:
|
||||||
2
.github/workflows/make_release.yaml
vendored
2
.github/workflows/make_release.yaml
vendored
|
|
@ -8,7 +8,7 @@ on:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHON_VERSION: 3.9
|
PYTHON_VERSION: "3.10"
|
||||||
NEW_VERSION: ${{ inputs.version }}
|
NEW_VERSION: ${{ inputs.version }}
|
||||||
NEW_TAG: v${{ inputs.version }}
|
NEW_TAG: v${{ inputs.version }}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -124,12 +124,12 @@ command. Instead, you can activate the virtual environment in your shell with:
|
||||||
|
|
||||||
$ poetry shell
|
$ poetry shell
|
||||||
|
|
||||||
You should see ``(beets-py3.9)`` prefix in your shell prompt. Now you can run
|
You should see ``(beets-py3.10)`` prefix in your shell prompt. Now you can run
|
||||||
commands directly, for example:
|
commands directly, for example:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
$ (beets-py3.9) pytest
|
$ (beets-py3.10) pytest
|
||||||
|
|
||||||
Additionally, poethepoet_ task runner assists us with the most common
|
Additionally, poethepoet_ task runner assists us with the most common
|
||||||
operations. Formatting, linting, testing are defined as ``poe`` tasks in
|
operations. Formatting, linting, testing are defined as ``poe`` tasks in
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from typing import TYPE_CHECKING, Union
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from beets import config, logging
|
from beets import config, logging
|
||||||
|
|
||||||
|
|
@ -117,8 +117,8 @@ SPECIAL_FIELDS = {
|
||||||
|
|
||||||
|
|
||||||
def _apply_metadata(
|
def _apply_metadata(
|
||||||
info: Union[AlbumInfo, TrackInfo],
|
info: AlbumInfo | TrackInfo,
|
||||||
db_obj: Union[Album, Item],
|
db_obj: Album | Item,
|
||||||
nullable_fields: Sequence[str] = [],
|
nullable_fields: Sequence[str] = [],
|
||||||
):
|
):
|
||||||
"""Set the db_obj's metadata to match the info."""
|
"""Set the db_obj's metadata to match the info."""
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,16 @@ import threading
|
||||||
import time
|
import time
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from collections.abc import Generator, Iterable, Iterator, Mapping, Sequence
|
from collections.abc import (
|
||||||
|
Callable,
|
||||||
|
Generator,
|
||||||
|
Iterable,
|
||||||
|
Iterator,
|
||||||
|
Mapping,
|
||||||
|
Sequence,
|
||||||
|
)
|
||||||
from sqlite3 import Connection, sqlite_version_info
|
from sqlite3 import Connection, sqlite_version_info
|
||||||
from typing import TYPE_CHECKING, Any, AnyStr, Callable, Generic
|
from typing import TYPE_CHECKING, Any, AnyStr, Generic
|
||||||
|
|
||||||
from typing_extensions import TypeVar # default value support
|
from typing_extensions import TypeVar # default value support
|
||||||
from unidecode import unidecode
|
from unidecode import unidecode
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING, Sequence
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from beets import config, dbcore, library, logging, plugins, util
|
from beets import config, dbcore, library, logging, plugins, util
|
||||||
from beets.importer.tasks import Action
|
from beets.importer.tasks import Action
|
||||||
|
|
@ -25,6 +25,8 @@ from . import stages as stagefuncs
|
||||||
from .state import ImportState
|
from .state import ImportState
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
from beets.util import PathBytes
|
from beets.util import PathBytes
|
||||||
|
|
||||||
from .tasks import ImportTask
|
from .tasks import ImportTask
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Callable
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from beets import config, plugins
|
from beets import config, plugins
|
||||||
from beets.util import MoveOperation, displayable_path, pipeline
|
from beets.util import MoveOperation, displayable_path, pipeline
|
||||||
|
|
@ -30,6 +30,8 @@ from .tasks import (
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
from beets import library
|
from beets import library
|
||||||
|
|
||||||
from .session import ImportSession
|
from .session import ImportSession
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,10 @@ import re
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from collections.abc import Callable, Iterable, Sequence
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from typing import TYPE_CHECKING, Any, Callable, Iterable, Sequence
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
import mediafile
|
import mediafile
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ from logging import (
|
||||||
RootLogger,
|
RootLogger,
|
||||||
StreamHandler,
|
StreamHandler,
|
||||||
)
|
)
|
||||||
from typing import TYPE_CHECKING, Any, Mapping, TypeVar, Union, overload
|
from typing import TYPE_CHECKING, Any, TypeVar, Union, overload
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"DEBUG",
|
"DEBUG",
|
||||||
|
|
@ -54,6 +54,8 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Mapping
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from __future__ import annotations
|
||||||
import abc
|
import abc
|
||||||
import re
|
import re
|
||||||
from functools import cache, cached_property
|
from functools import cache, cached_property
|
||||||
from typing import TYPE_CHECKING, Generic, Literal, Sequence, TypedDict, TypeVar
|
from typing import TYPE_CHECKING, Generic, Literal, TypedDict, TypeVar
|
||||||
|
|
||||||
import unidecode
|
import unidecode
|
||||||
from confuse import NotFoundError
|
from confuse import NotFoundError
|
||||||
|
|
@ -22,7 +22,7 @@ from beets.util.id_extractors import extract_release_id
|
||||||
from .plugins import BeetsPlugin, find_plugins, notify_info_yielded, send
|
from .plugins import BeetsPlugin, find_plugins, notify_info_yielded, send
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable, Sequence
|
||||||
|
|
||||||
from .autotag.hooks import AlbumInfo, Item, TrackInfo
|
from .autotag.hooks import AlbumInfo, Item, TrackInfo
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ from collections import defaultdict
|
||||||
from functools import cached_property, wraps
|
from functools import cached_property, wraps
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import GenericAlias
|
|
||||||
from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeVar
|
from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeVar
|
||||||
|
|
||||||
import mediafile
|
import mediafile
|
||||||
|
|
@ -450,9 +449,6 @@ def _get_plugin(name: str) -> BeetsPlugin | None:
|
||||||
for obj in reversed(namespace.__dict__.values()):
|
for obj in reversed(namespace.__dict__.values()):
|
||||||
if (
|
if (
|
||||||
inspect.isclass(obj)
|
inspect.isclass(obj)
|
||||||
and not isinstance(
|
|
||||||
obj, GenericAlias
|
|
||||||
) # seems to be needed for python <= 3.9 only
|
|
||||||
and issubclass(obj, BeetsPlugin)
|
and issubclass(obj, BeetsPlugin)
|
||||||
and obj != BeetsPlugin
|
and obj != BeetsPlugin
|
||||||
and not inspect.isabstract(obj)
|
and not inspect.isabstract(obj)
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ import warnings
|
||||||
from difflib import SequenceMatcher
|
from difflib import SequenceMatcher
|
||||||
from functools import cache
|
from functools import cache
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import Any, Callable, Literal
|
from typing import TYPE_CHECKING, Any, Literal
|
||||||
|
|
||||||
import confuse
|
import confuse
|
||||||
|
|
||||||
|
|
@ -42,6 +42,9 @@ from beets.dbcore import query as db_query
|
||||||
from beets.util import as_string
|
from beets.util import as_string
|
||||||
from beets.util.functemplate import template
|
from beets.util.functemplate import template
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
# On Windows platforms, use colorama to support "ANSI" terminal colors.
|
# On Windows platforms, use colorama to support "ANSI" terminal colors.
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
try:
|
try:
|
||||||
|
|
@ -1601,7 +1604,7 @@ def _raw_main(args: list[str], lib=None) -> None:
|
||||||
):
|
):
|
||||||
from beets.ui.commands.config import config_edit
|
from beets.ui.commands.config import config_edit
|
||||||
|
|
||||||
return config_edit()
|
return config_edit(options)
|
||||||
|
|
||||||
test_lib = bool(lib)
|
test_lib = bool(lib)
|
||||||
subcommands, lib = _setup(options, lib)
|
subcommands, lib = _setup(options, lib)
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,10 @@ def config_func(lib, opts, args):
|
||||||
|
|
||||||
# Open in editor.
|
# Open in editor.
|
||||||
elif opts.edit:
|
elif opts.edit:
|
||||||
config_edit()
|
# Note: This branch *should* be unreachable
|
||||||
|
# since the normal flow should be short-circuited
|
||||||
|
# by the special case in ui._raw_main
|
||||||
|
config_edit(opts)
|
||||||
|
|
||||||
# Dump configuration.
|
# Dump configuration.
|
||||||
else:
|
else:
|
||||||
|
|
@ -41,11 +44,11 @@ def config_func(lib, opts, args):
|
||||||
print("Empty configuration")
|
print("Empty configuration")
|
||||||
|
|
||||||
|
|
||||||
def config_edit():
|
def config_edit(cli_options):
|
||||||
"""Open a program to edit the user configuration.
|
"""Open a program to edit the user configuration.
|
||||||
An empty config file is created if no existing config file exists.
|
An empty config file is created if no existing config file exists.
|
||||||
"""
|
"""
|
||||||
path = config.user_config_path()
|
path = cli_options.config or config.user_config_path()
|
||||||
editor = editor_command()
|
editor = editor_command()
|
||||||
try:
|
try:
|
||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import tempfile
|
||||||
import traceback
|
import traceback
|
||||||
import warnings
|
import warnings
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from collections.abc import Sequence
|
from collections.abc import Callable, Sequence
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import cache
|
from functools import cache
|
||||||
|
|
@ -41,7 +41,6 @@ from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
AnyStr,
|
AnyStr,
|
||||||
Callable,
|
|
||||||
ClassVar,
|
ClassVar,
|
||||||
Generic,
|
Generic,
|
||||||
NamedTuple,
|
NamedTuple,
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import subprocess
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import Any, ClassVar, Mapping
|
from typing import TYPE_CHECKING, Any, ClassVar
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from beets import logging, util
|
from beets import logging, util
|
||||||
|
|
@ -37,6 +37,9 @@ from beets.util import (
|
||||||
syspath,
|
syspath,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Mapping
|
||||||
|
|
||||||
PROXY_URL = "https://images.weserv.nl/"
|
PROXY_URL = "https://images.weserv.nl/"
|
||||||
|
|
||||||
log = logging.getLogger("beets")
|
log = logging.getLogger("beets")
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,6 @@ def compile_func(arg_names, statements, name="_the_func", debug=False):
|
||||||
decorator_list=[],
|
decorator_list=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
# The ast.Module signature changed in 3.8 to accept a list of types to
|
|
||||||
# ignore.
|
|
||||||
mod = ast.Module([func_def], [])
|
mod = ast.Module([func_def], [])
|
||||||
|
|
||||||
ast.fix_missing_locations(mod)
|
ast.fix_missing_locations(mod)
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,9 @@ import os
|
||||||
import stat
|
import stat
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
|
|
||||||
def is_hidden(path: Union[bytes, Path]) -> bool:
|
def is_hidden(path: bytes | Path) -> bool:
|
||||||
"""
|
"""
|
||||||
Determine whether the given path is treated as a 'hidden file' by the OS.
|
Determine whether the given path is treated as a 'hidden file' by the OS.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,13 @@ from __future__ import annotations
|
||||||
import queue
|
import queue
|
||||||
import sys
|
import sys
|
||||||
from threading import Lock, Thread
|
from threading import Lock, Thread
|
||||||
from typing import Callable, Generator, TypeVar
|
from typing import TYPE_CHECKING, TypeVar
|
||||||
|
|
||||||
from typing_extensions import TypeVarTuple, Unpack
|
from typing_extensions import TypeVarTuple, Unpack
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Generator
|
||||||
|
|
||||||
BUBBLE = "__PIPELINE_BUBBLE__"
|
BUBBLE = "__PIPELINE_BUBBLE__"
|
||||||
POISON = "__PIPELINE_POISON__"
|
POISON = "__PIPELINE_POISON__"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,7 @@ from __future__ import annotations
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import (
|
from typing import TYPE_CHECKING, Literal, overload
|
||||||
TYPE_CHECKING,
|
|
||||||
Iterable,
|
|
||||||
Iterator,
|
|
||||||
Literal,
|
|
||||||
Sequence,
|
|
||||||
overload,
|
|
||||||
)
|
|
||||||
|
|
||||||
import confuse
|
import confuse
|
||||||
from requests_oauthlib import OAuth1Session
|
from requests_oauthlib import OAuth1Session
|
||||||
|
|
@ -42,6 +35,8 @@ from beets.autotag.hooks import AlbumInfo, TrackInfo
|
||||||
from beets.metadata_plugins import MetadataSourcePlugin
|
from beets.metadata_plugins import MetadataSourcePlugin
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Iterable, Iterator, Sequence
|
||||||
|
|
||||||
from beets.importer import ImportSession
|
from beets.importer import ImportSession
|
||||||
from beets.library import Item
|
from beets.library import Item
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,7 @@ class BaseServer:
|
||||||
if not self.ctrl_sock:
|
if not self.ctrl_sock:
|
||||||
self.ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
self.ctrl_sock.connect((self.ctrl_host, self.ctrl_port))
|
self.ctrl_sock.connect((self.ctrl_host, self.ctrl_port))
|
||||||
self.ctrl_sock.sendall((f"{message}\n").encode("utf-8"))
|
self.ctrl_sock.sendall((f"{message}\n").encode())
|
||||||
|
|
||||||
def _send_event(self, event):
|
def _send_event(self, event):
|
||||||
"""Notify subscribed connections of an event."""
|
"""Notify subscribed connections of an event."""
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ autotagger. Requires the pyacoustid library.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from collections.abc import Iterable
|
||||||
from functools import cached_property, partial
|
from functools import cached_property, partial
|
||||||
from typing import Iterable
|
|
||||||
|
|
||||||
import acoustid
|
import acoustid
|
||||||
import confuse
|
import confuse
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING, Literal, Sequence
|
from typing import TYPE_CHECKING, Literal
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
|
@ -32,6 +32,8 @@ from beets.metadata_plugins import (
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
from beets.library import Item, Library
|
from beets.library import Item, Library
|
||||||
|
|
||||||
from ._typing import JSONDict
|
from ._typing import JSONDict
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import time
|
||||||
import traceback
|
import traceback
|
||||||
from functools import cache
|
from functools import cache
|
||||||
from string import ascii_lowercase
|
from string import ascii_lowercase
|
||||||
from typing import TYPE_CHECKING, Sequence, cast
|
from typing import TYPE_CHECKING, cast
|
||||||
|
|
||||||
import confuse
|
import confuse
|
||||||
from discogs_client import Client, Master, Release
|
from discogs_client import Client, Master, Release
|
||||||
|
|
@ -43,7 +43,7 @@ from beets.autotag.hooks import AlbumInfo, TrackInfo
|
||||||
from beets.metadata_plugins import MetadataSourcePlugin
|
from beets.metadata_plugins import MetadataSourcePlugin
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable, Sequence
|
||||||
|
|
||||||
from beets.library import Item
|
from beets.library import Item
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ from collections import OrderedDict
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import TYPE_CHECKING, AnyStr, ClassVar, Literal, Tuple, Type
|
from typing import TYPE_CHECKING, AnyStr, ClassVar, Literal
|
||||||
|
|
||||||
import confuse
|
import confuse
|
||||||
import requests
|
import requests
|
||||||
|
|
@ -86,7 +86,7 @@ class Candidate:
|
||||||
path: None | bytes = None,
|
path: None | bytes = None,
|
||||||
url: None | str = None,
|
url: None | str = None,
|
||||||
match: None | MetadataMatch = None,
|
match: None | MetadataMatch = None,
|
||||||
size: None | Tuple[int, int] = None,
|
size: None | tuple[int, int] = None,
|
||||||
):
|
):
|
||||||
self._log = log
|
self._log = log
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
@ -682,7 +682,7 @@ class GoogleImages(RemoteArtSource):
|
||||||
"""
|
"""
|
||||||
if not (album.albumartist and album.album):
|
if not (album.albumartist and album.album):
|
||||||
return
|
return
|
||||||
search_string = f"{album.albumartist},{album.album}".encode("utf-8")
|
search_string = f"{album.albumartist},{album.album}".encode()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = self.request(
|
response = self.request(
|
||||||
|
|
@ -1293,7 +1293,7 @@ class CoverArtUrl(RemoteArtSource):
|
||||||
|
|
||||||
|
|
||||||
# All art sources. The order they will be tried in is specified by the config.
|
# All art sources. The order they will be tried in is specified by the config.
|
||||||
ART_SOURCES: set[Type[ArtSource]] = {
|
ART_SOURCES: set[type[ArtSource]] = {
|
||||||
FileSystem,
|
FileSystem,
|
||||||
CoverArtArchive,
|
CoverArtArchive,
|
||||||
ITunesStore,
|
ITunesStore,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ class ImportSourcePlugin(BeetsPlugin):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize the plugin and read configuration."""
|
"""Initialize the plugin and read configuration."""
|
||||||
super(ImportSourcePlugin, self).__init__()
|
super().__init__()
|
||||||
self.config.add(
|
self.config.add(
|
||||||
{
|
{
|
||||||
"suggest_removal": False,
|
"suggest_removal": False,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import os
|
||||||
import traceback
|
import traceback
|
||||||
from functools import singledispatchmethod
|
from functools import singledispatchmethod
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Union
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import pylast
|
import pylast
|
||||||
import yaml
|
import yaml
|
||||||
|
|
@ -352,7 +352,7 @@ class LastGenrePlugin(plugins.BeetsPlugin):
|
||||||
combined = old + new
|
combined = old + new
|
||||||
return self._resolve_genres(combined)
|
return self._resolve_genres(combined)
|
||||||
|
|
||||||
def _get_genre(self, obj: LibModel) -> tuple[Union[str, None], ...]:
|
def _get_genre(self, obj: LibModel) -> tuple[str | None, ...]:
|
||||||
"""Get the final genre string for an Album or Item object.
|
"""Get the final genre string for an Album or Item object.
|
||||||
|
|
||||||
`self.sources` specifies allowed genre sources. Starting with the first
|
`self.sources` specifies allowed genre sources. Starting with the first
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ from html import unescape
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Iterable, Iterator, NamedTuple
|
from typing import TYPE_CHECKING, NamedTuple
|
||||||
from urllib.parse import quote, quote_plus, urlencode, urlparse
|
from urllib.parse import quote, quote_plus, urlencode, urlparse
|
||||||
|
|
||||||
import langdetect
|
import langdetect
|
||||||
|
|
@ -42,6 +42,8 @@ from beets.autotag.distance import string_dist
|
||||||
from beets.util.config import sanitize_choices
|
from beets.util.config import sanitize_choices
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Iterable, Iterator
|
||||||
|
|
||||||
from beets.importer import ImportTask
|
from beets.importer import ImportTask
|
||||||
from beets.library import Item, Library
|
from beets.library import Item, Library
|
||||||
from beets.logging import BeetsLogger as Logger
|
from beets.logging import BeetsLogger as Logger
|
||||||
|
|
@ -958,7 +960,7 @@ class LyricsPlugin(RequestHandler, plugins.BeetsPlugin):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def backends(self) -> list[Backend]:
|
def backends(self) -> list[Backend]:
|
||||||
user_sources = self.config["sources"].get()
|
user_sources = self.config["sources"].as_str_seq()
|
||||||
|
|
||||||
chosen = sanitize_choices(user_sources, self.BACKEND_BY_NAME)
|
chosen = sanitize_choices(user_sources, self.BACKEND_BY_NAME)
|
||||||
if "google" in chosen and not self.config["google_API_key"].get():
|
if "google" in chosen and not self.config["google_API_key"].get():
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ from __future__ import annotations
|
||||||
import itertools
|
import itertools
|
||||||
import traceback
|
import traceback
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import TYPE_CHECKING, Any, Iterable, Sequence
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
import mediafile
|
import mediafile
|
||||||
import musicbrainzngs
|
import musicbrainzngs
|
||||||
|
|
@ -40,6 +40,8 @@ from beetsplug.musicbrainz import (
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Iterable, Sequence
|
||||||
|
|
||||||
from beets.autotag import AlbumMatch
|
from beets.autotag import AlbumMatch
|
||||||
from beets.library import Item
|
from beets.library import Item
|
||||||
from beetsplug._typing import JSONDict
|
from beetsplug._typing import JSONDict
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ from collections import Counter
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from itertools import product
|
from itertools import product
|
||||||
from typing import TYPE_CHECKING, Any, Iterable, Sequence
|
from typing import TYPE_CHECKING, Any
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
import musicbrainzngs
|
import musicbrainzngs
|
||||||
|
|
@ -34,6 +34,7 @@ from beets.metadata_plugins import MetadataSourcePlugin
|
||||||
from beets.util.id_extractors import extract_release_id
|
from beets.util.id_extractors import extract_release_id
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Iterable, Sequence
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from beets.library import Item
|
from beets.library import Item
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ from abc import ABC, abstractmethod
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from multiprocessing.pool import ThreadPool
|
from multiprocessing.pool import ThreadPool
|
||||||
from threading import Event, Thread
|
from threading import Event, Thread
|
||||||
from typing import TYPE_CHECKING, Any, Callable, TypeVar
|
from typing import TYPE_CHECKING, Any, TypeVar
|
||||||
|
|
||||||
from beets import ui
|
from beets import ui
|
||||||
from beets.plugins import BeetsPlugin
|
from beets.plugins import BeetsPlugin
|
||||||
|
|
@ -36,7 +36,7 @@ from beets.util import command_output, displayable_path, syspath
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import optparse
|
import optparse
|
||||||
from collections.abc import Sequence
|
from collections.abc import Callable, Sequence
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
|
|
||||||
from confuse import ConfigView
|
from confuse import ConfigView
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import re
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from typing import TYPE_CHECKING, Any, Literal, Sequence, Union
|
from typing import TYPE_CHECKING, Any, Literal, Union
|
||||||
|
|
||||||
import confuse
|
import confuse
|
||||||
import requests
|
import requests
|
||||||
|
|
@ -43,6 +43,8 @@ from beets.metadata_plugins import (
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
from beets.library import Library
|
from beets.library import Library
|
||||||
from beetsplug._typing import JSONDict
|
from beetsplug._typing import JSONDict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ below!
|
||||||
Unreleased
|
Unreleased
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
Beets now requires Python 3.10 or later since support for EOL Python 3.9 has
|
||||||
|
been dropped.
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
|
|
||||||
- :doc:`plugins/ftintitle`: Added argument for custom feat. words in ftintitle.
|
- :doc:`plugins/ftintitle`: Added argument for custom feat. words in ftintitle.
|
||||||
|
|
@ -32,6 +35,10 @@ Bug fixes:
|
||||||
audio-features endpoint, the plugin logs a warning once and skips audio
|
audio-features endpoint, the plugin logs a warning once and skips audio
|
||||||
features for all remaining tracks in the session, avoiding unnecessary API
|
features for all remaining tracks in the session, avoiding unnecessary API
|
||||||
calls and rate limit exhaustion.
|
calls and rate limit exhaustion.
|
||||||
|
- Running `beet --config <mypath> config -e` now edits `<mypath>` rather than
|
||||||
|
the default config path. :bug:`5652`
|
||||||
|
- :doc:`plugins/lyrics`: Accepts strings for lyrics sources (previously only
|
||||||
|
accepted a list of strings). :bug:`5962`
|
||||||
|
|
||||||
For plugin developers:
|
For plugin developers:
|
||||||
|
|
||||||
|
|
@ -41,6 +48,8 @@ For plugin developers:
|
||||||
|
|
||||||
For packagers:
|
For packagers:
|
||||||
|
|
||||||
|
- The minimum supported Python version is now 3.10.
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
|
|
||||||
- The documentation chapter :doc:`dev/paths` has been moved to the "For
|
- The documentation chapter :doc:`dev/paths` has been moved to the "For
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
Beets requires `Python 3.9 or later`_. You can install it using package
|
Beets requires `Python 3.10 or later`_. You can install it using package
|
||||||
managers, pipx_, pip_ or by using package managers.
|
managers, pipx_, pip_ or by using package managers.
|
||||||
|
|
||||||
.. _python 3.9 or later: https://python.org/download/
|
.. _python 3.10 or later: https://python.org/download/
|
||||||
|
|
||||||
Using ``pipx`` or ``pip``
|
Using ``pipx`` or ``pip``
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,18 @@ from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from collections.abc import Callable
|
||||||
from contextlib import redirect_stdout
|
from contextlib import redirect_stdout
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, NamedTuple
|
from typing import NamedTuple, TypeAlias
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import tomli
|
import tomli
|
||||||
from packaging.version import Version, parse
|
from packaging.version import Version, parse
|
||||||
from sphinx.ext import intersphinx
|
from sphinx.ext import intersphinx
|
||||||
from typing_extensions import TypeAlias
|
|
||||||
|
|
||||||
from docs.conf import rst_epilog
|
from docs.conf import rst_epilog
|
||||||
|
|
||||||
|
|
|
||||||
2948
poetry.lock
generated
2948
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -17,7 +17,6 @@ classifiers = [
|
||||||
"Environment :: Web Environment",
|
"Environment :: Web Environment",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.9",
|
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
|
|
@ -42,7 +41,7 @@ Changelog = "https://github.com/beetbox/beets/blob/master/docs/changelog.rst"
|
||||||
"Bug Tracker" = "https://github.com/beetbox/beets/issues"
|
"Bug Tracker" = "https://github.com/beetbox/beets/issues"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.9,<4"
|
python = ">=3.10,<4"
|
||||||
|
|
||||||
colorama = { version = "*", markers = "sys_platform == 'win32'" }
|
colorama = { version = "*", markers = "sys_platform == 'win32'" }
|
||||||
confuse = ">=2.1.0"
|
confuse = ">=2.1.0"
|
||||||
|
|
@ -228,7 +227,7 @@ cmd = "ruff format"
|
||||||
|
|
||||||
[tool.poe.tasks.format-docs]
|
[tool.poe.tasks.format-docs]
|
||||||
help = "Format the documentation"
|
help = "Format the documentation"
|
||||||
cmd = "docstrfmt"
|
cmd = "docstrfmt docs *.rst"
|
||||||
|
|
||||||
[tool.poe.tasks.lint]
|
[tool.poe.tasks.lint]
|
||||||
help = "Check the code for linting issues. Accepts ruff options."
|
help = "Check the code for linting issues. Accepts ruff options."
|
||||||
|
|
@ -286,7 +285,6 @@ extend-exclude = [
|
||||||
"docs/api/**/*",
|
"docs/api/**/*",
|
||||||
"README_kr.rst",
|
"README_kr.rst",
|
||||||
]
|
]
|
||||||
files = ["docs", "*.rst"]
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
target-version = "py39"
|
target-version = "py39"
|
||||||
|
|
@ -305,9 +303,7 @@ select = [
|
||||||
"N", # pep8-naming
|
"N", # pep8-naming
|
||||||
"PT", # flake8-pytest-style
|
"PT", # flake8-pytest-style
|
||||||
# "RUF", # ruff
|
# "RUF", # ruff
|
||||||
# "UP", # pyupgrade
|
"UP", # pyupgrade
|
||||||
"UP031", # do not use percent formatting
|
|
||||||
"UP032", # use f-string instead of format call
|
|
||||||
"TCH", # flake8-type-checking
|
"TCH", # flake8-type-checking
|
||||||
"W", # pycodestyle
|
"W", # pycodestyle
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from flask.testing import Client
|
from flask.testing import Client
|
||||||
|
|
@ -58,9 +58,7 @@ class TestAuraResponse:
|
||||||
def get_response_data(self, client: Client, item):
|
def get_response_data(self, client: Client, item):
|
||||||
"""Return a callback accepting `endpoint` and `params` parameters."""
|
"""Return a callback accepting `endpoint` and `params` parameters."""
|
||||||
|
|
||||||
def get(
|
def get(endpoint: str, params: dict[str, str]) -> dict[str, Any] | None:
|
||||||
endpoint: str, params: dict[str, str]
|
|
||||||
) -> Optional[dict[str, Any]]:
|
|
||||||
"""Add additional `params` and GET the given endpoint.
|
"""Add additional `params` and GET the given endpoint.
|
||||||
|
|
||||||
`include` parameter is added to every call to check that the
|
`include` parameter is added to every call to check that the
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
"""Tests for the 'ftintitle' plugin."""
|
"""Tests for the 'ftintitle' plugin."""
|
||||||
|
|
||||||
from typing import Dict, Generator, Optional, Tuple, Union
|
from collections.abc import Generator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ def env() -> Generator[FtInTitlePluginFunctional, None, None]:
|
||||||
|
|
||||||
def set_config(
|
def set_config(
|
||||||
env: FtInTitlePluginFunctional,
|
env: FtInTitlePluginFunctional,
|
||||||
cfg: Optional[Dict[str, Union[str, bool, list[str]]]],
|
cfg: dict[str, str | bool | list[str]] | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
cfg = {} if cfg is None else cfg
|
cfg = {} if cfg is None else cfg
|
||||||
defaults = {
|
defaults = {
|
||||||
|
|
@ -57,7 +57,7 @@ def add_item(
|
||||||
path: str,
|
path: str,
|
||||||
artist: str,
|
artist: str,
|
||||||
title: str,
|
title: str,
|
||||||
albumartist: Optional[str],
|
albumartist: str | None,
|
||||||
) -> Item:
|
) -> Item:
|
||||||
return env.add_item(
|
return env.add_item(
|
||||||
path=path,
|
path=path,
|
||||||
|
|
@ -250,10 +250,10 @@ def add_item(
|
||||||
)
|
)
|
||||||
def test_ftintitle_functional(
|
def test_ftintitle_functional(
|
||||||
env: FtInTitlePluginFunctional,
|
env: FtInTitlePluginFunctional,
|
||||||
cfg: Optional[Dict[str, Union[str, bool, list[str]]]],
|
cfg: dict[str, str | bool | list[str]] | None,
|
||||||
cmd_args: Tuple[str, ...],
|
cmd_args: tuple[str, ...],
|
||||||
given: Tuple[str, str, Optional[str]],
|
given: tuple[str, str, str | None],
|
||||||
expected: Tuple[str, str],
|
expected: tuple[str, str],
|
||||||
) -> None:
|
) -> None:
|
||||||
set_config(env, cfg)
|
set_config(env, cfg)
|
||||||
ftintitle.FtInTitlePlugin()
|
ftintitle.FtInTitlePlugin()
|
||||||
|
|
@ -287,7 +287,7 @@ def test_ftintitle_functional(
|
||||||
def test_find_feat_part(
|
def test_find_feat_part(
|
||||||
artist: str,
|
artist: str,
|
||||||
albumartist: str,
|
albumartist: str,
|
||||||
expected: Optional[str],
|
expected: str | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
assert ftintitle.find_feat_part(artist, albumartist) == expected
|
assert ftintitle.find_feat_part(artist, albumartist) == expected
|
||||||
|
|
||||||
|
|
@ -307,7 +307,7 @@ def test_find_feat_part(
|
||||||
)
|
)
|
||||||
def test_split_on_feat(
|
def test_split_on_feat(
|
||||||
given: str,
|
given: str,
|
||||||
expected: Tuple[str, Optional[str]],
|
expected: tuple[str, str | None],
|
||||||
) -> None:
|
) -> None:
|
||||||
assert ftintitle.split_on_feat(given) == expected
|
assert ftintitle.split_on_feat(given) == expected
|
||||||
|
|
||||||
|
|
@ -359,7 +359,7 @@ def test_contains_feat(given: str, expected: bool) -> None:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_custom_words(
|
def test_custom_words(
|
||||||
given: str, custom_words: Optional[list[str]], expected: bool
|
given: str, custom_words: list[str] | None, expected: bool
|
||||||
) -> None:
|
) -> None:
|
||||||
if custom_words is None:
|
if custom_words is None:
|
||||||
custom_words = []
|
custom_words = []
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,13 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import TYPE_CHECKING, Callable
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from beets import plugins
|
from beets import plugins
|
||||||
from beets.test.helper import PluginTestCase, capture_log
|
from beets.test.helper import PluginTestCase, capture_log
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Iterator
|
from collections.abc import Callable, Iterator
|
||||||
|
|
||||||
|
|
||||||
class HookTestCase(PluginTestCase):
|
class HookTestCase(PluginTestCase):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import os.path
|
|
||||||
|
|
||||||
from beets.library import Album, Item
|
from beets.library import Album, Item
|
||||||
from beets.test.helper import PluginTestCase
|
from beets.test.helper import PluginTestCase
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
"""Various tests for querying the library database."""
|
"""Various tests for querying the library database."""
|
||||||
|
|
||||||
from mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import beets.library
|
import beets.library
|
||||||
from beets import config, dbcore
|
from beets import config, dbcore
|
||||||
|
|
|
||||||
|
|
@ -128,3 +128,11 @@ class ConfigCommandTest(BeetsTestCase):
|
||||||
with patch("os.execlp") as execlp:
|
with patch("os.execlp") as execlp:
|
||||||
self.run_command("config", "-e")
|
self.run_command("config", "-e")
|
||||||
execlp.assert_called_once_with("myeditor", "myeditor", self.config_path)
|
execlp.assert_called_once_with("myeditor", "myeditor", self.config_path)
|
||||||
|
|
||||||
|
def test_edit_config_with_custom_config_path(self):
|
||||||
|
os.environ["EDITOR"] = "myeditor"
|
||||||
|
with patch("os.execlp") as execlp:
|
||||||
|
self.run_command("--config", self.cli_config_path, "config", "-e")
|
||||||
|
execlp.assert_called_once_with(
|
||||||
|
"myeditor", "myeditor", self.cli_config_path
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue