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

View file

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

View file

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

View file

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

View file

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

View file

@ -68,6 +68,10 @@ BytesOrStr = Union[str, bytes]
PathLike = Union[BytesOrStr, Path] PathLike = Union[BytesOrStr, Path]
Replacements: TypeAlias = "Sequence[tuple[Pattern[str], str]]" 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): class HumanReadableError(Exception):
"""An Exception that can include a human-readable error message to """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"]) sel = ui.input_options(["aBort", "skip", "continue"])
if sel == "s": if sel == "s":
return importer.action.SKIP return importer.Action.SKIP
elif sel == "c": elif sel == "c":
return None return None
elif sel == "b": elif sel == "b":

View file

@ -24,7 +24,7 @@ import yaml
from beets import plugins, ui, util from beets import plugins, ui, util
from beets.dbcore import types from beets.dbcore import types
from beets.importer import action from beets.importer import Action
from beets.ui.commands import PromptChoice, _do_query from beets.ui.commands import PromptChoice, _do_query
# These "safe" types can avoid the format/parse cycle that most fields go # 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. # Save the new data.
if success: 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. # to the files if needed without re-applying metadata.
return action.RETAG return Action.RETAG
else: else:
# Edit cancelled / no edits made. Revert changes. # Edit cancelled / no edits made. Revert changes.
for obj in task.items: 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. # Album already has art (probably a re-import); skip it.
return 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. # For as-is imports, don't search Web sources for art.
local = True local = True
elif task.choice_flag in ( elif task.choice_flag in (
importer.action.APPLY, importer.Action.APPLY,
importer.action.RETAG, importer.Action.RETAG,
): ):
# Search everywhere for art. # Search everywhere for art.
local = False local = False

View file

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

View file

@ -19,7 +19,7 @@ import re
import confuse import confuse
from mediafile import MediaFile from mediafile import MediaFile
from beets.importer import action from beets.importer import Action
from beets.plugins import BeetsPlugin from beets.plugins import BeetsPlugin
from beets.ui import Subcommand, decargs, input_yn from beets.ui import Subcommand, decargs, input_yn
@ -105,7 +105,7 @@ class ZeroPlugin(BeetsPlugin):
self.fields_to_progs[field] = [] self.fields_to_progs[field] = []
def import_task_choice_event(self, session, task): 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._log.warning('cannot zero in "as-is" mode')
self.warned = True self.warned = True
# TODO request write in as-is mode # 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``. ``a``, ``s``, ``u``, ``t``, ``g``, ``e``, ``i``, ``b``.
Additionally, the callback function can optionally specify the next action to 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 ``autotag.Proposal`` value to update the set of current proposals to be
considered. considered.

View file

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

View file

@ -34,7 +34,7 @@ from mediafile import MediaFile
from beets import config, importer, logging, util from beets import config, importer, logging, util
from beets.autotag import AlbumInfo, AlbumMatch, TrackInfo 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 import _common
from beets.test.helper import ( from beets.test.helper import (
NEEDS_REFLINK, NEEDS_REFLINK,
@ -324,52 +324,52 @@ class ImportSingletonTest(ImportTestCase):
def test_apply_asis_adds_track(self): def test_apply_asis_adds_track(self):
assert self.lib.items().get() is None assert self.lib.items().get() is None
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
assert self.lib.items().get().title == "Tag Track 1" assert self.lib.items().get().title == "Tag Track 1"
def test_apply_asis_does_not_add_album(self): def test_apply_asis_does_not_add_album(self):
assert self.lib.albums().get() is None assert self.lib.albums().get() is None
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
assert self.lib.albums().get() is None assert self.lib.albums().get() is None
def test_apply_asis_adds_singleton_path(self): def test_apply_asis_adds_singleton_path(self):
self.assert_lib_dir_empty() self.assert_lib_dir_empty()
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
self.assert_file_in_lib(b"singletons", b"Tag Track 1.mp3") self.assert_file_in_lib(b"singletons", b"Tag Track 1.mp3")
def test_apply_candidate_adds_track(self): def test_apply_candidate_adds_track(self):
assert self.lib.items().get() is None assert self.lib.items().get() is None
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
assert self.lib.items().get().title == "Applied Track 1" assert self.lib.items().get().title == "Applied Track 1"
def test_apply_candidate_does_not_add_album(self): 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() self.importer.run()
assert self.lib.albums().get() is None assert self.lib.albums().get() is None
def test_apply_candidate_adds_singleton_path(self): def test_apply_candidate_adds_singleton_path(self):
self.assert_lib_dir_empty() self.assert_lib_dir_empty()
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
self.assert_file_in_lib(b"singletons", b"Applied Track 1.mp3") self.assert_file_in_lib(b"singletons", b"Applied Track 1.mp3")
def test_skip_does_not_add_first_track(self): 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() self.importer.run()
assert self.lib.items().get() is None assert self.lib.items().get() is None
def test_skip_adds_other_tracks(self): def test_skip_adds_other_tracks(self):
self.prepare_album_for_import(2) self.prepare_album_for_import(2)
self.importer.add_choice(importer.action.SKIP) self.importer.add_choice(importer.Action.SKIP)
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
assert len(self.lib.items()) == 1 assert len(self.lib.items()) == 1
@ -385,8 +385,8 @@ class ImportSingletonTest(ImportTestCase):
self.setup_importer() self.setup_importer()
self.importer.paths = import_files 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() self.importer.run()
assert len(self.lib.items()) == 2 assert len(self.lib.items()) == 2
@ -406,7 +406,7 @@ class ImportSingletonTest(ImportTestCase):
# As-is item import. # As-is item import.
assert self.lib.albums().get() is None assert self.lib.albums().get() is None
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
for item in self.lib.items(): for item in self.lib.items():
@ -421,7 +421,7 @@ class ImportSingletonTest(ImportTestCase):
# Autotagged. # Autotagged.
assert self.lib.albums().get() is None assert self.lib.albums().get() is None
self.importer.clear_choices() self.importer.clear_choices()
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
for item in self.lib.items(): for item in self.lib.items():
@ -449,41 +449,41 @@ class ImportTest(ImportTestCase):
def test_apply_asis_adds_album(self): def test_apply_asis_adds_album(self):
assert self.lib.albums().get() is None assert self.lib.albums().get() is None
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
assert self.lib.albums().get().album == "Tag Album" assert self.lib.albums().get().album == "Tag Album"
def test_apply_asis_adds_tracks(self): def test_apply_asis_adds_tracks(self):
assert self.lib.items().get() is None assert self.lib.items().get() is None
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
assert self.lib.items().get().title == "Tag Track 1" assert self.lib.items().get().title == "Tag Track 1"
def test_apply_asis_adds_album_path(self): def test_apply_asis_adds_album_path(self):
self.assert_lib_dir_empty() self.assert_lib_dir_empty()
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
self.assert_file_in_lib(b"Tag Artist", b"Tag Album", b"Tag Track 1.mp3") self.assert_file_in_lib(b"Tag Artist", b"Tag Album", b"Tag Track 1.mp3")
def test_apply_candidate_adds_album(self): def test_apply_candidate_adds_album(self):
assert self.lib.albums().get() is None assert self.lib.albums().get() is None
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
assert self.lib.albums().get().album == "Applied Album" assert self.lib.albums().get().album == "Applied Album"
def test_apply_candidate_adds_tracks(self): def test_apply_candidate_adds_tracks(self):
assert self.lib.items().get() is None assert self.lib.items().get() is None
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
assert self.lib.items().get().title == "Applied Track 1" assert self.lib.items().get().title == "Applied Track 1"
def test_apply_candidate_adds_album_path(self): def test_apply_candidate_adds_album_path(self):
self.assert_lib_dir_empty() self.assert_lib_dir_empty()
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
self.assert_file_in_lib( self.assert_file_in_lib(
b"Applied Artist", b"Applied Album", b"Applied Track 1.mp3" b"Applied Artist", b"Applied Album", b"Applied Track 1.mp3"
@ -496,14 +496,14 @@ class ImportTest(ImportTestCase):
mediafile.genre = "Tag Genre" mediafile.genre = "Tag Genre"
mediafile.save() mediafile.save()
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
assert self.lib.items().get().genre == "" assert self.lib.items().get().genre == ""
def test_apply_from_scratch_keeps_format(self): def test_apply_from_scratch_keeps_format(self):
config["import"]["from_scratch"] = True config["import"]["from_scratch"] = True
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
assert self.lib.items().get().format == "MP3" assert self.lib.items().get().format == "MP3"
@ -511,7 +511,7 @@ class ImportTest(ImportTestCase):
config["import"]["from_scratch"] = True config["import"]["from_scratch"] = True
bitrate = 80000 bitrate = 80000
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
assert self.lib.items().get().bitrate == bitrate 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") import_file = os.path.join(self.import_dir, b"album", b"track_1.mp3")
self.assertExists(import_file) self.assertExists(import_file)
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
self.assertNotExists(import_file) 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") import_file = os.path.join(self.import_dir, b"album", b"track_1.mp3")
self.assertExists(import_file) self.assertExists(import_file)
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
self.assertNotExists(import_file) self.assertNotExists(import_file)
def test_skip_does_not_add_track(self): 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() self.importer.run()
assert self.lib.items().get() is None assert self.lib.items().get() is None
def test_skip_non_album_dirs(self): def test_skip_non_album_dirs(self):
self.assertIsDir(os.path.join(self.import_dir, b"album")) self.assertIsDir(os.path.join(self.import_dir, b"album"))
self.touch(b"cruft", dir=self.import_dir) 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() self.importer.run()
assert len(self.lib.albums()) == 1 assert len(self.lib.albums()) == 1
def test_unmatched_tracks_not_added(self): def test_unmatched_tracks_not_added(self):
self.prepare_album_for_import(2) self.prepare_album_for_import(2)
self.matcher.matching = self.matcher.MISSING self.matcher.matching = self.matcher.MISSING
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
assert len(self.lib.items()) == 1 assert len(self.lib.items()) == 1
@ -577,7 +577,7 @@ class ImportTest(ImportTestCase):
def test_asis_no_data_source(self): def test_asis_no_data_source(self):
assert self.lib.items().get() is None assert self.lib.items().get() is None
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
@ -599,7 +599,7 @@ class ImportTest(ImportTestCase):
# As-is album import. # As-is album import.
assert self.lib.albums().get() is None assert self.lib.albums().get() is None
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
for album in self.lib.albums(): for album in self.lib.albums():
@ -621,7 +621,7 @@ class ImportTest(ImportTestCase):
# Autotagged. # Autotagged.
assert self.lib.albums().get() is None assert self.lib.albums().get() is None
self.importer.clear_choices() self.importer.clear_choices()
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
for album in self.lib.albums(): for album in self.lib.albums():
@ -656,9 +656,9 @@ class ImportTracksTest(ImportTestCase):
assert self.lib.items().get() is None assert self.lib.items().get() is None
assert self.lib.albums().get() is None assert self.lib.albums().get() is None
self.importer.add_choice(importer.action.TRACKS) 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.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
assert self.lib.items().get().title == "Applied Track 1" assert self.lib.items().get().title == "Applied Track 1"
assert self.lib.albums().get() is None assert self.lib.albums().get() is None
@ -666,9 +666,9 @@ class ImportTracksTest(ImportTestCase):
def test_apply_tracks_adds_singleton_path(self): def test_apply_tracks_adds_singleton_path(self):
self.assert_lib_dir_empty() self.assert_lib_dir_empty()
self.importer.add_choice(importer.action.TRACKS) 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.APPLY) self.importer.add_choice(importer.Action.APPLY)
self.importer.run() self.importer.run()
self.assert_file_in_lib(b"singletons", b"Applied Track 1.mp3") self.assert_file_in_lib(b"singletons", b"Applied Track 1.mp3")
@ -687,7 +687,7 @@ class ImportCompilationTest(ImportTestCase):
self.matcher.restore() self.matcher.restore()
def test_asis_homogenous_sets_albumartist(self): def test_asis_homogenous_sets_albumartist(self):
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
assert self.lib.albums().get().albumartist == "Tag Artist" assert self.lib.albums().get().albumartist == "Tag Artist"
for item in self.lib.items(): for item in self.lib.items():
@ -699,7 +699,7 @@ class ImportCompilationTest(ImportTestCase):
self.import_media[1].artist = "Another Artist" self.import_media[1].artist = "Another Artist"
self.import_media[1].save() self.import_media[1].save()
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
assert self.lib.albums().get().albumartist == "Various Artists" assert self.lib.albums().get().albumartist == "Various Artists"
for item in self.lib.items(): for item in self.lib.items():
@ -711,7 +711,7 @@ class ImportCompilationTest(ImportTestCase):
self.import_media[1].artist = "Another Artist" self.import_media[1].artist = "Another Artist"
self.import_media[1].save() self.import_media[1].save()
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
for item in self.lib.items(): for item in self.lib.items():
assert item.comp assert item.comp
@ -722,7 +722,7 @@ class ImportCompilationTest(ImportTestCase):
self.import_media[1].artist = "Other Artist" self.import_media[1].artist = "Other Artist"
self.import_media[1].save() self.import_media[1].save()
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
assert self.lib.albums().get().albumartist == "Other Artist" assert self.lib.albums().get().albumartist == "Other Artist"
for item in self.lib.items(): for item in self.lib.items():
@ -736,7 +736,7 @@ class ImportCompilationTest(ImportTestCase):
mediafile.mb_albumartistid = "Album Artist ID" mediafile.mb_albumartistid = "Album Artist ID"
mediafile.save() mediafile.save()
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
assert self.lib.albums().get().albumartist == "Album Artist" assert self.lib.albums().get().albumartist == "Album Artist"
assert self.lib.albums().get().mb_albumartistid == "Album Artist ID" assert self.lib.albums().get().mb_albumartistid == "Album Artist ID"
@ -755,7 +755,7 @@ class ImportCompilationTest(ImportTestCase):
mediafile.mb_albumartistid = "Album Artist ID" mediafile.mb_albumartistid = "Album Artist ID"
mediafile.save() mediafile.save()
self.importer.add_choice(importer.action.ASIS) self.importer.add_choice(importer.Action.ASIS)
self.importer.run() self.importer.run()
assert self.lib.albums().get().albumartist == "Album Artist" assert self.lib.albums().get().albumartist == "Album Artist"
assert self.lib.albums().get().albumartists == [ assert self.lib.albums().get().albumartists == [
@ -802,7 +802,7 @@ class ImportExistingTest(ImportTestCase):
self.importer.run() self.importer.run()
assert len(self.lib.items()) == 1 assert len(self.lib.items()) == 1
self.reimporter.add_choice(importer.action.APPLY) self.reimporter.add_choice(importer.Action.APPLY)
self.reimporter.run() self.reimporter.run()
assert len(self.lib.items()) == 1 assert len(self.lib.items()) == 1
@ -810,18 +810,18 @@ class ImportExistingTest(ImportTestCase):
self.importer.run() self.importer.run()
assert len(self.lib.albums()) == 1 assert len(self.lib.albums()) == 1
self.reimporter.add_choice(importer.action.APPLY) self.reimporter.add_choice(importer.Action.APPLY)
self.reimporter.run() self.reimporter.run()
assert len(self.lib.albums()) == 1 assert len(self.lib.albums()) == 1
def test_does_not_duplicate_singleton_track(self): def test_does_not_duplicate_singleton_track(self):
self.importer.add_choice(importer.action.TRACKS) 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.importer.run()
assert len(self.lib.items()) == 1 assert len(self.lib.items()) == 1
self.reimporter.add_choice(importer.action.TRACKS) self.reimporter.add_choice(importer.Action.TRACKS)
self.reimporter.add_choice(importer.action.APPLY) self.reimporter.add_choice(importer.Action.APPLY)
self.reimporter.run() self.reimporter.run()
assert len(self.lib.items()) == 1 assert len(self.lib.items()) == 1
@ -831,7 +831,7 @@ class ImportExistingTest(ImportTestCase):
medium.title = "New Title" medium.title = "New Title"
medium.save() medium.save()
self.reimporter.add_choice(importer.action.ASIS) self.reimporter.add_choice(importer.Action.ASIS)
self.reimporter.run() self.reimporter.run()
assert self.lib.items().get().title == "New Title" assert self.lib.items().get().title == "New Title"
@ -846,7 +846,7 @@ class ImportExistingTest(ImportTestCase):
) )
self.assert_file_in_lib(old_path) 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.reimporter.run()
self.assert_file_in_lib( self.assert_file_in_lib(
b"Applied Artist", b"Applied Album", b"New Title.mp3" b"Applied Artist", b"Applied Album", b"New Title.mp3"
@ -865,7 +865,7 @@ class ImportExistingTest(ImportTestCase):
self.assert_file_in_lib(old_path) self.assert_file_in_lib(old_path)
config["import"]["copy"] = False config["import"]["copy"] = False
self.reimporter.add_choice(importer.action.ASIS) self.reimporter.add_choice(importer.Action.ASIS)
self.reimporter.run() self.reimporter.run()
self.assert_file_not_in_lib( self.assert_file_not_in_lib(
b"Applied Artist", b"Applied Album", b"New Title.mp3" b"Applied Artist", b"Applied Album", b"New Title.mp3"
@ -880,7 +880,7 @@ class ImportExistingTest(ImportTestCase):
) )
self.reimporter = self.setup_importer() self.reimporter = self.setup_importer()
self.reimporter.add_choice(importer.action.APPLY) self.reimporter.add_choice(importer.Action.APPLY)
self.reimporter.run() self.reimporter.run()
new_path = os.path.join( new_path = os.path.join(
b"Applied Artist", b"Applied Album", b"Applied Track 1.mp3" 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 = self.setup_importer(move=True)
self.reimporter.add_choice(importer.action.APPLY) self.reimporter.add_choice(importer.Action.APPLY)
self.reimporter.run() self.reimporter.run()
self.assertNotExists(self.import_media[0].path) self.assertNotExists(self.import_media[0].path)
@ -913,9 +913,9 @@ class GroupAlbumsImportTest(ImportTestCase):
self.setup_importer() self.setup_importer()
# Split tracks into two albums and use both as-is # Split tracks into two albums and use both as-is
self.importer.add_choice(importer.action.ALBUMS) 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.ASIS) self.importer.add_choice(importer.Action.ASIS)
def tearDown(self): def tearDown(self):
super().tearDown() super().tearDown()
@ -972,7 +972,7 @@ class GlobalGroupAlbumsImportTest(GroupAlbumsImportTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.importer.clear_choices() self.importer.clear_choices()
self.importer.default_choice = importer.action.ASIS self.importer.default_choice = importer.Action.ASIS
config["import"]["group_albums"] = True config["import"]["group_albums"] = True
@ -1019,7 +1019,7 @@ class InferAlbumDataTest(BeetsTestCase):
) )
def test_asis_homogenous_single_artist(self): 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() self.task.align_album_level_fields()
assert not self.items[0].comp assert not self.items[0].comp
assert self.items[0].albumartist == self.items[2].artist assert self.items[0].albumartist == self.items[2].artist
@ -1027,7 +1027,7 @@ class InferAlbumDataTest(BeetsTestCase):
def test_asis_heterogenous_va(self): def test_asis_heterogenous_va(self):
self.items[0].artist = "another artist" self.items[0].artist = "another artist"
self.items[1].artist = "some other 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() self.task.align_album_level_fields()
@ -1037,7 +1037,7 @@ class InferAlbumDataTest(BeetsTestCase):
def test_asis_comp_applied_to_all_items(self): def test_asis_comp_applied_to_all_items(self):
self.items[0].artist = "another artist" self.items[0].artist = "another artist"
self.items[1].artist = "some other 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() self.task.align_album_level_fields()
@ -1047,7 +1047,7 @@ class InferAlbumDataTest(BeetsTestCase):
def test_asis_majority_artist_single_artist(self): def test_asis_majority_artist_single_artist(self):
self.items[0].artist = "another artist" 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() self.task.align_album_level_fields()
@ -1060,7 +1060,7 @@ class InferAlbumDataTest(BeetsTestCase):
for item in self.items: for item in self.items:
item.albumartist = "some album artist" item.albumartist = "some album artist"
item.mb_albumartistid = "some album artist id" 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() self.task.align_album_level_fields()
@ -1089,7 +1089,7 @@ class InferAlbumDataTest(BeetsTestCase):
def test_small_single_artist_album(self): def test_small_single_artist_album(self):
self.items = [self.items[0]] self.items = [self.items[0]]
self.task.items = self.items 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() self.task.align_album_level_fields()
assert not self.items[0].comp assert not self.items[0].comp
@ -1599,7 +1599,7 @@ class ReimportTest(ImportTestCase):
def _setup_session(self, singletons=False): def _setup_session(self, singletons=False):
self.setup_importer(import_dir=self.libdir, singletons=singletons) 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): def _album(self):
return self.lib.albums().get() return self.lib.albums().get()
@ -1845,7 +1845,7 @@ class ImportMusicBrainzIdTest(ImportTestCase):
search_ids=[self.MB_RELEASE_PREFIX + self.ID_RELEASE_0] 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() self.importer.run()
assert self.lib.albums().get().album == "VALID_RELEASE_0" 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(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() self.importer.run()
assert self.lib.albums().get().album == "VALID_RELEASE_1" 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] 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() self.importer.run()
assert self.lib.items().get().title == "VALID_RECORDING_0" 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(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() self.importer.run()
assert self.lib.items().get().title == "VALID_RECORDING_1" 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 import config, plugins, ui
from beets.dbcore import types from beets.dbcore import types
from beets.importer import ( from beets.importer import (
Action,
ArchiveImportTask, ArchiveImportTask,
SentinelImportTask, SentinelImportTask,
SingletonImportTask, SingletonImportTask,
action,
) )
from beets.library import Item from beets.library import Item
from beets.plugins import MetadataSourcePlugin from beets.plugins import MetadataSourcePlugin
@ -389,7 +389,7 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
"aBort", "aBort",
) + ("Foo", "baR") ) + ("Foo", "baR")
self.importer.add_choice(action.SKIP) self.importer.add_choice(Action.SKIP)
self.importer.run() self.importer.run()
self.mock_input_options.assert_called_once_with( self.mock_input_options.assert_called_once_with(
opts, default="a", require=ANY opts, default="a", require=ANY
@ -424,7 +424,7 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
) + ("Foo", "baR") ) + ("Foo", "baR")
config["import"]["singletons"] = True config["import"]["singletons"] = True
self.importer.add_choice(action.SKIP) self.importer.add_choice(Action.SKIP)
self.importer.run() self.importer.run()
self.mock_input_options.assert_called_with( self.mock_input_options.assert_called_with(
opts, default="a", require=ANY opts, default="a", require=ANY
@ -461,7 +461,7 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
"enter Id", "enter Id",
"aBort", "aBort",
) + ("baZ",) ) + ("baZ",)
self.importer.add_choice(action.SKIP) self.importer.add_choice(Action.SKIP)
self.importer.run() self.importer.run()
self.mock_input_options.assert_called_once_with( self.mock_input_options.assert_called_once_with(
opts, default="a", require=ANY opts, default="a", require=ANY
@ -523,7 +523,7 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
return [ui.commands.PromptChoice("f", "Foo", self.foo)] return [ui.commands.PromptChoice("f", "Foo", self.foo)]
def foo(self, session, task): def foo(self, session, task):
return action.SKIP return Action.SKIP
self.register_plugin(DummyPlugin) self.register_plugin(DummyPlugin)
# Default options + extra choices by the plugin ('Foo', 'Bar') # Default options + extra choices by the plugin ('Foo', 'Bar')