Fix the rest of the tests

This commit is contained in:
Šarūnas Nejus 2026-02-15 13:17:22 +00:00
parent ce8128b8c4
commit 9618137dbf
No known key found for this signature in database
17 changed files with 91 additions and 89 deletions

View file

@ -75,7 +75,7 @@ def item(lib=None, **kwargs):
artist="the artist",
albumartist="the album artist",
album="the album",
genre="the genre",
genres=["the genre"],
lyricist="the lyricist",
composer="the composer",
arranger="the arranger",

View file

@ -79,7 +79,8 @@ TRACK_ATTR_MAP = {
"month": "month",
"day": "day",
"bpm": "bpm",
"genre": "genre",
"genre": "genres",
"genres": "genres",
"recording-mbid": "mb_trackid", # beets trackid is MB recording
"track-mbid": "mb_releasetrackid",
"composer": "composer",
@ -109,7 +110,8 @@ ALBUM_ATTR_MAP = {
"year": "year",
"month": "month",
"day": "day",
"genre": "genre",
"genre": "genres",
"genres": "genres",
"release-mbid": "mb_albumid",
"release-group-mbid": "mb_releasegroupid",
}

View file

@ -1351,7 +1351,7 @@ class Server(BaseServer):
"AlbumArtist": "albumartist",
"AlbumArtistSort": "albumartist_sort",
"Label": "label",
"Genre": "genre",
"Genre": "genres",
"Date": "year",
"OriginalDate": "original_year",
"Composer": "composer",

View file

@ -672,10 +672,13 @@ class MusicBrainzPlugin(MusicBrainzAPIMixin, MetadataSourcePlugin):
for source in sources:
for genreitem in source:
genres[genreitem["name"]] += int(genreitem["count"])
info.genres = [
genre
for genre, _count in sorted(genres.items(), key=lambda g: -g[1])
]
if genres:
info.genres = [
genre
for genre, _count in sorted(
genres.items(), key=lambda g: -g[1]
)
]
# We might find links to external sources (Discogs, Bandcamp, ...)
external_ids = self.config["external_ids"].get()

View file

@ -121,7 +121,7 @@ instance the following configuration exports the ``id`` and ``genre`` fields:
output: extm3u
fields:
- id
- genre
- genres
playlists:
- name: all.m3u
query: ''
@ -132,7 +132,7 @@ look as follows:
::
#EXTM3U
#EXTINF:805 id="1931" genre="Progressive%20Rock",Led Zeppelin - Stairway to Heaven
#EXTINF:805 id="1931" genres="Progressive%20Rock",Led Zeppelin - Stairway to Heaven
../music/singles/Led Zeppelin/Stairway to Heaven.mp3
To give a usage example, the webm3u_ and Beetstream_ plugins read the exported

View file

@ -143,9 +143,9 @@ Optional command flags:
:ref:`set_fields` configuration dictionary. You can use the option multiple
times on the command line, like so:
::
.. code-block:: sh
beet import --set genre="Alternative Rock" --set mood="emotional"
beet import --set genres="Alternative Rock" --set mood="emotional"
.. _py7zr: https://pypi.org/project/py7zr/

View file

@ -853,11 +853,11 @@ set_fields
A dictionary indicating fields to set to values for newly imported music. Here's
an example:
::
.. code-block:: yaml
set_fields:
genre: 'To Listen'
collection: 'Unordered'
genres: To Listen
collection: Unordered
Other field/value pairs supplied via the ``--set`` option on the command-line
override any settings here for fields with the same name.
@ -1172,9 +1172,9 @@ Here's an example file:
color: yes
paths:
default: $genre/$albumartist/$album/$track $title
default: %first{$genres}/$albumartist/$album/$track $title
singleton: Singletons/$artist - $title
comp: $genre/$album/$track $title
comp: %first{$genres}/$album/$track $title
albumtype:soundtrack: Soundtracks/$album/$track $title
.. only:: man

View file

@ -11,7 +11,7 @@ class IHatePluginTest(unittest.TestCase):
def test_hate(self):
match_pattern = {}
test_item = Item(
genre="TestGenre", album="TestAlbum", artist="TestArtist"
genres=["TestGenre"], album="TestAlbum", artist="TestArtist"
)
task = importer.SingletonImportTask(None, test_item)
@ -27,19 +27,19 @@ class IHatePluginTest(unittest.TestCase):
assert IHatePlugin.do_i_hate_this(task, match_pattern)
# Query is blocked by AND clause.
match_pattern = ["album:notthis genre:testgenre"]
match_pattern = ["album:notthis genres:testgenre"]
assert not IHatePlugin.do_i_hate_this(task, match_pattern)
# Both queries are blocked by AND clause with unmatched condition.
match_pattern = [
"album:notthis genre:testgenre",
"album:notthis genres:testgenre",
"artist:testartist album:notthis",
]
assert not IHatePlugin.do_i_hate_this(task, match_pattern)
# Only one query should fire.
match_pattern = [
"album:testalbum genre:testgenre",
"album:testalbum genres:testgenre",
"artist:testartist album:notthis",
]
assert IHatePlugin.do_i_hate_this(task, match_pattern)

View file

@ -146,7 +146,7 @@ class LastGenrePluginTest(IOMixin, PluginTestCase):
albumartist="Pretend Artist",
artist="Pretend Artist",
title="Pretend Track",
genre="Original Genre",
genres=["Original Genre"],
)
album = self.lib.add_album([item])
@ -159,8 +159,8 @@ class LastGenrePluginTest(IOMixin, PluginTestCase):
assert "genres:" in output
album.load()
assert album.genre == "Original Genre"
assert album.items()[0].genre == "Original Genre"
assert album.genres == ["Original Genre"]
assert album.items()[0].genres == ["Original Genre"]
def test_no_duplicate(self):
"""Remove duplicated genres."""

View file

@ -76,11 +76,11 @@ class SmartPlaylistTest(BeetsTestCase):
{"name": "one_non_empty_sort", "query": ["foo year+", "bar"]},
{
"name": "multiple_sorts",
"query": ["foo year+", "bar genre-"],
"query": ["foo year+", "bar genres-"],
},
{
"name": "mixed",
"query": ["foo year+", "bar", "baz genre+ id-"],
"query": ["foo year+", "bar", "baz genres+ id-"],
},
]
)
@ -102,11 +102,11 @@ class SmartPlaylistTest(BeetsTestCase):
# Multiple queries store individual sorts in the tuple
assert all(isinstance(x, NullSort) for x in sorts["only_empty_sorts"])
assert sorts["one_non_empty_sort"] == [sort("year"), NullSort()]
assert sorts["multiple_sorts"] == [sort("year"), sort("genre", False)]
assert sorts["multiple_sorts"] == [sort("year"), sort("genres", False)]
assert sorts["mixed"] == [
sort("year"),
NullSort(),
MultipleSort([sort("genre"), sort("id", False)]),
MultipleSort([sort("genres"), sort("id", False)]),
]
def test_matches(self):
@ -259,7 +259,7 @@ class SmartPlaylistTest(BeetsTestCase):
type(i).title = PropertyMock(return_value="fake Title")
type(i).length = PropertyMock(return_value=300.123)
type(i).path = PropertyMock(return_value=b"/tagada.mp3")
a = {"id": 456, "genre": "Fake Genre"}
a = {"id": 456, "genres": "Fake Genre"}
i.__getitem__.side_effect = a.__getitem__
i.evaluate_template.side_effect = lambda pl, _: pl.replace(
b"$title",
@ -280,7 +280,7 @@ class SmartPlaylistTest(BeetsTestCase):
config["smartplaylist"]["output"] = "extm3u"
config["smartplaylist"]["relative_to"] = False
config["smartplaylist"]["playlist_dir"] = str(dir)
config["smartplaylist"]["fields"] = ["id", "genre"]
config["smartplaylist"]["fields"] = ["id", "genres"]
try:
spl.update_playlists(lib)
except Exception:
@ -297,7 +297,7 @@ class SmartPlaylistTest(BeetsTestCase):
assert content == (
b"#EXTM3U\n"
b'#EXTINF:300 id="456" genre="Fake%20Genre",Fake Artist - fake Title\n'
b'#EXTINF:300 id="456" genres="Fake%20Genre",Fake Artist - fake Title\n'
b"/tagada.mp3\n"
)

View file

@ -310,7 +310,7 @@ class ImportSingletonTest(AutotagImportTestCase):
config["import"]["set_fields"] = {
"collection": collection,
"genre": genre,
"genres": genre,
"title": "$title - formatted",
"disc": disc,
}
@ -322,7 +322,7 @@ class ImportSingletonTest(AutotagImportTestCase):
for item in self.lib.items():
item.load() # TODO: Not sure this is necessary.
assert item.genre == genre
assert item.genres == [genre]
assert item.collection == collection
assert item.title == "Tag Track 1 - formatted"
assert item.disc == disc
@ -337,7 +337,7 @@ class ImportSingletonTest(AutotagImportTestCase):
for item in self.lib.items():
item.load()
assert item.genre == genre
assert item.genres == [genre]
assert item.collection == collection
assert item.title == "Applied Track 1 - formatted"
assert item.disc == disc
@ -373,12 +373,12 @@ class ImportTest(PathsMixin, AutotagImportTestCase):
config["import"]["from_scratch"] = True
for mediafile in self.import_media:
mediafile.genre = "Tag Genre"
mediafile.genres = ["Tag Genre"]
mediafile.save()
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert self.lib.items().get().genre == ""
assert not self.lib.items().get().genres
def test_apply_from_scratch_keeps_format(self):
config["import"]["from_scratch"] = True
@ -470,7 +470,7 @@ class ImportTest(PathsMixin, AutotagImportTestCase):
disc = 0
config["import"]["set_fields"] = {
"genre": genre,
"genres": genre,
"collection": collection,
"comments": comments,
"album": "$album - formatted",
@ -483,11 +483,10 @@ class ImportTest(PathsMixin, AutotagImportTestCase):
self.importer.run()
for album in self.lib.albums():
album.load() # TODO: Not sure this is necessary.
assert album.genre == genre
assert album.genres == [genre]
assert album.comments == comments
for item in album.items():
assert item.get("genre", with_album=False) == genre
assert item.get("genres", with_album=False) == [genre]
assert item.get("collection", with_album=False) == collection
assert item.get("comments", with_album=False) == comments
assert (
@ -505,11 +504,10 @@ class ImportTest(PathsMixin, AutotagImportTestCase):
self.importer.run()
for album in self.lib.albums():
album.load()
assert album.genre == genre
assert album.genres == [genre]
assert album.comments == comments
for item in album.items():
assert item.get("genre", with_album=False) == genre
assert item.get("genres", with_album=False) == [genre]
assert item.get("collection", with_album=False) == collection
assert item.get("comments", with_album=False) == comments
assert (

View file

@ -66,15 +66,17 @@ class StoreTest(ItemInDBTestCase):
assert new_year == 1987
def test_store_only_writes_dirty_fields(self):
original_genre = self.i.genre
self.i._values_fixed["genre"] = "beatboxing" # change w/o dirtying
original_genres = self.i.genres
self.i._values_fixed["genres"] = ["beatboxing"] # change w/o dirtying
self.i.store()
new_genre = (
self.lib._connection()
.execute("select genre from items where title = ?", (self.i.title,))
.fetchone()["genre"]
.execute(
"select genres from items where title = ?", (self.i.title,)
)
.fetchone()["genres"]
)
assert new_genre == original_genre
assert [new_genre] == original_genres
def test_store_clears_dirty_flags(self):
self.i.composer = "tvp"

View file

@ -71,7 +71,7 @@ class TestGet:
album="baz",
year=2001,
comp=True,
genre="rock",
genres=["rock"],
),
helper.create_item(
title="second",
@ -80,7 +80,7 @@ class TestGet:
album="baz",
year=2002,
comp=True,
genre="Rock",
genres=["Rock"],
),
]
album = helper.lib.add_album(album_items)
@ -94,7 +94,7 @@ class TestGet:
album="foo",
year=2003,
comp=False,
genre="Hard Rock",
genres=["Hard Rock"],
comments="caf\xe9",
)
@ -125,12 +125,12 @@ class TestGet:
("comments:caf\xe9", ["third"]),
("comp:true", ["first", "second"]),
("comp:false", ["third"]),
("genre:=rock", ["first"]),
("genre:=Rock", ["second"]),
('genre:="Hard Rock"', ["third"]),
('genre:=~"hard rock"', ["third"]),
("genre:=~rock", ["first", "second"]),
('genre:="hard rock"', []),
("genres:=rock", ["first"]),
("genres:=Rock", ["second"]),
('genres:="Hard Rock"', ["third"]),
('genres:=~"hard rock"', ["third"]),
("genres:=~rock", ["first", "second"]),
('genres:="hard rock"', []),
("popebear", []),
("pope:bear", []),
("singleton:true", ["third"]),
@ -243,13 +243,7 @@ class TestGet:
class TestMatch:
@pytest.fixture(scope="class")
def item(self):
return _common.item(
album="the album",
disc=6,
genre="the genre",
year=1,
bitrate=128000,
)
return _common.item(album="the album", disc=6, year=1, bitrate=128000)
@pytest.mark.parametrize(
"q, should_match",
@ -260,9 +254,9 @@ class TestMatch:
(SubstringQuery("album", "album"), True),
(SubstringQuery("album", "ablum"), False),
(SubstringQuery("disc", "6"), True),
(StringQuery("genre", "the genre"), True),
(StringQuery("genre", "THE GENRE"), True),
(StringQuery("genre", "genre"), False),
(StringQuery("album", "the album"), True),
(StringQuery("album", "THE ALBUM"), True),
(StringQuery("album", "album"), False),
(NumericQuery("year", "1"), True),
(NumericQuery("year", "10"), False),
(NumericQuery("bitrate", "100000..200000"), True),

View file

@ -33,7 +33,7 @@ class DummyDataTestCase(BeetsTestCase):
albums = [
Album(
album="Album A",
genre="Rock",
genres=["Rock"],
year=2001,
flex1="Flex1-1",
flex2="Flex2-A",
@ -41,7 +41,7 @@ class DummyDataTestCase(BeetsTestCase):
),
Album(
album="Album B",
genre="Rock",
genres=["Rock"],
year=2001,
flex1="Flex1-2",
flex2="Flex2-A",
@ -49,7 +49,7 @@ class DummyDataTestCase(BeetsTestCase):
),
Album(
album="Album C",
genre="Jazz",
genres=["Jazz"],
year=2005,
flex1="Flex1-1",
flex2="Flex2-B",
@ -236,19 +236,19 @@ class SortAlbumFixedFieldTest(DummyDataTestCase):
def test_sort_two_field_asc(self):
q = ""
s1 = dbcore.query.FixedFieldSort("genre", True)
s1 = dbcore.query.FixedFieldSort("genres", True)
s2 = dbcore.query.FixedFieldSort("album", True)
sort = dbcore.query.MultipleSort()
sort.add_sort(s1)
sort.add_sort(s2)
results = self.lib.albums(q, sort)
assert results[0]["genre"] <= results[1]["genre"]
assert results[1]["genre"] <= results[2]["genre"]
assert results[1]["genre"] == "Rock"
assert results[2]["genre"] == "Rock"
assert results[0]["genres"] <= results[1]["genres"]
assert results[1]["genres"] <= results[2]["genres"]
assert results[1]["genres"] == ["Rock"]
assert results[2]["genres"] == ["Rock"]
assert results[1]["album"] <= results[2]["album"]
# same thing with query string
q = "genre+ album+"
q = "genres+ album+"
results2 = self.lib.albums(q)
for r1, r2 in zip(results, results2):
assert r1.id == r2.id
@ -388,7 +388,7 @@ class CaseSensitivityTest(DummyDataTestCase):
album = Album(
album="album",
genre="alternative",
genres=["alternative"],
year="2001",
flex1="flex1",
flex2="flex2-A",

View file

@ -63,6 +63,6 @@ class ListTest(IOMixin, BeetsTestCase):
assert "the artist - the album - 0001" == stdout.strip()
def test_list_album_format(self):
stdout = self._run_list(album=True, fmt="$genre")
stdout = self._run_list(album=True, fmt="$genres")
assert "the genre" in stdout
assert "the album" not in stdout

View file

@ -103,22 +103,22 @@ class UpdateTest(IOMixin, BeetsTestCase):
def test_selective_modified_metadata_moved(self):
mf = MediaFile(syspath(self.i.path))
mf.title = "differentTitle"
mf.genre = "differentGenre"
mf.genres = ["differentGenre"]
mf.save()
self._update(move=True, fields=["title"])
item = self.lib.items().get()
assert b"differentTitle" in item.path
assert item.genre != "differentGenre"
assert item.genres != ["differentGenre"]
def test_selective_modified_metadata_not_moved(self):
mf = MediaFile(syspath(self.i.path))
mf.title = "differentTitle"
mf.genre = "differentGenre"
mf.genres = ["differentGenre"]
mf.save()
self._update(move=False, fields=["title"])
item = self.lib.items().get()
assert b"differentTitle" not in item.path
assert item.genre != "differentGenre"
assert item.genres != ["differentGenre"]
def test_modified_album_metadata_moved(self):
mf = MediaFile(syspath(self.i.path))
@ -141,22 +141,22 @@ class UpdateTest(IOMixin, BeetsTestCase):
def test_selective_modified_album_metadata_moved(self):
mf = MediaFile(syspath(self.i.path))
mf.album = "differentAlbum"
mf.genre = "differentGenre"
mf.genres = ["differentGenre"]
mf.save()
self._update(move=True, fields=["album"])
item = self.lib.items().get()
assert b"differentAlbum" in item.path
assert item.genre != "differentGenre"
assert item.genres != ["differentGenre"]
def test_selective_modified_album_metadata_not_moved(self):
mf = MediaFile(syspath(self.i.path))
mf.album = "differentAlbum"
mf.genre = "differentGenre"
mf.genres = ["differentGenre"]
mf.save()
self._update(move=True, fields=["genre"])
self._update(move=True, fields=["genres"])
item = self.lib.items().get()
assert b"differentAlbum" not in item.path
assert item.genre == "differentGenre"
assert item.genres == ["differentGenre"]
def test_mtime_match_skips_update(self):
mf = MediaFile(syspath(self.i.path))

View file

@ -32,8 +32,11 @@ class TestFieldDiff:
p({"title": "foo"}, {"title": "bar"}, "title", f"title: {diff_fmt('foo', 'bar')}", id="string_full_replace"),
p({"title": "prefix foo"}, {"title": "prefix bar"}, "title", "title: prefix [text_diff_removed]foo[/] -> prefix [text_diff_added]bar[/]", id="string_partial_change"),
p({"year": 2000}, {"year": 2001}, "year", f"year: {diff_fmt('2000', '2001')}", id="int_changed"),
p({}, {"genre": "Rock"}, "genre", "genre: -> [text_diff_added]Rock[/]", id="field_added"),
p({"genre": "Rock"}, {}, "genre", "genre: [text_diff_removed]Rock[/] -> ", id="field_removed"),
p({}, {"title": "Title"}, "title", "title: -> [text_diff_added]Title[/]", id="field_added"),
p({"title": "Title"}, {}, "title", "title: [text_diff_removed]Title[/] -> ", id="field_removed"),
# TODO: there's some weirdness around empty list values handling
p({}, {"genres": ["Rock"]}, "genres", "genres: [text_diff_removed][/] -> [text_diff_added]Rock[/]", id="list_field_added"),
p({"genres": ["Rock"]}, {}, "genres", "genres: [text_diff_removed]Rock[/] -> [text_diff_added][/]", id="list_field_removed"),
p({"track": 1}, {"track": 2}, "track", f"track: {diff_fmt('01', '02')}", id="formatted_value_changed"),
p({"mb_trackid": None}, {"mb_trackid": "1234"}, "mb_trackid", "mb_trackid: -> [text_diff_added]1234[/]", id="none_to_value"),
p({}, {"new_flex": "foo"}, "new_flex", "[text_diff_added]new_flex: foo[/]", id="flex_field_added"),