Renamed all action occurrences with Action.

This commit is contained in:
Sebastian Mohr 2025-05-13 12:11:40 +02:00
parent 9147577b2b
commit 68acaa6470
16 changed files with 227 additions and 174 deletions

View file

@ -0,0 +1,38 @@
# 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.
"""Provides the basic, interface-agnostic workflow for importing and
autotagging music files.
"""
from .session import ImportAbortError, ImportSession
from .tasks import (
Action,
ArchiveImportTask,
ImportTask,
SentinelImportTask,
SingletonImportTask,
)
# Note: Stages are not exposed to the public API
__all__ = [
"ImportSession",
"ImportAbortError",
"Action",
"ImportTask",
"ArchiveImportTask",
"SentinelImportTask",
"SingletonImportTask",
]

View file

@ -18,10 +18,10 @@ import time
from typing import TYPE_CHECKING, Sequence
from beets import config, dbcore, library, logging, plugins, util
from beets.importer.tasks import action
from beets.importer.tasks import Action
from beets.util import displayable_path, normpath, pipeline, syspath
from .stages import *
from . import stages as stagefuncs
from .state import ImportState
if TYPE_CHECKING:
@ -162,15 +162,15 @@ class ImportSession:
# Duplicate: log all three choices (skip, keep both, and trump).
if task.should_remove_duplicates:
self.tag_log("duplicate-replace", paths)
elif task.choice_flag in (action.ASIS, action.APPLY):
elif task.choice_flag in (Action.ASIS, Action.APPLY):
self.tag_log("duplicate-keep", paths)
elif task.choice_flag is action.SKIP:
elif task.choice_flag is Action.SKIP:
self.tag_log("duplicate-skip", paths)
else:
# Non-duplicate: log "skip" and "asis" choices.
if task.choice_flag is action.ASIS:
if task.choice_flag is Action.ASIS:
self.tag_log("asis", paths)
elif task.choice_flag is action.SKIP:
elif task.choice_flag is Action.SKIP:
self.tag_log("skip", paths)
def should_resume(self, path: PathBytes):
@ -192,17 +192,17 @@ class ImportSession:
# Set up the pipeline.
if self.query is None:
stages = [read_tasks(self)]
stages = [stagefuncs.read_tasks(self)]
else:
stages = [query_tasks(self)]
stages = [stagefuncs.query_tasks(self)]
# In pretend mode, just log what would otherwise be imported.
if self.config["pretend"]:
stages += [log_files(self)]
stages += [stagefuncs.log_files(self)]
else:
if self.config["group_albums"] and not self.config["singletons"]:
# Split directory tasks into one task for each album.
stages += [group_albums(self)]
stages += [stagefuncs.group_albums(self)]
# These stages either talk to the user to get a decision or,
# in the case of a non-autotagged import, just choose to
@ -210,17 +210,20 @@ class ImportSession:
# also add the music to the library database, so later
# stages need to read and write data from there.
if self.config["autotag"]:
stages += [lookup_candidates(self), user_query(self)]
stages += [
stagefuncs.lookup_candidates(self),
stagefuncs.user_query(self),
]
else:
stages += [import_asis(self)]
stages += [stagefuncs.import_asis(self)]
# Plugin stages.
for stage_func in plugins.early_import_stages():
stages.append(plugin_stage(self, stage_func))
stages.append(stagefuncs.plugin_stage(self, stage_func))
for stage_func in plugins.import_stages():
stages.append(plugin_stage(self, stage_func))
stages.append(stagefuncs.plugin_stage(self, stage_func))
stages += [manipulate_files(self)]
stages += [stagefuncs.manipulate_files(self)]
pl = pipeline.Pipeline(stages)

View file

@ -22,7 +22,7 @@ from beets import config, plugins
from beets.util import MoveOperation, displayable_path, pipeline
from .tasks import (
action,
Action,
ImportTask,
ImportTaskFactory,
SentinelImportTask,
@ -173,7 +173,7 @@ def user_query(session: ImportSession, task: ImportTask):
plugins.send("import_task_choice", session=session, task=task)
# As-tracks: transition to singleton workflow.
if task.choice_flag is action.TRACKS:
if task.choice_flag is Action.TRACKS:
# Set up a little pipeline for dealing with the singletons.
def emitter(task):
for item in task.items:
@ -186,7 +186,7 @@ def user_query(session: ImportSession, task: ImportTask):
)
# As albums: group items by albums and create task for each album
if task.choice_flag is action.ALBUMS:
if task.choice_flag is Action.ALBUMS:
return _extend_pipeline(
[task],
group_albums(session),
@ -194,7 +194,7 @@ def user_query(session: ImportSession, task: ImportTask):
user_query(session),
)
resolve_duplicates(session, task)
_resolve_duplicates(session, task)
if task.should_merge_duplicates:
# Create a new task for tagging the current items
@ -216,7 +216,7 @@ def user_query(session: ImportSession, task: ImportTask):
[merged_task], lookup_candidates(session), user_query(session)
)
apply_choice(session, task)
_apply_choice(session, task)
return task
@ -231,8 +231,8 @@ def import_asis(session: ImportSession, task: ImportTask):
return
log.info("{}", displayable_path(task.paths))
task.set_choice(action.ASIS)
apply_choice(session, task)
task.set_choice(Action.ASIS)
_apply_choice(session, task)
@pipeline.mutator_stage
@ -312,7 +312,7 @@ def manipulate_files(session: ImportSession, task: ImportTask):
# Private functions only used in the stages above
def apply_choice(session: ImportSession, task: ImportTask):
def _apply_choice(session: ImportSession, task: ImportTask):
"""Apply the task's choice to the Album or Item it contains and add
it to the library.
"""
@ -335,11 +335,11 @@ def apply_choice(session: ImportSession, task: ImportTask):
task.set_fields(session.lib)
def resolve_duplicates(session: ImportSession, task: ImportTask):
def _resolve_duplicates(session: ImportSession, task: ImportTask):
"""Check if a task conflicts with items or albums already imported
and ask the session to resolve this.
"""
if task.choice_flag in (action.ASIS, action.APPLY, action.RETAG):
if task.choice_flag in (Action.ASIS, Action.APPLY, Action.RETAG):
found_duplicates = task.find_duplicates(session.lib)
if found_duplicates:
log.debug(
@ -360,7 +360,7 @@ def resolve_duplicates(session: ImportSession, task: ImportTask):
if duplicate_action == "s":
# Skip new.
task.set_choice(action.SKIP)
task.set_choice(Action.SKIP)
elif duplicate_action == "k":
# Keep both. Do nothing; leave the choice intact.
pass

View file

@ -31,7 +31,7 @@ from beets import autotag, config, dbcore, library, plugins, util
from .state import ImportState
if TYPE_CHECKING:
from .session import ImportSession
from .session import ImportSession, PathBytes
# Global logger.
log = logging.getLogger("beets")
@ -62,18 +62,26 @@ REIMPORT_FRESH_FIELDS_ITEM = list(REIMPORT_FRESH_FIELDS_ALBUM)
log = logging.getLogger("beets")
action = Enum("action", ["SKIP", "ASIS", "TRACKS", "APPLY", "ALBUMS", "RETAG"])
# The RETAG action represents "don't apply any match, but do record
# new metadata". It's not reachable via the standard command prompt but
# can be used by plugins.
class ImportAbortError(Exception):
"""Raised when the user aborts the tagging operation."""
pass
class Action(Enum):
"""Enumeration of possible actions for an import task."""
SKIP = "SKIP"
ASIS = "ASIS"
TRACKS = "TRACKS"
APPLY = "APPLY"
ALBUMS = "ALBUMS"
RETAG = "RETAG"
# The RETAG action represents "don't apply any match, but do record
# new metadata". It's not reachable via the standard command prompt but
# can be used by plugins.
class BaseImportTask:
"""An abstract base class for importer tasks.
@ -143,7 +151,7 @@ class ImportTask(BaseImportTask):
system.
"""
choice_flag: action | None = None
choice_flag: Action | None = None
match: autotag.AlbumMatch | autotag.TrackMatch | None = None
# Keep track of the current task item
@ -165,7 +173,7 @@ class ImportTask(BaseImportTask):
self.search_ids = [] # user-supplied candidate IDs.
def set_choice(
self, choice: action | autotag.AlbumMatch | autotag.TrackMatch
self, choice: Action | autotag.AlbumMatch | autotag.TrackMatch
):
"""Given an AlbumMatch or TrackMatch object or an action constant,
indicates that an action has been selected for this task.
@ -174,20 +182,20 @@ class ImportTask(BaseImportTask):
use isinstance to check for them.
"""
# Not part of the task structure:
assert choice != action.APPLY # Only used internally.
assert choice != Action.APPLY # Only used internally.
if choice in (
action.SKIP,
action.ASIS,
action.TRACKS,
action.ALBUMS,
action.RETAG,
Action.SKIP,
Action.ASIS,
Action.TRACKS,
Action.ALBUMS,
Action.RETAG,
):
# TODO: redesign to stricten the type
self.choice_flag = choice # type: ignore[assignment]
self.match = None
else:
self.choice_flag = action.APPLY # Implicit choice.
self.choice_flag = Action.APPLY # Implicit choice.
self.match = choice # type: ignore[assignment]
def save_progress(self):
@ -205,11 +213,11 @@ class ImportTask(BaseImportTask):
@property
def apply(self):
return self.choice_flag == action.APPLY
return self.choice_flag == Action.APPLY
@property
def skip(self):
return self.choice_flag == action.SKIP
return self.choice_flag == Action.SKIP
# Convenient data.
@ -219,10 +227,10 @@ class ImportTask(BaseImportTask):
(in which case the data comes from the files' current metadata)
or APPLY (in which case the data comes from the choice).
"""
if self.choice_flag in (action.ASIS, action.RETAG):
if self.choice_flag in (Action.ASIS, Action.RETAG):
likelies, consensus = autotag.current_metadata(self.items)
return likelies
elif self.choice_flag is action.APPLY and self.match:
elif self.choice_flag is Action.APPLY and self.match:
return self.match.info.copy()
assert False
@ -232,9 +240,9 @@ class ImportTask(BaseImportTask):
If the tasks applies an album match the method only returns the
matched items.
"""
if self.choice_flag in (action.ASIS, action.RETAG):
if self.choice_flag in (Action.ASIS, Action.RETAG):
return list(self.items)
elif self.choice_flag == action.APPLY and isinstance(
elif self.choice_flag == Action.APPLY and isinstance(
self.match, autotag.AlbumMatch
):
return list(self.match.mapping.keys())
@ -401,7 +409,7 @@ class ImportTask(BaseImportTask):
"""
changes = {}
if self.choice_flag == action.ASIS:
if self.choice_flag == Action.ASIS:
# Taking metadata "as-is". Guess whether this album is VA.
plur_albumartist, freq = util.plurality(
[i.albumartist or i.artist for i in self.items]
@ -418,7 +426,7 @@ class ImportTask(BaseImportTask):
changes["albumartist"] = config["va_name"].as_str()
changes["comp"] = True
elif self.choice_flag in (action.APPLY, action.RETAG):
elif self.choice_flag in (Action.APPLY, Action.RETAG):
# Applying autotagged metadata. Just get AA from the first
# item.
if not self.items[0].albumartist:
@ -473,7 +481,7 @@ class ImportTask(BaseImportTask):
# old paths.
item.move(operation)
if write and (self.apply or self.choice_flag == action.RETAG):
if write and (self.apply or self.choice_flag == Action.RETAG):
item.try_write()
with session.lib.transaction():
@ -490,7 +498,7 @@ class ImportTask(BaseImportTask):
self.remove_replaced(lib)
self.album = lib.add_album(self.imported_items())
if self.choice_flag == action.APPLY and isinstance(
if self.choice_flag == Action.APPLY and isinstance(
self.match, autotag.AlbumMatch
):
# Copy album flexible fields to the DB
@ -672,10 +680,10 @@ class SingletonImportTask(ImportTask):
(in which case the data comes from the files' current metadata)
or APPLY (in which case the data comes from the choice).
"""
assert self.choice_flag in (action.ASIS, action.RETAG, action.APPLY)
if self.choice_flag in (action.ASIS, action.RETAG):
assert self.choice_flag in (Action.ASIS, Action.RETAG, Action.APPLY)
if self.choice_flag in (Action.ASIS, Action.RETAG):
return dict(self.item)
elif self.choice_flag is action.APPLY:
elif self.choice_flag is Action.APPLY:
return self.match.info.copy()
def imported_items(self):

View file

@ -658,9 +658,9 @@ class ImportSessionFixture(ImportSession):
>>> lib = Library(':memory:')
>>> importer = ImportSessionFixture(lib, paths=['/path/to/import'])
>>> importer.add_choice(importer.action.SKIP)
>>> importer.add_choice(importer.action.ASIS)
>>> importer.default_choice = importer.action.APPLY
>>> importer.add_choice(importer.Action.SKIP)
>>> importer.add_choice(importer.Action.ASIS)
>>> importer.default_choice = importer.Action.APPLY
>>> importer.run()
This imports ``/path/to/import`` into `lib`. It skips the first
@ -673,7 +673,7 @@ class ImportSessionFixture(ImportSession):
self._choices = []
self._resolutions = []
default_choice = importer.action.APPLY
default_choice = importer.Action.APPLY
def add_choice(self, choice):
self._choices.append(choice)
@ -687,7 +687,7 @@ class ImportSessionFixture(ImportSession):
except IndexError:
choice = self.default_choice
if choice == importer.action.APPLY:
if choice == importer.Action.APPLY:
return task.candidates[0]
elif isinstance(choice, int):
return task.candidates[choice - 1]
@ -707,7 +707,7 @@ class ImportSessionFixture(ImportSession):
res = self.default_resolution
if res == self.Resolution.SKIP:
task.set_choice(importer.action.SKIP)
task.set_choice(importer.Action.SKIP)
elif res == self.Resolution.REMOVE:
task.should_remove_duplicates = True
elif res == self.Resolution.MERGE:
@ -720,7 +720,7 @@ class TerminalImportSessionFixture(TerminalImportSession):
super().__init__(*args, **kwargs)
self._choices = []
default_choice = importer.action.APPLY
default_choice = importer.Action.APPLY
def add_choice(self, choice):
self._choices.append(choice)
@ -742,15 +742,15 @@ class TerminalImportSessionFixture(TerminalImportSession):
except IndexError:
choice = self.default_choice
if choice == importer.action.APPLY:
if choice == importer.Action.APPLY:
self.io.addinput("A")
elif choice == importer.action.ASIS:
elif choice == importer.Action.ASIS:
self.io.addinput("U")
elif choice == importer.action.ALBUMS:
elif choice == importer.Action.ALBUMS:
self.io.addinput("G")
elif choice == importer.action.TRACKS:
elif choice == importer.Action.TRACKS:
self.io.addinput("T")
elif choice == importer.action.SKIP:
elif choice == importer.Action.SKIP:
self.io.addinput("S")
else:
self.io.addinput("M")

View file

@ -811,12 +811,12 @@ def _summary_judgment(rec):
if config["import"]["quiet"]:
if rec == Recommendation.strong:
return importer.action.APPLY
return importer.Action.APPLY
else:
action = config["import"]["quiet_fallback"].as_choice(
{
"skip": importer.action.SKIP,
"asis": importer.action.ASIS,
"skip": importer.Action.SKIP,
"asis": importer.Action.ASIS,
}
)
elif config["import"]["timid"]:
@ -824,17 +824,17 @@ def _summary_judgment(rec):
elif rec == Recommendation.none:
action = config["import"]["none_rec_action"].as_choice(
{
"skip": importer.action.SKIP,
"asis": importer.action.ASIS,
"skip": importer.Action.SKIP,
"asis": importer.Action.ASIS,
"ask": None,
}
)
else:
return None
if action == importer.action.SKIP:
if action == importer.Action.SKIP:
print_("Skipping.")
elif action == importer.action.ASIS:
elif action == importer.Action.ASIS:
print_("Importing as-is.")
return action
@ -1064,7 +1064,7 @@ class TerminalImportSession(importer.ImportSession):
# Take immediate action if appropriate.
action = _summary_judgment(task.rec)
if action == importer.action.APPLY:
if action == importer.Action.APPLY:
match = task.candidates[0]
show_change(task.cur_artist, task.cur_album, match)
return match
@ -1074,7 +1074,7 @@ class TerminalImportSession(importer.ImportSession):
# Loop until we have a choice.
while True:
# Ask for a choice from the user. The result of
# `choose_candidate` may be an `importer.action`, an
# `choose_candidate` may be an `importer.Action`, an
# `AlbumMatch` object for a specific selection, or a
# `PromptChoice`.
choices = self._get_choices(task)
@ -1089,7 +1089,7 @@ class TerminalImportSession(importer.ImportSession):
)
# Basic choices that require no more action here.
if choice in (importer.action.SKIP, importer.action.ASIS):
if choice in (importer.Action.SKIP, importer.Action.ASIS):
# Pass selection to main control flow.
return choice
@ -1097,7 +1097,7 @@ class TerminalImportSession(importer.ImportSession):
# function.
elif choice in choices:
post_choice = choice.callback(self, task)
if isinstance(post_choice, importer.action):
if isinstance(post_choice, importer.Action):
return post_choice
elif isinstance(post_choice, autotag.Proposal):
# Use the new candidates and continue around the loop.
@ -1121,7 +1121,7 @@ class TerminalImportSession(importer.ImportSession):
# Take immediate action if appropriate.
action = _summary_judgment(task.rec)
if action == importer.action.APPLY:
if action == importer.Action.APPLY:
match = candidates[0]
show_item_change(task.item, match)
return match
@ -1135,12 +1135,12 @@ class TerminalImportSession(importer.ImportSession):
candidates, True, rec, item=task.item, choices=choices
)
if choice in (importer.action.SKIP, importer.action.ASIS):
if choice in (importer.Action.SKIP, importer.Action.ASIS):
return choice
elif choice in choices:
post_choice = choice.callback(self, task)
if isinstance(post_choice, importer.action):
if isinstance(post_choice, importer.Action):
return post_choice
elif isinstance(post_choice, autotag.Proposal):
candidates = post_choice.candidates
@ -1203,7 +1203,7 @@ class TerminalImportSession(importer.ImportSession):
if sel == "s":
# Skip new.
task.set_choice(importer.action.SKIP)
task.set_choice(importer.Action.SKIP)
elif sel == "k":
# Keep both. Do nothing; leave the choice intact.
pass
@ -1239,16 +1239,16 @@ class TerminalImportSession(importer.ImportSession):
"""
# Standard, built-in choices.
choices = [
PromptChoice("s", "Skip", lambda s, t: importer.action.SKIP),
PromptChoice("u", "Use as-is", lambda s, t: importer.action.ASIS),
PromptChoice("s", "Skip", lambda s, t: importer.Action.SKIP),
PromptChoice("u", "Use as-is", lambda s, t: importer.Action.ASIS),
]
if task.is_album:
choices += [
PromptChoice(
"t", "as Tracks", lambda s, t: importer.action.TRACKS
"t", "as Tracks", lambda s, t: importer.Action.TRACKS
),
PromptChoice(
"g", "Group albums", lambda s, t: importer.action.ALBUMS
"g", "Group albums", lambda s, t: importer.Action.ALBUMS
),
]
choices += [

View file

@ -68,6 +68,10 @@ BytesOrStr = Union[str, bytes]
PathLike = Union[BytesOrStr, Path]
Replacements: TypeAlias = "Sequence[tuple[Pattern[str], str]]"
# Here for now to allow for a easy replace later on
# once we can move to a PathLike (mainly used in importer)
PathBytes = bytes
class HumanReadableError(Exception):
"""An Exception that can include a human-readable error message to

View file

@ -194,7 +194,7 @@ class BadFiles(BeetsPlugin):
sel = ui.input_options(["aBort", "skip", "continue"])
if sel == "s":
return importer.action.SKIP
return importer.Action.SKIP
elif sel == "c":
return None
elif sel == "b":

View file

@ -24,7 +24,7 @@ import yaml
from beets import plugins, ui, util
from beets.dbcore import types
from beets.importer import action
from beets.importer import Action
from beets.ui.commands import PromptChoice, _do_query
# These "safe" types can avoid the format/parse cycle that most fields go
@ -380,9 +380,9 @@ class EditPlugin(plugins.BeetsPlugin):
# Save the new data.
if success:
# Return action.RETAG, which makes the importer write the tags
# Return Action.RETAG, which makes the importer write the tags
# to the files if needed without re-applying metadata.
return action.RETAG
return Action.RETAG
else:
# Edit cancelled / no edits made. Revert changes.
for obj in task.items:

View file

@ -1306,12 +1306,12 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin):
):
# Album already has art (probably a re-import); skip it.
return
if task.choice_flag == importer.action.ASIS:
if task.choice_flag == importer.Action.ASIS:
# For as-is imports, don't search Web sources for art.
local = True
elif task.choice_flag in (
importer.action.APPLY,
importer.action.RETAG,
importer.Action.APPLY,
importer.Action.RETAG,
):
# Search everywhere for art.
local = False

View file

@ -15,7 +15,7 @@
"""Warns you about things you hate (or even blocks import)."""
from beets.importer import action
from beets.importer import Action
from beets.library import Album, Item, parse_query_string
from beets.plugins import BeetsPlugin
@ -65,11 +65,11 @@ class IHatePlugin(BeetsPlugin):
skip_queries = self.config["skip"].as_str_seq()
warn_queries = self.config["warn"].as_str_seq()
if task.choice_flag == action.APPLY:
if task.choice_flag == Action.APPLY:
if skip_queries or warn_queries:
self._log.debug("processing your hate")
if self.do_i_hate_this(task, skip_queries):
task.choice_flag = action.SKIP
task.choice_flag = Action.SKIP
self._log.info("skipped: {0}", summary(task))
return
if self.do_i_hate_this(task, warn_queries):

View file

@ -19,7 +19,7 @@ import re
import confuse
from mediafile import MediaFile
from beets.importer import action
from beets.importer import Action
from beets.plugins import BeetsPlugin
from beets.ui import Subcommand, decargs, input_yn
@ -105,7 +105,7 @@ class ZeroPlugin(BeetsPlugin):
self.fields_to_progs[field] = []
def import_task_choice_event(self, session, task):
if task.choice_flag == action.ASIS and not self.warned:
if task.choice_flag == Action.ASIS and not self.warned:
self._log.warning('cannot zero in "as-is" mode')
self.warned = True
# TODO request write in as-is mode

View file

@ -648,6 +648,6 @@ by the choices on the core importer prompt, and hence should not be used:
``a``, ``s``, ``u``, ``t``, ``g``, ``e``, ``i``, ``b``.
Additionally, the callback function can optionally specify the next action to
be performed by returning a ``importer.action`` value. It may also return a
be performed by returning a ``importer.Action`` value. It may also return a
``autotag.Proposal`` value to update the set of current proposals to be
considered.

View file

@ -59,7 +59,7 @@ class ImportAddedTest(PluginMixin, ImportTestCase):
self.matcher = AutotagStub().install()
self.matcher.matching = AutotagStub.IDENT
self.importer = self.setup_importer()
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
def tearDown(self):
super().tearDown()

View file

@ -34,7 +34,7 @@ from mediafile import MediaFile
from beets import config, importer, logging, util
from beets.autotag import AlbumInfo, AlbumMatch, TrackInfo
from beets.importer import albums_in_dir
from beets.importer.tasks import albums_in_dir
from beets.test import _common
from beets.test.helper import (
NEEDS_REFLINK,
@ -324,52 +324,52 @@ class ImportSingletonTest(ImportTestCase):
def test_apply_asis_adds_track(self):
assert self.lib.items().get() is None
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
assert self.lib.items().get().title == "Tag Track 1"
def test_apply_asis_does_not_add_album(self):
assert self.lib.albums().get() is None
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
assert self.lib.albums().get() is None
def test_apply_asis_adds_singleton_path(self):
self.assert_lib_dir_empty()
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
self.assert_file_in_lib(b"singletons", b"Tag Track 1.mp3")
def test_apply_candidate_adds_track(self):
assert self.lib.items().get() is None
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert self.lib.items().get().title == "Applied Track 1"
def test_apply_candidate_does_not_add_album(self):
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert self.lib.albums().get() is None
def test_apply_candidate_adds_singleton_path(self):
self.assert_lib_dir_empty()
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
self.assert_file_in_lib(b"singletons", b"Applied Track 1.mp3")
def test_skip_does_not_add_first_track(self):
self.importer.add_choice(importer.action.SKIP)
self.importer.add_choice(importer.Action.SKIP)
self.importer.run()
assert self.lib.items().get() is None
def test_skip_adds_other_tracks(self):
self.prepare_album_for_import(2)
self.importer.add_choice(importer.action.SKIP)
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.SKIP)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
assert len(self.lib.items()) == 1
@ -385,8 +385,8 @@ class ImportSingletonTest(ImportTestCase):
self.setup_importer()
self.importer.paths = import_files
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
assert len(self.lib.items()) == 2
@ -406,7 +406,7 @@ class ImportSingletonTest(ImportTestCase):
# As-is item import.
assert self.lib.albums().get() is None
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
for item in self.lib.items():
@ -421,7 +421,7 @@ class ImportSingletonTest(ImportTestCase):
# Autotagged.
assert self.lib.albums().get() is None
self.importer.clear_choices()
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
for item in self.lib.items():
@ -449,41 +449,41 @@ class ImportTest(ImportTestCase):
def test_apply_asis_adds_album(self):
assert self.lib.albums().get() is None
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
assert self.lib.albums().get().album == "Tag Album"
def test_apply_asis_adds_tracks(self):
assert self.lib.items().get() is None
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
assert self.lib.items().get().title == "Tag Track 1"
def test_apply_asis_adds_album_path(self):
self.assert_lib_dir_empty()
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
self.assert_file_in_lib(b"Tag Artist", b"Tag Album", b"Tag Track 1.mp3")
def test_apply_candidate_adds_album(self):
assert self.lib.albums().get() is None
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert self.lib.albums().get().album == "Applied Album"
def test_apply_candidate_adds_tracks(self):
assert self.lib.items().get() is None
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert self.lib.items().get().title == "Applied Track 1"
def test_apply_candidate_adds_album_path(self):
self.assert_lib_dir_empty()
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
self.assert_file_in_lib(
b"Applied Artist", b"Applied Album", b"Applied Track 1.mp3"
@ -496,14 +496,14 @@ class ImportTest(ImportTestCase):
mediafile.genre = "Tag Genre"
mediafile.save()
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert self.lib.items().get().genre == ""
def test_apply_from_scratch_keeps_format(self):
config["import"]["from_scratch"] = True
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert self.lib.items().get().format == "MP3"
@ -511,7 +511,7 @@ class ImportTest(ImportTestCase):
config["import"]["from_scratch"] = True
bitrate = 80000
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert self.lib.items().get().bitrate == bitrate
@ -521,7 +521,7 @@ class ImportTest(ImportTestCase):
import_file = os.path.join(self.import_dir, b"album", b"track_1.mp3")
self.assertExists(import_file)
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
self.assertNotExists(import_file)
@ -531,26 +531,26 @@ class ImportTest(ImportTestCase):
import_file = os.path.join(self.import_dir, b"album", b"track_1.mp3")
self.assertExists(import_file)
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
self.assertNotExists(import_file)
def test_skip_does_not_add_track(self):
self.importer.add_choice(importer.action.SKIP)
self.importer.add_choice(importer.Action.SKIP)
self.importer.run()
assert self.lib.items().get() is None
def test_skip_non_album_dirs(self):
self.assertIsDir(os.path.join(self.import_dir, b"album"))
self.touch(b"cruft", dir=self.import_dir)
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert len(self.lib.albums()) == 1
def test_unmatched_tracks_not_added(self):
self.prepare_album_for_import(2)
self.matcher.matching = self.matcher.MISSING
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert len(self.lib.items()) == 1
@ -577,7 +577,7 @@ class ImportTest(ImportTestCase):
def test_asis_no_data_source(self):
assert self.lib.items().get() is None
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
with pytest.raises(AttributeError):
@ -599,7 +599,7 @@ class ImportTest(ImportTestCase):
# As-is album import.
assert self.lib.albums().get() is None
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
for album in self.lib.albums():
@ -621,7 +621,7 @@ class ImportTest(ImportTestCase):
# Autotagged.
assert self.lib.albums().get() is None
self.importer.clear_choices()
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
for album in self.lib.albums():
@ -656,9 +656,9 @@ class ImportTracksTest(ImportTestCase):
assert self.lib.items().get() is None
assert self.lib.albums().get() is None
self.importer.add_choice(importer.action.TRACKS)
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.TRACKS)
self.importer.add_choice(importer.Action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert self.lib.items().get().title == "Applied Track 1"
assert self.lib.albums().get() is None
@ -666,9 +666,9 @@ class ImportTracksTest(ImportTestCase):
def test_apply_tracks_adds_singleton_path(self):
self.assert_lib_dir_empty()
self.importer.add_choice(importer.action.TRACKS)
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.TRACKS)
self.importer.add_choice(importer.Action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
self.assert_file_in_lib(b"singletons", b"Applied Track 1.mp3")
@ -687,7 +687,7 @@ class ImportCompilationTest(ImportTestCase):
self.matcher.restore()
def test_asis_homogenous_sets_albumartist(self):
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
assert self.lib.albums().get().albumartist == "Tag Artist"
for item in self.lib.items():
@ -699,7 +699,7 @@ class ImportCompilationTest(ImportTestCase):
self.import_media[1].artist = "Another Artist"
self.import_media[1].save()
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
assert self.lib.albums().get().albumartist == "Various Artists"
for item in self.lib.items():
@ -711,7 +711,7 @@ class ImportCompilationTest(ImportTestCase):
self.import_media[1].artist = "Another Artist"
self.import_media[1].save()
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
for item in self.lib.items():
assert item.comp
@ -722,7 +722,7 @@ class ImportCompilationTest(ImportTestCase):
self.import_media[1].artist = "Other Artist"
self.import_media[1].save()
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
assert self.lib.albums().get().albumartist == "Other Artist"
for item in self.lib.items():
@ -736,7 +736,7 @@ class ImportCompilationTest(ImportTestCase):
mediafile.mb_albumartistid = "Album Artist ID"
mediafile.save()
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
assert self.lib.albums().get().albumartist == "Album Artist"
assert self.lib.albums().get().mb_albumartistid == "Album Artist ID"
@ -755,7 +755,7 @@ class ImportCompilationTest(ImportTestCase):
mediafile.mb_albumartistid = "Album Artist ID"
mediafile.save()
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.run()
assert self.lib.albums().get().albumartist == "Album Artist"
assert self.lib.albums().get().albumartists == [
@ -802,7 +802,7 @@ class ImportExistingTest(ImportTestCase):
self.importer.run()
assert len(self.lib.items()) == 1
self.reimporter.add_choice(importer.action.APPLY)
self.reimporter.add_choice(importer.Action.APPLY)
self.reimporter.run()
assert len(self.lib.items()) == 1
@ -810,18 +810,18 @@ class ImportExistingTest(ImportTestCase):
self.importer.run()
assert len(self.lib.albums()) == 1
self.reimporter.add_choice(importer.action.APPLY)
self.reimporter.add_choice(importer.Action.APPLY)
self.reimporter.run()
assert len(self.lib.albums()) == 1
def test_does_not_duplicate_singleton_track(self):
self.importer.add_choice(importer.action.TRACKS)
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.TRACKS)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert len(self.lib.items()) == 1
self.reimporter.add_choice(importer.action.TRACKS)
self.reimporter.add_choice(importer.action.APPLY)
self.reimporter.add_choice(importer.Action.TRACKS)
self.reimporter.add_choice(importer.Action.APPLY)
self.reimporter.run()
assert len(self.lib.items()) == 1
@ -831,7 +831,7 @@ class ImportExistingTest(ImportTestCase):
medium.title = "New Title"
medium.save()
self.reimporter.add_choice(importer.action.ASIS)
self.reimporter.add_choice(importer.Action.ASIS)
self.reimporter.run()
assert self.lib.items().get().title == "New Title"
@ -846,7 +846,7 @@ class ImportExistingTest(ImportTestCase):
)
self.assert_file_in_lib(old_path)
self.reimporter.add_choice(importer.action.ASIS)
self.reimporter.add_choice(importer.Action.ASIS)
self.reimporter.run()
self.assert_file_in_lib(
b"Applied Artist", b"Applied Album", b"New Title.mp3"
@ -865,7 +865,7 @@ class ImportExistingTest(ImportTestCase):
self.assert_file_in_lib(old_path)
config["import"]["copy"] = False
self.reimporter.add_choice(importer.action.ASIS)
self.reimporter.add_choice(importer.Action.ASIS)
self.reimporter.run()
self.assert_file_not_in_lib(
b"Applied Artist", b"Applied Album", b"New Title.mp3"
@ -880,7 +880,7 @@ class ImportExistingTest(ImportTestCase):
)
self.reimporter = self.setup_importer()
self.reimporter.add_choice(importer.action.APPLY)
self.reimporter.add_choice(importer.Action.APPLY)
self.reimporter.run()
new_path = os.path.join(
b"Applied Artist", b"Applied Album", b"Applied Track 1.mp3"
@ -899,7 +899,7 @@ class ImportExistingTest(ImportTestCase):
)
self.reimporter = self.setup_importer(move=True)
self.reimporter.add_choice(importer.action.APPLY)
self.reimporter.add_choice(importer.Action.APPLY)
self.reimporter.run()
self.assertNotExists(self.import_media[0].path)
@ -913,9 +913,9 @@ class GroupAlbumsImportTest(ImportTestCase):
self.setup_importer()
# Split tracks into two albums and use both as-is
self.importer.add_choice(importer.action.ALBUMS)
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.action.ASIS)
self.importer.add_choice(importer.Action.ALBUMS)
self.importer.add_choice(importer.Action.ASIS)
self.importer.add_choice(importer.Action.ASIS)
def tearDown(self):
super().tearDown()
@ -972,7 +972,7 @@ class GlobalGroupAlbumsImportTest(GroupAlbumsImportTest):
def setUp(self):
super().setUp()
self.importer.clear_choices()
self.importer.default_choice = importer.action.ASIS
self.importer.default_choice = importer.Action.ASIS
config["import"]["group_albums"] = True
@ -1019,7 +1019,7 @@ class InferAlbumDataTest(BeetsTestCase):
)
def test_asis_homogenous_single_artist(self):
self.task.set_choice(importer.action.ASIS)
self.task.set_choice(importer.Action.ASIS)
self.task.align_album_level_fields()
assert not self.items[0].comp
assert self.items[0].albumartist == self.items[2].artist
@ -1027,7 +1027,7 @@ class InferAlbumDataTest(BeetsTestCase):
def test_asis_heterogenous_va(self):
self.items[0].artist = "another artist"
self.items[1].artist = "some other artist"
self.task.set_choice(importer.action.ASIS)
self.task.set_choice(importer.Action.ASIS)
self.task.align_album_level_fields()
@ -1037,7 +1037,7 @@ class InferAlbumDataTest(BeetsTestCase):
def test_asis_comp_applied_to_all_items(self):
self.items[0].artist = "another artist"
self.items[1].artist = "some other artist"
self.task.set_choice(importer.action.ASIS)
self.task.set_choice(importer.Action.ASIS)
self.task.align_album_level_fields()
@ -1047,7 +1047,7 @@ class InferAlbumDataTest(BeetsTestCase):
def test_asis_majority_artist_single_artist(self):
self.items[0].artist = "another artist"
self.task.set_choice(importer.action.ASIS)
self.task.set_choice(importer.Action.ASIS)
self.task.align_album_level_fields()
@ -1060,7 +1060,7 @@ class InferAlbumDataTest(BeetsTestCase):
for item in self.items:
item.albumartist = "some album artist"
item.mb_albumartistid = "some album artist id"
self.task.set_choice(importer.action.ASIS)
self.task.set_choice(importer.Action.ASIS)
self.task.align_album_level_fields()
@ -1089,7 +1089,7 @@ class InferAlbumDataTest(BeetsTestCase):
def test_small_single_artist_album(self):
self.items = [self.items[0]]
self.task.items = self.items
self.task.set_choice(importer.action.ASIS)
self.task.set_choice(importer.Action.ASIS)
self.task.align_album_level_fields()
assert not self.items[0].comp
@ -1599,7 +1599,7 @@ class ReimportTest(ImportTestCase):
def _setup_session(self, singletons=False):
self.setup_importer(import_dir=self.libdir, singletons=singletons)
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
def _album(self):
return self.lib.albums().get()
@ -1845,7 +1845,7 @@ class ImportMusicBrainzIdTest(ImportTestCase):
search_ids=[self.MB_RELEASE_PREFIX + self.ID_RELEASE_0]
)
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert self.lib.albums().get().album == "VALID_RELEASE_0"
@ -1858,7 +1858,7 @@ class ImportMusicBrainzIdTest(ImportTestCase):
)
self.importer.add_choice(2) # Pick the 2nd best match (release 1).
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert self.lib.albums().get().album == "VALID_RELEASE_1"
@ -1867,7 +1867,7 @@ class ImportMusicBrainzIdTest(ImportTestCase):
search_ids=[self.MB_RECORDING_PREFIX + self.ID_RECORDING_0]
)
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert self.lib.items().get().title == "VALID_RECORDING_0"
@ -1880,7 +1880,7 @@ class ImportMusicBrainzIdTest(ImportTestCase):
)
self.importer.add_choice(2) # Pick the 2nd best match (recording 1).
self.importer.add_choice(importer.action.APPLY)
self.importer.add_choice(importer.Action.APPLY)
self.importer.run()
assert self.lib.items().get().title == "VALID_RECORDING_1"

View file

@ -24,10 +24,10 @@ from mediafile import MediaFile
from beets import config, plugins, ui
from beets.dbcore import types
from beets.importer import (
Action,
ArchiveImportTask,
SentinelImportTask,
SingletonImportTask,
action,
)
from beets.library import Item
from beets.plugins import MetadataSourcePlugin
@ -389,7 +389,7 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
"aBort",
) + ("Foo", "baR")
self.importer.add_choice(action.SKIP)
self.importer.add_choice(Action.SKIP)
self.importer.run()
self.mock_input_options.assert_called_once_with(
opts, default="a", require=ANY
@ -424,7 +424,7 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
) + ("Foo", "baR")
config["import"]["singletons"] = True
self.importer.add_choice(action.SKIP)
self.importer.add_choice(Action.SKIP)
self.importer.run()
self.mock_input_options.assert_called_with(
opts, default="a", require=ANY
@ -461,7 +461,7 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
"enter Id",
"aBort",
) + ("baZ",)
self.importer.add_choice(action.SKIP)
self.importer.add_choice(Action.SKIP)
self.importer.run()
self.mock_input_options.assert_called_once_with(
opts, default="a", require=ANY
@ -523,7 +523,7 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
return [ui.commands.PromptChoice("f", "Foo", self.foo)]
def foo(self, session, task):
return action.SKIP
return Action.SKIP
self.register_plugin(DummyPlugin)
# Default options + extra choices by the plugin ('Foo', 'Bar')