Replace format calls with f-strings

This commit is contained in:
Šarūnas Nejus 2025-08-30 18:41:40 +01:00
parent 6c21482b7a
commit 4a361bd501
No known key found for this signature in database
GPG key ID: DD28F6704DBE3435
50 changed files with 335 additions and 531 deletions

View file

@ -238,11 +238,7 @@ There are a few coding conventions we use in beets:
.. code-block:: python .. code-block:: python
with g.lib.transaction() as tx: with g.lib.transaction() as tx:
rows = tx.query( rows = tx.query("SELECT DISTINCT {field} FROM {model._table} ORDER BY {sort_field}")
"SELECT DISTINCT '{0}' FROM '{1}' ORDER BY '{2}'".format(
field, model._table, sort_field
)
)
To fetch Item objects from the database, use lib.items(…) and supply a query To fetch Item objects from the database, use lib.items(…) and supply a query
as an argument. Resist the urge to write raw SQL for your query. If you must as an argument. Resist the urge to write raw SQL for your query. If you must

View file

@ -35,7 +35,7 @@ class IncludeLazyConfig(confuse.LazyConfig):
except confuse.NotFoundError: except confuse.NotFoundError:
pass pass
except confuse.ConfigReadError as err: except confuse.ConfigReadError as err:
stderr.write("configuration `import` failed: {}".format(err.reason)) stderr.write(f"configuration `import` failed: {err.reason}")
config = IncludeLazyConfig("beets", __name__) config = IncludeLazyConfig("beets", __name__)

View file

@ -79,9 +79,9 @@ def string_dist(str1: str | None, str2: str | None) -> float:
# "something, the". # "something, the".
for word in SD_END_WORDS: for word in SD_END_WORDS:
if str1.endswith(", %s" % word): if str1.endswith(", %s" % word):
str1 = "{} {}".format(word, str1[: -len(word) - 2]) str1 = f"{word} {str1[: -len(word) - 2]}"
if str2.endswith(", %s" % word): if str2.endswith(", %s" % word):
str2 = "{} {}".format(word, str2[: -len(word) - 2]) str2 = f"{word} {str2[: -len(word) - 2]}"
# Perform a couple of basic normalizing substitutions. # Perform a couple of basic normalizing substitutions.
for pat, repl in SD_REPLACE: for pat, repl in SD_REPLACE:
@ -230,7 +230,7 @@ class Distance:
"""Adds all the distance penalties from `dist`.""" """Adds all the distance penalties from `dist`."""
if not isinstance(dist, Distance): if not isinstance(dist, Distance):
raise ValueError( raise ValueError(
"`dist` must be a Distance object, not {}".format(type(dist)) f"`dist` must be a Distance object, not {type(dist)}"
) )
for key, penalties in dist._penalties.items(): for key, penalties in dist._penalties.items():
self._penalties.setdefault(key, []).extend(penalties) self._penalties.setdefault(key, []).extend(penalties)

View file

@ -390,9 +390,9 @@ class Model(ABC, Generic[D]):
return obj return obj
def __repr__(self) -> str: def __repr__(self) -> str:
return "{}({})".format( return (
type(self).__name__, f"{type(self).__name__}"
", ".join(f"{k}={v!r}" for k, v in dict(self).items()), f"({', '.join(f'{k}={v!r}' for k, v in dict(self).items())})"
) )
def clear_dirty(self): def clear_dirty(self):
@ -409,9 +409,9 @@ class Model(ABC, Generic[D]):
exception is raised otherwise. exception is raised otherwise.
""" """
if not self._db: if not self._db:
raise ValueError("{} has no database".format(type(self).__name__)) raise ValueError(f"{type(self).__name__} has no database")
if need_id and not self.id: if need_id and not self.id:
raise ValueError("{} has no id".format(type(self).__name__)) raise ValueError(f"{type(self).__name__} has no id")
return self._db return self._db
@ -595,9 +595,7 @@ class Model(ABC, Generic[D]):
with db.transaction() as tx: with db.transaction() as tx:
# Main table update. # Main table update.
if assignments: if assignments:
query = "UPDATE {} SET {} WHERE id=?".format( query = f"UPDATE {self._table} SET {','.join(assignments)} WHERE id=?"
self._table, ",".join(assignments)
)
subvars.append(self.id) subvars.append(self.id)
tx.mutate(query, subvars) tx.mutate(query, subvars)
@ -607,9 +605,9 @@ class Model(ABC, Generic[D]):
self._dirty.remove(key) self._dirty.remove(key)
value = self._type(key).to_sql(value) value = self._type(key).to_sql(value)
tx.mutate( tx.mutate(
"INSERT INTO {} " f"INSERT INTO {self._flex_table} "
"(entity_id, key, value) " "(entity_id, key, value) "
"VALUES (?, ?, ?);".format(self._flex_table), "VALUES (?, ?, ?);",
(self.id, key, value), (self.id, key, value),
) )
@ -1173,9 +1171,7 @@ class Database:
columns = [] columns = []
for name, typ in fields.items(): for name, typ in fields.items():
columns.append(f"{name} {typ.sql}") columns.append(f"{name} {typ.sql}")
setup_sql = "CREATE TABLE {} ({});\n".format( setup_sql = f"CREATE TABLE {table} ({', '.join(columns)});\n"
table, ", ".join(columns)
)
else: else:
# Table exists does not match the field set. # Table exists does not match the field set.
@ -1183,8 +1179,8 @@ class Database:
for name, typ in fields.items(): for name, typ in fields.items():
if name in current_fields: if name in current_fields:
continue continue
setup_sql += "ALTER TABLE {} ADD COLUMN {} {};\n".format( setup_sql += (
table, name, typ.sql f"ALTER TABLE {table} ADD COLUMN {name} {typ.sql};\n"
) )
with self.transaction() as tx: with self.transaction() as tx:
@ -1195,18 +1191,16 @@ class Database:
for the given entity (if they don't exist). for the given entity (if they don't exist).
""" """
with self.transaction() as tx: with self.transaction() as tx:
tx.script( tx.script(f"""
""" CREATE TABLE IF NOT EXISTS {flex_table} (
CREATE TABLE IF NOT EXISTS {0} (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
entity_id INTEGER, entity_id INTEGER,
key TEXT, key TEXT,
value TEXT, value TEXT,
UNIQUE(entity_id, key) ON CONFLICT REPLACE); UNIQUE(entity_id, key) ON CONFLICT REPLACE);
CREATE INDEX IF NOT EXISTS {0}_by_entity CREATE INDEX IF NOT EXISTS {flex_table}_by_entity
ON {0} (entity_id); ON {flex_table} (entity_id);
""".format(flex_table) """)
)
# Querying. # Querying.

View file

@ -475,7 +475,7 @@ class NumericQuery(FieldQuery[str]):
else: else:
if self.rangemin is not None and self.rangemax is not None: if self.rangemin is not None and self.rangemax is not None:
return ( return (
"{0} >= ? AND {0} <= ?".format(self.field), f"{self.field} >= ? AND {self.field} <= ?",
(self.rangemin, self.rangemax), (self.rangemin, self.rangemax),
) )
elif self.rangemin is not None: elif self.rangemin is not None:
@ -800,9 +800,7 @@ class DateInterval:
def __init__(self, start: datetime | None, end: datetime | None): def __init__(self, start: datetime | None, end: datetime | None):
if start is not None and end is not None and not start < end: if start is not None and end is not None and not start < end:
raise ValueError( raise ValueError(f"start date {start} is not before end date {end}")
"start date {} is not before end date {}".format(start, end)
)
self.start = start self.start = start
self.end = end self.end = end
@ -1074,9 +1072,9 @@ class FixedFieldSort(FieldSort):
if self.case_insensitive: if self.case_insensitive:
field = ( field = (
"(CASE " "(CASE "
"WHEN TYPEOF({0})='text' THEN LOWER({0}) " f"WHEN TYPEOF({self.field})='text' THEN LOWER({self.field}) "
"WHEN TYPEOF({0})='blob' THEN LOWER({0}) " f"WHEN TYPEOF({self.field})='blob' THEN LOWER({self.field}) "
"ELSE {0} END)".format(self.field) f"ELSE {self.field} END)"
) )
else: else:
field = self.field field = self.field

View file

@ -194,7 +194,7 @@ class BasePaddedInt(BaseInteger[N]):
self.digits = digits self.digits = digits
def format(self, value: int | N) -> str: def format(self, value: int | N) -> str:
return "{0:0{1}d}".format(value or 0, self.digits) return f"{value or 0:0{self.digits}d}"
class PaddedInt(BasePaddedInt[int]): class PaddedInt(BasePaddedInt[int]):
@ -219,7 +219,7 @@ class ScaledInt(Integer):
self.suffix = suffix self.suffix = suffix
def format(self, value: int) -> str: def format(self, value: int) -> str:
return "{}{}".format((value or 0) // self.unit, self.suffix) return f"{(value or 0) // self.unit}{self.suffix}"
class Id(NullInteger): class Id(NullInteger):
@ -249,7 +249,7 @@ class BaseFloat(Type[float, N]):
self.digits = digits self.digits = digits
def format(self, value: float | N) -> str: def format(self, value: float | N) -> str:
return "{0:.{1}f}".format(value or 0, self.digits) return f"{value or 0:.{self.digits}f}"
class Float(BaseFloat[float]): class Float(BaseFloat[float]):

View file

@ -341,9 +341,7 @@ def _resolve_duplicates(session: ImportSession, task: ImportTask):
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(f"found duplicates: {[o.id for o in found_duplicates]}")
"found duplicates: {}".format([o.id for o in found_duplicates])
)
# Get the default action to follow from config. # Get the default action to follow from config.
duplicate_action = config["import"]["duplicate_action"].as_choice( duplicate_action = config["import"]["duplicate_action"].as_choice(

View file

@ -844,12 +844,9 @@ class Item(LibModel):
# This must not use `with_album=True`, because that might access # This must not use `with_album=True`, because that might access
# the database. When debugging, that is not guaranteed to succeed, and # the database. When debugging, that is not guaranteed to succeed, and
# can even deadlock due to the database lock. # can even deadlock due to the database lock.
return "{}({})".format( return (
type(self).__name__, f"{type(self).__name__}"
", ".join( f"({', '.join(f'{k}={self[k]!r}' for k in self.keys(with_album=False))})"
"{}={!r}".format(k, self[k])
for k in self.keys(with_album=False)
),
) )
def keys(self, computed=False, with_album=True): def keys(self, computed=False, with_album=True):

View file

@ -424,9 +424,9 @@ def types(model_cls: type[AnyModel]) -> dict[str, Type]:
for field in plugin_types: for field in plugin_types:
if field in types and plugin_types[field] != types[field]: if field in types and plugin_types[field] != types[field]:
raise PluginConflictError( raise PluginConflictError(
"Plugin {} defines flexible field {} " f"Plugin {plugin.name} defines flexible field {field} "
"which has already been defined with " "which has already been defined with "
"another type.".format(plugin.name, field) "another type."
) )
types.update(plugin_types) types.update(plugin_types)
return types return types
@ -560,8 +560,8 @@ def feat_tokens(for_artist: bool = True) -> str:
feat_words = ["ft", "featuring", "feat", "feat.", "ft."] feat_words = ["ft", "featuring", "feat", "feat.", "ft."]
if for_artist: if for_artist:
feat_words += ["with", "vs", "and", "con", "&"] feat_words += ["with", "vs", "and", "con", "&"]
return r"(?<=[\s(\[])(?:{})(?=\s)".format( return (
"|".join(re.escape(x) for x in feat_words) rf"(?<=[\s(\[])(?:{'|'.join(re.escape(x) for x in feat_words)})(?=\s)"
) )

View file

@ -726,7 +726,7 @@ def get_replacements():
replacements.append((re.compile(pattern), repl)) replacements.append((re.compile(pattern), repl))
except re.error: except re.error:
raise UserError( raise UserError(
"malformed regular expression in replace: {}".format(pattern) f"malformed regular expression in replace: {pattern}"
) )
return replacements return replacements
@ -1163,7 +1163,7 @@ def show_model_changes(new, old=None, fields=None, always=False):
continue continue
changes.append( changes.append(
" {}: {}".format(field, colorize("text_highlight", new_fmt[field])) f" {field}: {colorize('text_highlight', new_fmt[field])}"
) )
# Print changes. # Print changes.
@ -1204,22 +1204,16 @@ def show_path_changes(path_changes):
# Print every change over two lines # Print every change over two lines
for source, dest in zip(sources, destinations): for source, dest in zip(sources, destinations):
color_source, color_dest = colordiff(source, dest) color_source, color_dest = colordiff(source, dest)
print_("{0} \n -> {1}".format(color_source, color_dest)) print_(f"{color_source} \n -> {color_dest}")
else: else:
# Print every change on a single line, and add a header # Print every change on a single line, and add a header
title_pad = max_width - len("Source ") + len(" -> ") title_pad = max_width - len("Source ") + len(" -> ")
print_("Source {0} Destination".format(" " * title_pad)) print_(f"Source {' ' * title_pad} Destination")
for source, dest in zip(sources, destinations): for source, dest in zip(sources, destinations):
pad = max_width - len(source) pad = max_width - len(source)
color_source, color_dest = colordiff(source, dest) color_source, color_dest = colordiff(source, dest)
print_( print_(f"{color_source} {' ' * pad} -> {color_dest}")
"{0} {1} -> {2}".format(
color_source,
" " * pad,
color_dest,
)
)
# Helper functions for option parsing. # Helper functions for option parsing.
@ -1245,9 +1239,7 @@ def _store_dict(option, opt_str, value, parser):
raise ValueError raise ValueError
except ValueError: except ValueError:
raise UserError( raise UserError(
"supplied argument `{}' is not of the form `key=value'".format( f"supplied argument `{value}' is not of the form `key=value'"
value
)
) )
option_values[key] = value option_values[key] = value
@ -1426,8 +1418,8 @@ class Subcommand:
@root_parser.setter @root_parser.setter
def root_parser(self, root_parser): def root_parser(self, root_parser):
self._root_parser = root_parser self._root_parser = root_parser
self.parser.prog = "{} {}".format( self.parser.prog = (
as_string(root_parser.get_prog_name()), self.name f"{as_string(root_parser.get_prog_name())} {self.name}"
) )
@ -1637,10 +1629,8 @@ def _ensure_db_directory_exists(path):
newpath = os.path.dirname(path) newpath = os.path.dirname(path)
if not os.path.isdir(newpath): if not os.path.isdir(newpath):
if input_yn( if input_yn(
"The database directory {} does not \ f"The database directory {util.displayable_path(newpath)} does not \
exist. Create it (Y/n)?".format( exist. Create it (Y/n)?"
util.displayable_path(newpath)
)
): ):
os.makedirs(newpath) os.makedirs(newpath)
@ -1660,9 +1650,8 @@ def _open_library(config: confuse.LazyConfig) -> library.Library:
except (sqlite3.OperationalError, sqlite3.DatabaseError) as db_error: except (sqlite3.OperationalError, sqlite3.DatabaseError) as db_error:
log.debug("{}", traceback.format_exc()) log.debug("{}", traceback.format_exc())
raise UserError( raise UserError(
"database file {} cannot not be opened: {}".format( f"database file {util.displayable_path(dbpath)} cannot not be"
util.displayable_path(dbpath), db_error f" opened: {db_error}"
)
) )
log.debug( log.debug(
"library database: {0}\nlibrary directory: {1}", "library database: {0}\nlibrary directory: {1}",

View file

@ -112,15 +112,11 @@ def _parse_logfiles(logfiles):
yield from _paths_from_logfile(syspath(normpath(logfile))) yield from _paths_from_logfile(syspath(normpath(logfile)))
except ValueError as err: except ValueError as err:
raise ui.UserError( raise ui.UserError(
"malformed logfile {}: {}".format( f"malformed logfile {util.displayable_path(logfile)}: {err}"
util.displayable_path(logfile), str(err)
)
) from err ) from err
except OSError as err: except OSError as err:
raise ui.UserError( raise ui.UserError(
"unreadable logfile {}: {}".format( f"unreadable logfile {util.displayable_path(logfile)}: {err}"
util.displayable_path(logfile), str(err)
)
) from err ) from err
@ -213,10 +209,10 @@ def get_singleton_disambig_fields(info: hooks.TrackInfo) -> Sequence[str]:
out = [] out = []
chosen_fields = config["match"]["singleton_disambig_fields"].as_str_seq() chosen_fields = config["match"]["singleton_disambig_fields"].as_str_seq()
calculated_values = { calculated_values = {
"index": "Index {}".format(str(info.index)), "index": f"Index {info.index}",
"track_alt": "Track {}".format(info.track_alt), "track_alt": f"Track {info.track_alt}",
"album": ( "album": (
"[{}]".format(info.album) f"[{info.album}]"
if ( if (
config["import"]["singleton_album_disambig"].get() config["import"]["singleton_album_disambig"].get()
and info.get("album") and info.get("album")
@ -242,7 +238,7 @@ def get_album_disambig_fields(info: hooks.AlbumInfo) -> Sequence[str]:
chosen_fields = config["match"]["album_disambig_fields"].as_str_seq() chosen_fields = config["match"]["album_disambig_fields"].as_str_seq()
calculated_values = { calculated_values = {
"media": ( "media": (
"{}x{}".format(info.mediums, info.media) f"{info.mediums}x{info.media}"
if (info.mediums and info.mediums > 1) if (info.mediums and info.mediums > 1)
else info.media else info.media
), ),
@ -277,7 +273,7 @@ def dist_string(dist):
"""Formats a distance (a float) as a colorized similarity percentage """Formats a distance (a float) as a colorized similarity percentage
string. string.
""" """
string = "{:.1f}%".format(((1 - dist) * 100)) string = f"{(1 - dist) * 100:.1f}%"
return dist_colorize(string, dist) return dist_colorize(string, dist)
@ -295,7 +291,7 @@ def penalty_string(distance, limit=None):
if limit and len(penalties) > limit: if limit and len(penalties) > limit:
penalties = penalties[:limit] + ["..."] penalties = penalties[:limit] + ["..."]
# Prefix penalty string with U+2260: Not Equal To # Prefix penalty string with U+2260: Not Equal To
penalty_string = "\u2260 {}".format(", ".join(penalties)) penalty_string = f"\u2260 {', '.join(penalties)}"
return ui.colorize("changed", penalty_string) return ui.colorize("changed", penalty_string)
@ -697,11 +693,9 @@ class AlbumChange(ChangeRepresentation):
# Missing and unmatched tracks. # Missing and unmatched tracks.
if self.match.extra_tracks: if self.match.extra_tracks:
print_( print_(
"Missing tracks ({0}/{1} - {2:.1%}):".format( "Missing tracks"
len(self.match.extra_tracks), f" ({len(self.match.extra_tracks)}/{len(self.match.info.tracks)} -"
len(self.match.info.tracks), f" {len(self.match.extra_tracks) / len(self.match.info.tracks):.1%}):"
len(self.match.extra_tracks) / len(self.match.info.tracks),
)
) )
for track_info in self.match.extra_tracks: for track_info in self.match.extra_tracks:
line = f" ! {track_info.title} (#{self.format_index(track_info)})" line = f" ! {track_info.title} (#{self.format_index(track_info)})"
@ -711,9 +705,9 @@ class AlbumChange(ChangeRepresentation):
if self.match.extra_items: if self.match.extra_items:
print_(f"Unmatched tracks ({len(self.match.extra_items)}):") print_(f"Unmatched tracks ({len(self.match.extra_items)}):")
for item in self.match.extra_items: for item in self.match.extra_items:
line = " ! {} (#{})".format(item.title, self.format_index(item)) line = f" ! {item.title} (#{self.format_index(item)})"
if item.length: if item.length:
line += " ({})".format(human_seconds_short(item.length)) line += f" ({human_seconds_short(item.length)})"
print_(ui.colorize("text_warning", line)) print_(ui.colorize("text_warning", line))
@ -769,7 +763,7 @@ def summarize_items(items, singleton):
""" """
summary_parts = [] summary_parts = []
if not singleton: if not singleton:
summary_parts.append("{} items".format(len(items))) summary_parts.append(f"{len(items)} items")
format_counts = {} format_counts = {}
for item in items: for item in items:
@ -789,10 +783,11 @@ def summarize_items(items, singleton):
average_bitrate = sum([item.bitrate for item in items]) / len(items) average_bitrate = sum([item.bitrate for item in items]) / len(items)
total_duration = sum([item.length for item in items]) total_duration = sum([item.length for item in items])
total_filesize = sum([item.filesize for item in items]) total_filesize = sum([item.filesize for item in items])
summary_parts.append("{}kbps".format(int(average_bitrate / 1000))) summary_parts.append(f"{int(average_bitrate / 1000)}kbps")
if items[0].format == "FLAC": if items[0].format == "FLAC":
sample_bits = "{}kHz/{} bit".format( sample_bits = (
round(int(items[0].samplerate) / 1000, 1), items[0].bitdepth f"{round(int(items[0].samplerate) / 1000, 1)}kHz"
f"/{items[0].bitdepth} bit"
) )
summary_parts.append(sample_bits) summary_parts.append(sample_bits)
summary_parts.append(human_seconds_short(total_duration)) summary_parts.append(human_seconds_short(total_duration))
@ -885,7 +880,7 @@ def choose_candidate(
if singleton: if singleton:
print_("No matching recordings found.") print_("No matching recordings found.")
else: else:
print_("No matching release found for {} tracks.".format(itemcount)) print_(f"No matching release found for {itemcount} tracks.")
print_( print_(
"For help, see: " "For help, see: "
"https://beets.readthedocs.org/en/latest/faq.html#nomatch" "https://beets.readthedocs.org/en/latest/faq.html#nomatch"
@ -910,23 +905,21 @@ def choose_candidate(
# Display list of candidates. # Display list of candidates.
print_("") print_("")
print_( print_(
'Finding tags for {} "{} - {}".'.format( f"Finding tags for {'track' if singleton else 'album'}"
"track" if singleton else "album", f'"{item.artist if singleton else cur_artist} -'
item.artist if singleton else cur_artist, f' {item.title if singleton else cur_album}".'
item.title if singleton else cur_album,
)
) )
print_(ui.indent(2) + "Candidates:") print_(ui.indent(2) + "Candidates:")
for i, match in enumerate(candidates): for i, match in enumerate(candidates):
# Index, metadata, and distance. # Index, metadata, and distance.
index0 = "{0}.".format(i + 1) index0 = f"{i + 1}."
index = dist_colorize(index0, match.distance) index = dist_colorize(index0, match.distance)
dist = "({:.1f}%)".format((1 - match.distance) * 100) dist = f"({(1 - match.distance) * 100:.1f}%)"
distance = dist_colorize(dist, match.distance) distance = dist_colorize(dist, match.distance)
metadata = "{0} - {1}".format( metadata = (
match.info.artist, f"{match.info.artist} -"
match.info.title if singleton else match.info.album, f" {match.info.title if singleton else match.info.album}"
) )
if i == 0: if i == 0:
metadata = dist_colorize(metadata, match.distance) metadata = dist_colorize(metadata, match.distance)
@ -1015,7 +1008,7 @@ def manual_id(session, task):
Input an ID, either for an album ("release") or a track ("recording"). Input an ID, either for an album ("release") or a track ("recording").
""" """
prompt = "Enter {} ID:".format("release" if task.is_album else "recording") prompt = f"Enter {'release' if task.is_album else 'recording'} ID:"
search_id = input_(prompt).strip() search_id = input_(prompt).strip()
if task.is_album: if task.is_album:
@ -1043,7 +1036,7 @@ class TerminalImportSession(importer.ImportSession):
path_str0 = displayable_path(task.paths, "\n") path_str0 = displayable_path(task.paths, "\n")
path_str = ui.colorize("import_path", path_str0) path_str = ui.colorize("import_path", path_str0)
items_str0 = "({} items)".format(len(task.items)) items_str0 = f"({len(task.items)} items)"
items_str = ui.colorize("import_path_items", items_str0) items_str = ui.colorize("import_path_items", items_str0)
print_(" ".join([path_str, items_str])) print_(" ".join([path_str, items_str]))
@ -1217,8 +1210,8 @@ class TerminalImportSession(importer.ImportSession):
def should_resume(self, path): def should_resume(self, path):
return ui.input_yn( return ui.input_yn(
"Import of the directory:\n{}\n" f"Import of the directory:\n{displayable_path(path)}\n"
"was interrupted. Resume (Y/n)?".format(displayable_path(path)) "was interrupted. Resume (Y/n)?"
) )
def _get_choices(self, task): def _get_choices(self, task):
@ -1317,7 +1310,8 @@ def import_files(lib, paths: list[bytes], query):
loghandler = logging.FileHandler(logpath, encoding="utf-8") loghandler = logging.FileHandler(logpath, encoding="utf-8")
except OSError: except OSError:
raise ui.UserError( raise ui.UserError(
f"Could not open log file for writing: {displayable_path(logpath)}" "Could not open log file for writing:"
f" {displayable_path(logpath)}"
) )
else: else:
loghandler = None loghandler = None
@ -1362,9 +1356,7 @@ def import_func(lib, opts, args: list[str]):
for path in byte_paths: for path in byte_paths:
if not os.path.exists(syspath(normpath(path))): if not os.path.exists(syspath(normpath(path))):
raise ui.UserError( raise ui.UserError(
"no such file or directory: {}".format( f"no such file or directory: {displayable_path(path)}"
displayable_path(path)
)
) )
# Check the directories from the logfiles, but don't throw an error in # Check the directories from the logfiles, but don't throw an error in
@ -1374,9 +1366,7 @@ def import_func(lib, opts, args: list[str]):
for path in paths_from_logfiles: for path in paths_from_logfiles:
if not os.path.exists(syspath(normpath(path))): if not os.path.exists(syspath(normpath(path))):
log.warning( log.warning(
"No such file or directory: {}".format( f"No such file or directory: {displayable_path(path)}"
displayable_path(path)
)
) )
continue continue
@ -1808,7 +1798,7 @@ def remove_items(lib, query, album, delete, force):
if not force: if not force:
# Prepare confirmation with user. # Prepare confirmation with user.
album_str = ( album_str = (
" in {} album{}".format(len(albums), "s" if len(albums) > 1 else "") f" in {len(albums)} album{'s' if len(albums) > 1 else ''}"
if album if album
else "" else ""
) )
@ -1816,14 +1806,17 @@ def remove_items(lib, query, album, delete, force):
if delete: if delete:
fmt = "$path - $title" fmt = "$path - $title"
prompt = "Really DELETE" prompt = "Really DELETE"
prompt_all = "Really DELETE {} file{}{}".format( prompt_all = (
len(items), "s" if len(items) > 1 else "", album_str "Really DELETE"
f" {len(items)} file{'s' if len(items) > 1 else ''}{album_str}"
) )
else: else:
fmt = "" fmt = ""
prompt = "Really remove from the library?" prompt = "Really remove from the library?"
prompt_all = "Really remove {} item{}{} from the library?".format( prompt_all = (
len(items), "s" if len(items) > 1 else "", album_str "Really remove"
f" {len(items)} item{'s' if len(items) > 1 else ''}{album_str}"
" from the library?"
) )
# Helpers for printing affected items # Helpers for printing affected items
@ -1906,23 +1899,13 @@ def show_stats(lib, query, exact):
if exact: if exact:
size_str += f" ({total_size} bytes)" size_str += f" ({total_size} bytes)"
print_( print_(f"""Tracks: {total_items}
"""Tracks: {} Total time: {human_seconds(total_time)}
Total time: {}{} {f" ({total_time:.2f} seconds)" if exact else ""}
{}: {} {"Total size" if exact else "Approximate total size"}: {size_str}
Artists: {} Artists: {len(artists)}
Albums: {} Albums: {len(albums)}
Album artists: {}""".format( Album artists: {len(album_artists)}""")
total_items,
human_seconds(total_time),
f" ({total_time:.2f} seconds)" if exact else "",
"Total size" if exact else "Approximate total size",
size_str,
len(artists),
len(albums),
len(album_artists),
),
)
def stats_func(lib, opts, args): def stats_func(lib, opts, args):
@ -1977,7 +1960,7 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm, inherit):
# Apply changes *temporarily*, preview them, and collect modified # Apply changes *temporarily*, preview them, and collect modified
# objects. # objects.
print_("Modifying {} {}s.".format(len(objs), "album" if album else "item")) print_(f"Modifying {len(objs)} {'album' if album else 'item'}s.")
changed = [] changed = []
templates = { templates = {
key: functemplate.template(value) for key, value in mods.items() key: functemplate.template(value) for key, value in mods.items()
@ -2213,9 +2196,7 @@ def move_func(lib, opts, args):
if dest is not None: if dest is not None:
dest = normpath(dest) dest = normpath(dest)
if not os.path.isdir(syspath(dest)): if not os.path.isdir(syspath(dest)):
raise ui.UserError( raise ui.UserError(f"no such directory: {displayable_path(dest)}")
"no such directory: {}".format(displayable_path(dest))
)
move_items( move_items(
lib, lib,
@ -2486,7 +2467,7 @@ def completion_script(commands):
# Command aliases # Command aliases
yield " local aliases='%s'\n" % " ".join(aliases.keys()) yield " local aliases='%s'\n" % " ".join(aliases.keys())
for alias, cmd in aliases.items(): for alias, cmd in aliases.items():
yield " local alias__{}={}\n".format(alias.replace("-", "_"), cmd) yield f" local alias__{alias.replace('-', '_')}={cmd}\n"
yield "\n" yield "\n"
# Fields # Fields
@ -2502,8 +2483,9 @@ def completion_script(commands):
for option_type, option_list in opts.items(): for option_type, option_list in opts.items():
if option_list: if option_list:
option_list = " ".join(option_list) option_list = " ".join(option_list)
yield " local {}__{}='{}'\n".format( yield (
option_type, cmd.replace("-", "_"), option_list " local"
f" {option_type}__{cmd.replace('-', '_')}='{option_list}'\n"
) )
yield " _beet_dispatch\n" yield " _beet_dispatch\n"

View file

@ -112,7 +112,7 @@ class HumanReadableError(Exception):
elif hasattr(self.reason, "strerror"): # i.e., EnvironmentError elif hasattr(self.reason, "strerror"): # i.e., EnvironmentError
return self.reason.strerror return self.reason.strerror
else: else:
return '"{}"'.format(str(self.reason)) return f'"{self.reason}"'
def get_message(self): def get_message(self):
"""Create the human-readable description of the error, sans """Create the human-readable description of the error, sans
@ -142,18 +142,16 @@ class FilesystemError(HumanReadableError):
def get_message(self): def get_message(self):
# Use a nicer English phrasing for some specific verbs. # Use a nicer English phrasing for some specific verbs.
if self.verb in ("move", "copy", "rename"): if self.verb in ("move", "copy", "rename"):
clause = "while {} {} to {}".format( clause = (
self._gerund(), f"while {self._gerund()} {displayable_path(self.paths[0])} to"
displayable_path(self.paths[0]), f" {displayable_path(self.paths[1])}"
displayable_path(self.paths[1]),
) )
elif self.verb in ("delete", "write", "create", "read"): elif self.verb in ("delete", "write", "create", "read"):
clause = "while {} {}".format( clause = f"while {self._gerund()} {displayable_path(self.paths[0])}"
self._gerund(), displayable_path(self.paths[0])
)
else: else:
clause = "during {} of paths {}".format( clause = (
self.verb, ", ".join(displayable_path(p) for p in self.paths) f"during {self.verb} of paths"
f" {', '.join(displayable_path(p) for p in self.paths)}"
) )
return f"{self._reasonstr()} {clause}" return f"{self._reasonstr()} {clause}"
@ -226,9 +224,8 @@ def sorted_walk(
except OSError as exc: except OSError as exc:
if logger: if logger:
logger.warning( logger.warning(
"could not list directory {}: {}".format( f"could not list directory {displayable_path(bytes_path)}:"
displayable_path(bytes_path), exc.strerror f" {exc.strerror}"
)
) )
return return
dirs = [] dirs = []

View file

@ -54,7 +54,7 @@ def resize_url(url: str, maxwidth: int, quality: int = 0) -> str:
if quality > 0: if quality > 0:
params["q"] = quality params["q"] = quality
return "{}?{}".format(PROXY_URL, urlencode(params)) return f"{PROXY_URL}?{urlencode(params)}"
class LocalBackendNotAvailableError(Exception): class LocalBackendNotAvailableError(Exception):

View file

@ -165,9 +165,7 @@ class Call:
self.original = original self.original = original
def __repr__(self): def __repr__(self):
return "Call({}, {}, {})".format( return f"Call({self.ident!r}, {self.args!r}, {self.original!r})"
repr(self.ident), repr(self.args), repr(self.original)
)
def evaluate(self, env): def evaluate(self, env):
"""Evaluate the function call in the environment, returning a """Evaluate the function call in the environment, returning a

View file

@ -42,9 +42,7 @@ def call(args):
try: try:
return util.command_output(args).stdout return util.command_output(args).stdout
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
raise ABSubmitError( raise ABSubmitError(f"{args[0]} exited with status {e.returncode}")
"{} exited with status {}".format(args[0], e.returncode)
)
class AcousticBrainzSubmitPlugin(plugins.BeetsPlugin): class AcousticBrainzSubmitPlugin(plugins.BeetsPlugin):
@ -63,9 +61,7 @@ class AcousticBrainzSubmitPlugin(plugins.BeetsPlugin):
# Explicit path to extractor # Explicit path to extractor
if not os.path.isfile(self.extractor): if not os.path.isfile(self.extractor):
raise ui.UserError( raise ui.UserError(
"Extractor command does not exist: {0}.".format( f"Extractor command does not exist: {self.extractor}."
self.extractor
)
) )
else: else:
# Implicit path to extractor, search for it in path # Implicit path to extractor, search for it in path

View file

@ -243,7 +243,7 @@ class AURADocument:
else: else:
# Increment page token by 1 # Increment page token by 1
next_url = request.url.replace( next_url = request.url.replace(
f"page={page}", "page={}".format(page + 1) f"page={page}", f"page={page + 1}"
) )
# Get only the items in the page range # Get only the items in the page range
data = [ data = [
@ -427,9 +427,7 @@ class TrackDocument(AURADocument):
return self.error( return self.error(
"404 Not Found", "404 Not Found",
"No track with the requested id.", "No track with the requested id.",
"There is no track with an id of {} in the library.".format( f"There is no track with an id of {track_id} in the library.",
track_id
),
) )
return self.single_resource_document( return self.single_resource_document(
self.get_resource_object(self.lib, track) self.get_resource_object(self.lib, track)
@ -513,9 +511,7 @@ class AlbumDocument(AURADocument):
return self.error( return self.error(
"404 Not Found", "404 Not Found",
"No album with the requested id.", "No album with the requested id.",
"There is no album with an id of {} in the library.".format( f"There is no album with an id of {album_id} in the library.",
album_id
),
) )
return self.single_resource_document( return self.single_resource_document(
self.get_resource_object(self.lib, album) self.get_resource_object(self.lib, album)
@ -600,9 +596,7 @@ class ArtistDocument(AURADocument):
return self.error( return self.error(
"404 Not Found", "404 Not Found",
"No artist with the requested id.", "No artist with the requested id.",
"There is no artist with an id of {} in the library.".format( f"There is no artist with an id of {artist_id} in the library.",
artist_id
),
) )
return self.single_resource_document(artist_resource) return self.single_resource_document(artist_resource)
@ -727,9 +721,7 @@ class ImageDocument(AURADocument):
return self.error( return self.error(
"404 Not Found", "404 Not Found",
"No image with the requested id.", "No image with the requested id.",
"There is no image with an id of {} in the library.".format( f"There is no image with an id of {image_id} in the library.",
image_id
),
) )
return self.single_resource_document(image_resource) return self.single_resource_document(image_resource)
@ -775,9 +767,7 @@ def audio_file(track_id):
return AURADocument.error( return AURADocument.error(
"404 Not Found", "404 Not Found",
"No track with the requested id.", "No track with the requested id.",
"There is no track with an id of {} in the library.".format( f"There is no track with an id of {track_id} in the library.",
track_id
),
) )
path = os.fsdecode(track.path) path = os.fsdecode(track.path)
@ -785,9 +775,8 @@ def audio_file(track_id):
return AURADocument.error( return AURADocument.error(
"404 Not Found", "404 Not Found",
"No audio file for the requested track.", "No audio file for the requested track.",
( f"There is no audio file for track {track_id} at the expected"
"There is no audio file for track {} at the expected location" " location",
).format(track_id),
) )
file_mimetype = guess_type(path)[0] file_mimetype = guess_type(path)[0]
@ -795,10 +784,8 @@ def audio_file(track_id):
return AURADocument.error( return AURADocument.error(
"500 Internal Server Error", "500 Internal Server Error",
"Requested audio file has an unknown mimetype.", "Requested audio file has an unknown mimetype.",
( f"The audio file for track {track_id} has an unknown mimetype. "
"The audio file for track {} has an unknown mimetype. " f"Its file extension is {path.split('.')[-1]}.",
"Its file extension is {}."
).format(track_id, path.split(".")[-1]),
) )
# Check that the Accept header contains the file's mimetype # Check that the Accept header contains the file's mimetype
@ -810,10 +797,8 @@ def audio_file(track_id):
return AURADocument.error( return AURADocument.error(
"406 Not Acceptable", "406 Not Acceptable",
"Unsupported MIME type or bitrate parameter in Accept header.", "Unsupported MIME type or bitrate parameter in Accept header.",
( f"The audio file for track {track_id} is only available as"
"The audio file for track {} is only available as {} and " f" {file_mimetype} and bitrate parameters are not supported.",
"bitrate parameters are not supported."
).format(track_id, file_mimetype),
) )
return send_file( return send_file(
@ -896,9 +881,7 @@ def image_file(image_id):
return AURADocument.error( return AURADocument.error(
"404 Not Found", "404 Not Found",
"No image with the requested id.", "No image with the requested id.",
"There is no image with an id of {} in the library".format( f"There is no image with an id of {image_id} in the library",
image_id
),
) )
return send_file(img_path) return send_file(img_path)

View file

@ -110,9 +110,7 @@ class BadFiles(BeetsPlugin):
self._log.debug("checking path: {}", dpath) self._log.debug("checking path: {}", dpath)
if not os.path.exists(item.path): if not os.path.exists(item.path):
ui.print_( ui.print_(
"{}: file does not exist".format( f"{ui.colorize('text_error', dpath)}: file does not exist"
ui.colorize("text_error", dpath)
)
) )
# Run the checker against the file if one is found # Run the checker against the file if one is found
@ -141,25 +139,21 @@ class BadFiles(BeetsPlugin):
if status > 0: if status > 0:
error_lines.append( error_lines.append(
"{}: checker exited with status {}".format( f"{ui.colorize('text_error', dpath)}: checker exited with"
ui.colorize("text_error", dpath), status f" status {status}"
)
) )
for line in output: for line in output:
error_lines.append(f" {line}") error_lines.append(f" {line}")
elif errors > 0: elif errors > 0:
error_lines.append( error_lines.append(
"{}: checker found {} errors or warnings".format( f"{ui.colorize('text_warning', dpath)}: checker found"
ui.colorize("text_warning", dpath), errors f" {status} errors or warnings"
)
) )
for line in output: for line in output:
error_lines.append(f" {line}") error_lines.append(f" {line}")
elif self.verbose: elif self.verbose:
error_lines.append( error_lines.append(f"{ui.colorize('text_success', dpath)}: ok")
"{}: ok".format(ui.colorize("text_success", dpath))
)
return error_lines return error_lines
@ -180,9 +174,8 @@ class BadFiles(BeetsPlugin):
def on_import_task_before_choice(self, task, session): def on_import_task_before_choice(self, task, session):
if hasattr(task, "_badfiles_checks_failed"): if hasattr(task, "_badfiles_checks_failed"):
ui.print_( ui.print_(
"{} one or more files failed checks:".format( f"{ui.colorize('text_warning', 'BAD')} one or more files failed"
ui.colorize("text_warning", "BAD") " checks:"
)
) )
for error in task._badfiles_checks_failed: for error in task._badfiles_checks_failed:
for error_line in error: for error_line in error:

View file

@ -212,14 +212,10 @@ class BeatportClient:
try: try:
response = self.api.get(self._make_url(endpoint), params=kwargs) response = self.api.get(self._make_url(endpoint), params=kwargs)
except Exception as e: except Exception as e:
raise BeatportAPIError( raise BeatportAPIError(f"Error connecting to Beatport API: {e}")
"Error connecting to Beatport API: {}".format(e)
)
if not response: if not response:
raise BeatportAPIError( raise BeatportAPIError(
"Error {0.status_code} for '{0.request.path_url}".format( f"Error {response.status_code} for '{response.request.path_url}"
response
)
) )
return response.json()["results"] return response.json()["results"]
@ -275,15 +271,14 @@ class BeatportRelease(BeatportObject):
self.genre = data.get("genre") self.genre = data.get("genre")
if "slug" in data: if "slug" in data:
self.url = "https://beatport.com/release/{}/{}".format( self.url = (
data["slug"], data["id"] f"https://beatport.com/release/{data['slug']}/{data['id']}"
) )
def __str__(self) -> str: def __str__(self) -> str:
return "<BeatportRelease: {} - {} ({})>".format( return (
self.artists_str(), "<BeatportRelease: "
self.name, f"{self.artists_str()} - {self.name} ({self.catalog_number})>"
self.catalog_number,
) )
@ -311,9 +306,7 @@ class BeatportTrack(BeatportObject):
except ValueError: except ValueError:
pass pass
if "slug" in data: if "slug" in data:
self.url = "https://beatport.com/track/{}/{}".format( self.url = f"https://beatport.com/track/{data['slug']}/{data['id']}"
data["slug"], data["id"]
)
self.track_number = data.get("trackNumber") self.track_number = data.get("trackNumber")
self.bpm = data.get("bpm") self.bpm = data.get("bpm")
self.initial_key = str((data.get("key") or {}).get("shortName")) self.initial_key = str((data.get("key") or {}).get("shortName"))

View file

@ -759,7 +759,7 @@ class Connection:
"""Create a new connection for the accepted socket `client`.""" """Create a new connection for the accepted socket `client`."""
self.server = server self.server = server
self.sock = sock self.sock = sock
self.address = "{}:{}".format(*sock.sock.getpeername()) self.address = ":".join(map(str, sock.sock.getpeername()))
def debug(self, message, kind=" "): def debug(self, message, kind=" "):
"""Log a debug message about this connection.""" """Log a debug message about this connection."""
@ -899,9 +899,7 @@ class MPDConnection(Connection):
return return
except BPDIdleError as e: except BPDIdleError as e:
self.idle_subscriptions = e.subsystems self.idle_subscriptions = e.subsystems
self.debug( self.debug(f"awaiting: {' '.join(e.subsystems)}", kind="z")
"awaiting: {}".format(" ".join(e.subsystems)), kind="z"
)
yield bluelet.call(self.server.dispatch_events()) yield bluelet.call(self.server.dispatch_events())
@ -933,7 +931,7 @@ class ControlConnection(Connection):
func = command.delegate("ctrl_", self) func = command.delegate("ctrl_", self)
yield bluelet.call(func(*command.args)) yield bluelet.call(func(*command.args))
except (AttributeError, TypeError) as e: except (AttributeError, TypeError) as e:
yield self.send("ERROR: {}".format(e.args[0])) yield self.send(f"ERROR: {e.args[0]}")
except Exception: except Exception:
yield self.send( yield self.send(
["ERROR: server error", traceback.format_exc().rstrip()] ["ERROR: server error", traceback.format_exc().rstrip()]
@ -1011,7 +1009,7 @@ class Command:
# If the command accepts a variable number of arguments skip the check. # If the command accepts a variable number of arguments skip the check.
if wrong_num and not argspec.varargs: if wrong_num and not argspec.varargs:
raise TypeError( raise TypeError(
'wrong number of arguments for "{}"'.format(self.name), f'wrong number of arguments for "{self.name}"',
self.name, self.name,
) )
@ -1110,10 +1108,8 @@ class Server(BaseServer):
self.lib = library self.lib = library
self.player = gstplayer.GstPlayer(self.play_finished) self.player = gstplayer.GstPlayer(self.play_finished)
self.cmd_update(None) self.cmd_update(None)
log.info("Server ready and listening on {}:{}".format(host, port)) log.info(f"Server ready and listening on {host}:{port}")
log.debug( log.debug(f"Listening for control signals on {host}:{ctrl_port}")
"Listening for control signals on {}:{}".format(host, ctrl_port)
)
def run(self): def run(self):
self.player.run() self.player.run()
@ -1142,9 +1138,7 @@ class Server(BaseServer):
pass pass
for tagtype, field in self.tagtype_map.items(): for tagtype, field in self.tagtype_map.items():
info_lines.append( info_lines.append(f"{tagtype}: {getattr(item, field)}")
"{}: {}".format(tagtype, str(getattr(item, field)))
)
return info_lines return info_lines
@ -1303,19 +1297,12 @@ class Server(BaseServer):
yield ( yield (
"bitrate: " + str(item.bitrate / 1000), "bitrate: " + str(item.bitrate / 1000),
"audio: {}:{}:{}".format( f"audio: {item.samplerate}:{item.bitdepth}:{item.channels}",
str(item.samplerate),
str(item.bitdepth),
str(item.channels),
),
) )
(pos, total) = self.player.time() (pos, total) = self.player.time()
yield ( yield (
"time: {}:{}".format( f"time: {int(pos)}:{int(total)}",
str(int(pos)),
str(int(total)),
),
"elapsed: " + f"{pos:.3f}", "elapsed: " + f"{pos:.3f}",
"duration: " + f"{total:.3f}", "duration: " + f"{total:.3f}",
) )

View file

@ -55,8 +55,8 @@ def span_from_str(span_str):
years = [int(x) for x in re.findall(r"\d+", span_str)] years = [int(x) for x in re.findall(r"\d+", span_str)]
if not years: if not years:
raise ui.UserError( raise ui.UserError(
"invalid range defined for year bucket '%s': no " "invalid range defined for year bucket '%s': no year found"
"year found" % span_str % span_str
) )
try: try:
years = [normalize_year(x, years[0]) for x in years] years = [normalize_year(x, years[0]) for x in years]
@ -125,11 +125,8 @@ def str2fmt(s):
"fromnchars": len(m.group("fromyear")), "fromnchars": len(m.group("fromyear")),
"tonchars": len(m.group("toyear")), "tonchars": len(m.group("toyear")),
} }
res["fmt"] = "{}%s{}{}{}".format( res["fmt"] = (
m.group("bef"), f"{m['bef']}%s{m['sep']}{'%s' if res['tonchars'] else ''}{m['after']}"
m.group("sep"),
"%s" if res["tonchars"] else "",
m.group("after"),
) )
return res return res

View file

@ -64,9 +64,7 @@ def get_format(fmt=None):
command = format_info["command"] command = format_info["command"]
extension = format_info.get("extension", fmt) extension = format_info.get("extension", fmt)
except KeyError: except KeyError:
raise ui.UserError( raise ui.UserError(f'convert: format {fmt} needs the "command" field')
'convert: format {} needs the "command" field'.format(fmt)
)
except ConfigTypeError: except ConfigTypeError:
command = config["convert"]["formats"][fmt].get(str) command = config["convert"]["formats"][fmt].get(str)
extension = fmt extension = fmt
@ -77,8 +75,8 @@ def get_format(fmt=None):
command = config["convert"]["command"].as_str() command = config["convert"]["command"].as_str()
elif "opts" in keys: elif "opts" in keys:
# Undocumented option for backwards compatibility with < 1.3.1. # Undocumented option for backwards compatibility with < 1.3.1.
command = "ffmpeg -i $source -y {} $dest".format( command = (
config["convert"]["opts"].as_str() f"ffmpeg -i $source -y {config['convert']['opts'].as_str()} $dest"
) )
if "extension" in keys: if "extension" in keys:
extension = config["convert"]["extension"].as_str() extension = config["convert"]["extension"].as_str()
@ -125,18 +123,25 @@ class ConvertPlugin(BeetsPlugin):
"id3v23": "inherit", "id3v23": "inherit",
"formats": { "formats": {
"aac": { "aac": {
"command": "ffmpeg -i $source -y -vn -acodec aac " "command": (
"-aq 1 $dest", "ffmpeg -i $source -y -vn -acodec aac -aq 1 $dest"
),
"extension": "m4a", "extension": "m4a",
}, },
"alac": { "alac": {
"command": "ffmpeg -i $source -y -vn -acodec alac $dest", "command": (
"ffmpeg -i $source -y -vn -acodec alac $dest"
),
"extension": "m4a", "extension": "m4a",
}, },
"flac": "ffmpeg -i $source -y -vn -acodec flac $dest", "flac": "ffmpeg -i $source -y -vn -acodec flac $dest",
"mp3": "ffmpeg -i $source -y -vn -aq 2 $dest", "mp3": "ffmpeg -i $source -y -vn -aq 2 $dest",
"opus": "ffmpeg -i $source -y -vn -acodec libopus -ab 96k $dest", "opus": (
"ogg": "ffmpeg -i $source -y -vn -acodec libvorbis -aq 3 $dest", "ffmpeg -i $source -y -vn -acodec libopus -ab 96k $dest"
),
"ogg": (
"ffmpeg -i $source -y -vn -acodec libvorbis -aq 3 $dest"
),
"wma": "ffmpeg -i $source -y -vn -acodec wmav2 -vn $dest", "wma": "ffmpeg -i $source -y -vn -acodec wmav2 -vn $dest",
}, },
"max_bitrate": None, "max_bitrate": None,
@ -323,7 +328,7 @@ class ConvertPlugin(BeetsPlugin):
raise raise
except OSError as exc: except OSError as exc:
raise ui.UserError( raise ui.UserError(
"convert: couldn't invoke '{}': {}".format(" ".join(args), exc) f"convert: couldn't invoke {' '.join(args)!r}: {exc}"
) )
if not quiet and not pretend: if not quiet and not pretend:

View file

@ -552,7 +552,7 @@ class DiscogsPlugin(MetadataSourcePlugin):
idx, medium_idx, sub_idx = self.get_track_index( idx, medium_idx, sub_idx = self.get_track_index(
subtracks[0]["position"] subtracks[0]["position"]
) )
position = "{}{}".format(idx or "", medium_idx or "") position = f"{idx or ''}{medium_idx or ''}"
if tracklist and not tracklist[-1]["position"]: if tracklist and not tracklist[-1]["position"]:
# Assume the previous index track contains the track title. # Assume the previous index track contains the track title.
@ -574,8 +574,8 @@ class DiscogsPlugin(MetadataSourcePlugin):
# option is set # option is set
if self.config["index_tracks"]: if self.config["index_tracks"]:
for subtrack in subtracks: for subtrack in subtracks:
subtrack["title"] = "{}: {}".format( subtrack["title"] = (
index_track["title"], subtrack["title"] f"{index_track['title']}: {subtrack['title']}"
) )
tracklist.extend(subtracks) tracklist.extend(subtracks)
else: else:

View file

@ -46,9 +46,7 @@ def edit(filename, log):
try: try:
subprocess.call(cmd) subprocess.call(cmd)
except OSError as exc: except OSError as exc:
raise ui.UserError( raise ui.UserError(f"could not run editor command {cmd[0]!r}: {exc}")
"could not run editor command {!r}: {}".format(cmd[0], exc)
)
def dump(arg): def dump(arg):
@ -71,9 +69,7 @@ def load(s):
for d in yaml.safe_load_all(s): for d in yaml.safe_load_all(s):
if not isinstance(d, dict): if not isinstance(d, dict):
raise ParseError( raise ParseError(
"each entry must be a dictionary; found {}".format( f"each entry must be a dictionary; found {type(d).__name__}"
type(d).__name__
)
) )
# Convert all keys to strings. They started out as strings, # Convert all keys to strings. They started out as strings,

View file

@ -35,8 +35,9 @@ def _confirm(objs, album):
to items). to items).
""" """
noun = "album" if album else "file" noun = "album" if album else "file"
prompt = "Modify artwork for {} {}{} (Y/n)?".format( prompt = (
len(objs), noun, "s" if len(objs) > 1 else "" "Modify artwork for"
f" {len(objs)} {noun}{'s' if len(objs) > 1 else ''} (Y/n)?"
) )
# Show all the items or albums. # Show all the items or albums.
@ -110,9 +111,7 @@ class EmbedCoverArtPlugin(BeetsPlugin):
imagepath = normpath(opts.file) imagepath = normpath(opts.file)
if not os.path.isfile(syspath(imagepath)): if not os.path.isfile(syspath(imagepath)):
raise ui.UserError( raise ui.UserError(
"image file {} not found".format( f"image file {displayable_path(imagepath)} not found"
displayable_path(imagepath)
)
) )
items = lib.items(args) items = lib.items(args)
@ -137,7 +136,7 @@ class EmbedCoverArtPlugin(BeetsPlugin):
response = requests.get(opts.url, timeout=5) response = requests.get(opts.url, timeout=5)
response.raise_for_status() response.raise_for_status()
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
self._log.error("{}".format(e)) self._log.error(f"{e}")
return return
extension = guess_extension(response.headers["Content-Type"]) extension = guess_extension(response.headers["Content-Type"])
if extension is None: if extension is None:
@ -149,7 +148,7 @@ class EmbedCoverArtPlugin(BeetsPlugin):
with open(tempimg, "wb") as f: with open(tempimg, "wb") as f:
f.write(response.content) f.write(response.content)
except Exception as e: except Exception as e:
self._log.error("Unable to save image: {}".format(e)) self._log.error(f"Unable to save image: {e}")
return return
items = lib.items(args) items = lib.items(args)
# Confirm with user. # Confirm with user.

View file

@ -38,9 +38,7 @@ def api_url(host, port, endpoint):
hostname_list.insert(0, "http://") hostname_list.insert(0, "http://")
hostname = "".join(hostname_list) hostname = "".join(hostname_list)
joined = urljoin( joined = urljoin(f"{hostname}:{port}", endpoint)
"{hostname}:{port}".format(hostname=hostname, port=port), endpoint
)
scheme, netloc, path, query_string, fragment = urlsplit(joined) scheme, netloc, path, query_string, fragment = urlsplit(joined)
query_params = parse_qs(query_string) query_params = parse_qs(query_string)
@ -81,12 +79,12 @@ def create_headers(user_id, token=None):
headers = {} headers = {}
authorization = ( authorization = (
'MediaBrowser UserId="{user_id}", ' f'MediaBrowser UserId="{user_id}", '
'Client="other", ' 'Client="other", '
'Device="beets", ' 'Device="beets", '
'DeviceId="beets", ' 'DeviceId="beets", '
'Version="0.0.0"' 'Version="0.0.0"'
).format(user_id=user_id) )
headers["x-emby-authorization"] = authorization headers["x-emby-authorization"] = authorization

View file

@ -89,8 +89,9 @@ class FishPlugin(BeetsPlugin):
"-o", "-o",
"--output", "--output",
default="~/.config/fish/completions/beet.fish", default="~/.config/fish/completions/beet.fish",
help="where to save the script. default: " help=(
"~/.config/fish/completions", "where to save the script. default: ~/.config/fish/completions"
),
) )
return [cmd] return [cmd]
@ -127,16 +128,12 @@ class FishPlugin(BeetsPlugin):
totstring += "" if nobasicfields else get_standard_fields(fields) totstring += "" if nobasicfields else get_standard_fields(fields)
totstring += get_extravalues(lib, extravalues) if extravalues else "" totstring += get_extravalues(lib, extravalues) if extravalues else ""
totstring += ( totstring += (
"\n" "\n" + "# ====== setup basic beet completion =====" + "\n" * 2
+ "# ====== {} =====".format("setup basic beet completion")
+ "\n" * 2
) )
totstring += get_basic_beet_options() totstring += get_basic_beet_options()
totstring += ( totstring += (
"\n" "\n"
+ "# ====== {} =====".format( + "# ====== setup field completion for subcommands ====="
"setup field completion for subcommands"
)
+ "\n" + "\n"
) )
totstring += get_subcommands(cmd_names_help, nobasicfields, extravalues) totstring += get_subcommands(cmd_names_help, nobasicfields, extravalues)
@ -226,11 +223,7 @@ def get_subcommands(cmd_name_and_help, nobasicfields, extravalues):
for cmdname, cmdhelp in cmd_name_and_help: for cmdname, cmdhelp in cmd_name_and_help:
cmdname = _escape(cmdname) cmdname = _escape(cmdname)
word += ( word += "\n" + f"# ------ fieldsetups for {cmdname} -------" + "\n"
"\n"
+ "# ------ {} -------".format("fieldsetups for " + cmdname)
+ "\n"
)
word += BL_NEED2.format( word += BL_NEED2.format(
("-a " + cmdname), ("-f " + "-d " + wrap(clean_whitespace(cmdhelp))) ("-a " + cmdname), ("-f " + "-d " + wrap(clean_whitespace(cmdhelp)))
) )
@ -268,11 +261,7 @@ def get_all_commands(beetcmds):
name = _escape(name) name = _escape(name)
word += "\n" word += "\n"
word += ( word += ("\n" * 2) + f"# ====== completions for {name} =====" + "\n"
("\n" * 2)
+ "# ====== {} =====".format("completions for " + name)
+ "\n"
)
for option in cmd.parser._get_all_options()[1:]: for option in cmd.parser._get_all_options()[1:]:
cmd_l = ( cmd_l = (
@ -332,7 +321,7 @@ def clean_whitespace(word):
def wrap(word): def wrap(word):
# Need " or ' around strings but watch out if they're in the string # Need " or ' around strings but watch out if they're in the string
sptoken = '"' sptoken = '"'
if ('"') in word and ("'") in word: if '"' in word and ("'") in word:
word.replace('"', sptoken) word.replace('"', sptoken)
return '"' + word + '"' return '"' + word + '"'

View file

@ -112,7 +112,7 @@ def apply_matches(d, log):
for item in d: for item in d:
if not item.artist: if not item.artist:
item.artist = artist item.artist = artist
log.info("Artist replaced with: {}".format(item.artist)) log.info(f"Artist replaced with: {item.artist}")
# No artist field: remaining field is the title. # No artist field: remaining field is the title.
else: else:
@ -122,11 +122,11 @@ def apply_matches(d, log):
for item in d: for item in d:
if bad_title(item.title): if bad_title(item.title):
item.title = str(d[item][title_field]) item.title = str(d[item][title_field])
log.info("Title replaced with: {}".format(item.title)) log.info(f"Title replaced with: {item.title}")
if "track" in d[item] and item.track == 0: if "track" in d[item] and item.track == 0:
item.track = int(d[item]["track"]) item.track = int(d[item]["track"])
log.info("Track replaced with: {}".format(item.track)) log.info(f"Track replaced with: {item.track}")
# Plugin structure and hook into import process. # Plugin structure and hook into import process.

View file

@ -154,7 +154,7 @@ def search_pairs(item):
# examples include (live), (remix), and (acoustic). # examples include (live), (remix), and (acoustic).
r"(.+?)\s+[(].*[)]$", r"(.+?)\s+[(].*[)]$",
# Remove any featuring artists from the title # Remove any featuring artists from the title
r"(.*?) {}".format(plugins.feat_tokens(for_artist=False)), rf"(.*?) {plugins.feat_tokens(for_artist=False)}",
# Remove part of title after colon ':' for songs with subtitles # Remove part of title after colon ':' for songs with subtitles
r"(.+?)\s*:.*", r"(.+?)\s*:.*",
] ]

View file

@ -83,9 +83,7 @@ class MusicBrainzCollectionPlugin(BeetsPlugin):
collection = self.config["collection"].as_str() collection = self.config["collection"].as_str()
if collection: if collection:
if collection not in collection_ids: if collection not in collection_ids:
raise ui.UserError( raise ui.UserError(f"invalid collection ID: {collection}")
"invalid collection ID: {}".format(collection)
)
return collection return collection
# No specified collection. Just return the first collection ID # No specified collection. Just return the first collection ID

View file

@ -117,7 +117,7 @@ class MetaSyncPlugin(BeetsPlugin):
try: try:
cls = META_SOURCES[player] cls = META_SOURCES[player]
except KeyError: except KeyError:
self._log.error("Unknown metadata source '{}'".format(player)) self._log.error(f"Unknown metadata source '{player}'")
try: try:
meta_source_instances[player] = cls(self.config, self._log) meta_source_instances[player] = cls(self.config, self._log)

View file

@ -68,9 +68,7 @@ class MusicBrainzAPIError(util.HumanReadableError):
super().__init__(reason, verb, tb) super().__init__(reason, verb, tb)
def get_message(self): def get_message(self):
return "{} in {} with query {}".format( return f"{self._reasonstr()} in {self.verb} with query {self.query!r}"
self._reasonstr(), self.verb, repr(self.query)
)
RELEASE_INCLUDES = list( RELEASE_INCLUDES = list(

View file

@ -43,7 +43,7 @@ def play(
""" """
# Print number of tracks or albums to be played, log command to be run. # Print number of tracks or albums to be played, log command to be run.
item_type += "s" if len(selection) > 1 else "" item_type += "s" if len(selection) > 1 else ""
ui.print_("Playing {} {}.".format(len(selection), item_type)) ui.print_(f"Playing {len(selection)} {item_type}.")
log.debug("executing command: {} {!r}", command_str, open_args) log.debug("executing command: {} {!r}", command_str, open_args)
try: try:
@ -179,9 +179,7 @@ class PlayPlugin(BeetsPlugin):
ui.print_( ui.print_(
ui.colorize( ui.colorize(
"text_warning", "text_warning",
"You are about to queue {} {}.".format( f"You are about to queue {len(selection)} {item_type}.",
len(selection), item_type
),
) )
) )

View file

@ -133,21 +133,16 @@ class PlaylistPlugin(beets.plugins.BeetsPlugin):
try: try:
self.update_playlist(playlist, base_dir) self.update_playlist(playlist, base_dir)
except beets.util.FilesystemError: except beets.util.FilesystemError:
self._log.error( self._log.error("Failed to update playlist: {}", playlist)
"Failed to update playlist: {}".format(
beets.util.displayable_path(playlist)
)
)
def find_playlists(self): def find_playlists(self):
"""Find M3U playlists in the playlist directory.""" """Find M3U playlists in the playlist directory."""
playlist_dir = beets.util.syspath(self.playlist_dir)
try: try:
dir_contents = os.listdir(beets.util.syspath(self.playlist_dir)) dir_contents = os.listdir(playlist_dir)
except OSError: except OSError:
self._log.warning( self._log.warning(
"Unable to open playlist directory {}".format( "Unable to open playlist directory {}", self.playlist_dir
beets.util.displayable_path(self.playlist_dir)
)
) )
return return
@ -195,9 +190,10 @@ class PlaylistPlugin(beets.plugins.BeetsPlugin):
if changes or deletions: if changes or deletions:
self._log.info( self._log.info(
"Updated playlist {} ({} changes, {} deletions)".format( "Updated playlist {} ({} changes, {} deletions)",
filename, changes, deletions filename,
) changes,
deletions,
) )
beets.util.copy(new_playlist, filename, replace=True) beets.util.copy(new_playlist, filename, replace=True)
beets.util.remove(new_playlist) beets.util.remove(new_playlist)

View file

@ -22,9 +22,7 @@ def get_music_section(
): ):
"""Getting the section key for the music library in Plex.""" """Getting the section key for the music library in Plex."""
api_endpoint = append_token("library/sections", token) api_endpoint = append_token("library/sections", token)
url = urljoin( url = urljoin(f"{get_protocol(secure)}://{host}:{port}", api_endpoint)
"{}://{}:{}".format(get_protocol(secure), host, port), api_endpoint
)
# Sends request. # Sends request.
r = requests.get( r = requests.get(
@ -54,9 +52,7 @@ def update_plex(host, port, token, library_name, secure, ignore_cert_errors):
) )
api_endpoint = f"library/sections/{section_key}/refresh" api_endpoint = f"library/sections/{section_key}/refresh"
api_endpoint = append_token(api_endpoint, token) api_endpoint = append_token(api_endpoint, token)
url = urljoin( url = urljoin(f"{get_protocol(secure)}://{host}:{port}", api_endpoint)
"{}://{}:{}".format(get_protocol(secure), host, port), api_endpoint
)
# Sends request and returns requests object. # Sends request and returns requests object.
r = requests.get( r = requests.get(

View file

@ -70,9 +70,7 @@ def call(args: list[str], log: Logger, **kwargs: Any):
return command_output(args, **kwargs) return command_output(args, **kwargs)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
log.debug(e.output.decode("utf8", "ignore")) log.debug(e.output.decode("utf8", "ignore"))
raise ReplayGainError( raise ReplayGainError(f"{args[0]} exited with status {e.returncode}")
"{} exited with status {}".format(args[0], e.returncode)
)
def db_to_lufs(db: float) -> float: def db_to_lufs(db: float) -> float:
@ -170,9 +168,8 @@ class RgTask:
# `track_gains` without throwing FatalReplayGainError # `track_gains` without throwing FatalReplayGainError
# => raise non-fatal exception & continue # => raise non-fatal exception & continue
raise ReplayGainError( raise ReplayGainError(
"ReplayGain backend `{}` failed for track {}".format( f"ReplayGain backend `{self.backend_name}` failed for track"
self.backend_name, item f" {item}"
)
) )
self._store_track_gain(item, self.track_gains[0]) self._store_track_gain(item, self.track_gains[0])
@ -191,10 +188,8 @@ class RgTask:
# `album_gain` without throwing FatalReplayGainError # `album_gain` without throwing FatalReplayGainError
# => raise non-fatal exception & continue # => raise non-fatal exception & continue
raise ReplayGainError( raise ReplayGainError(
"ReplayGain backend `{}` failed " f"ReplayGain backend `{self.backend_name}` failed "
"for some tracks in album {}".format( f"for some tracks in album {self.album}"
self.backend_name, self.album
)
) )
for item, track_gain in zip(self.items, self.track_gains): for item, track_gain in zip(self.items, self.track_gains):
self._store_track_gain(item, track_gain) self._store_track_gain(item, track_gain)
@ -501,12 +496,10 @@ class FfmpegBackend(Backend):
if self._parse_float(b"M: " + line[1]) >= gating_threshold: if self._parse_float(b"M: " + line[1]) >= gating_threshold:
n_blocks += 1 n_blocks += 1
self._log.debug( self._log.debug(
"{}: {} blocks over {} LUFS".format( f"{item}: {n_blocks} blocks over {gating_threshold} LUFS"
item, n_blocks, gating_threshold
)
) )
self._log.debug("{}: gain {} LU, peak {}".format(item, gain, peak)) self._log.debug(f"{item}: gain {gain} LU, peak {peak}")
return Gain(gain, peak), n_blocks return Gain(gain, peak), n_blocks
@ -526,9 +519,7 @@ class FfmpegBackend(Backend):
if output[i].startswith(search): if output[i].startswith(search):
return i return i
raise ReplayGainError( raise ReplayGainError(
"ffmpeg output: missing {} after line {}".format( f"ffmpeg output: missing {search!r} after line {start_line}"
repr(search), start_line
)
) )
def _parse_float(self, line: bytes) -> float: def _parse_float(self, line: bytes) -> float:
@ -575,7 +566,7 @@ class CommandBackend(Backend):
# Explicit executable path. # Explicit executable path.
if not os.path.isfile(self.command): if not os.path.isfile(self.command):
raise FatalReplayGainError( raise FatalReplayGainError(
"replaygain command does not exist: {}".format(self.command) f"replaygain command does not exist: {self.command}"
) )
else: else:
# Check whether the program is in $PATH. # Check whether the program is in $PATH.
@ -1229,10 +1220,8 @@ class ReplayGainPlugin(BeetsPlugin):
if self.backend_name not in BACKENDS: if self.backend_name not in BACKENDS:
raise ui.UserError( raise ui.UserError(
"Selected ReplayGain backend {} is not supported. " f"Selected ReplayGain backend {self.backend_name} is not"
"Please select one of: {}".format( f" supported. Please select one of: {', '.join(BACKENDS)}"
self.backend_name, ", ".join(BACKENDS.keys())
)
) )
# FIXME: Consider renaming the configuration option to 'peak_method' # FIXME: Consider renaming the configuration option to 'peak_method'
@ -1240,10 +1229,9 @@ class ReplayGainPlugin(BeetsPlugin):
peak_method = self.config["peak"].as_str() peak_method = self.config["peak"].as_str()
if peak_method not in PeakMethod.__members__: if peak_method not in PeakMethod.__members__:
raise ui.UserError( raise ui.UserError(
"Selected ReplayGain peak method {} is not supported. " f"Selected ReplayGain peak method {peak_method} is not"
"Please select one of: {}".format( " supported. Please select one of:"
peak_method, ", ".join(PeakMethod.__members__) f" {', '.join(PeakMethod.__members__)}"
)
) )
# This only applies to plain old rg tags, r128 doesn't store peak # This only applies to plain old rg tags, r128 doesn't store peak
# values. # values.
@ -1526,18 +1514,16 @@ class ReplayGainPlugin(BeetsPlugin):
if opts.album: if opts.album:
albums = lib.albums(args) albums = lib.albums(args)
self._log.info( self._log.info(
"Analyzing {} albums ~ {} backend...".format( f"Analyzing {len(albums)} albums ~"
len(albums), self.backend_name f" {self.backend_name} backend..."
)
) )
for album in albums: for album in albums:
self.handle_album(album, write, force) self.handle_album(album, write, force)
else: else:
items = lib.items(args) items = lib.items(args)
self._log.info( self._log.info(
"Analyzing {} tracks ~ {} backend...".format( f"Analyzing {len(items)} tracks ~"
len(items), self.backend_name f" {self.backend_name} backend..."
)
) )
for item in items: for item in items:
self.handle_track(item, write, force) self.handle_track(item, write, force)
@ -1565,8 +1551,10 @@ class ReplayGainPlugin(BeetsPlugin):
dest="force", dest="force",
action="store_true", action="store_true",
default=False, default=False,
help="analyze all files, including those that " help=(
"already have ReplayGain metadata", "analyze all files, including those that already have"
" ReplayGain metadata"
),
) )
cmd.parser.add_option( cmd.parser.add_option(
"-w", "-w",

View file

@ -138,10 +138,9 @@ class SmartPlaylistPlugin(BeetsPlugin):
if name in args if name in args
} }
if not playlists: if not playlists:
unmatched = [name for name, _, _ in self._unmatched_playlists]
raise ui.UserError( raise ui.UserError(
"No playlist matching any of {} found".format( f"No playlist matching any of {unmatched} found"
[name for name, _, _ in self._unmatched_playlists]
)
) )
self._matched_playlists = playlists self._matched_playlists = playlists
@ -331,8 +330,9 @@ class SmartPlaylistPlugin(BeetsPlugin):
for key, value in attr for key, value in attr
] ]
attrs = "".join(al) attrs = "".join(al)
comment = "#EXTINF:{}{},{} - {}\n".format( comment = (
int(item.length), attrs, item.artist, item.title f"#EXTINF:{int(item.length)}{attrs},"
f"{item.artist} - {item.title}\n"
) )
f.write(comment.encode("utf-8") + entry.uri + b"\n") f.write(comment.encode("utf-8") + entry.uri + b"\n")
# Send an event when playlists were updated. # Send an event when playlists were updated.

View file

@ -168,8 +168,9 @@ class SpotifyPlugin(
c_secret: str = self.config["client_secret"].as_str() c_secret: str = self.config["client_secret"].as_str()
headers = { headers = {
"Authorization": "Basic {}".format( "Authorization": (
base64.b64encode(f"{c_id}:{c_secret}".encode()).decode() "Basic"
f" {base64.b64encode(f'{c_id}:{c_secret}'.encode()).decode()}"
) )
} }
response = requests.post( response = requests.post(
@ -182,7 +183,7 @@ class SpotifyPlugin(
response.raise_for_status() response.raise_for_status()
except requests.exceptions.HTTPError as e: except requests.exceptions.HTTPError as e:
raise ui.UserError( raise ui.UserError(
"Spotify authorization failed: {}\n{}".format(e, response.text) f"Spotify authorization failed: {e}\n{response.text}"
) )
self.access_token = response.json()["access_token"] self.access_token = response.json()["access_token"]
@ -236,7 +237,7 @@ class SpotifyPlugin(
if e.response.status_code == 401: if e.response.status_code == 401:
self._log.debug( self._log.debug(
f"{self.data_source} access token has expired. " f"{self.data_source} access token has expired. "
f"Reauthenticating." "Reauthenticating."
) )
self._authenticate() self._authenticate()
return self._handle_response( return self._handle_response(
@ -314,9 +315,7 @@ class SpotifyPlugin(
else: else:
raise ui.UserError( raise ui.UserError(
"Invalid `release_date_precision` returned " "Invalid `release_date_precision` returned "
"by {} API: '{}'".format( f"by {self.data_source} API: '{release_date_precision}'"
self.data_source, release_date_precision
)
) )
tracks_data = album_data["tracks"] tracks_data = album_data["tracks"]
@ -472,17 +471,17 @@ class SpotifyPlugin(
"-m", "-m",
"--mode", "--mode",
action="store", action="store",
help='"open" to open {} with playlist, ' help=(
'"list" to print (default)'.format(self.data_source), f'"open" to open {self.data_source} with playlist, '
'"list" to print (default)'
),
) )
spotify_cmd.parser.add_option( spotify_cmd.parser.add_option(
"-f", "-f",
"--show-failures", "--show-failures",
action="store_true", action="store_true",
dest="show_failures", dest="show_failures",
help="list tracks that did not match a {} ID".format( help=f"list tracks that did not match a {self.data_source} ID",
self.data_source
),
) )
spotify_cmd.func = queries spotify_cmd.func = queries
@ -647,9 +646,7 @@ class SpotifyPlugin(
spotify_ids = [track_data["id"] for track_data in results] spotify_ids = [track_data["id"] for track_data in results]
if self.config["mode"].get() == "open": if self.config["mode"].get() == "open":
self._log.info( self._log.info(
"Attempting to open {} with playlist".format( f"Attempting to open {self.data_source} with playlist"
self.data_source
)
) )
spotify_url = "spotify:trackset:Playlist:" + ",".join( spotify_url = "spotify:trackset:Playlist:" + ",".join(
spotify_ids spotify_ids

View file

@ -168,9 +168,7 @@ class SubsonicPlaylistPlugin(BeetsPlugin):
params["v"] = "1.12.0" params["v"] = "1.12.0"
params["c"] = "beets" params["c"] = "beets"
resp = requests.get( resp = requests.get(
"{}/rest/{}?{}".format( f"{self.config['base_url'].get()}/rest/{endpoint}?{urlencode(params)}",
self.config["base_url"].get(), endpoint, urlencode(params)
),
timeout=10, timeout=10,
) )
return resp return resp

View file

@ -202,7 +202,7 @@ class ThumbnailsPlugin(BeetsPlugin):
artfile = os.path.split(album.artpath)[1] artfile = os.path.split(album.artpath)[1]
with open(syspath(outfilename), "w") as f: with open(syspath(outfilename), "w") as f:
f.write("[Desktop Entry]\n") f.write("[Desktop Entry]\n")
f.write("Icon=./{}".format(artfile.decode("utf-8"))) f.write(f"Icon=./{artfile.decode('utf-8')}")
f.close() f.close()
self._log.debug("Wrote file {0}", displayable_path(outfilename)) self._log.debug("Wrote file {0}", displayable_path(outfilename))
@ -266,9 +266,7 @@ class GioURI(URIGetter):
g_file_ptr = self.libgio.g_file_new_for_path(path) g_file_ptr = self.libgio.g_file_new_for_path(path)
if not g_file_ptr: if not g_file_ptr:
raise RuntimeError( raise RuntimeError(
"No gfile pointer received for {}".format( f"No gfile pointer received for {displayable_path(path)}"
displayable_path(path)
)
) )
try: try:

View file

@ -44,6 +44,6 @@ class TypesPlugin(BeetsPlugin):
mytypes[key] = types.DATE mytypes[key] = types.DATE
else: else:
raise ConfigValueError( raise ConfigValueError(
"unknown type '{}' for the '{}' field".format(value, key) f"unknown type '{value}' for the '{key}' field"
) )
return mytypes return mytypes

View file

@ -232,9 +232,7 @@ def _get_unique_table_field_values(model, field, sort_field):
raise KeyError raise KeyError
with g.lib.transaction() as tx: with g.lib.transaction() as tx:
rows = tx.query( rows = tx.query(
"SELECT DISTINCT '{}' FROM '{}' ORDER BY '{}'".format( f"SELECT DISTINCT '{field}' FROM '{model._table}' ORDER BY '{sort_field}'"
field, model._table, sort_field
)
) )
return [row[0] for row in rows] return [row[0] for row in rows]

View file

@ -280,6 +280,7 @@ select = [
"PT", # flake8-pytest-style "PT", # flake8-pytest-style
# "RUF", # ruff # "RUF", # ruff
# "UP", # pyupgrade # "UP", # pyupgrade
"UP032", # use f-string instead of format call
"TCH", # flake8-type-checking "TCH", # flake8-type-checking
"W", # pycodestyle "W", # pycodestyle
] ]

View file

@ -89,8 +89,8 @@ class CAAHelper:
MBID_RELASE = "rid" MBID_RELASE = "rid"
MBID_GROUP = "rgid" MBID_GROUP = "rgid"
RELEASE_URL = "coverartarchive.org/release/{}".format(MBID_RELASE) RELEASE_URL = f"coverartarchive.org/release/{MBID_RELASE}"
GROUP_URL = "coverartarchive.org/release-group/{}".format(MBID_GROUP) GROUP_URL = f"coverartarchive.org/release-group/{MBID_GROUP}"
RELEASE_URL = "https://" + RELEASE_URL RELEASE_URL = "https://" + RELEASE_URL
GROUP_URL = "https://" + GROUP_URL GROUP_URL = "https://" + GROUP_URL
@ -305,10 +305,8 @@ class FSArtTest(UseThePlugin):
class CombinedTest(FetchImageTestCase, CAAHelper): class CombinedTest(FetchImageTestCase, CAAHelper):
ASIN = "xxxx" ASIN = "xxxx"
MBID = "releaseid" MBID = "releaseid"
AMAZON_URL = "https://images.amazon.com/images/P/{}.01.LZZZZZZZ.jpg".format( AMAZON_URL = f"https://images.amazon.com/images/P/{ASIN}.01.LZZZZZZZ.jpg"
ASIN AAO_URL = f"https://www.albumart.org/index_detail.php?asin={ASIN}"
)
AAO_URL = "https://www.albumart.org/index_detail.php?asin={}".format(ASIN)
def setUp(self): def setUp(self):
super().setUp() super().setUp()

View file

@ -49,14 +49,12 @@ class ConvertMixin:
""" """
if re.search("[^a-zA-Z0-9]", tag): if re.search("[^a-zA-Z0-9]", tag):
raise ValueError( raise ValueError(
"tag '{}' must only contain letters and digits".format(tag) f"tag '{tag}' must only contain letters and digits"
) )
# A Python script that copies the file and appends a tag. # A Python script that copies the file and appends a tag.
stub = os.path.join(_common.RSRC, b"convert_stub.py").decode("utf-8") stub = os.path.join(_common.RSRC, b"convert_stub.py").decode("utf-8")
return "{} {} $source $dest {}".format( return f"{shell_quote(sys.executable)} {shell_quote(stub)} $source $dest {tag}"
shell_quote(sys.executable), shell_quote(stub), tag
)
def file_endswith(self, path: Path, tag: str): def file_endswith(self, path: Path, tag: str):
"""Check the path is a file and if its content ends with `tag`.""" """Check the path is a file and if its content ends with `tag`."""

View file

@ -144,9 +144,7 @@ class EmbedartCliTest(IOMixin, PluginMixin, FetchImageHelper, BeetsTestCase):
if os.path.isfile(syspath(tmp_path)): if os.path.isfile(syspath(tmp_path)):
os.remove(syspath(tmp_path)) os.remove(syspath(tmp_path))
self.fail( self.fail(
"Artwork file {} was not deleted".format( f"Artwork file {displayable_path(tmp_path)} was not deleted"
displayable_path(tmp_path)
)
) )
def test_art_file_missing(self): def test_art_file_missing(self):

View file

@ -37,7 +37,7 @@ class IPFSPluginTest(PluginTestCase):
try: try:
if check_item.get("ipfs", with_album=False): if check_item.get("ipfs", with_album=False):
ipfs_item = os.fsdecode(os.path.basename(want_item.path)) ipfs_item = os.fsdecode(os.path.basename(want_item.path))
want_path = "/ipfs/{}/{}".format(test_album.ipfs, ipfs_item) want_path = f"/ipfs/{test_album.ipfs}/{ipfs_item}"
want_path = bytestring_path(want_path) want_path = bytestring_path(want_path)
assert check_item.path == want_path assert check_item.path == want_path
assert ( assert (

View file

@ -96,9 +96,7 @@ class PlayPluginTest(CleanupModulesMixin, PluginTestCase):
open_mock.assert_called_once_with(ANY, open_anything()) open_mock.assert_called_once_with(ANY, open_anything())
with open(open_mock.call_args[0][0][0], "rb") as f: with open(open_mock.call_args[0][0][0], "rb") as f:
playlist = f.read().decode("utf-8") playlist = f.read().decode("utf-8")
assert ( assert f"{self.item.filepath.parent}\n" == playlist
f"{os.path.dirname(self.item.path.decode('utf-8'))}\n" == playlist
)
def test_raw(self, open_mock): def test_raw(self, open_mock):
self.config["play"]["raw"] = True self.config["play"]["raw"] = True
@ -125,9 +123,7 @@ class PlayPluginTest(CleanupModulesMixin, PluginTestCase):
self.config["play"]["warning_threshold"] = 1 self.config["play"]["warning_threshold"] = 1
self.other_item = self.add_item(title="another NiceTitle") self.other_item = self.add_item(title="another NiceTitle")
expected_playlist = "{}\n{}".format( expected_playlist = f"{self.item.filepath}\n{self.other_item.filepath}"
self.item.path.decode("utf-8"), self.other_item.path.decode("utf-8")
)
with control_stdin("a"): with control_stdin("a"):
self.run_and_assert( self.run_and_assert(

View file

@ -91,14 +91,7 @@ class PlaylistQueryTest:
assert {i.title for i in results} == {"some item", "another item"} assert {i.title for i in results} == {"some item", "another item"}
def test_path_query_with_absolute_paths_in_playlist(self): def test_path_query_with_absolute_paths_in_playlist(self):
q = "playlist:{}".format( q = f"playlist:{quote(os.path.join(self.playlist_dir, 'absolute.m3u'))}"
quote(
os.path.join(
self.playlist_dir,
"absolute.m3u",
)
)
)
results = self.lib.items(q) results = self.lib.items(q)
assert {i.title for i in results} == {"some item", "another item"} assert {i.title for i in results} == {"some item", "another item"}
@ -108,14 +101,7 @@ class PlaylistQueryTest:
assert {i.title for i in results} == {"some item", "another item"} assert {i.title for i in results} == {"some item", "another item"}
def test_path_query_with_relative_paths_in_playlist(self): def test_path_query_with_relative_paths_in_playlist(self):
q = "playlist:{}".format( q = f"playlist:{quote(os.path.join(self.playlist_dir, 'relative.m3u'))}"
quote(
os.path.join(
self.playlist_dir,
"relative.m3u",
)
)
)
results = self.lib.items(q) results = self.lib.items(q)
assert {i.title for i in results} == {"some item", "another item"} assert {i.title for i in results} == {"some item", "another item"}
@ -125,15 +111,7 @@ class PlaylistQueryTest:
assert set(results) == set() assert set(results) == set()
def test_path_query_with_nonexisting_playlist(self): def test_path_query_with_nonexisting_playlist(self):
q = "playlist:{}".format( q = f"playlist:{os.path.join(self.playlist_dir, 'nonexisting.m3u')!r}"
quote(
os.path.join(
self.playlist_dir,
self.playlist_dir,
"nonexisting.m3u",
)
)
)
results = self.lib.items(q) results = self.lib.items(q)
assert set(results) == set() assert set(results) == set()
@ -141,20 +119,22 @@ class PlaylistQueryTest:
class PlaylistTestRelativeToLib(PlaylistQueryTest, PlaylistTestCase): class PlaylistTestRelativeToLib(PlaylistQueryTest, PlaylistTestCase):
def setup_test(self): def setup_test(self):
with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f: with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f:
f.write( f.writelines(
"{}\n".format(os.path.join(self.music_dir, "a", "b", "c.mp3")) [
) os.path.join(self.music_dir, "a", "b", "c.mp3") + "\n",
f.write( os.path.join(self.music_dir, "d", "e", "f.mp3") + "\n",
"{}\n".format(os.path.join(self.music_dir, "d", "e", "f.mp3")) os.path.join(self.music_dir, "nonexisting.mp3") + "\n",
) ]
f.write(
"{}\n".format(os.path.join(self.music_dir, "nonexisting.mp3"))
) )
with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f: with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f:
f.write("{}\n".format(os.path.join("a", "b", "c.mp3"))) f.writelines(
f.write("{}\n".format(os.path.join("d", "e", "f.mp3"))) [
f.write("{}\n".format("nonexisting.mp3")) os.path.join("a", "b", "c.mp3") + "\n",
os.path.join("d", "e", "f.mp3") + "\n",
"nonexisting.mp3" + "\n",
]
)
self.config["playlist"]["relative_to"] = "library" self.config["playlist"]["relative_to"] = "library"
@ -162,20 +142,22 @@ class PlaylistTestRelativeToLib(PlaylistQueryTest, PlaylistTestCase):
class PlaylistTestRelativeToDir(PlaylistQueryTest, PlaylistTestCase): class PlaylistTestRelativeToDir(PlaylistQueryTest, PlaylistTestCase):
def setup_test(self): def setup_test(self):
with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f: with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f:
f.write( f.writelines(
"{}\n".format(os.path.join(self.music_dir, "a", "b", "c.mp3")) [
) os.path.join(self.music_dir, "a", "b", "c.mp3") + "\n",
f.write( os.path.join(self.music_dir, "d", "e", "f.mp3") + "\n",
"{}\n".format(os.path.join(self.music_dir, "d", "e", "f.mp3")) os.path.join(self.music_dir, "nonexisting.mp3") + "\n",
) ]
f.write(
"{}\n".format(os.path.join(self.music_dir, "nonexisting.mp3"))
) )
with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f: with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f:
f.write("{}\n".format(os.path.join("a", "b", "c.mp3"))) f.writelines(
f.write("{}\n".format(os.path.join("d", "e", "f.mp3"))) [
f.write("{}\n".format("nonexisting.mp3")) os.path.join("a", "b", "c.mp3") + "\n",
os.path.join("d", "e", "f.mp3") + "\n",
"nonexisting.mp3" + "\n",
]
)
self.config["playlist"]["relative_to"] = self.music_dir self.config["playlist"]["relative_to"] = self.music_dir
@ -183,40 +165,33 @@ class PlaylistTestRelativeToDir(PlaylistQueryTest, PlaylistTestCase):
class PlaylistTestRelativeToPls(PlaylistQueryTest, PlaylistTestCase): class PlaylistTestRelativeToPls(PlaylistQueryTest, PlaylistTestCase):
def setup_test(self): def setup_test(self):
with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f: with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f:
f.write( f.writelines(
"{}\n".format(os.path.join(self.music_dir, "a", "b", "c.mp3")) [
) os.path.join(self.music_dir, "a", "b", "c.mp3") + "\n",
f.write( os.path.join(self.music_dir, "d", "e", "f.mp3") + "\n",
"{}\n".format(os.path.join(self.music_dir, "d", "e", "f.mp3")) os.path.join(self.music_dir, "nonexisting.mp3") + "\n",
) ]
f.write(
"{}\n".format(os.path.join(self.music_dir, "nonexisting.mp3"))
) )
with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f: with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f:
f.write( f.writelines(
"{}\n".format( [
os.path.relpath( os.path.relpath(
os.path.join(self.music_dir, "a", "b", "c.mp3"), os.path.join(self.music_dir, "a", "b", "c.mp3"),
start=self.playlist_dir, start=self.playlist_dir,
) )
) + "\n",
)
f.write(
"{}\n".format(
os.path.relpath( os.path.relpath(
os.path.join(self.music_dir, "d", "e", "f.mp3"), os.path.join(self.music_dir, "d", "e", "f.mp3"),
start=self.playlist_dir, start=self.playlist_dir,
) )
) + "\n",
)
f.write(
"{}\n".format(
os.path.relpath( os.path.relpath(
os.path.join(self.music_dir, "nonexisting.mp3"), os.path.join(self.music_dir, "nonexisting.mp3"),
start=self.playlist_dir, start=self.playlist_dir,
) )
) + "\n",
]
) )
self.config["playlist"]["relative_to"] = "playlist" self.config["playlist"]["relative_to"] = "playlist"
@ -226,20 +201,22 @@ class PlaylistTestRelativeToPls(PlaylistQueryTest, PlaylistTestCase):
class PlaylistUpdateTest: class PlaylistUpdateTest:
def setup_test(self): def setup_test(self):
with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f: with open(os.path.join(self.playlist_dir, "absolute.m3u"), "w") as f:
f.write( f.writelines(
"{}\n".format(os.path.join(self.music_dir, "a", "b", "c.mp3")) [
) os.path.join(self.music_dir, "a", "b", "c.mp3") + "\n",
f.write( os.path.join(self.music_dir, "d", "e", "f.mp3") + "\n",
"{}\n".format(os.path.join(self.music_dir, "d", "e", "f.mp3")) os.path.join(self.music_dir, "nonexisting.mp3") + "\n",
) ]
f.write(
"{}\n".format(os.path.join(self.music_dir, "nonexisting.mp3"))
) )
with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f: with open(os.path.join(self.playlist_dir, "relative.m3u"), "w") as f:
f.write("{}\n".format(os.path.join("a", "b", "c.mp3"))) f.writelines(
f.write("{}\n".format(os.path.join("d", "e", "f.mp3"))) [
f.write("{}\n".format("nonexisting.mp3")) os.path.join("a", "b", "c.mp3") + "\n",
os.path.join("d", "e", "f.mp3") + "\n",
"nonexisting.mp3" + "\n",
]
)
self.config["playlist"]["auto"] = True self.config["playlist"]["auto"] = True
self.config["playlist"]["relative_to"] = "library" self.config["playlist"]["relative_to"] = "library"
@ -249,9 +226,7 @@ class PlaylistTestItemMoved(PlaylistUpdateTest, PlaylistTestCase):
def test_item_moved(self): def test_item_moved(self):
# Emit item_moved event for an item that is in a playlist # Emit item_moved event for an item that is in a playlist
results = self.lib.items( results = self.lib.items(
"path:{}".format( f"path:{quote(os.path.join(self.music_dir, 'd', 'e', 'f.mp3'))}"
quote(os.path.join(self.music_dir, "d", "e", "f.mp3"))
)
) )
item = results[0] item = results[0]
beets.plugins.send( beets.plugins.send(
@ -265,9 +240,7 @@ class PlaylistTestItemMoved(PlaylistUpdateTest, PlaylistTestCase):
# Emit item_moved event for an item that is not in a playlist # Emit item_moved event for an item that is not in a playlist
results = self.lib.items( results = self.lib.items(
"path:{}".format( f"path:{quote(os.path.join(self.music_dir, 'x', 'y', 'z.mp3'))}"
quote(os.path.join(self.music_dir, "x", "y", "z.mp3"))
)
) )
item = results[0] item = results[0]
beets.plugins.send( beets.plugins.send(
@ -309,18 +282,14 @@ class PlaylistTestItemRemoved(PlaylistUpdateTest, PlaylistTestCase):
def test_item_removed(self): def test_item_removed(self):
# Emit item_removed event for an item that is in a playlist # Emit item_removed event for an item that is in a playlist
results = self.lib.items( results = self.lib.items(
"path:{}".format( f"path:{quote(os.path.join(self.music_dir, 'd', 'e', 'f.mp3'))}"
quote(os.path.join(self.music_dir, "d", "e", "f.mp3"))
)
) )
item = results[0] item = results[0]
beets.plugins.send("item_removed", item=item) beets.plugins.send("item_removed", item=item)
# Emit item_removed event for an item that is not in a playlist # Emit item_removed event for an item that is not in a playlist
results = self.lib.items( results = self.lib.items(
"path:{}".format( f"path:{quote(os.path.join(self.music_dir, 'x', 'y', 'z.mp3'))}"
quote(os.path.join(self.music_dir, "x", "y", "z.mp3"))
)
) )
item = results[0] item = results[0]
beets.plugins.send("item_removed", item=item) beets.plugins.send("item_removed", item=item)

View file

@ -69,7 +69,7 @@ class RandomTest(TestHelper, unittest.TestCase):
# Print a histogram (useful for debugging). # Print a histogram (useful for debugging).
if histogram: if histogram:
for i in range(len(self.items)): for i in range(len(self.items)):
print("{:2d} {}".format(i, "*" * positions.count(i))) print(f"{i:2d} {'*' * positions.count(i)}")
return self._stats(positions) return self._stats(positions)
mean1, stdev1, median1 = experiment("artist") mean1, stdev1, median1 = experiment("artist")

View file

@ -204,9 +204,7 @@ class ReplayGainCliTest:
# This test is a lot less interesting if the backend cannot write # This test is a lot less interesting if the backend cannot write
# both tag types. # both tag types.
self.skipTest( self.skipTest(
"r128 tags for opus not supported on backend {}".format( f"r128 tags for opus not supported on backend {self.backend}"
self.backend
)
) )
album_rg = self._add_album(1) album_rg = self._add_album(1)
@ -263,9 +261,7 @@ class ReplayGainCliTest:
def test_cli_writes_only_r128_tags(self): def test_cli_writes_only_r128_tags(self):
if not self.has_r128_support: if not self.has_r128_support:
self.skipTest( self.skipTest(
"r128 tags for opus not supported on backend {}".format( f"r128 tags for opus not supported on backend {self.backend}"
self.backend
)
) )
album = self._add_album(2, ext="opus") album = self._add_album(2, ext="opus")
@ -299,9 +295,7 @@ class ReplayGainCliTest:
def test_r128_targetlevel_has_effect(self): def test_r128_targetlevel_has_effect(self):
if not self.has_r128_support: if not self.has_r128_support:
self.skipTest( self.skipTest(
"r128 tags for opus not supported on backend {}".format( f"r128 tags for opus not supported on backend {self.backend}"
self.backend
)
) )
album = self._add_album(1, ext="opus") album = self._add_album(1, ext="opus")