From 0cd826d92fb7f8ad90ddede782871bfc4145f7f1 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Sat, 18 Apr 2026 16:45:19 +0200 Subject: [PATCH 1/7] Rename search_album to search_name. --- backend/beets_flask/importer/session.py | 18 +++++++++--------- backend/beets_flask/invoker/enqueue.py | 2 +- backend/tests/integration/test_flows.py | 6 +++--- .../components/import/candidates/actions.tsx | 8 ++++---- frontend/src/pythonTypes.ts | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/backend/beets_flask/importer/session.py b/backend/beets_flask/importer/session.py index a61969bc..b40472e0 100644 --- a/backend/beets_flask/importer/session.py +++ b/backend/beets_flask/importer/session.py @@ -154,7 +154,7 @@ class Search(TypedDict): search_ids: list[str] search_artist: str | None - search_album: str | None + search_name: str | None def _is_search(d: Any) -> TypeGuard[Search]: @@ -564,8 +564,8 @@ def lookup_candidates(self, task: BeetsImportTask): and search["search_artist"].strip() == "" ): search["search_artist"] = None - if search["search_album"] is not None and search["search_album"].strip() == "": - search["search_album"] = None + if search["search_name"] is not None and search["search_name"].strip() == "": + search["search_name"] = None search["search_ids"] = list( filter(lambda x: x.strip() != "", search["search_ids"]) ) @@ -575,10 +575,10 @@ def lookup_candidates(self, task: BeetsImportTask): try: _, _, prop = autotag.tag_album( task.items, - search_ids=search["search_ids"], - search_album=search["search_album"], - search_artist=search["search_artist"], - ) + search_ids=search["search_ids"], + search_name=search["search_name"], + search_artist=search["search_artist"], + ) except Exception as e: # TODO: With beets 2.6.0 this should be revisited # since beets should than be able to handle these exceptions @@ -614,8 +614,8 @@ def lookup_candidates(self, task: BeetsImportTask): error_text += f"ids: {', '.join(search['search_ids'])}; " if search["search_artist"]: error_text += f"artist: {search['search_artist']}; " - if search["search_album"]: - error_text += f"album: {search['search_album']}; " + if search["search_name"]: + error_text += f"album: {search['search_name']}; " error_text += NoCandidatesFoundException.metadata_plugin_info() raise NoCandidatesFoundException( error_text, diff --git a/backend/beets_flask/invoker/enqueue.py b/backend/beets_flask/invoker/enqueue.py index fa6505f7..cda010b8 100644 --- a/backend/beets_flask/invoker/enqueue.py +++ b/backend/beets_flask/invoker/enqueue.py @@ -209,7 +209,7 @@ def enqueue_preview(hash: str, path: str, extra_meta: ExtraJobMeta, **kwargs) -> def enqueue_preview_add_candidates( hash: str, path: str, extra_meta: ExtraJobMeta, **kwargs ) -> Job: - # May contain search_ids, search_artist, search_album + # May contain search_ids, search_artist, search_name # As always to allow task mapping search: TaskIdMappingArg[Search | Literal["skip"]] = kwargs.pop("search", None) diff --git a/backend/tests/integration/test_flows.py b/backend/tests/integration/test_flows.py index 621c74e7..16f3170b 100644 --- a/backend/tests/integration/test_flows.py +++ b/backend/tests/integration/test_flows.py @@ -310,7 +310,7 @@ async def test_add_candidates(self, db_session: Session, path: Path): id_99_red_balloons, ], # Nena 99 Red Balloons "search_artist": None, - "search_album": None, + "search_name": None, } }, ) @@ -352,7 +352,7 @@ async def test_add_candidates_fails(self, db_session: Session, path: Path): "non_existing_id", ], # Nena 99 Red Balloons "search_artist": None, - "search_album": None, + "search_name": None, } }, ) @@ -392,7 +392,7 @@ async def test_add_candidates_cleared(self, db_session: Session, path: Path): id_99_red_balloons, ], # Nena 99 Red Balloons "search_artist": None, - "search_album": None, + "search_name": None, } }, ) diff --git a/frontend/src/components/import/candidates/actions.tsx b/frontend/src/components/import/candidates/actions.tsx index 8a313725..b1d9fc16 100644 --- a/frontend/src/components/import/candidates/actions.tsx +++ b/frontend/src/components/import/candidates/actions.tsx @@ -217,7 +217,7 @@ export function CandidateSearch({ task }: { task: SerializedTaskState }) { const [search, setSearch] = useState({ search_ids: [], search_artist: null, - search_album: null, + search_name: null, }); /** Mutation for the search @@ -274,7 +274,7 @@ export function CandidateSearch({ task }: { task: SerializedTaskState }) { setSearch({ search_ids: [], search_artist: '', - search_album: '', + search_name: '', }); } catch (e) { // dont close the dialog @@ -332,11 +332,11 @@ export function CandidateSearch({ task }: { task: SerializedTaskState }) { id="input-search-artist" label="and album" placeholder="Album" - value={search.search_album || ''} + value={search.search_name || ''} onChange={(e) => { setSearch({ ...search, - search_album: e.target.value, + search_name: e.target.value, }); }} /> diff --git a/frontend/src/pythonTypes.ts b/frontend/src/pythonTypes.ts index ca67838a..e343760c 100644 --- a/frontend/src/pythonTypes.ts +++ b/frontend/src/pythonTypes.ts @@ -25,7 +25,7 @@ export interface SerializedProgressState { export interface Search { search_ids: Array; search_artist: null | string; - search_album: null | string; + search_name: null | string; } export interface LibraryStats { From fbdde378d184917294e5f769cb87f629db8d5f20 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Sat, 18 Apr 2026 16:49:44 +0200 Subject: [PATCH 2/7] Moved register for pickled files into temp folder. --- backend/tests/unit/test_importer/conftest.py | 34 +++++++++++--------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/backend/tests/unit/test_importer/conftest.py b/backend/tests/unit/test_importer/conftest.py index 5e284a6c..4a0f02d9 100644 --- a/backend/tests/unit/test_importer/conftest.py +++ b/backend/tests/unit/test_importer/conftest.py @@ -1,5 +1,7 @@ +import atexit import logging import shutil +import tempfile from pathlib import Path import pytest @@ -77,6 +79,7 @@ def valid_data_for_album_path(path: str | Path) -> dict: from beets.autotag import tag_album as _tag_album album_path: str +lookup_cache_dir: Path def use_mock_tag_album(a_dir: str): @@ -84,51 +87,52 @@ def use_mock_tag_album(a_dir: str): this allows to not make requests to the internet when testing the importer. """ - global album_path + global album_path, lookup_cache_dir album_path = a_dir + # Create temp lookup cache directory + lookup_cache_dir = Path(tempfile.mkdtemp(prefix="beets_lookup_cache_")) + atexit.register(shutil.rmtree, lookup_cache_dir, ignore_errors=True) + autotag.tag_album = tag_album def tag_album( items, search_artist: str | None = None, - search_album: str | None = None, + search_name: str | None = None, search_ids: list[str] = [], ): global album_path log.debug(f"Using monkey patched lookup {album_path=}") # Compute items hash based on the items - m = hashlib.md5() for item in items: m.update(item.path) if search_artist: m.update(search_artist.encode("utf-8")) - if search_album: - m.update(search_album.encode("utf-8")) + if search_name: + m.update(search_name.encode("utf-8")) for search_id in search_ids: m.update(search_id.encode("utf-8")) items_hash = m.hexdigest()[:8] - if (Path(album_path) / f"lookup_{items_hash}.pickle").exists(): - log.debug(f"Using cached lookup {album_path=}") - with open(Path(album_path) / f"lookup_{items_hash}.pickle", "rb") as f: - return pickle.load(f) + cache_file = lookup_cache_dir / f"lookup_{items_hash}.pickle" + if cache_file.exists(): + log.debug(f"Using cached lookup from temp dir {cache_file}") + with open(cache_file, "rb") as f: + return pickle.load(f) else: # TODO: This pickle contains absolute paths to the files # while undesired (no use in having them in the git repo) its for now the # easiest way... and we hope music brainz does not change its data too often! log.debug(f"Using default lookup {album_path=}") - res = _tag_album(items, search_artist, search_album, search_ids) - - outdir = Path(album_path) - if not outdir.is_dir(): - outdir = outdir.parent + res = _tag_album(items, search_artist, search_name, search_ids) - with open(outdir / f"lookup_{items_hash}.pickle", "wb") as f: + cache_file.parent.mkdir(parents=True, exist_ok=True) + with open(cache_file, "wb") as f: pickle.dump(res, f) return res From 15fb5832babebdd2454d2333aecb9c350f60175e Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Sat, 18 Apr 2026 16:50:24 +0200 Subject: [PATCH 3/7] Removed error handling as this is now catched by beets and should not propagate anymore. --- backend/beets_flask/importer/session.py | 28 ++----------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/backend/beets_flask/importer/session.py b/backend/beets_flask/importer/session.py index b40472e0..b8427429 100644 --- a/backend/beets_flask/importer/session.py +++ b/backend/beets_flask/importer/session.py @@ -572,36 +572,12 @@ def lookup_candidates(self, task: BeetsImportTask): log.debug(f"Using {search=} for {task_state.id=}, {task_state.paths=}") - try: - _, _, prop = autotag.tag_album( - task.items, + _, _, prop = autotag.tag_album( + task.items, search_ids=search["search_ids"], search_name=search["search_name"], search_artist=search["search_artist"], ) - except Exception as e: - # TODO: With beets 2.6.0 this should be revisited - # since beets should than be able to handle these exceptions - # gracefully upstream. - # https://github.com/beetbox/beets/pull/5965 - from beetsplug.musicbrainz import MusicBrainzAPIError - from beetsplug.spotify import APIError as SpotifyAPIError - - if isinstance(e, MusicBrainzAPIError): - raise NoCandidatesFoundException( - f"Failed to contact Musicbrainz API: {e.get_message()}", - persist_in_db=False, - ) - elif isinstance(e, SpotifyAPIError): - raise NoCandidatesFoundException( - f"Failed to contact Spotify API: {e}", - persist_in_db=False, - ) - else: - raise NoCandidatesFoundException( - f"Failed to contact online APIs.", - persist_in_db=False, - ) task_state.add_candidates(prop.candidates) From cba3f44dac41acc94d5e92ac32079f3d5c08b8ce Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Sat, 18 Apr 2026 18:20:01 +0200 Subject: [PATCH 4/7] Import changed for show_change. --- backend/beets_flask/importer/states.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/beets_flask/importer/states.py b/backend/beets_flask/importer/states.py index 6480332b..6b55cc6c 100644 --- a/backend/beets_flask/importer/states.py +++ b/backend/beets_flask/importer/states.py @@ -10,9 +10,9 @@ from typing import Literal, NotRequired, TypedDict, cast from uuid import uuid4 as uuid -import beets.ui.commands as uicommands from beets import importer from beets.ui import _open_library +from beets.ui.commands.import_.display import show_change from beets.util import bytestring_path, get_most_common_tags from deprecated import deprecated @@ -484,7 +484,7 @@ def type(self) -> Literal["album", "track"]: def diff_preview(self) -> str: """Diff preview of the match to the current meta data.""" out, err, _ = capture_stdout_stderr( - uicommands.show_change, + show_change, self.task_state.task.cur_artist, self.task_state.task.cur_album, self.match, From b46421102b2044753319da3eb4038f3bfbe6e5cd Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Sat, 18 Apr 2026 19:13:59 +0200 Subject: [PATCH 5/7] Fixed a few issues with our tag_albums caching for tests. --- backend/tests/conftest.py | 64 ++++++++++++++++- backend/tests/integration/test_flows.py | 17 +---- backend/tests/unit/test_importer/conftest.py | 73 -------------------- 3 files changed, 66 insertions(+), 88 deletions(-) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 35a33b4b..88c97d87 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -1,12 +1,17 @@ +import hashlib import logging import os +import pickle import shutil +import tempfile from collections.abc import Callable, Generator from contextlib import _GeneratorContextManager from pathlib import Path import pytest import yaml +from beets import autotag +from beets.autotag import tag_album as _tag_album from quart import Quart from quart.typing import TestClientProtocol from sqlalchemy.orm import Session @@ -46,7 +51,7 @@ def setup_and_teardown(tmpdir_factory): # we have one test that does replacements on this file # and assumes the default 4 workers with open(tmp_dir / "beets/config.yaml", "w") as f: - yaml.dump({"plugins": ["musicbrainz"]}, f) + yaml.dump({"plugins": ["musicbrainz", "spotify"]}, f) yield @@ -205,3 +210,60 @@ def local_redis(monkeypatch): yield log.debug("Unmocking beets_flask.redis") monkeypatch.undo() + + +lookup_cache_dir: Path + + +@pytest.fixture(scope="module", autouse=True) +def mock_tag_album(): + """Fixture that monkeypatches beets tag_album to use cached lookups.""" + # Create temp lookup cache directory once per module + global lookup_cache_dir + + lookup_cache_dir = Path(tempfile.mkdtemp(prefix="beets_lookup_cache_")) + + original_tag_album = autotag.tag_album + autotag.tag_album = tag_album + yield lookup_cache_dir + autotag.tag_album = original_tag_album + + +def tag_album( + items, + search_artist: str | None = None, + search_name: str | None = None, + search_ids: list[str] = [], +): + global lookup_cache_dir + # Compute items hash based on the items + m = hashlib.md5() + for item in items: + m.update(item.path) + if search_artist: + m.update(search_artist.encode("utf-8")) + if search_name: + m.update(search_name.encode("utf-8")) + for search_id in search_ids: + m.update(search_id.encode("utf-8")) + items_hash = m.hexdigest()[:8] + + cache_file = lookup_cache_dir / f"lookup_{items_hash}.pickle" + if cache_file.exists(): + log.debug(f"Using cached lookup from temp dir {cache_file}") + with open(cache_file, "rb") as f: + return pickle.load(f) + else: + # TODO: This pickle contains absolute paths to the files + # while undesired (no use in having them in the git repo) its for now the + # easiest way... and we hope music brainz does not change its data too often! + res = _tag_album(items, search_artist, search_name, search_ids) + + cache_file.parent.mkdir(parents=True, exist_ok=True) + with open(cache_file, "wb") as f: + pickle.dump(res, f) + + return res + + +autotag.tag_album = tag_album diff --git a/backend/tests/integration/test_flows.py b/backend/tests/integration/test_flows.py index 16f3170b..df3a9f33 100644 --- a/backend/tests/integration/test_flows.py +++ b/backend/tests/integration/test_flows.py @@ -41,7 +41,6 @@ from tests.unit.test_importer.conftest import ( VALID_PATHS, album_path_absolute, - use_mock_tag_album, ) @@ -100,7 +99,6 @@ class TestPreview(SendStatusMockMixin, IsolatedDBMixin, IsolatedBeetsLibraryMixi ) def path(self, request) -> Path: path = album_path_absolute(request.param) - use_mock_tag_album(str(path)) return path async def test_preview( @@ -165,7 +163,6 @@ class TestPreviewMultipleTasks( @pytest.fixture() def path(self) -> Path: path = album_path_absolute("multi_flat") - use_mock_tag_album(str(path)) return path @pytest.mark.parametrize( @@ -248,7 +245,6 @@ class TestImportBest(SendStatusMockMixin, IsolatedDBMixin, IsolatedBeetsLibraryM @pytest.fixture() def path(self) -> Path: path = album_path_absolute(VALID_PATHS[0]) - use_mock_tag_album(str(path)) return path def check_mapping_consistency(self, db_session: Session): @@ -712,7 +708,6 @@ class TestImportAuto(SendStatusMockMixin, IsolatedDBMixin, IsolatedBeetsLibraryM @pytest.fixture() def path(self) -> Path: path = album_path_absolute(VALID_PATHS[0]) - use_mock_tag_album(str(path)) return path async def test_import_auto_accept(self, db_session: Session, path: Path): @@ -764,7 +759,6 @@ class TestImportAutoFails( @pytest.fixture() def path(self) -> Path: path = album_path_absolute(VALID_PATHS[0]) - use_mock_tag_album(str(path)) return path async def test_import_auto_fails(self, db_session: Session, path: Path): @@ -824,7 +818,6 @@ class TestChooseCandidatesSingleTask( @pytest.fixture() def path_single_task(self) -> Path: path = album_path_absolute(VALID_PATHS[0]) - use_mock_tag_album(str(path)) return path async def test_choose_candidates( @@ -850,6 +843,7 @@ async def test_choose_candidates( assert s_state_indb.folder.full_path == str(path_single_task) assert len(s_state_indb.tasks) == 1 + # Should have to candidates (one from mb and one from spotify) choosen_candidate = s_state_indb.tasks[0].candidates[-2] exc = await run_import_candidate( @@ -886,7 +880,6 @@ class TestMultipleTasks( @pytest.fixture() def path_multiple_tasks(self) -> Path: path = album_path_absolute("multi") - use_mock_tag_album(str(path)) return path async def test_choose_candidates_multiple_tasks( @@ -922,9 +915,7 @@ async def test_choose_candidates_multiple_tasks( candidates: TaskIdMappingArg[CandidateChoice] = {} assert candidates is not None for task in s_state_indb.tasks: - print(task.paths) - print([c.metadata for c in task.candidates]) - assert len(task.candidates) > 2, "Should have candidates" + assert len(task.candidates) >= 2, "Should have candidates" candidates[task.id] = task.candidates[-2].id # Check that we have the same number of candidates as tasks @@ -983,7 +974,7 @@ async def test_duplicate_action( assert duplicate_actions is not None for task in s_state_indb.tasks: - assert len(task.candidates) > 2, "Should have candidates" + assert len(task.candidates) >= 2, "Should have candidates" candidates[task.id] = task.candidates[-2].id duplicate_actions[task.id] = duplicate_action @@ -1013,7 +1004,6 @@ class TestPluginEvents( @pytest.fixture() def path(self) -> Path: path = album_path_absolute(VALID_PATHS[0]) - use_mock_tag_album(str(path)) return path async def test_preview_events(self, db_session: Session, path: Path): @@ -1114,7 +1104,6 @@ class TestImportBootleg( @pytest.fixture() def path(self) -> Path: path = album_path_absolute(VALID_PATHS[0]) - use_mock_tag_album(str(path)) return path async def test_import_bootleg(self, db_session: Session, path: Path): diff --git a/backend/tests/unit/test_importer/conftest.py b/backend/tests/unit/test_importer/conftest.py index 4a0f02d9..dcc8e28b 100644 --- a/backend/tests/unit/test_importer/conftest.py +++ b/backend/tests/unit/test_importer/conftest.py @@ -1,7 +1,5 @@ -import atexit import logging import shutil -import tempfile from pathlib import Path import pytest @@ -68,74 +66,3 @@ def valid_data_for_album_path(path: str | Path) -> dict: } else: raise NotImplementedError(f"Unknown test album path {p=}") - - -# ----------------- Monkeypath beets to use cached responses ----------------- # - -import hashlib -import pickle - -from beets import autotag -from beets.autotag import tag_album as _tag_album - -album_path: str -lookup_cache_dir: Path - - -def use_mock_tag_album(a_dir: str): - """Use a cached lookup for the tag_album function in beets - this allows to not make requests to the internet when testing - the importer. - """ - global album_path, lookup_cache_dir - album_path = a_dir - - # Create temp lookup cache directory - lookup_cache_dir = Path(tempfile.mkdtemp(prefix="beets_lookup_cache_")) - atexit.register(shutil.rmtree, lookup_cache_dir, ignore_errors=True) - - autotag.tag_album = tag_album - - -def tag_album( - items, - search_artist: str | None = None, - search_name: str | None = None, - search_ids: list[str] = [], -): - global album_path - log.debug(f"Using monkey patched lookup {album_path=}") - - # Compute items hash based on the items - m = hashlib.md5() - for item in items: - m.update(item.path) - if search_artist: - m.update(search_artist.encode("utf-8")) - if search_name: - m.update(search_name.encode("utf-8")) - for search_id in search_ids: - m.update(search_id.encode("utf-8")) - items_hash = m.hexdigest()[:8] - - cache_file = lookup_cache_dir / f"lookup_{items_hash}.pickle" - - if cache_file.exists(): - log.debug(f"Using cached lookup from temp dir {cache_file}") - with open(cache_file, "rb") as f: - return pickle.load(f) - else: - # TODO: This pickle contains absolute paths to the files - # while undesired (no use in having them in the git repo) its for now the - # easiest way... and we hope music brainz does not change its data too often! - log.debug(f"Using default lookup {album_path=}") - res = _tag_album(items, search_artist, search_name, search_ids) - - cache_file.parent.mkdir(parents=True, exist_ok=True) - with open(cache_file, "wb") as f: - pickle.dump(res, f) - - return res - - -autotag.tag_album = tag_album From 35cd50dd0e078061a3e206e8ce81e1f2f84d96e7 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Sat, 18 Apr 2026 19:16:25 +0200 Subject: [PATCH 6/7] Beets update to 2.6.1 --- CHANGELOG.md | 1 + backend/pyproject.toml | 2 +- backend/uv.lock | 93 +++++++++++++++++++++++++++++++++++------- 3 files changed, 81 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cf803af..0fba3d82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - We now use `uv` (Universal Virtualenv) to manage python dependencies and run scripts in CI/CD. This should improve dependency resolution and installation times. - We now ship a static ffmpeg binary instead of installing ffmpeg via apt. This should reduce image size and improve compatibility across different host systems. - Added a database migration setup using [Alembic](https://alembic.sqlalchemy.org/) for future database migrations. +- Upgraded `beets` from `v2.5.1` to `v2.6.1` ## [1.2.0] - 25-12-17 diff --git a/backend/pyproject.toml b/backend/pyproject.toml index a13dd56b..bdb1ed74 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ dependencies = [ "quart>=0.20.0", "confuse>=2.0.1", - "beets==2.5.1", + "beets==2.6.1", "sqlalchemy>=2.0.35", "rq>=2.0.0", "watchdog>=5.0.3", diff --git a/backend/uv.lock b/backend/uv.lock index 602e97df..270668a8 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -175,7 +175,7 @@ wheels = [ [[package]] name = "beets" -version = "2.5.1" +version = "2.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -183,16 +183,19 @@ dependencies = [ { name = "jellyfish" }, { name = "lap" }, { name = "mediafile" }, - { name = "musicbrainzngs" }, + { name = "numba" }, { name = "numpy" }, + { name = "packaging" }, { name = "platformdirs" }, { name = "pyyaml" }, + { name = "requests-ratelimiter" }, + { name = "scipy" }, { name = "typing-extensions" }, { name = "unidecode" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/32/2b5ae0038c442e783b4f00b4145b15357a2f2358fd985c60a1f890751bb0/beets-2.5.1.tar.gz", hash = "sha256:7feefd70804fbcf26516089f472bac34c6a77e8e20ec539252fd1bafc91de9a2", size = 2147257, upload-time = "2025-10-14T22:52:55.631Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/f3/f429c70cdf8de80367be64515fe432b1cf44c60a9774cc75ae0aff36e48b/beets-2.6.1.tar.gz", hash = "sha256:1376b992ee18ee1b5de08d3586625e78d4338edb6b47e24f8e74932d8551f672", size = 2175882, upload-time = "2026-02-02T02:30:36.544Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/26/c459ae5217a69d1a2c83ddb80b61480764e990049b7d9f6a5b82660457f4/beets-2.5.1-py3-none-any.whl", hash = "sha256:3e58f33d898d007e6bfd385bd145d2c39325ef6b6f831f7269d037bbcb542bf7", size = 573677, upload-time = "2025-10-14T22:52:53.728Z" }, + { url = "https://files.pythonhosted.org/packages/d2/a8/fbdd2eeaea7add3103d52e8c84490ed7db383db5d5227aa8583e2abe284e/beets-2.6.1-py3-none-any.whl", hash = "sha256:d3654fc7fef2c26d35113d40346d873c073ca981e0db1f21e4575ca1860ede02", size = 604352, upload-time = "2026-02-02T02:30:34.303Z" }, ] [[package]] @@ -287,7 +290,7 @@ requires-dist = [ { name = "aiofiles" }, { name = "aiohttp" }, { name = "alembic", specifier = ">=1.18.4" }, - { name = "beets", specifier = "==2.5.1" }, + { name = "beets", specifier = "==2.6.1" }, { name = "cachetools", specifier = ">=5.3.3" }, { name = "confuse", specifier = ">=2.0.1" }, { name = "deprecated", specifier = ">=1.2.18" }, @@ -1090,6 +1093,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8b/34/b11ab24abb78c73a1b82f6471c2d71bdd1bf2c8f30768ed2f26f1dddc083/libtmux-0.55.0-py3-none-any.whl", hash = "sha256:4b746533856e022c759e5c5cae97f4932e85dae316a2afd4391d6d0e891d6ab8", size = 80094, upload-time = "2026-03-08T00:57:54.141Z" }, ] +[[package]] +name = "llvmlite" +version = "0.47.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/88/a8952b6d5c21e74cbf158515b779666f692846502623e9e3c39d8e8ba25f/llvmlite-0.47.0.tar.gz", hash = "sha256:62031ce968ec74e95092184d4b0e857e444f8fdff0b8f9213707699570c33ccc", size = 193614, upload-time = "2026-03-31T18:29:53.497Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/48/4b7fe0e34c169fa2f12532916133e0b219d2823b540733651b34fdac509a/llvmlite-0.47.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:306a265f408c259067257a732c8e159284334018b4083a9e35f67d19792b164f", size = 37232769, upload-time = "2026-03-31T18:28:43.735Z" }, + { url = "https://files.pythonhosted.org/packages/e6/4b/e3f2cd17822cf772a4a51a0a8080b0032e6d37b2dbe8cfb724eac4e31c52/llvmlite-0.47.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5853bf26160857c0c2573415ff4efe01c4c651e59e2c55c2a088740acfee51cd", size = 56275178, upload-time = "2026-03-31T18:28:48.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/55/a3b4a543185305a9bdf3d9759d53646ed96e55e7dfd43f53e7a421b8fbae/llvmlite-0.47.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:003bcf7fa579e14db59c1a1e113f93ab8a06b56a4be31c7f08264d1d4072d077", size = 55128632, upload-time = "2026-03-31T18:28:52.901Z" }, + { url = "https://files.pythonhosted.org/packages/2f/f5/d281ae0f79378a5a91f308ea9fdb9f9cc068fddd09629edc0725a5a8fde1/llvmlite-0.47.0-cp312-cp312-win_amd64.whl", hash = "sha256:f3079f25bdc24cd9d27c4b2b5e68f5f60c4fdb7e8ad5ee2b9b006007558f9df7", size = 38138692, upload-time = "2026-03-31T18:28:57.147Z" }, +] + [[package]] name = "mako" version = "1.3.10" @@ -1206,15 +1221,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] -[[package]] -name = "musicbrainzngs" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/67/3e74ae93d90ceeba72ed1a266dd3ca9abd625f315f0afd35f9b034acedd1/musicbrainzngs-0.7.1.tar.gz", hash = "sha256:ab1c0100fd0b305852e65f2ed4113c6de12e68afd55186987b8ed97e0f98e627", size = 117469, upload-time = "2020-01-11T17:38:47.581Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/fd/cef7b2580436910ccd2f8d3deec0f3c81743e15c0eb5b97dde3fbf33c0c8/musicbrainzngs-0.7.1-py2.py3-none-any.whl", hash = "sha256:e841a8f975104c0a72290b09f59326050194081a5ae62ee512f41915090e1a10", size = 25289, upload-time = "2020-01-11T17:38:45.469Z" }, -] - [[package]] name = "mutagen" version = "1.47.0" @@ -1349,6 +1355,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] +[[package]] +name = "numba" +version = "0.65.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llvmlite" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/61/7299643b9c18d669e04be7c5bcb64d985070d07553274817b45b049e7bfe/numba-0.65.0.tar.gz", hash = "sha256:edad0d9f6682e93624c00125a471ae4df186175d71fd604c983c377cdc03e68b", size = 2764131, upload-time = "2026-04-01T03:52:01.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/2f/8bd31a1ea43c01ac215283d83aa5f8d5acbe7a36c85b82f1757bfe9ccb31/numba-0.65.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:b27ee4847e1bfb17e9604d100417ee7c1d10f15a6711c6213404b3da13a0b2aa", size = 2680705, upload-time = "2026-04-01T03:51:32.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/36/88406bd58600cc696417b8e5dd6a056478da808f3eaf48d18e2421e0c2d9/numba-0.65.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a52d92ffd297c10364bce60cd1fcb88f99284ab5df085f2c6bcd1cb33b529a6f", size = 3801411, upload-time = "2026-04-01T03:51:34.321Z" }, + { url = "https://files.pythonhosted.org/packages/0c/61/ce753a1d7646dd477e16d15e89473703faebb8995d2f71d7ad69a540b565/numba-0.65.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da8e371e328c06d0010c3d8b44b21858652831b85bcfba78cb22c042e22dbd8e", size = 3501622, upload-time = "2026-04-01T03:51:36.348Z" }, + { url = "https://files.pythonhosted.org/packages/7d/86/db87a5393f1b1fabef53ac3ba4e6b938bb27e40a04ad7cc512098fcae032/numba-0.65.0-cp312-cp312-win_amd64.whl", hash = "sha256:59bb9f2bb9f1238dfd8e927ba50645c18ae769fef4f3d58ea0ea22a2683b91f5", size = 2749979, upload-time = "2026-04-01T03:51:37.88Z" }, +] + [[package]] name = "numpy" version = "2.4.3" @@ -1675,6 +1697,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] +[[package]] +name = "pyrate-limiter" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/0c/6e78218e6ef726be35a4c0a5e2e281e36ddd940566800219e96d13de99ad/pyrate_limiter-4.1.0.tar.gz", hash = "sha256:be1ac413a263aa410b98757d1b01a880650948a1fc3a959512f15865eb58dbf3", size = 306136, upload-time = "2026-03-22T14:43:03.739Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/fd/57181fafae08385d00ea2702be246ab8035352a0a8e1f63391c2bcad74d4/pyrate_limiter-4.1.0-py3-none-any.whl", hash = "sha256:2696b4e4a6cffb3d40fc76662baccb766697893f0979e12bebbfc7d3b6b19603", size = 38197, upload-time = "2026-03-22T14:43:01.975Z" }, +] + [[package]] name = "pysocks" version = "1.7.1" @@ -1908,6 +1939,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "requests-ratelimiter" +version = "0.9.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyrate-limiter" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/bf/02d3ddb3a47fc8889a87f96d17b73f4e2550c52b37e9891072fd9995e8f4/requests_ratelimiter-0.9.3.tar.gz", hash = "sha256:a706807210e0a5554be585f0cd6892cb57784343c4d40c7473456b26274a775c", size = 15810, upload-time = "2026-04-02T18:01:42.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/d1/96098646044b3ec25ab3c14f7a77ebd692dc0b8e305ad25e984b6c6460d2/requests_ratelimiter-0.9.3-py3-none-any.whl", hash = "sha256:f0d2c616891ba84d84aa9940c4251fd516bc6471d44313230653062eedc860d4", size = 11022, upload-time = "2026-04-02T18:01:41.603Z" }, +] + [[package]] name = "rich" version = "14.3.3" @@ -1992,6 +2036,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" }, ] +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, +] + [[package]] name = "selenium" version = "4.41.0" From 1c1e82ff416aaa7487826285f8d90e30397b5b8e Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Sat, 18 Apr 2026 19:26:12 +0200 Subject: [PATCH 7/7] Removed now unused imports --- backend/tests/unit/test_importer/test_session.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/tests/unit/test_importer/test_session.py b/backend/tests/unit/test_importer/test_session.py index dba94180..b37029fe 100644 --- a/backend/tests/unit/test_importer/test_session.py +++ b/backend/tests/unit/test_importer/test_session.py @@ -10,7 +10,6 @@ from .conftest import ( VALID_PATHS, album_path_absolute, - use_mock_tag_album, ) log = logging.getLogger(__name__) @@ -26,7 +25,6 @@ def test_generate_lookup(): """ for path in VALID_PATHS: p = Path(__file__).parent.parent.parent / "data" / "audio" / path - use_mock_tag_album(str(p)) state = SessionState(p) session = PreviewSession(state) @@ -48,7 +46,6 @@ class TestPreviewSessions: def get_state(self, path: str): p = album_path_absolute(path) self.session = PreviewSession(SessionState(p)) - use_mock_tag_album(str(p)) return self.session.run_sync() @pytest.mark.parametrize("path", VALID_PATHS)