From a9f7ee8d1e3f1af4f10bd84bd82cf7c3fbd8dd02 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 19 Oct 2025 16:33:08 -0700 Subject: [PATCH] working on changing position in import process - may consider options for pre or post import --- beetsplug/titlecase.py | 21 ++++++------ poetry.lock | 4 +-- pyproject.toml | 3 +- test/plugins/test_titlecase.py | 61 ++++++++++++++++++++++++---------- 4 files changed, 58 insertions(+), 31 deletions(-) diff --git a/beetsplug/titlecase.py b/beetsplug/titlecase.py index b57754aea..29b648837 100644 --- a/beetsplug/titlecase.py +++ b/beetsplug/titlecase.py @@ -20,10 +20,8 @@ from beets.dbcore import types from beets import ui from titlecase import titlecase from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from beets.importer import ImportSession, ImportTask - from beets.library import Item +from beets.library import Item +from beets.importer import ImportSession, ImportTask __author__ = "henryoberholtzer@gmail.com" __version__ = "1.0" @@ -58,6 +56,7 @@ class TitlecasePlugin(BeetsPlugin): self.config.add( { + "auto": True, "preserve": [], "small_first_last": True, "titlecase_metadata": True, @@ -66,6 +65,7 @@ class TitlecasePlugin(BeetsPlugin): } ) """ + auto - automatically apply to new imports preserve - provide a list of words/acronyms with specific case requirements small_first_last - if small characters should be title cased at beginning titlecase_metadata - if metadata fields should have title case applied @@ -98,6 +98,8 @@ class TitlecasePlugin(BeetsPlugin): self.preserve[word.upper()] = word self.__init_field_list__() + self.import_stages = [self.imported] + def __init_field_list__(self) -> None: """ Creates the set for fields to process in tagging. If we have include_fields from config, the shared fields will be used. @@ -106,17 +108,16 @@ class TitlecasePlugin(BeetsPlugin): Last, the EXCLUDED_INFO_FIELDS are removed to prevent unitentional modification. """ initial_field_list = set([ - k for k, v in Item()._fields.items() if - isinstance(v, types.STRING) or - isinstance(v, types.SEMICOLONS_SPACE_DSV) or - isinstance(v, types.MULTI_VALUE_DSV) - ) + k for k, v in Item()._fields.items() if + isinstance(v, types.String) or + isinstance(v, types.DelimitedString) + ]) if (incl := self.config["include_fields"].as_str_seq()): initial_field_list = initial_field_list.intersection(set(incl)) if (excl := self.config["exclude_fields"].as_str_seq()): initial_field_list -= set(excl) initial_field_list -= set(EXCLUDED_INFO_FIELDS) - self.fields_to_process = basic_fields_list + self.fields_to_process = initial_field_list def __preserved__(self, word, **kwargs) -> str | None: """ Callback function for words to preserve case of.""" diff --git a/poetry.lock b/poetry.lock index e125ac896..58e874deb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3413,7 +3413,7 @@ files = [ name = "titlecase" version = "2.4.1" description = "Python Port of John Gruber's titlecase.pl" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "titlecase-2.4.1.tar.gz", hash = "sha256:7d83a277ccbbda11a2944e78a63e5ccaf3d32f828c594312e4862f9a07f635f5"}, @@ -3688,4 +3688,4 @@ web = ["flask", "flask-cors"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4" -content-hash = "1db39186aca430ef6f1fd9e51b9dcc3ed91880a458bc21b22d950ed8589fdf5a" +content-hash = "fab72aa11a8622829c1f4b5d6d76ab87632f1649a04602bba26202a16a02d482" diff --git a/pyproject.toml b/pyproject.toml index 98d316b95..7529e2263 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,8 +76,7 @@ requests = { version = "*", optional = true } resampy = { version = ">=0.4.3", optional = true } requests-oauthlib = { version = ">=0.6.1", optional = true } soco = { version = "*", optional = true } -titlecase = { version = ">=2.4.1", optional = true } - +titlecase = "^2.4.1" pydata-sphinx-theme = { version = "*", optional = true } sphinx = { version = "*", optional = true } sphinx-design = { version = "^0.6.1", optional = true } diff --git a/test/plugins/test_titlecase.py b/test/plugins/test_titlecase.py index aa830bf99..ccff8fcd3 100644 --- a/test/plugins/test_titlecase.py +++ b/test/plugins/test_titlecase.py @@ -17,8 +17,8 @@ import pytest from beets import config -from beets.test.helper import BeetsTestCase -from beetsplug.titlecase import TitlecasePlugin +from beets.test.helper import PluginTestCase +from beetsplug.titlecase import TitlecasePlugin, EXCLUDED_INFO_FIELDS @pytest.mark.parametrize("given, expected", [("a", "A"), @@ -35,30 +35,57 @@ def test_basic_titlecase(given, expected): assert TitlecasePlugin().titlecase(given) == expected -class TitlecasePluginTest(BeetsTestCase): +class TitlecasePluginTest(PluginTestCase): + plugin = "titlecase" + preload_plugin = False def test_preserved_case(self): """ Test using given strings to preserve case """ - names_to_preserve = ["easyFun", "A.D.O.R.", - "D.R.", "ABBA", "LaTeX"] - config["titlecase"]["preserve"] = names_to_preserve - for name in names_to_preserve: - assert TitlecasePlugin().titlecase( - name.lower()) == name + names_to_preserve = ["easyFun", "A.D.O.R.", "D.R.", "ABBA", "LaTeX"] + with self.configure_plugin({ + "preserve": names_to_preserve}): + config["titlecase"]["preserve"] = names_to_preserve + for name in names_to_preserve: + assert TitlecasePlugin().titlecase( + name.lower()) == name def test_small_first_last(self): - config["titlecase"]["small_first_last"] = False - assert TitlecasePlugin().titlecase( - "A Simple Trial") == "a Simple Trial" - config["titlecase"]["small_first_last"] = True - assert TitlecasePlugin().titlecase( - "A simple Trial") == "A Simple Trial" + with self.configure_plugin({ + "small_first_last": False}): + assert TitlecasePlugin().titlecase( + "A Simple Trial") == "a Simple Trial" + with self.configure_plugin({ + "small_first_last": True}): + assert TitlecasePlugin().titlecase( + "A simple Trial") == "A Simple Trial" def test_ui_command(self): assert 1 == 3 def test_imported(self): - assert 1 == 3 + item = self.add_item( + artist="A poorly cased artist", + albumartist="not vEry good tItle caSE", + mb_artistid="case sensitive field") + assert item.artist == "A Poorly Cased Artist" + assert item.albumartist == "Not Very Good Title Case" - def test_init_field_list(self): + def test_field_list_default_excluded(self): + excluded = list(EXCLUDED_INFO_FIELDS) + config["titlecase"]["include_fields"] = excluded + t = TitlecasePlugin() + for field in excluded: + assert field not in t.fields_to_process + + def test_field_list_included(self): + config["titlecase"]["include_fields"] = ["album", "albumartist"] + t = TitlecasePlugin() + t.fields_to_process == ["album", "albumartist"] + + def test_field_list_exclude(self): + excluded = ["album", "albumartist"] + config["titlecase"]["exclude_fields"] = excluded + t = TitlecasePlugin() + for field in excluded: + assert field not in t.fields_to_process