From 916d40f86fadcd8bd52ee72c2db67f90a3e75619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sun, 19 Jan 2025 16:07:32 +0000 Subject: [PATCH 1/9] Remove outdated namespace package definition and update docs See https://realpython.com/python-namespace-package. This setup is backwards-compatible, so plugins using the old pkgutil-based setup will continue working fine. This setup has an advantage where external plugins will now be able to import modules from 'beetsplug' package for typing purposes. Previously, mypy could not resolve these modules due to presence of `__init__.py`. --- beetsplug/__init__.py | 20 --------------- docs/changelog.rst | 2 ++ docs/dev/plugins.rst | 59 +++++++++++++++++++++++++------------------ setup.cfg | 2 ++ 4 files changed, 38 insertions(+), 45 deletions(-) delete mode 100644 beetsplug/__init__.py diff --git a/beetsplug/__init__.py b/beetsplug/__init__.py deleted file mode 100644 index ad573cdb3..000000000 --- a/beetsplug/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# This file is part of beets. -# Copyright 2016, Adrian Sampson. -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. - -"""A namespace package for beets plugins.""" - -# Make this a namespace package. -from pkgutil import extend_path - -__path__ = extend_path(__path__, __name__) diff --git a/docs/changelog.rst b/docs/changelog.rst index 54d085599..62cd0c4cc 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -74,6 +74,8 @@ Bug fixes: For packagers: * The minimum supported Python version is now 3.9. +* External plugin developers: ``beetsplug/__init__.py`` file can be removed + from your plugin as beets now uses native/implicit namespace package setup. Other changes: diff --git a/docs/dev/plugins.rst b/docs/dev/plugins.rst index fa7fa645e..3daaf1e5c 100644 --- a/docs/dev/plugins.rst +++ b/docs/dev/plugins.rst @@ -3,46 +3,55 @@ Writing Plugins --------------- -A beets plugin is just a Python module inside the ``beetsplug`` namespace -package. (Check out this `Stack Overflow question about namespace packages`_ if -you haven't heard of them.) So, to make one, create a directory called -``beetsplug`` and put two files in it: one called ``__init__.py`` and one called -``myawesomeplugin.py`` (but don't actually call it that). Your directory -structure should look like this:: +A beets plugin is just a Python module or package inside the ``beetsplug`` +namespace package. (Check out this `Stack Overflow question about namespace +packages`_ if you haven't heard of them.) So, to make one, create a directory +called ``beetsplug`` and add either your plugin module:: beetsplug/ - __init__.py myawesomeplugin.py -.. _Stack Overflow question about namespace packages: - https://stackoverflow.com/questions/1675734/how-do-i-create-a-namespace-package-in-python/1676069#1676069 +or your plugin subpackage:: -Then, you'll need to put this stuff in ``__init__.py`` to make ``beetsplug`` a -namespace package:: + beetsplug/ + myawesomeplugin/ + __init__.py + myawesomeplugin.py - from pkgutil import extend_path - __path__ = extend_path(__path__, __name__) +.. attention:: -That's all for ``__init__.py``; you can can leave it alone. The meat of your -plugin goes in ``myawesomeplugin.py``. There, you'll have to import the -``beets.plugins`` module and define a subclass of the ``BeetsPlugin`` class -found therein. Here's a skeleton of a plugin file:: + You do not anymore need to add a ``__init__.py`` file to the ``beetsplug`` + directory. Python treats your plugin as a namespace package automatically, + thus we do not depend on ``pkgutil``-based setup in the ``__init__.py`` + file anymore. + +The meat of your plugin goes in ``myawesomeplugin.py``. There, you'll have to +import ``BeetsPlugin`` from ``beets.plugins`` and subclass it, for example + +.. code-block:: python from beets.plugins import BeetsPlugin - class MyPlugin(BeetsPlugin): + class MyAwesomePlugin(BeetsPlugin): pass Once you have your ``BeetsPlugin`` subclass, there's a variety of things your plugin can do. (Read on!) To use your new plugin, make sure the directory that contains your -``beetsplug`` directory is in the Python -path (using ``PYTHONPATH`` or by installing in a `virtualenv`_, for example). -Then, as described above, edit your ``config.yaml`` to include -``plugins: myawesomeplugin`` (substituting the name of the Python module -containing your plugin). +``beetsplug`` directory is in the Python path (using ``PYTHONPATH`` or by +installing in a `virtualenv`_, for example). Then, add your plugin to beets +configuration +.. code-block:: yaml + + # config.yaml + plugins: + - myawesomeplugin + +and you're good to go! + +.. _Stack Overflow question about namespace packages: https://stackoverflow.com/a/27586272/9582674 .. _virtualenv: https://pypi.org/project/virtualenv .. _add_subcommands: @@ -249,13 +258,13 @@ The events currently available are: during a ``beet import`` interactive session. Plugins can use this event for :ref:`appending choices to the prompt ` by returning a list of ``PromptChoices``. Parameters: ``task`` and ``session``. - + * `mb_track_extract`: called after the metadata is obtained from MusicBrainz. The parameter is a ``dict`` containing the tags retrieved from MusicBrainz for a track. Plugins must return a new (potentially empty) ``dict`` with additional ``field: value`` pairs, which the autotagger will apply to the item, as flexible attributes if ``field`` is not a hardcoded - field. Fields already present on the track are overwritten. + field. Fields already present on the track are overwritten. Parameter: ``data`` * `mb_album_extract`: Like `mb_track_extract`, but for album tags. Overwrites diff --git a/setup.cfg b/setup.cfg index 8e3d7e3b8..ba580f2c9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,3 +37,5 @@ allow_any_generics = false # FIXME: Would be better to actually type the libraries (if under our control), # or write our own stubs. For now, silence errors ignore_missing_imports = true +namespace_packages = true +explicit_package_bases = true From 89f1ef4d2fe068f5a41047dee5c9815acea620b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Thu, 30 Jan 2025 12:19:25 +0000 Subject: [PATCH 2/9] Add documentation links --- docs/dev/plugins.rst | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/dev/plugins.rst b/docs/dev/plugins.rst index 3daaf1e5c..96e69153d 100644 --- a/docs/dev/plugins.rst +++ b/docs/dev/plugins.rst @@ -4,9 +4,9 @@ Writing Plugins --------------- A beets plugin is just a Python module or package inside the ``beetsplug`` -namespace package. (Check out this `Stack Overflow question about namespace -packages`_ if you haven't heard of them.) So, to make one, create a directory -called ``beetsplug`` and add either your plugin module:: +namespace package. (Check out `this article`_ and `this Stack Overflow +question`_ if you haven't heard about namespace packages.) So, to make one, +create a directory called ``beetsplug`` and add either your plugin module:: beetsplug/ myawesomeplugin.py @@ -38,10 +38,9 @@ import ``BeetsPlugin`` from ``beets.plugins`` and subclass it, for example Once you have your ``BeetsPlugin`` subclass, there's a variety of things your plugin can do. (Read on!) -To use your new plugin, make sure the directory that contains your -``beetsplug`` directory is in the Python path (using ``PYTHONPATH`` or by -installing in a `virtualenv`_, for example). Then, add your plugin to beets -configuration +To use your new plugin, package your plugin (see how to do this with `poetry`_ +or `setuptools`_, for example) and install it into your ``beets`` virtual +environment. Then, add your plugin to beets configuration .. code-block:: yaml @@ -51,8 +50,10 @@ configuration and you're good to go! -.. _Stack Overflow question about namespace packages: https://stackoverflow.com/a/27586272/9582674 -.. _virtualenv: https://pypi.org/project/virtualenv +.. _this article: https://realpython.com/python-namespace-package/#setting-up-some-namespace-packages +.. _this Stack Overflow question: https://stackoverflow.com/a/27586272/9582674 +.. _poetry: https://python-poetry.org/docs/pyproject/#packages +.. _setuptools: https://setuptools.pypa.io/en/latest/userguide/package_discovery.html#finding-simple-packages .. _add_subcommands: From f52079071376bc230a056c8e9edd9bf94205837c Mon Sep 17 00:00:00 2001 From: valrus Date: Sat, 11 Jan 2025 20:03:28 -0800 Subject: [PATCH 3/9] s/macthin/matching/ --- test/plugins/test_importadded.py | 2 +- test/test_importer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/plugins/test_importadded.py b/test/plugins/test_importadded.py index d48ec6c46..453824dfb 100644 --- a/test/plugins/test_importadded.py +++ b/test/plugins/test_importadded.py @@ -57,7 +57,7 @@ class ImportAddedTest(PluginMixin, ImportTestCase): os.path.getmtime(mfile.path) for mfile in self.import_media ) self.matcher = AutotagStub().install() - self.matcher.macthin = AutotagStub.GOOD + self.matcher.matching = AutotagStub.GOOD self.importer = self.setup_importer() self.importer.add_choice(importer.action.APPLY) diff --git a/test/test_importer.py b/test/test_importer.py index ad6b837f5..b282277e2 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -440,7 +440,7 @@ class ImportTest(ImportTestCase): self.prepare_album_for_import(1) self.setup_importer() self.matcher = AutotagStub().install() - self.matcher.macthin = AutotagStub.GOOD + self.matcher.matching = AutotagStub.GOOD def tearDown(self): super().tearDown() From d298738612323341e2462801b6d67acd28f02de3 Mon Sep 17 00:00:00 2001 From: valrus Date: Sat, 11 Jan 2025 20:03:38 -0800 Subject: [PATCH 4/9] add missing space in comment --- beets/test/helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beets/test/helper.py b/beets/test/helper.py index 4effa47f8..c0d785d6e 100644 --- a/beets/test/helper.py +++ b/beets/test/helper.py @@ -675,7 +675,7 @@ class ImportSessionFixture(ImportSession): >>> importer.run() This imports ``/path/to/import`` into `lib`. It skips the first - album and imports thesecond one with metadata from the tags. For the + album and imports the second one with metadata from the tags. For the remaining albums, the metadata from the autotagger will be applied. """ From 99d2da66dc8196c60a85786c5d0f965b4f0a4c45 Mon Sep 17 00:00:00 2001 From: valrus Date: Sat, 11 Jan 2025 20:41:19 -0800 Subject: [PATCH 5/9] use actual value of matcher, not typo'd one --- test/plugins/test_importadded.py | 2 +- test/test_importer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/plugins/test_importadded.py b/test/plugins/test_importadded.py index 453824dfb..608afb399 100644 --- a/test/plugins/test_importadded.py +++ b/test/plugins/test_importadded.py @@ -57,7 +57,7 @@ class ImportAddedTest(PluginMixin, ImportTestCase): os.path.getmtime(mfile.path) for mfile in self.import_media ) self.matcher = AutotagStub().install() - self.matcher.matching = AutotagStub.GOOD + self.matcher.matching = AutotagStub.IDENT self.importer = self.setup_importer() self.importer.add_choice(importer.action.APPLY) diff --git a/test/test_importer.py b/test/test_importer.py index b282277e2..a28b646cf 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -440,7 +440,7 @@ class ImportTest(ImportTestCase): self.prepare_album_for_import(1) self.setup_importer() self.matcher = AutotagStub().install() - self.matcher.matching = AutotagStub.GOOD + self.matcher.matching = AutotagStub.IDENT def tearDown(self): super().tearDown() From 12fa3432a9a2ba447e211365166e875963033b03 Mon Sep 17 00:00:00 2001 From: seth-milojevic <52462300+seth-milojevic@users.noreply.github.com> Date: Sat, 8 Feb 2025 00:20:10 -0500 Subject: [PATCH 6/9] Close file descriptor generated from tempfile.mkstemp() Without explicitly closing this file descriptor, the temp file would be kept open until the program exited and could not be deleted by the fetchart plugin. --- beets/util/__init__.py | 5 ++++- docs/changelog.rst | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 32a63b216..ac4e3bc3c 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -1130,7 +1130,10 @@ def get_temp_filename( tempdir = get_module_tempdir(module) tempdir.mkdir(parents=True, exist_ok=True) - _, filename = tempfile.mkstemp(dir=tempdir, prefix=prefix, suffix=suffix) + descriptor, filename = tempfile.mkstemp( + dir=tempdir, prefix=prefix, suffix=suffix + ) + os.close(descriptor) return bytestring_path(filename) diff --git a/docs/changelog.rst b/docs/changelog.rst index 62cd0c4cc..b39e1b291 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,6 +22,9 @@ New features: Bug fixes: +* Fix fetchart bug where a tempfile could not be deleted due to never being + properly closed. + :bug:`5521` * :doc:`plugins/lyrics`: LRCLib will fallback to plain lyrics if synced lyrics are not found and `synced` flag is set to `yes`. * Synchronise files included in the source distribution with what we used to From 5685cf43cf14a03e7be2e8d3c16866c8ea772daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sat, 8 Feb 2025 08:52:35 +0000 Subject: [PATCH 7/9] Align musica lyrics source expected lyrics with whats returned --- test/plugins/lyrics_pages.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/plugins/lyrics_pages.py b/test/plugins/lyrics_pages.py index 84c9e2441..2d681e111 100644 --- a/test/plugins/lyrics_pages.py +++ b/test/plugins/lyrics_pages.py @@ -456,16 +456,6 @@ lyrics_pages = [ LyricsPage.make( "https://www.musica.com/letras.asp?letra=59862", """ - Lady Madonna, children at your feet - Wonder how you manage to make ends meet - Who finds the money when you pay the rent? - Did you think that money was heaven sent? - - Friday night arrives without a suitcase - Sunday morning creeping like a nun - Monday's child has learned to tie his bootlace - See how they run - Lady Madonna, baby at your breast Wonders how you manage to feed the rest From 6205e19b74bc529d3b0a5a95bccd142dd5b1c127 Mon Sep 17 00:00:00 2001 From: seth-milojevic <52462300+seth-milojevic@users.noreply.github.com> Date: Sat, 8 Feb 2025 12:41:37 -0500 Subject: [PATCH 8/9] Update docs/changelog.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Šarūnas Nejus --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index b39e1b291..fd1149178 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,7 +22,7 @@ New features: Bug fixes: -* Fix fetchart bug where a tempfile could not be deleted due to never being +* :doc:`plugins/fetchart`: Fix fetchart bug where a tempfile could not be deleted due to never being properly closed. :bug:`5521` * :doc:`plugins/lyrics`: LRCLib will fallback to plain lyrics if synced lyrics From 5a9c769f5cd413eb4f92d4e2d91ed9d3067456fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Sat, 1 Feb 2025 15:59:20 +0000 Subject: [PATCH 9/9] Link to specific bug report/feature request templates in docs --- docs/faq.rst | 18 ++++++++++-------- docs/guides/tagger.rst | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index b740c6503..ac7818ab2 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -174,9 +174,8 @@ pages. …report a bug in beets? ----------------------- -We use the `issue tracker `__ -on GitHub. `Enter a new issue `__ -there to report a bug. Please follow these guidelines when reporting an issue: +We use the `issue tracker`_ on GitHub where you can `open a new ticket`_. +Please follow these guidelines when reporting an issue: - Most importantly: if beets is crashing, please `include the traceback `__. Tracebacks can be more @@ -206,6 +205,7 @@ If you've never reported a bug before, Mozilla has some well-written `general guidelines for good bug reports`_. +.. _issue tracker: https://github.com/beetbox/beets/issues .. _general guidelines for good bug reports: https://developer.mozilla.org/en-US/docs/Mozilla/QA/Bug_writing_guidelines @@ -343,11 +343,11 @@ read the file. You can also use specialized programs for checking file integrity---for example, type ``metaflac --list music.flac`` to check FLAC files. -If beets still complains about a file that seems to be valid, `file a -bug `__ and we'll look into -it. There's always a possibility that there's a bug "upstream" in the -`Mutagen `__ library used by beets, -in which case we'll forward the bug to that project's tracker. +If beets still complains about a file that seems to be valid, `open a new +ticket`_ and we'll look into it. There's always a possibility that there's +a bug "upstream" in the `Mutagen `__ +library used by beets, in which case we'll forward the bug to that project's +tracker. .. _importhang: @@ -398,3 +398,5 @@ try `this Super User answer`_. .. _this Super User answer: https://superuser.com/a/284361/4569 .. _pip: https://pip.pypa.io/en/stable/ +.. _open a new ticket: + https://github.com/beetbox/beets/issues/new?template=bug-report.md diff --git a/docs/guides/tagger.rst b/docs/guides/tagger.rst index 68ad908e8..bf1ecbd8a 100644 --- a/docs/guides/tagger.rst +++ b/docs/guides/tagger.rst @@ -76,7 +76,8 @@ all of these limitations. Musepack, Windows Media, Opus, and AIFF files are supported. (Do you use some other format? Please `file a feature request`_!) -.. _file a feature request: https://github.com/beetbox/beets/issues/new +.. _file a feature request: + https://github.com/beetbox/beets/issues/new?template=feature-request.md Now that that's out of the way, let's tag some music.