Skip to content
51 changes: 38 additions & 13 deletions beetsplug/lastgenre/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,12 @@ def _try_resolve_stage(stage_label: str, keep_genres, new_genres):

def commands(self):
lastgenre_cmd = ui.Subcommand("lastgenre", help="fetch genres")
lastgenre_cmd.parser.add_option(
"-p",
"--pretend",
action="store_true",
help="show actions but do nothing",
)
lastgenre_cmd.parser.add_option(
"-f",
"--force",
Expand Down Expand Up @@ -521,45 +527,64 @@ def commands(self):

def lastgenre_func(lib, opts, args):
write = ui.should_write()
pretend = getattr(opts, "pretend", False)
self.config.set_args(opts)

if opts.album:
# Fetch genres for whole albums
for album in lib.albums(args):
album.genre, src = self._get_genre(album)
album_genre, src = self._get_genre(album)
prefix = "Pretend: " if pretend else ""
self._log.info(
'genre for album "{0.album}" ({1}): {0.genre}',
'{}genre for album "{.album}" ({}): {}',
prefix,
album,
src,
album_genre,
)
if "track" in self.sources:
album.store(inherit=False)
else:
album.store()
if not pretend:
album.genre = album_genre
if "track" in self.sources:
album.store(inherit=False)
else:
album.store()

for item in album.items():
# If we're using track-level sources, also look up each
# track on the album.
if "track" in self.sources:
item.genre, src = self._get_genre(item)
item.store()
item_genre, src = self._get_genre(item)
self._log.info(
'genre for track "{0.title}" ({1}): {0.genre}',
'{}genre for track "{.title}" ({}): {}',
prefix,
item,
src,
item_genre,
)
if not pretend:
item.genre = item_genre
item.store()

if write:
if write and not pretend:
item.try_write()
else:
# Just query singletons, i.e. items that are not part of
# an album
for item in lib.items(args):
item.genre, src = self._get_genre(item)
item.store()
item_genre, src = self._get_genre(item)
prefix = "Pretend: " if pretend else ""
self._log.info(
"genre for track {0.title} ({1}): {0.genre}", item, src
'{}genre for track "{0.title}" ({1}): {}',
prefix,
item,
src,
item_genre,
)
if not pretend:
item.genre = item_genre
item.store()
if write and not pretend:
item.try_write()

lastgenre_cmd.func = lastgenre_func
return [lastgenre_cmd]
Expand Down
3 changes: 3 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ Unreleased

New features:

- :doc:`plugins/lastgenre`: Add a ``--pretend`` option to preview genre changes
without storing or writing them.

Bug fixes:

- :doc:`plugins/spotify` Fixed an issue where track matching and lookups could
Expand Down
6 changes: 5 additions & 1 deletion docs/plugins/lastgenre.rst
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ tags** and will only **fetch new genres for empty tags**. When ``force`` is
``yes`` the setting of the ``whitelist`` option (as documented in Usage_)
applies to any existing or newly fetched genres.

The follwing configurations are possible:
The following configurations are possible:

**Setup 1** (default)

Expand Down Expand Up @@ -213,5 +213,9 @@ fetch genres for albums or items matching a certain query.
By default, ``beet lastgenre`` matches albums. To match individual tracks or
singletons, use the ``-A`` switch: ``beet lastgenre -A [QUERY]``.

To preview the changes that would be made without applying them, use the ``-p``
or ``--pretend`` flag. This shows which genres would be set but does not write
or store any changes.

To disable automatic genre fetching on import, set the ``auto`` config option to
false.
39 changes: 38 additions & 1 deletion test/plugins/test_lastgenre.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

"""Tests for the 'lastgenre' plugin."""

from unittest.mock import Mock
from unittest.mock import Mock, patch

import pytest

Expand Down Expand Up @@ -131,6 +131,43 @@ def test_prefer_specific_without_canonical(self):
"math rock",
]

def test_pretend_option_skips_library_updates(self):
item = self.create_item(
album="Pretend Album",
albumartist="Pretend Artist",
artist="Pretend Artist",
title="Pretend Track",
genre="Original Genre",
)
album = self.lib.add_album([item])

command = self.plugin.commands()[0]
opts, args = command.parser.parse_args(["--pretend"])

with patch.object(lastgenre.ui, "should_write", return_value=True):
with patch.object(
self.plugin,
"_get_genre",
return_value=("Mock Genre", "mock stage"),
) as mock_get_genre:
with patch.object(self.plugin._log, "info") as log_info:
# Mock try_write to verify it's never called in pretend mode
with patch.object(item, "try_write") as mock_try_write:
command.func(self.lib, opts, args)

mock_get_genre.assert_called_once()

assert any(
call.args[1] == "Pretend: " for call in log_info.call_args_list
)

# Verify that try_write was never called (file operations skipped)
mock_try_write.assert_not_called()

stored_album = self.lib.get_album(album.id)
assert stored_album.genre == "Original Genre"
assert stored_album.items()[0].genre == "Original Genre"

def test_no_duplicate(self):
"""Remove duplicated genres."""
self._setup_config(count=99)
Expand Down
Loading