From 4782e96599e39efebd1a592a1df5acecc9c11c55 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <39738318+semohr@users.noreply.github.com> Date: Wed, 1 Oct 2025 12:28:18 +0200 Subject: [PATCH 1/5] Move `vfs.py` to `beetsplug._utils` package to avoid polluting core namespace (#6017) This PR moves the `vfs.py` module, which is only used by plugins, to avoid polluting the main beets namespace. Also exposes the `vfs` and `art` module from beets with a deprecation warning. --- .git-blame-ignore-revs | 2 +- beets/__init__.py | 15 +++++++++++++++ beetsplug/_utils/__init__.py | 3 +++ {beets => beetsplug/_utils}/vfs.py | 18 +++++++++++++----- beetsplug/bench.py | 3 ++- beetsplug/bpd/__init__.py | 3 ++- docs/changelog.rst | 3 +++ test/{ => plugins/utils}/test_vfs.py | 2 +- 8 files changed, 40 insertions(+), 9 deletions(-) rename {beets => beetsplug/_utils}/vfs.py (82%) rename test/{ => plugins/utils}/test_vfs.py (97%) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 2ee64a97d..14b50859f 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -72,4 +72,4 @@ d93ddf8dd43e4f9ed072a03829e287c78d2570a2 # Moved plugin docs Further Reading chapter 33f1a5d0bef8ca08be79ee7a0d02a018d502680d # Moved art.py utility module from beets into beetsplug -28aee0fde463f1e18dfdba1994e2bdb80833722f \ No newline at end of file +28aee0fde463f1e18dfdba1994e2bdb80833722f diff --git a/beets/__init__.py b/beets/__init__.py index 10b0f58b0..65094330b 100644 --- a/beets/__init__.py +++ b/beets/__init__.py @@ -17,10 +17,25 @@ from sys import stderr import confuse +from .util import deprecate_imports + __version__ = "2.4.0" __author__ = "Adrian Sampson " +def __getattr__(name: str): + """Handle deprecated imports.""" + return deprecate_imports( + old_module=__name__, + new_module_by_name={ + "art": "beetsplug._utils", + "vfs": "beetsplug._utils", + }, + name=name, + version="3.0.0", + ) + + class IncludeLazyConfig(confuse.LazyConfig): """A version of Confuse's LazyConfig that also merges in data from YAML files specified in an `include` setting. diff --git a/beetsplug/_utils/__init__.py b/beetsplug/_utils/__init__.py index e69de29bb..7453f88bf 100644 --- a/beetsplug/_utils/__init__.py +++ b/beetsplug/_utils/__init__.py @@ -0,0 +1,3 @@ +from . import art, vfs + +__all__ = ["art", "vfs"] diff --git a/beets/vfs.py b/beetsplug/_utils/vfs.py similarity index 82% rename from beets/vfs.py rename to beetsplug/_utils/vfs.py index 4fd133f5a..6294b644c 100644 --- a/beets/vfs.py +++ b/beetsplug/_utils/vfs.py @@ -16,17 +16,25 @@ libraries. """ -from typing import Any, NamedTuple +from __future__ import annotations + +from typing import TYPE_CHECKING, NamedTuple from beets import util +if TYPE_CHECKING: + from beets.library import Library + class Node(NamedTuple): - files: dict[str, Any] - dirs: dict[str, Any] + files: dict[str, int] + # Maps filenames to Item ids. + + dirs: dict[str, Node] + # Maps directory names to child nodes. -def _insert(node, path, itemid): +def _insert(node: Node, path: list[str], itemid: int): """Insert an item into a virtual filesystem node.""" if len(path) == 1: # Last component. Insert file. @@ -40,7 +48,7 @@ def _insert(node, path, itemid): _insert(node.dirs[dirname], rest, itemid) -def libtree(lib): +def libtree(lib: Library) -> Node: """Generates a filesystem-like directory tree for the files contained in `lib`. Filesystem nodes are (files, dirs) named tuples in which both components are dictionaries. The first diff --git a/beetsplug/bench.py b/beetsplug/bench.py index cf72527e8..d77f1f92a 100644 --- a/beetsplug/bench.py +++ b/beetsplug/bench.py @@ -17,10 +17,11 @@ import cProfile import timeit -from beets import importer, library, plugins, ui, vfs +from beets import importer, library, plugins, ui from beets.autotag import match from beets.plugins import BeetsPlugin from beets.util.functemplate import Template +from beetsplug._utils import vfs def aunique_benchmark(lib, prof): diff --git a/beetsplug/bpd/__init__.py b/beetsplug/bpd/__init__.py index aa7013150..1a4f505dd 100644 --- a/beetsplug/bpd/__init__.py +++ b/beetsplug/bpd/__init__.py @@ -30,10 +30,11 @@ from typing import TYPE_CHECKING import beets import beets.ui -from beets import dbcore, logging, vfs +from beets import dbcore, logging from beets.library import Item from beets.plugins import BeetsPlugin from beets.util import as_string, bluelet +from beetsplug._utils import vfs if TYPE_CHECKING: from beets.dbcore.query import Query diff --git a/docs/changelog.rst b/docs/changelog.rst index 38037955e..b22bcd44b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -48,6 +48,9 @@ Other changes: - Moved ``art.py`` utility module from ``beets`` into ``beetsplug`` namespace as it is not used in the core beets codebase. It can now be found in ``beetsplug._utils``. +- Moved ``vfs.py`` utility module from ``beets`` into ``beetsplug`` namespace as + it is not used in the core beets codebase. It can now be found in + ``beetsplug._utils``. - :class:`beets.metadata_plugin.MetadataSourcePlugin`: Remove discogs specific disambiguation stripping. diff --git a/test/test_vfs.py b/test/plugins/utils/test_vfs.py similarity index 97% rename from test/test_vfs.py rename to test/plugins/utils/test_vfs.py index 7f75fbd83..9505075f9 100644 --- a/test/test_vfs.py +++ b/test/plugins/utils/test_vfs.py @@ -14,9 +14,9 @@ """Tests for the virtual filesystem builder..""" -from beets import vfs from beets.test import _common from beets.test.helper import BeetsTestCase +from beetsplug._utils import vfs class VFSTest(BeetsTestCase): From f6ca68319d25e78eefa76d1e6e0da6a4329fbe58 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <39738318+semohr@users.noreply.github.com> Date: Wed, 1 Oct 2025 12:58:57 +0200 Subject: [PATCH 2/5] Add git commit suffix to __version__ for development installs (#5967) Make it obvious when beets is installed from from a non major version. When installed locally this adds a git hash suffix and the distance to the last release. closes #4448 --- .gitignore | 3 +++ beets/__init__.py | 6 +++++- beets/_version.py | 7 +++++++ docs/changelog.rst | 2 ++ extra/release.py | 6 ------ pyproject.toml | 13 +++++++++++-- 6 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 beets/_version.py diff --git a/.gitignore b/.gitignore index 90ef7387d..102e1c3e4 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,6 @@ ENV/ # pyright pyrightconfig.json + +# Versioning +beets/_version.py diff --git a/beets/__init__.py b/beets/__init__.py index 65094330b..5f4c6657d 100644 --- a/beets/__init__.py +++ b/beets/__init__.py @@ -17,9 +17,10 @@ from sys import stderr import confuse +# Version management using poetry-dynamic-versioning +from ._version import __version__, __version_tuple__ from .util import deprecate_imports -__version__ = "2.4.0" __author__ = "Adrian Sampson " @@ -54,3 +55,6 @@ class IncludeLazyConfig(confuse.LazyConfig): config = IncludeLazyConfig("beets", __name__) + + +__all__ = ["__version__", "__version_tuple__", "config"] diff --git a/beets/_version.py b/beets/_version.py new file mode 100644 index 000000000..4dea56035 --- /dev/null +++ b/beets/_version.py @@ -0,0 +1,7 @@ +# This file is auto-generated during the build process. +# Do not edit this file directly. +# Placeholders are replaced during substitution. +# Run `git update-index --assume-unchanged beets/_version.py` +# to ignore local changes to this file. +__version__ = "0.0.0" +__version_tuple__ = (0, 0, 0) diff --git a/docs/changelog.rst b/docs/changelog.rst index b22bcd44b..a39b3db63 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -53,6 +53,8 @@ Other changes: ``beetsplug._utils``. - :class:`beets.metadata_plugin.MetadataSourcePlugin`: Remove discogs specific disambiguation stripping. +- When installing ``beets`` via git or locally the version string now reflects + the current git branch and commit hash. :bug:`4448` For developers and plugin authors: diff --git a/extra/release.py b/extra/release.py index 647cc49c9..b47de8966 100755 --- a/extra/release.py +++ b/extra/release.py @@ -174,12 +174,6 @@ FILENAME_AND_UPDATE_TEXT: list[tuple[Path, UpdateVersionCallable]] = [ PYPROJECT, lambda text, new: re.sub(r"(?<=\nversion = )[^\n]+", f'"{new}"', text), ), - ( - BASE / "beets" / "__init__.py", - lambda text, new: re.sub( - r"(?<=__version__ = )[^\n]+", f'"{new}"', text - ), - ), (CHANGELOG, update_changelog), (BASE / "docs" / "conf.py", update_docs_config), ] diff --git a/pyproject.toml b/pyproject.toml index 2546360ad..8338ce1c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -156,9 +156,18 @@ web = ["flask", "flask-cors"] [tool.poetry.scripts] beet = "beets.ui:main" + +[tool.poetry-dynamic-versioning] +enable = true +vcs = "git" +format = "{base}.dev{distance}+{commit}" + +[tool.poetry-dynamic-versioning.files."beets/_version.py"] +persistent-substitution = true + [build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +build-backend = "poetry_dynamic_versioning.backend" [tool.pipx-install] poethepoet = ">=0.26" From 70a4d0462dea54b6dd59affd8c65489be795ead8 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 1 Oct 2025 17:15:13 -0400 Subject: [PATCH 3/5] Persist spotify track attributes even if audio features are missing --- beetsplug/spotify.py | 11 +++++------ docs/changelog.rst | 3 +++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/beetsplug/spotify.py b/beetsplug/spotify.py index 0f6e0012b..f78041094 100644 --- a/beetsplug/spotify.py +++ b/beetsplug/spotify.py @@ -694,14 +694,13 @@ class SpotifyPlugin( audio_features = self.track_audio_features(spotify_track_id) if audio_features is None: self._log.info("No audio features found for: {}", item) - continue - for feature in audio_features.keys(): - if feature in self.spotify_audio_features.keys(): - item[self.spotify_audio_features[feature]] = audio_features[ - feature - ] + else: + for feature, value in audio_features.items(): + if feature in self.spotify_audio_features: + item[self.spotify_audio_features[feature]] = value item["spotify_updated"] = time.time() item.store() + self._log.debug("Stored spotify_track_popularity={} for {} (item id {})", item.get("spotify_track_popularity"), item.get("title"), item.id) if write: item.try_write() diff --git a/docs/changelog.rst b/docs/changelog.rst index a39b3db63..8c799783f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -19,6 +19,9 @@ New features: Bug fixes: +- :doc:`plugins/spotify` Ensure ``spotifysync`` keeps popularity, ISRC, + and related fields current even when audio features requests fail. + :bug:`6061` - :doc:`plugins/spotify` Fixed an issue where track matching and lookups could return incorrect or misleading results when using the Spotify plugin. The problem occurred primarily when no album was provided or when the album field From 7a097bb4b6dd2ec1f121737c36c82dc182951995 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 1 Oct 2025 17:47:26 -0400 Subject: [PATCH 4/5] lint --- docs/changelog.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 8c799783f..e74f0caa2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -19,9 +19,8 @@ New features: Bug fixes: -- :doc:`plugins/spotify` Ensure ``spotifysync`` keeps popularity, ISRC, - and related fields current even when audio features requests fail. - :bug:`6061` +- :doc:`plugins/spotify` Ensure ``spotifysync`` keeps popularity, ISRC, and + related fields current even when audio features requests fail. :bug:`6061` - :doc:`plugins/spotify` Fixed an issue where track matching and lookups could return incorrect or misleading results when using the Spotify plugin. The problem occurred primarily when no album was provided or when the album field From b66b2b51b5171a84bd3d20b96b6b101447ba132f Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 1 Oct 2025 17:48:41 -0400 Subject: [PATCH 5/5] Remove potentially expensive item.get() calls --- beetsplug/spotify.py | 1 - 1 file changed, 1 deletion(-) diff --git a/beetsplug/spotify.py b/beetsplug/spotify.py index f78041094..7cb9e330d 100644 --- a/beetsplug/spotify.py +++ b/beetsplug/spotify.py @@ -700,7 +700,6 @@ class SpotifyPlugin( item[self.spotify_audio_features[feature]] = value item["spotify_updated"] = time.time() item.store() - self._log.debug("Stored spotify_track_popularity={} for {} (item id {})", item.get("spotify_track_popularity"), item.get("title"), item.id) if write: item.try_write()