mirror of
https://github.com/beetbox/beets.git
synced 2026-07-22 02:06:50 -04:00
lint: enable flake8-pie, flake8-return, flake8-builtins (#6691)
- Enables additional Ruff lint rules in `pyproject.toml`: `PIE`, `RET`, and `A`. - Applies the required cleanup across the codebase, mostly as non-functional refactors: - simplifies return/control-flow patterns - renames variables that shadow Python builtins like `id`, `dir`, `type`, `hash`, and `input` - adds a few targeted `noqa` exceptions where names are part of stable APIs or intentional signatures - Touches core modules, plugins, tests, and docs, but the architectural impact is low: this is mainly a repo-wide consistency and maintainability pass rather than a behavior change. - Updates `.git-blame-ignore-revs` so these broad lint-only commits are ignored in blame, keeping future history easier to review.
This commit is contained in:
@@ -197,3 +197,9 @@ adfb73b66dba0fc799880af176ef6f9ac0a7ee68
|
||||
ca8544d4bcab503e1e4c2cd6688655c8211aa7a1
|
||||
# Fix autobpm tests
|
||||
5804a21620098e8da78b1f4a5f85063033586632
|
||||
# Enable and fix flake8-pie
|
||||
3a419c6a347803fc962f018f5ad027b5399ab3ae
|
||||
# Enable and fix flake8-return
|
||||
d0dc5f86a626765c86cdadc348595429f4c6e6ef
|
||||
# Enable and fix flake8-builtins
|
||||
b7974ace83ab193b6417bdcecaf68a742bf5a1a8
|
||||
|
||||
@@ -528,9 +528,8 @@ class TrackInfo(Info):
|
||||
if not raw_track[k] and (v := album.get(k)):
|
||||
track[k] = v
|
||||
|
||||
merged = (
|
||||
return (
|
||||
album_info.item_data
|
||||
| {"tracktotal": len(album_info.tracks)}
|
||||
| track.item_data
|
||||
)
|
||||
return merged
|
||||
|
||||
@@ -519,8 +519,7 @@ def tag_item(
|
||||
if candidates:
|
||||
assert rec is not None
|
||||
return Proposal(_sort_candidates(candidates.values()), rec)
|
||||
else:
|
||||
return Proposal([], Recommendation.none)
|
||||
return Proposal([], Recommendation.none)
|
||||
|
||||
# Search terms.
|
||||
search_artist = search_artist or item.artist
|
||||
|
||||
@@ -135,8 +135,7 @@ class FormattedMapping(Mapping[str, str]):
|
||||
def __getitem__(self, key: str) -> str:
|
||||
if key in self.model_keys:
|
||||
return self._get_formatted(self.model, key)
|
||||
else:
|
||||
raise KeyError(key)
|
||||
raise KeyError(key)
|
||||
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
return iter(self.model_keys)
|
||||
@@ -210,10 +209,11 @@ class LazyConvertDict:
|
||||
"""
|
||||
if key in self._converted:
|
||||
return self._converted[key]
|
||||
elif key in self._data:
|
||||
if key in self._data:
|
||||
value = self._convert(key, self._data[key])
|
||||
self._converted[key] = value
|
||||
return value
|
||||
return None
|
||||
|
||||
def __delitem__(self, key: str):
|
||||
"""Delete both converted and base data"""
|
||||
@@ -253,8 +253,7 @@ class LazyConvertDict:
|
||||
"""
|
||||
if key in self:
|
||||
return self[key]
|
||||
else:
|
||||
return default
|
||||
return default
|
||||
|
||||
def __contains__(self, key: Any) -> bool:
|
||||
"""Determine whether `key` is an attribute on this object."""
|
||||
@@ -520,17 +519,15 @@ class Model(ABC, Generic[D]):
|
||||
getters = self._getters()
|
||||
if key in getters: # Computed.
|
||||
return getters[key](self)
|
||||
elif key in self._fields: # Fixed.
|
||||
if key in self._fields: # Fixed.
|
||||
if key in self._values_fixed:
|
||||
return self._values_fixed[key]
|
||||
else:
|
||||
return self._type(key).null
|
||||
elif key in self._values_flex: # Flexible.
|
||||
return self._type(key).null
|
||||
if key in self._values_flex: # Flexible.
|
||||
return self._values_flex[key]
|
||||
elif raise_:
|
||||
if raise_:
|
||||
raise KeyError(key)
|
||||
else:
|
||||
return default
|
||||
return default
|
||||
|
||||
get = _get
|
||||
|
||||
@@ -625,11 +622,10 @@ class Model(ABC, Generic[D]):
|
||||
def __getattr__(self, key):
|
||||
if key.startswith("_"):
|
||||
raise AttributeError(f"model has no attribute {key!r}")
|
||||
else:
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
raise AttributeError(f"no such field {key!r}")
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
raise AttributeError(f"no such field {key!r}")
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key.startswith("_"):
|
||||
@@ -884,9 +880,8 @@ class Results(Generic[AnyModel]):
|
||||
objects = self.sort.sort(list(self._get_objects()))
|
||||
return iter(objects)
|
||||
|
||||
else:
|
||||
# Objects are pre-sorted (i.e., by the database).
|
||||
return self._get_objects()
|
||||
# Objects are pre-sorted (i.e., by the database).
|
||||
return self._get_objects()
|
||||
|
||||
def _get_indexed_flex_attrs(self) -> dict[int, FlexAttrs]:
|
||||
"""Index flexible attributes by the entity id they belong to"""
|
||||
@@ -907,8 +902,7 @@ class Results(Generic[AnyModel]):
|
||||
values = {k: v for (k, v) in cols.items() if not k[:4] == "flex"}
|
||||
|
||||
# Construct the Python object
|
||||
obj = self.model_class._awaken(self.db, values, flex_values)
|
||||
return obj
|
||||
return self.model_class._awaken(self.db, values, flex_values)
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Get the number of matching objects."""
|
||||
@@ -916,16 +910,15 @@ class Results(Generic[AnyModel]):
|
||||
# Fully materialized. Just count the objects.
|
||||
return len(self._objects)
|
||||
|
||||
elif self.query:
|
||||
if self.query:
|
||||
# A slow query. Fall back to testing every object.
|
||||
count = 0
|
||||
for obj in self:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
else:
|
||||
# A fast query. Just count the rows.
|
||||
return self._row_count
|
||||
# A fast query. Just count the rows.
|
||||
return self._row_count
|
||||
|
||||
def __nonzero__(self) -> bool:
|
||||
"""Does this result contain any objects?"""
|
||||
@@ -1223,10 +1216,9 @@ class Database:
|
||||
with self._shared_map_lock:
|
||||
if thread_id in self._connections:
|
||||
return self._connections[thread_id]
|
||||
else:
|
||||
conn = self._create_connection()
|
||||
self._connections[thread_id] = conn
|
||||
return conn
|
||||
conn = self._create_connection()
|
||||
self._connections[thread_id] = conn
|
||||
return conn
|
||||
|
||||
def _create_connection(self) -> Connection:
|
||||
"""Create a SQLite connection to the underlying database.
|
||||
|
||||
@@ -161,9 +161,8 @@ class FieldQuery(Query, Generic[P]):
|
||||
def clause(self) -> tuple[str | None, Sequence[SQLiteType]]:
|
||||
if self.fast:
|
||||
return self.col_clause()
|
||||
else:
|
||||
# Matching a flexattr. This is a slow query.
|
||||
return None, ()
|
||||
# Matching a flexattr. This is a slow query.
|
||||
return None, ()
|
||||
|
||||
@classmethod
|
||||
def value_match(cls, pattern: P, value: Any):
|
||||
@@ -477,28 +476,25 @@ class NumericQuery(FieldQuery[str]):
|
||||
|
||||
if self.point is not None:
|
||||
return value == self.point
|
||||
else:
|
||||
if self.rangemin is not None and value < self.rangemin:
|
||||
return False
|
||||
if self.rangemax is not None and value > self.rangemax:
|
||||
return False
|
||||
return True
|
||||
if self.rangemin is not None and value < self.rangemin:
|
||||
return False
|
||||
if self.rangemax is not None and value > self.rangemax:
|
||||
return False
|
||||
return True
|
||||
|
||||
def col_clause(self) -> tuple[str, Sequence[SQLiteType]]:
|
||||
if self.point is not None:
|
||||
return f"{self.field}=?", (self.point,)
|
||||
else:
|
||||
if self.rangemin is not None and self.rangemax is not None:
|
||||
return (
|
||||
f"{self.field} >= ? AND {self.field} <= ?",
|
||||
(self.rangemin, self.rangemax),
|
||||
)
|
||||
elif self.rangemin is not None:
|
||||
return f"{self.field} >= ?", (self.rangemin,)
|
||||
elif self.rangemax is not None:
|
||||
return f"{self.field} <= ?", (self.rangemax,)
|
||||
else:
|
||||
return "1", ()
|
||||
if self.rangemin is not None and self.rangemax is not None:
|
||||
return (
|
||||
f"{self.field} >= ? AND {self.field} <= ?",
|
||||
(self.rangemin, self.rangemax),
|
||||
)
|
||||
if self.rangemin is not None:
|
||||
return f"{self.field} >= ?", (self.rangemin,)
|
||||
if self.rangemax is not None:
|
||||
return f"{self.field} <= ?", (self.rangemax,)
|
||||
return "1", ()
|
||||
|
||||
|
||||
class InQuery(Generic[AnySQLiteType], FieldQuery[Sequence[AnySQLiteType]]):
|
||||
@@ -632,10 +628,9 @@ class NotQuery(Query):
|
||||
clause, subvals = self.subquery.clause()
|
||||
if clause:
|
||||
return f"not ({clause})", subvals
|
||||
else:
|
||||
# If there is no clause, there is nothing to negate. All the logic
|
||||
# is handled by match() for slow queries.
|
||||
return clause, subvals
|
||||
# If there is no clause, there is nothing to negate. All the logic
|
||||
# is handled by match() for slow queries.
|
||||
return clause, subvals
|
||||
|
||||
def match(self, obj: Model) -> bool:
|
||||
return not self.subquery.match(obj)
|
||||
@@ -681,10 +676,9 @@ def _parse_periods(pattern: str) -> tuple[Period | None, Period | None]:
|
||||
if len(parts) == 1:
|
||||
instant = Period.parse(parts[0])
|
||||
return (instant, instant)
|
||||
else:
|
||||
start = Period.parse(parts[0])
|
||||
end = Period.parse(parts[1])
|
||||
return (start, end)
|
||||
start = Period.parse(parts[0])
|
||||
end = Period.parse(parts[1])
|
||||
return (start, end)
|
||||
|
||||
|
||||
class Period:
|
||||
@@ -742,11 +736,11 @@ class Period:
|
||||
def find_date_and_format(
|
||||
string: str,
|
||||
) -> tuple[None, None] | tuple[datetime, int]:
|
||||
for ord, format in enumerate(cls.date_formats):
|
||||
for format_option in format:
|
||||
for ord_, format_ in enumerate(cls.date_formats):
|
||||
for format_option in format_:
|
||||
try:
|
||||
date = datetime.strptime(string, format_option)
|
||||
return date, ord
|
||||
return date, ord_
|
||||
except ValueError:
|
||||
# Parsing failed.
|
||||
pass
|
||||
@@ -791,21 +785,19 @@ class Period:
|
||||
date = self.date
|
||||
if "year" == self.precision:
|
||||
return date.replace(year=date.year + 1, month=1)
|
||||
elif "month" == precision:
|
||||
if "month" == precision:
|
||||
if date.month < 12:
|
||||
return date.replace(month=date.month + 1)
|
||||
else:
|
||||
return date.replace(year=date.year + 1, month=1)
|
||||
elif "day" == precision:
|
||||
return date.replace(year=date.year + 1, month=1)
|
||||
if "day" == precision:
|
||||
return date + timedelta(days=1)
|
||||
elif "hour" == precision:
|
||||
if "hour" == precision:
|
||||
return date + timedelta(hours=1)
|
||||
elif "minute" == precision:
|
||||
if "minute" == precision:
|
||||
return date + timedelta(minutes=1)
|
||||
elif "second" == precision:
|
||||
if "second" == precision:
|
||||
return date + timedelta(seconds=1)
|
||||
else:
|
||||
raise ValueError(f"unhandled precision {precision}")
|
||||
raise ValueError(f"unhandled precision {precision}")
|
||||
|
||||
|
||||
class DateInterval:
|
||||
|
||||
@@ -158,8 +158,7 @@ def construct_query_part(
|
||||
# Apply negation.
|
||||
if negate:
|
||||
return query.NotQuery(out_query)
|
||||
else:
|
||||
return out_query
|
||||
return out_query
|
||||
|
||||
|
||||
# TYPING ERROR
|
||||
@@ -218,13 +217,12 @@ def sort_from_strings(
|
||||
"""Create a `Sort` from a list of sort criteria (strings)."""
|
||||
if not sort_parts:
|
||||
return sort.NullSort()
|
||||
elif len(sort_parts) == 1:
|
||||
if len(sort_parts) == 1:
|
||||
return construct_sort_part(model_cls, sort_parts[0], case_insensitive)
|
||||
else:
|
||||
s = sort.MultipleSort()
|
||||
for part in sort_parts:
|
||||
s.add_sort(construct_sort_part(model_cls, part, case_insensitive))
|
||||
return s
|
||||
s = sort.MultipleSort()
|
||||
for part in sort_parts:
|
||||
s.add_sort(construct_sort_part(model_cls, part, case_insensitive))
|
||||
return s
|
||||
|
||||
|
||||
def parse_sorted_query(
|
||||
|
||||
@@ -94,10 +94,9 @@ class Type(ABC, Generic[T, N]):
|
||||
# `self.null` might be `None`
|
||||
if value is None:
|
||||
return ""
|
||||
elif isinstance(value, bytes):
|
||||
if isinstance(value, bytes):
|
||||
return value.decode("utf-8", "ignore")
|
||||
else:
|
||||
return str(value)
|
||||
return str(value)
|
||||
|
||||
def parse(self, string: str) -> T | N:
|
||||
"""Parse a (possibly human-written) string and return the
|
||||
@@ -116,10 +115,9 @@ class Type(ABC, Generic[T, N]):
|
||||
# TYPING ERROR
|
||||
if value is None:
|
||||
return self.null
|
||||
else:
|
||||
# TODO This should eventually be replaced by
|
||||
# `self.model_type(value)`
|
||||
return cast(T, value)
|
||||
# TODO This should eventually be replaced by
|
||||
# `self.model_type(value)`
|
||||
return cast(T, value)
|
||||
|
||||
def from_sql(self, sql_value: SQLiteType) -> T | N:
|
||||
"""Receives the value stored in the SQL backend and return the
|
||||
@@ -139,8 +137,7 @@ class Type(ABC, Generic[T, N]):
|
||||
sql_value = bytes(sql_value).decode("utf-8", "ignore")
|
||||
if isinstance(sql_value, str):
|
||||
return self.parse(sql_value)
|
||||
else:
|
||||
return self.normalize(sql_value)
|
||||
return self.normalize(sql_value)
|
||||
|
||||
def to_sql(self, model_value: Any) -> SQLiteType:
|
||||
"""Convert a value as stored in the model object to a value used
|
||||
@@ -272,8 +269,7 @@ class BaseString(Type[T, N]):
|
||||
def normalize(self, value: Any) -> T | N:
|
||||
if value is None:
|
||||
return self.null
|
||||
else:
|
||||
return self.model_type(value)
|
||||
return self.model_type(value)
|
||||
|
||||
|
||||
class String(BaseString[str, Any]):
|
||||
@@ -396,12 +392,11 @@ class BasePathType(Type[bytes, N]):
|
||||
# Paths stored internally as encoded bytes.
|
||||
return util.bytestring_path(value)
|
||||
|
||||
elif isinstance(value, BLOB_TYPE):
|
||||
if isinstance(value, BLOB_TYPE):
|
||||
# We unwrap buffers to bytes.
|
||||
return bytes(value)
|
||||
|
||||
else:
|
||||
return value
|
||||
return value
|
||||
|
||||
def from_sql(self, sql_value):
|
||||
return pathutils.expand_path_from_db(self.normalize(sql_value))
|
||||
@@ -459,8 +454,7 @@ class MusicalKey(String):
|
||||
def normalize(self, key):
|
||||
if key is None:
|
||||
return None
|
||||
else:
|
||||
return self.parse(key)
|
||||
return self.parse(key)
|
||||
|
||||
|
||||
class DurationType(Float):
|
||||
@@ -471,8 +465,7 @@ class DurationType(Float):
|
||||
def format(self, value):
|
||||
if not beets.config["format_raw_length"].get(bool):
|
||||
return human_seconds_short(value or 0.0)
|
||||
else:
|
||||
return value
|
||||
return value
|
||||
|
||||
def parse(self, string):
|
||||
try:
|
||||
|
||||
@@ -42,8 +42,6 @@ log = logging.getLogger("beets")
|
||||
class ImportAbortError(Exception):
|
||||
"""Raised when the user aborts the tagging operation."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ImportSession:
|
||||
"""Controls an import action. Subclasses should implement methods to
|
||||
|
||||
@@ -75,8 +75,6 @@ log = logging.getLogger("beets")
|
||||
class ImportAbortError(Exception):
|
||||
"""Raised when the user aborts the tagging operation."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class Action(Enum):
|
||||
"""Enumeration of possible actions for an import task."""
|
||||
@@ -237,7 +235,7 @@ class ImportTask(BaseImportTask):
|
||||
if self.choice_flag in (Action.ASIS, Action.RETAG):
|
||||
likelies, _ = util.get_most_common_tags(self.items)
|
||||
return likelies
|
||||
elif self.choice_flag is Action.APPLY and self.match:
|
||||
if self.choice_flag is Action.APPLY and self.match:
|
||||
return self.match.info.copy()
|
||||
assert False
|
||||
|
||||
@@ -249,12 +247,11 @@ class ImportTask(BaseImportTask):
|
||||
"""
|
||||
if self.choice_flag in (Action.ASIS, Action.RETAG):
|
||||
return self.items
|
||||
elif self.choice_flag == Action.APPLY and isinstance(
|
||||
if self.choice_flag == Action.APPLY and isinstance(
|
||||
self.match, AlbumMatch
|
||||
):
|
||||
return self.match.items
|
||||
else:
|
||||
return []
|
||||
return []
|
||||
|
||||
def apply_metadata(self) -> None:
|
||||
"""Copy metadata from match info to the items."""
|
||||
@@ -693,8 +690,9 @@ class SingletonImportTask(ImportTask):
|
||||
assert self.choice_flag in (Action.ASIS, Action.RETAG, Action.APPLY)
|
||||
if self.choice_flag in (Action.ASIS, Action.RETAG):
|
||||
return dict(self.item)
|
||||
elif self.choice_flag is Action.APPLY:
|
||||
if self.choice_flag is Action.APPLY:
|
||||
return self.match.info.copy()
|
||||
return None
|
||||
|
||||
def imported_items(self):
|
||||
return [self.item]
|
||||
@@ -1065,8 +1063,7 @@ class ImportTaskFactory:
|
||||
item = self.read_item(path)
|
||||
if item:
|
||||
return SingletonImportTask(self.toppath, item)
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
def album(self, paths: Iterable[util.PathBytes], dirs=None):
|
||||
"""Return a `ImportTask` with all media files from paths.
|
||||
@@ -1092,8 +1089,7 @@ class ImportTaskFactory:
|
||||
|
||||
if len(items) > 0:
|
||||
return ImportTask(self.toppath, dirs, items)
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
def sentinel(self, paths: Iterable[util.PathBytes] | None = None):
|
||||
"""Return a `SentinelImportTask` indicating the end of a
|
||||
@@ -1115,7 +1111,7 @@ class ImportTaskFactory:
|
||||
"Archive importing requires either "
|
||||
"'copy' or 'move' to be enabled."
|
||||
)
|
||||
return
|
||||
return None
|
||||
|
||||
log.debug("Extracting archive: {}", util.displayable_path(self.toppath))
|
||||
archive_task = ArchiveImportTask(self.toppath)
|
||||
@@ -1123,7 +1119,7 @@ class ImportTaskFactory:
|
||||
archive_task.extract()
|
||||
except Exception as exc:
|
||||
log.error("extraction failed: {}", exc)
|
||||
return
|
||||
return None
|
||||
|
||||
# Now read albums from the extracted directory.
|
||||
self.toppath = archive_task.toppath
|
||||
|
||||
@@ -216,12 +216,11 @@ class FormattedItemMapping(dbcore.db.FormattedMapping):
|
||||
"""
|
||||
if self.for_path and key in self.album_keys:
|
||||
return self._get_formatted(self.album, key)
|
||||
elif key in self.model_keys:
|
||||
if key in self.model_keys:
|
||||
return self._get_formatted(self.model, key)
|
||||
elif key in self.album_keys:
|
||||
if key in self.album_keys:
|
||||
return self._get_formatted(self.album, key)
|
||||
else:
|
||||
raise KeyError(key)
|
||||
raise KeyError(key)
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Get the value for a key.
|
||||
@@ -237,7 +236,7 @@ class FormattedItemMapping(dbcore.db.FormattedMapping):
|
||||
try:
|
||||
if key == "artist" and not value:
|
||||
return self._get("albumartist")
|
||||
elif key == "albumartist" and not value:
|
||||
if key == "albumartist" and not value:
|
||||
return self._get("artist")
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -556,7 +555,7 @@ class Album(LibModel):
|
||||
if oldart and samefile(path, oldart):
|
||||
# Art already set.
|
||||
return
|
||||
elif samefile(path, artdest):
|
||||
if samefile(path, artdest):
|
||||
# Art already in place.
|
||||
self.artpath = path
|
||||
return
|
||||
@@ -1339,8 +1338,7 @@ class DefaultTemplateFunctions:
|
||||
|
||||
if condition:
|
||||
return trueval
|
||||
else:
|
||||
return falseval
|
||||
return falseval
|
||||
|
||||
@staticmethod
|
||||
def tmpl_asciify(s):
|
||||
@@ -1555,5 +1553,4 @@ class DefaultTemplateFunctions:
|
||||
"""
|
||||
if field in self.item:
|
||||
return trueval if trueval else self.item.formatted().get(field)
|
||||
else:
|
||||
return falseval
|
||||
return falseval
|
||||
|
||||
@@ -207,5 +207,4 @@ def getLogger(name: None = ...) -> RootLogger: ...
|
||||
def getLogger(name=None) -> BeetsLogger | RootLogger: # noqa: N802
|
||||
if name:
|
||||
return my_manager.getLogger(name) # type: ignore[return-value]
|
||||
else:
|
||||
return Logger.root
|
||||
return Logger.root
|
||||
|
||||
@@ -256,7 +256,7 @@ class MetadataSourcePlugin(BeetsPlugin, metaclass=abc.ABCMeta):
|
||||
single calls to album_for_id.
|
||||
"""
|
||||
|
||||
return (self.album_for_id(id) for id in ids)
|
||||
return (self.album_for_id(id_) for id_ in ids)
|
||||
|
||||
def tracks_for_ids(self, ids: Iterable[str]) -> Iterable[TrackInfo | None]:
|
||||
"""Batch lookup of track metadata for a list of track IDs.
|
||||
@@ -267,7 +267,7 @@ class MetadataSourcePlugin(BeetsPlugin, metaclass=abc.ABCMeta):
|
||||
single calls to track_for_id.
|
||||
"""
|
||||
|
||||
return (self.track_for_id(id) for id in ids)
|
||||
return (self.track_for_id(id_) for id_ in ids)
|
||||
|
||||
def _extract_id(self, url: str) -> str | None:
|
||||
"""Extract an ID from a URL for this metadata source plugin.
|
||||
|
||||
@@ -384,15 +384,15 @@ class TestHelper(RunMixin, ConfigMixin):
|
||||
"""Delete the temporary directory created by `create_temp_dir`."""
|
||||
shutil.rmtree(self.temp_dir_path)
|
||||
|
||||
def touch(self, path, dir=None, content=""):
|
||||
def touch(self, path, dir_=None, content=""):
|
||||
"""Create a file at `path` with given content.
|
||||
|
||||
If `dir` is given, it is prepended to `path`. After that, if the
|
||||
path is relative, it is resolved with respect to
|
||||
`self.temp_dir`.
|
||||
"""
|
||||
if dir:
|
||||
path = os.path.join(dir, path)
|
||||
if dir_:
|
||||
path = os.path.join(dir_, path)
|
||||
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(self.temp_dir, path)
|
||||
@@ -481,8 +481,6 @@ class PluginTestCase(PluginMixin, BeetsTestCase):
|
||||
DEPRECATED: Use PluginTestHelper instead.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PluginTestHelper(PluginMixin, TestHelper):
|
||||
"""Helper mixin for pytest-based plugin tests.
|
||||
@@ -657,10 +655,9 @@ class ImportSessionFixture(ImportSession):
|
||||
|
||||
if choice == importer.Action.APPLY:
|
||||
return task.candidates[0]
|
||||
elif isinstance(choice, int):
|
||||
if isinstance(choice, int):
|
||||
return task.candidates[choice - 1]
|
||||
else:
|
||||
return choice
|
||||
return choice
|
||||
|
||||
choose_item = choose_match
|
||||
|
||||
@@ -807,13 +804,13 @@ class AutotagStub:
|
||||
)
|
||||
|
||||
def _make_album_match(self, artist, album, tracks, distance=0, missing=0):
|
||||
id = f" {'M' * distance}" if distance else ""
|
||||
id_ = f" {'M' * distance}" if distance else ""
|
||||
|
||||
if artist is None:
|
||||
artist = "Various Artists"
|
||||
else:
|
||||
artist = f"{artist.replace('Tag', 'Applied')}{id}"
|
||||
album = f"{album.replace('Tag', 'Applied')}{id}"
|
||||
artist = f"{artist.replace('Tag', 'Applied')}{id_}"
|
||||
album = f"{album.replace('Tag', 'Applied')}{id_}"
|
||||
|
||||
track_infos = []
|
||||
for i in range(tracks - missing):
|
||||
@@ -824,8 +821,8 @@ class AutotagStub:
|
||||
album=album,
|
||||
tracks=track_infos,
|
||||
va=False,
|
||||
album_id=f"albumid{id}",
|
||||
artist_id=f"artistid{id}",
|
||||
album_id=f"albumid{id_}",
|
||||
artist_id=f"artistid{id_}",
|
||||
albumtype="soundtrack",
|
||||
data_source="match_source",
|
||||
bandcamp_album_id="bc_url",
|
||||
|
||||
@@ -139,9 +139,8 @@ def _bool_fallback(a, b):
|
||||
if a is None:
|
||||
assert isinstance(b, bool)
|
||||
return b
|
||||
else:
|
||||
assert isinstance(a, bool)
|
||||
return a
|
||||
assert isinstance(a, bool)
|
||||
return a
|
||||
|
||||
|
||||
def should_write(write_opt=None):
|
||||
@@ -354,8 +353,7 @@ def input_options(
|
||||
low, high = numrange
|
||||
if low <= resp <= high:
|
||||
return resp
|
||||
else:
|
||||
resp = None
|
||||
resp = None
|
||||
|
||||
# Try a normal letter input.
|
||||
if resp:
|
||||
@@ -397,7 +395,7 @@ def input_select_objects(prompt, objs, rep, prompt_all=None):
|
||||
if choice == "y": # Yes.
|
||||
return objs
|
||||
|
||||
elif choice == "s": # Select.
|
||||
if choice == "s": # Select.
|
||||
out = []
|
||||
for obj in objs:
|
||||
rep(obj)
|
||||
@@ -413,8 +411,8 @@ def input_select_objects(prompt, objs, rep, prompt_all=None):
|
||||
return out
|
||||
return out
|
||||
|
||||
else: # No.
|
||||
return []
|
||||
# No.
|
||||
return []
|
||||
|
||||
|
||||
@cache
|
||||
@@ -602,7 +600,7 @@ class Subcommand:
|
||||
|
||||
func: Callable[[library.Library, optparse.Values, list[str]], Any]
|
||||
|
||||
def __init__(self, name, parser=None, help="", aliases=(), hide=False):
|
||||
def __init__(self, name, parser=None, help="", aliases=(), hide=False): # noqa: A002
|
||||
"""Creates a new subcommand. name is the primary way to invoke
|
||||
the subcommand; aliases are alternate names. parser is an
|
||||
OptionParser responsible for parsing the subcommand's options.
|
||||
@@ -947,6 +945,7 @@ def _raw_main(args: list[str] | None) -> None:
|
||||
|
||||
plugins.send("cli_exit", lib=lib)
|
||||
lib._close()
|
||||
return None
|
||||
|
||||
|
||||
def main(args: list[str] | None = None) -> None:
|
||||
|
||||
@@ -131,12 +131,11 @@ class ChangeRepresentation:
|
||||
return (
|
||||
f"* {track_media} {track_info.medium}: {track_info.disctitle}"
|
||||
)
|
||||
elif self.match.info.mediums > 1:
|
||||
if self.match.info.mediums > 1:
|
||||
return f"* {track_media} {track_info.medium}"
|
||||
elif track_info.disctitle:
|
||||
if track_info.disctitle:
|
||||
return f"* {track_media}: {track_info.disctitle}"
|
||||
else:
|
||||
return ""
|
||||
return ""
|
||||
|
||||
def format_index(self, track_info: TrackInfo | Item) -> str:
|
||||
"""Return a string representing the track index of the given
|
||||
@@ -154,10 +153,8 @@ class ChangeRepresentation:
|
||||
if config["per_disc_numbering"]:
|
||||
if mediums and mediums > 1:
|
||||
return f"{medium}-{medium_index}"
|
||||
else:
|
||||
return str(medium_index if medium_index is not None else index)
|
||||
else:
|
||||
return str(index)
|
||||
return str(medium_index if medium_index is not None else index)
|
||||
return str(index)
|
||||
|
||||
def make_track_numbers(
|
||||
self, item: Item, track_info: TrackInfo
|
||||
@@ -191,11 +188,10 @@ class ChangeRepresentation:
|
||||
# If there's no title, we use the filename. Don't colordiff.
|
||||
cur_title = displayable_path(os.path.basename(item.path))
|
||||
return cur_title, new_title, True
|
||||
else:
|
||||
# If there is a title, highlight differences.
|
||||
cur_title = item.title.strip()
|
||||
cur_col, new_col = colordiff(cur_title, new_title)
|
||||
return cur_col, new_col, cur_title != new_title
|
||||
# If there is a title, highlight differences.
|
||||
cur_title = item.title.strip()
|
||||
cur_col, new_col = colordiff(cur_title, new_title)
|
||||
return cur_col, new_col, cur_title != new_title
|
||||
|
||||
@staticmethod
|
||||
def make_track_lengths(
|
||||
|
||||
@@ -48,7 +48,7 @@ class TerminalImportSession(importer.ImportSession):
|
||||
|
||||
if len(actions) == 1:
|
||||
return actions[0]
|
||||
elif len(actions) > 1:
|
||||
if len(actions) > 1:
|
||||
raise plugins.PluginConflictError(
|
||||
"Only one handler for `import_task_before_choice` may return "
|
||||
"an action."
|
||||
@@ -60,7 +60,7 @@ class TerminalImportSession(importer.ImportSession):
|
||||
match = task.candidates[0]
|
||||
show_change(task.cur_artist, task.cur_album, match)
|
||||
return match
|
||||
elif action is not None:
|
||||
if action is not None:
|
||||
return action
|
||||
|
||||
# Loop until we have a choice.
|
||||
@@ -87,11 +87,11 @@ class TerminalImportSession(importer.ImportSession):
|
||||
|
||||
# Plugin-provided choices. We invoke the associated callback
|
||||
# function.
|
||||
elif choice in choices:
|
||||
if choice in choices:
|
||||
post_choice = choice.callback(self, task)
|
||||
if isinstance(post_choice, importer.Action):
|
||||
return post_choice
|
||||
elif isinstance(post_choice, Proposal):
|
||||
if isinstance(post_choice, Proposal):
|
||||
# Use the new candidates and continue around the loop.
|
||||
task.candidates = post_choice.candidates
|
||||
task.rec = post_choice.recommendation
|
||||
@@ -117,7 +117,7 @@ class TerminalImportSession(importer.ImportSession):
|
||||
match = candidates[0]
|
||||
show_item_change(task.item, match)
|
||||
return match
|
||||
elif action is not None:
|
||||
if action is not None:
|
||||
return action
|
||||
|
||||
while True:
|
||||
@@ -130,11 +130,11 @@ class TerminalImportSession(importer.ImportSession):
|
||||
if choice in (importer.Action.SKIP, importer.Action.ASIS):
|
||||
return choice
|
||||
|
||||
elif choice in choices:
|
||||
if choice in choices:
|
||||
post_choice = choice.callback(self, task)
|
||||
if isinstance(post_choice, importer.Action):
|
||||
return post_choice
|
||||
elif isinstance(post_choice, Proposal):
|
||||
if isinstance(post_choice, Proposal):
|
||||
candidates = post_choice.candidates
|
||||
rec = post_choice.recommendation
|
||||
|
||||
@@ -340,10 +340,9 @@ def _summary_judgment(rec: Recommendation) -> importer.Action | None:
|
||||
if config["import"]["quiet"]:
|
||||
if rec == Recommendation.strong:
|
||||
return importer.Action.APPLY
|
||||
else:
|
||||
action = config["import"]["quiet_fallback"].as_choice(
|
||||
{"skip": importer.Action.SKIP, "asis": importer.Action.ASIS}
|
||||
)
|
||||
action = config["import"]["quiet_fallback"].as_choice(
|
||||
{"skip": importer.Action.SKIP, "asis": importer.Action.ASIS}
|
||||
)
|
||||
elif config["import"]["timid"]:
|
||||
return None
|
||||
elif rec == Recommendation.none:
|
||||
@@ -412,8 +411,7 @@ def choose_candidate(
|
||||
sel = ui.input_options(choice_opts)
|
||||
if sel in choice_actions:
|
||||
return choice_actions[sel]
|
||||
else:
|
||||
assert False
|
||||
assert False
|
||||
|
||||
# Is the change good enough?
|
||||
bypass_candidates = False
|
||||
@@ -501,7 +499,7 @@ def choose_candidate(
|
||||
)
|
||||
if sel == "a":
|
||||
return match
|
||||
elif sel in choice_actions:
|
||||
if sel in choice_actions:
|
||||
return choice_actions[sel]
|
||||
|
||||
|
||||
@@ -517,8 +515,7 @@ def manual_search(session, task):
|
||||
if task.is_album:
|
||||
_, _, prop = tag_album(task.items, artist, name)
|
||||
return prop
|
||||
else:
|
||||
return tag_item(task.item, artist, name)
|
||||
return tag_item(task.item, artist, name)
|
||||
|
||||
|
||||
def manual_id(session, task):
|
||||
@@ -532,8 +529,7 @@ def manual_id(session, task):
|
||||
if task.is_album:
|
||||
_, _, prop = tag_album(task.items, search_ids=search_id.split())
|
||||
return prop
|
||||
else:
|
||||
return tag_item(task.item, search_ids=search_id.split())
|
||||
return tag_item(task.item, search_ids=search_id.split())
|
||||
|
||||
|
||||
def abort_action(session, task):
|
||||
|
||||
@@ -23,7 +23,7 @@ def do_query(lib, query, album, also_items=True):
|
||||
|
||||
if album and not albums:
|
||||
raise UserError("No matching albums found.")
|
||||
elif not album and not items:
|
||||
if not album and not items:
|
||||
raise UserError("No matching items found.")
|
||||
|
||||
return items, albums
|
||||
|
||||
@@ -105,12 +105,11 @@ class HumanReadableError(Exception):
|
||||
"""Get the reason as a string."""
|
||||
if isinstance(self.reason, str):
|
||||
return self.reason
|
||||
elif isinstance(self.reason, bytes):
|
||||
if isinstance(self.reason, bytes):
|
||||
return self.reason.decode("utf-8", "ignore")
|
||||
elif hasattr(self.reason, "strerror"): # i.e., EnvironmentError
|
||||
if hasattr(self.reason, "strerror"): # i.e., EnvironmentError
|
||||
return self.reason.strerror
|
||||
else:
|
||||
return f'"{self.reason}"'
|
||||
return f'"{self.reason}"'
|
||||
|
||||
def get_message(self):
|
||||
"""Create the human-readable description of the error, sans
|
||||
@@ -412,9 +411,9 @@ def displayable_path(
|
||||
|
||||
if isinstance(path, (list, tuple)):
|
||||
return separator.join(displayable_path(p) for p in path)
|
||||
elif isinstance(path, str):
|
||||
if isinstance(path, str):
|
||||
return path
|
||||
elif not isinstance(path, bytes):
|
||||
if not isinstance(path, bytes):
|
||||
# A non-string object: just get its unicode representation.
|
||||
return str(path)
|
||||
|
||||
@@ -602,10 +601,7 @@ def hardlink(path: bytes, dest: bytes, replace: bool = False):
|
||||
(path, dest),
|
||||
traceback.format_exc(),
|
||||
)
|
||||
else:
|
||||
raise FilesystemError(
|
||||
exc, "link", (path, dest), traceback.format_exc()
|
||||
)
|
||||
raise FilesystemError(exc, "link", (path, dest), traceback.format_exc())
|
||||
|
||||
|
||||
def reflink(
|
||||
@@ -620,7 +616,7 @@ def reflink(
|
||||
Otherwise, errors are re-raised as FilesystemError with an explanation.
|
||||
"""
|
||||
if samefile(path, dest):
|
||||
return
|
||||
return None
|
||||
|
||||
if os.path.exists(syspath(dest)) and not replace:
|
||||
raise FilesystemError("target exists", "rename", (path, dest))
|
||||
@@ -797,12 +793,11 @@ def as_string(value: Any) -> str:
|
||||
"""
|
||||
if value is None:
|
||||
return ""
|
||||
elif isinstance(value, memoryview):
|
||||
if isinstance(value, memoryview):
|
||||
return bytes(value).decode("utf-8", "ignore")
|
||||
elif isinstance(value, bytes):
|
||||
if isinstance(value, bytes):
|
||||
return value.decode("utf-8", "ignore")
|
||||
else:
|
||||
return str(value)
|
||||
return str(value)
|
||||
|
||||
|
||||
def plurality(objs: Iterable[T]) -> tuple[T, int]:
|
||||
@@ -919,8 +914,7 @@ def get_max_filename_length() -> int:
|
||||
except OSError:
|
||||
return limit
|
||||
return min(res[9], limit)
|
||||
else:
|
||||
return limit
|
||||
return limit
|
||||
|
||||
|
||||
def open_anything() -> str:
|
||||
|
||||
@@ -80,7 +80,6 @@ class LocalBackend(ABC):
|
||||
"""Return the backend version if its dependencies are satisfied or
|
||||
raise `LocalBackendNotAvailableError`.
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def available(cls) -> bool:
|
||||
@@ -105,12 +104,10 @@ class LocalBackend(ABC):
|
||||
|
||||
On error, logs a warning and returns `path_in`.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_size(self, path_in: bytes) -> tuple[int, int] | None:
|
||||
"""Return the (width, height) of the image or None if unavailable."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def deinterlace(
|
||||
@@ -120,12 +117,10 @@ class LocalBackend(ABC):
|
||||
|
||||
On error, logs a warning and returns `path_in`.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_format(self, path_in: bytes) -> str | None:
|
||||
"""Return the image format (e.g., 'PNG') or None if undetectable."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def convert_format(
|
||||
@@ -135,7 +130,6 @@ class LocalBackend(ABC):
|
||||
|
||||
On error, logs a warning and returns `source`.
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
def can_compare(self) -> bool:
|
||||
@@ -204,8 +198,7 @@ class IMBackend(LocalBackend):
|
||||
# cls._version is never None here, but mypy doesn't get that
|
||||
if cls._version is _NOT_AVAILABLE or cls._version is None:
|
||||
raise LocalBackendNotAvailableError()
|
||||
else:
|
||||
return cls._version
|
||||
return cls._version
|
||||
|
||||
convert_cmd: list[str]
|
||||
identify_cmd: list[str]
|
||||
@@ -564,8 +557,7 @@ class PILBackend(LocalBackend):
|
||||
)
|
||||
return path_out
|
||||
|
||||
else:
|
||||
return path_out
|
||||
return path_out
|
||||
except OSError:
|
||||
log.error(
|
||||
"PIL cannot create thumbnail for '{}'",
|
||||
@@ -699,8 +691,7 @@ class ArtResizer:
|
||||
def method(self) -> str:
|
||||
if self.local_method is not None:
|
||||
return self.local_method.NAME
|
||||
else:
|
||||
return "WEBPROXY"
|
||||
return "WEBPROXY"
|
||||
|
||||
def resize(
|
||||
self,
|
||||
@@ -723,9 +714,8 @@ class ArtResizer:
|
||||
quality=quality,
|
||||
max_filesize=max_filesize,
|
||||
)
|
||||
else:
|
||||
# Handled by `proxy_url` already.
|
||||
return path_in
|
||||
# Handled by `proxy_url` already.
|
||||
return path_in
|
||||
|
||||
def deinterlace(
|
||||
self, path_in: bytes, path_out: bytes | None = None
|
||||
@@ -736,9 +726,8 @@ class ArtResizer:
|
||||
"""
|
||||
if self.local_method is not None:
|
||||
return self.local_method.deinterlace(path_in, path_out)
|
||||
else:
|
||||
# FIXME: Should probably issue a warning?
|
||||
return path_in
|
||||
# FIXME: Should probably issue a warning?
|
||||
return path_in
|
||||
|
||||
def proxy_url(self, maxwidth: int, url: str, quality: int = 0) -> str:
|
||||
"""Modifies an image URL according the method, returning a new
|
||||
@@ -748,8 +737,7 @@ class ArtResizer:
|
||||
if self.local:
|
||||
# Going to be handled by `resize()`.
|
||||
return url
|
||||
else:
|
||||
return resize_url(url, maxwidth, quality)
|
||||
return resize_url(url, maxwidth, quality)
|
||||
|
||||
@property
|
||||
def local(self) -> bool:
|
||||
@@ -766,10 +754,9 @@ class ArtResizer:
|
||||
"""
|
||||
if self.local_method is not None:
|
||||
return self.local_method.get_size(path_in)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"image cannot be obtained without artresizer backend"
|
||||
)
|
||||
raise RuntimeError(
|
||||
"image cannot be obtained without artresizer backend"
|
||||
)
|
||||
|
||||
def get_format(self, path_in: bytes) -> str | None:
|
||||
"""Returns the format of the image as a string.
|
||||
@@ -778,9 +765,8 @@ class ArtResizer:
|
||||
"""
|
||||
if self.local_method is not None:
|
||||
return self.local_method.get_format(path_in)
|
||||
else:
|
||||
# FIXME: Should probably issue a warning?
|
||||
return None
|
||||
# FIXME: Should probably issue a warning?
|
||||
return None
|
||||
|
||||
def reformat(
|
||||
self, path_in: bytes, new_format: str, deinterlaced: bool = True
|
||||
@@ -820,8 +806,7 @@ class ArtResizer:
|
||||
|
||||
if self.local_method is not None:
|
||||
return self.local_method.can_compare
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
def compare(
|
||||
self, im1: bytes, im2: bytes, compare_threshold: float
|
||||
@@ -832,9 +817,8 @@ class ArtResizer:
|
||||
"""
|
||||
if self.local_method is not None:
|
||||
return self.local_method.compare(im1, im2, compare_threshold)
|
||||
else:
|
||||
# FIXME: Should probably issue a warning?
|
||||
return None
|
||||
# FIXME: Should probably issue a warning?
|
||||
return None
|
||||
|
||||
@property
|
||||
def can_write_metadata(self) -> bool:
|
||||
@@ -842,8 +826,7 @@ class ArtResizer:
|
||||
|
||||
if self.local_method is not None:
|
||||
return self.local_method.can_write_metadata
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
def write_metadata(self, file: bytes, metadata: Mapping[str, str]) -> None:
|
||||
"""Write key-value metadata to the image file.
|
||||
|
||||
@@ -24,8 +24,6 @@ class Event:
|
||||
and communicate with the scheduler.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class WaitableEvent(Event):
|
||||
"""A waitable event is one encapsulating an action that can be
|
||||
@@ -45,7 +43,6 @@ class WaitableEvent(Event):
|
||||
"""Called when an associated file descriptor becomes ready
|
||||
(i.e., is returned from a select() call).
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ValueEvent(Event):
|
||||
@@ -455,8 +452,7 @@ class Connection:
|
||||
out = self._buf[:size]
|
||||
self._buf = self._buf[size:]
|
||||
return ValueEvent(out)
|
||||
else:
|
||||
return ReceiveEvent(self, size)
|
||||
return ReceiveEvent(self, size)
|
||||
|
||||
def send(self, data):
|
||||
"""Sends data on the socket, returning the number of bytes
|
||||
@@ -541,8 +537,7 @@ class SendEvent(WaitableEvent):
|
||||
def fire(self):
|
||||
if self.sendall:
|
||||
return self.conn.sock.sendall(self.data)
|
||||
else:
|
||||
return self.conn.sock.send(self.data)
|
||||
return self.conn.sock.send(self.data)
|
||||
|
||||
|
||||
# Public interface for threads; each returns an event object that
|
||||
@@ -595,8 +590,7 @@ def read(fd, bufsize=None):
|
||||
|
||||
return DelegationEvent(reader())
|
||||
|
||||
else:
|
||||
return ReadEvent(fd, bufsize)
|
||||
return ReadEvent(fd, bufsize)
|
||||
|
||||
|
||||
def write(fd, data):
|
||||
|
||||
@@ -140,9 +140,8 @@ class Symbol:
|
||||
if self.ident in env.values:
|
||||
# Substitute for a value.
|
||||
return env.values[self.ident]
|
||||
else:
|
||||
# Keep original text.
|
||||
return self.original
|
||||
# Keep original text.
|
||||
return self.original
|
||||
|
||||
def translate(self):
|
||||
"""Compile the variable lookup."""
|
||||
@@ -175,8 +174,7 @@ class Call:
|
||||
# the exception will help debug.
|
||||
return f"<{exc}>"
|
||||
return str(out)
|
||||
else:
|
||||
return self.original
|
||||
return self.original
|
||||
|
||||
def translate(self):
|
||||
"""Compile the function call."""
|
||||
|
||||
@@ -22,8 +22,6 @@ from beets.util import FilesystemError, mkdirall, normpath, syspath
|
||||
class EmptyPlaylistError(Exception):
|
||||
"""Raised when a playlist file without media files is saved or loaded."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class M3UFile:
|
||||
"""Reads and writes m3u or m3u8 playlist files."""
|
||||
|
||||
@@ -59,7 +59,7 @@ def _invalidate_queue(q, val=None, sync=True):
|
||||
required (because it's not reentrant!).
|
||||
"""
|
||||
|
||||
def _qsize(len=len):
|
||||
def _qsize(len=len): # noqa: A002
|
||||
return 1
|
||||
|
||||
def _put(item):
|
||||
@@ -224,10 +224,9 @@ def _allmsgs(obj):
|
||||
"""
|
||||
if isinstance(obj, MultiMessage):
|
||||
return obj.messages
|
||||
elif obj == BUBBLE:
|
||||
if obj == BUBBLE:
|
||||
return []
|
||||
else:
|
||||
return [obj]
|
||||
return [obj]
|
||||
|
||||
|
||||
class PipelineThread(Thread):
|
||||
|
||||
@@ -39,7 +39,7 @@ def get_art(log, item):
|
||||
mf = mediafile.MediaFile(syspath(item.path))
|
||||
except mediafile.UnreadableFileError as exc:
|
||||
log.warning("Could not extract art from {.filepath}: {}", item, exc)
|
||||
return
|
||||
return None
|
||||
|
||||
return mf.art
|
||||
|
||||
@@ -65,7 +65,7 @@ def embed_item(
|
||||
if is_similar is None:
|
||||
log.warning("Error while checking art similarity; skipping.")
|
||||
return
|
||||
elif not is_similar:
|
||||
if not is_similar:
|
||||
log.info("Image not similar; skipping.")
|
||||
return
|
||||
|
||||
@@ -143,10 +143,9 @@ def resize_image(log, imagepath, maxwidth, quality):
|
||||
maxwidth,
|
||||
quality,
|
||||
)
|
||||
imagepath = ArtResizer.shared.resize(
|
||||
return ArtResizer.shared.resize(
|
||||
maxwidth, syspath(imagepath), quality=quality
|
||||
)
|
||||
return imagepath
|
||||
|
||||
|
||||
def check_art_similarity(
|
||||
@@ -176,13 +175,13 @@ def extract(log, outpath, item):
|
||||
outpath = bytestring_path(outpath)
|
||||
if not art:
|
||||
log.info("No album art present in {}, skipping.", item)
|
||||
return
|
||||
return None
|
||||
|
||||
# Add an extension to the filename.
|
||||
ext = mediafile.image_extension(art)
|
||||
if not ext:
|
||||
log.warning("Unknown image type in {.filepath}.", item)
|
||||
return
|
||||
return None
|
||||
outpath += bytestring_path(f".{ext}")
|
||||
|
||||
log.info(
|
||||
@@ -198,6 +197,7 @@ def extract_first(log, outpath, items):
|
||||
real_path = extract(log, outpath, item)
|
||||
if real_path:
|
||||
return real_path
|
||||
return None
|
||||
|
||||
|
||||
def clear_item(item, log):
|
||||
|
||||
@@ -97,7 +97,7 @@ class AcousticBrainzSubmitPlugin(plugins.BeetsPlugin):
|
||||
"AcousticBrainz server base URL must start "
|
||||
"with an HTTP scheme"
|
||||
)
|
||||
elif base_url[-1] != "/":
|
||||
if base_url[-1] != "/":
|
||||
base_url = f"{base_url}/"
|
||||
self.url = f"{base_url}{{mbid}}/low-level"
|
||||
|
||||
@@ -134,11 +134,10 @@ class AcousticBrainzSubmitPlugin(plugins.BeetsPlugin):
|
||||
"accepts new submissions. See the base_url configuration "
|
||||
"option."
|
||||
)
|
||||
else:
|
||||
# Get items from arguments
|
||||
items = lib.items(args)
|
||||
self.opts = opts
|
||||
util.par_map(self.analyze_submit, items)
|
||||
# Get items from arguments
|
||||
items = lib.items(args)
|
||||
self.opts = opts
|
||||
util.par_map(self.analyze_submit, items)
|
||||
|
||||
def analyze_submit(self, item):
|
||||
analysis = self._get_analysis(item)
|
||||
|
||||
@@ -98,7 +98,7 @@ class AcousticPlugin(plugins.BeetsPlugin):
|
||||
"AcousticBrainz server base URL must start "
|
||||
"with an HTTP scheme"
|
||||
)
|
||||
elif self.base_url[-1] != "/":
|
||||
if self.base_url[-1] != "/":
|
||||
self.base_url = f"{self.base_url}/"
|
||||
|
||||
if self.config["auto"]:
|
||||
|
||||
@@ -65,9 +65,9 @@ class AlbumTypesPlugin(BeetsPlugin):
|
||||
res = ""
|
||||
albumtypes = item.albumtypes
|
||||
is_va = item.mb_albumartistid == VARIOUS_ARTISTS_ID
|
||||
for type in types:
|
||||
if type[0] in albumtypes and type[1]:
|
||||
if not is_va or (type[0] not in ignore_va and is_va):
|
||||
res += f"{bracket_l}{type[1]}{bracket_r}"
|
||||
for type_ in types:
|
||||
if type_[0] in albumtypes and type_[1]:
|
||||
if not is_va or (type_[0] not in ignore_va and is_va):
|
||||
res += f"{bracket_l}{type_[1]}{bracket_r}"
|
||||
|
||||
return res
|
||||
|
||||
@@ -664,8 +664,7 @@ class ImageDocument(AURADocument):
|
||||
# Check the image actually exists
|
||||
if os.path.isfile(img_path):
|
||||
return img_path
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_resource_object(lib: Library, image_id):
|
||||
|
||||
@@ -108,6 +108,7 @@ class BadFiles(BeetsPlugin):
|
||||
return self.check_mp3val
|
||||
if ext == "flac":
|
||||
return self.check_flac
|
||||
return None
|
||||
|
||||
def check_item(self, item):
|
||||
# First, check whether the path exists. If not, the user
|
||||
@@ -226,7 +227,7 @@ class BadFiles(BeetsPlugin):
|
||||
# Errors always take precedence over warnings.
|
||||
if found_error and error_action != "ask":
|
||||
return self.handle_import_action(error_action, "error")
|
||||
elif found_warning and warning_action != "ask":
|
||||
if found_warning and warning_action != "ask":
|
||||
return self.handle_import_action(warning_action, "warning")
|
||||
|
||||
# Defer the quiet check to after automatic import action options are handled
|
||||
@@ -240,12 +241,12 @@ class BadFiles(BeetsPlugin):
|
||||
|
||||
if sel == "s":
|
||||
return importer.Action.SKIP
|
||||
elif sel == "c":
|
||||
if sel == "c":
|
||||
return None
|
||||
elif sel == "b":
|
||||
if sel == "b":
|
||||
raise importer.ImportAbortError()
|
||||
else:
|
||||
raise Exception(f"Unexpected selection: {sel}")
|
||||
raise Exception(f"Unexpected selection: {sel}")
|
||||
return None
|
||||
|
||||
def command(self, lib, opts, args):
|
||||
# Get items from arguments
|
||||
|
||||
@@ -294,8 +294,8 @@ class BeatportTrack(BeatportObject):
|
||||
self.length = timedelta(milliseconds=data.get("lengthMs", 0) or 0)
|
||||
if not self.length:
|
||||
try:
|
||||
min, sec = data.get("length", "0:0").split(":")
|
||||
self.length = timedelta(minutes=int(min), seconds=int(sec))
|
||||
min_, sec = data.get("length", "0:0").split(":")
|
||||
self.length = timedelta(minutes=int(min_), seconds=int(sec))
|
||||
except ValueError:
|
||||
pass
|
||||
if "slug" in data:
|
||||
@@ -509,5 +509,4 @@ class BeatportPlugin(MetadataSourcePlugin):
|
||||
def _get_tracks(self, query):
|
||||
"""Returns a list of TrackInfo objects for a Beatport query."""
|
||||
bp_tracks = self.client.search(query, release_type="track")
|
||||
tracks = [self._get_track_info(x) for x in bp_tracks]
|
||||
return tracks
|
||||
return [self._get_track_info(x) for x in bp_tracks]
|
||||
|
||||
@@ -165,11 +165,10 @@ def cast_arg(t, val):
|
||||
"""
|
||||
if t == "intbool":
|
||||
return cast_arg(bool, cast_arg(int, val))
|
||||
else:
|
||||
try:
|
||||
return t(val)
|
||||
except ValueError:
|
||||
raise ArgumentTypeError()
|
||||
try:
|
||||
return t(val)
|
||||
except ValueError:
|
||||
raise ArgumentTypeError()
|
||||
|
||||
|
||||
class BPDCloseError(Exception):
|
||||
@@ -346,7 +345,6 @@ class BaseServer:
|
||||
|
||||
def cmd_ping(self, conn):
|
||||
"""Succeeds."""
|
||||
pass
|
||||
|
||||
def cmd_idle(self, conn, *subsystems):
|
||||
subsystems = subsystems or SUBSYSTEMS
|
||||
@@ -604,7 +602,6 @@ class BaseServer:
|
||||
|
||||
def cmd_urlhandlers(self, conn):
|
||||
"""Indicates supported URL schemes. None by default."""
|
||||
pass
|
||||
|
||||
def cmd_playlistinfo(self, conn, index=None):
|
||||
"""Gives metadata information about the entire playlist or a
|
||||
@@ -669,10 +666,9 @@ class BaseServer:
|
||||
self.current_index = -1
|
||||
return self.cmd_play(conn)
|
||||
return self.cmd_stop(conn)
|
||||
elif self.single and not self.repeat:
|
||||
if self.single and not self.repeat:
|
||||
return self.cmd_stop(conn)
|
||||
else:
|
||||
return self.cmd_play(conn)
|
||||
return self.cmd_play(conn)
|
||||
|
||||
def cmd_previous(self, conn):
|
||||
"""Step back to the last song."""
|
||||
@@ -714,6 +710,7 @@ class BaseServer:
|
||||
|
||||
self.paused = False
|
||||
self._send_event("player")
|
||||
return None
|
||||
|
||||
def cmd_playid(self, conn, track_id=0):
|
||||
track_id = cast_arg(int, track_id)
|
||||
@@ -1410,8 +1407,8 @@ class Server(BaseServer):
|
||||
_, key = self._tagtype_lookup(tag)
|
||||
queries.append(Item.field_query(key, value, query_type))
|
||||
return dbcore.query.AndQuery(queries)
|
||||
else: # No key-value pairs.
|
||||
return dbcore.query.TrueQuery()
|
||||
# No key-value pairs.
|
||||
return dbcore.query.TrueQuery()
|
||||
|
||||
def cmd_search(self, conn, *kv):
|
||||
"""Perform a substring match for items."""
|
||||
@@ -1528,8 +1525,7 @@ class Server(BaseServer):
|
||||
output_id = cast_arg(int, output_id)
|
||||
if output_id == 0:
|
||||
raise BPDError(ERROR_ARG, "cannot disable this output")
|
||||
else:
|
||||
raise ArgumentIndexError()
|
||||
raise ArgumentIndexError()
|
||||
|
||||
# Playback control. The functions below hook into the
|
||||
# half-implementations provided by the base class. Together, they're
|
||||
|
||||
@@ -200,8 +200,7 @@ class GstPlayer:
|
||||
# reason, we cache recent.
|
||||
if self.playing and self.cached_time:
|
||||
return self.cached_time
|
||||
else:
|
||||
return (0, 0)
|
||||
return (0, 0)
|
||||
|
||||
def seek(self, position):
|
||||
"""Seeks to position (in seconds)."""
|
||||
|
||||
@@ -40,8 +40,7 @@ def bpm(max_strokes):
|
||||
|
||||
# Return average BPM
|
||||
# bpm = (max_strokes-1) / sum(dt) * 60
|
||||
ave = sum([1.0 / dti * 60 for dti in dt]) / len(dt)
|
||||
return ave
|
||||
return sum([1.0 / dti * 60 for dti in dt]) / len(dt)
|
||||
|
||||
|
||||
class BPMPlugin(BeetsPlugin):
|
||||
|
||||
@@ -216,14 +216,13 @@ class BucketPlugin(plugins.BeetsPlugin):
|
||||
if ys["from"] <= int(year) <= ys["to"]:
|
||||
if "str" in ys:
|
||||
return ys["str"]
|
||||
else:
|
||||
return format_span(
|
||||
self.ys_repr_mode["fmt"],
|
||||
ys["from"],
|
||||
ys["to"],
|
||||
self.ys_repr_mode["fromnchars"],
|
||||
self.ys_repr_mode["tonchars"],
|
||||
)
|
||||
return format_span(
|
||||
self.ys_repr_mode["fmt"],
|
||||
ys["from"],
|
||||
ys["to"],
|
||||
self.ys_repr_mode["fromnchars"],
|
||||
self.ys_repr_mode["tonchars"],
|
||||
)
|
||||
return year
|
||||
|
||||
def find_bucket_alpha(self, s):
|
||||
|
||||
@@ -104,7 +104,7 @@ def acoustid_match(log, path):
|
||||
util.displayable_path(repr(path)),
|
||||
exc,
|
||||
)
|
||||
return None
|
||||
return
|
||||
fp = fp.decode()
|
||||
_fingerprints[path] = fp
|
||||
try:
|
||||
@@ -117,23 +117,23 @@ def acoustid_match(log, path):
|
||||
util.displayable_path(repr(path)),
|
||||
exc,
|
||||
)
|
||||
return None
|
||||
return
|
||||
log.debug("chroma: fingerprinted {}", util.displayable_path(repr(path)))
|
||||
|
||||
# Ensure the response is usable and parse it.
|
||||
if res["status"] != "ok" or not res.get("results"):
|
||||
log.debug("no match found")
|
||||
return None
|
||||
return
|
||||
result = res["results"][0] # Best match.
|
||||
if result["score"] < SCORE_THRESH:
|
||||
log.debug("no results above threshold")
|
||||
return None
|
||||
return
|
||||
_acoustids[path] = result["id"]
|
||||
|
||||
# Get recording and releases from the result
|
||||
if not result.get("recordings"):
|
||||
log.debug("no recordings found")
|
||||
return None
|
||||
return
|
||||
recording_ids = []
|
||||
releases = []
|
||||
for recording in result["recordings"]:
|
||||
@@ -466,6 +466,7 @@ def fingerprint_item(log, item, write=False, quiet=False):
|
||||
return item.acoustid_fingerprint
|
||||
except acoustid.FingerprintGenerationError as exc:
|
||||
log.info("fingerprint generation failed: {}", exc)
|
||||
return None
|
||||
|
||||
|
||||
# Classes for search.
|
||||
|
||||
@@ -372,8 +372,7 @@ class ConvertPlugin(BeetsPlugin):
|
||||
if no_convert_query:
|
||||
query, _ = parse_query_string(no_convert_query, Item)
|
||||
return query.match(item)
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
def should_transcode(self, item: Item) -> bool:
|
||||
"""Determine whether the item should be transcoded as part of
|
||||
|
||||
@@ -30,9 +30,9 @@ from string import ascii_lowercase
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import confuse
|
||||
import requests
|
||||
from discogs_client import Client, Master, Release
|
||||
from discogs_client.exceptions import DiscogsAPIError
|
||||
from requests.exceptions import ConnectionError
|
||||
|
||||
import beets
|
||||
import beets.ui
|
||||
@@ -58,7 +58,7 @@ API_SECRET = "plxtUTqoCzwxZpqdPysCwGuBSmZNdZVy"
|
||||
|
||||
# Exceptions that discogs_client should really handle but does not.
|
||||
CONNECTION_ERRORS = (
|
||||
ConnectionError,
|
||||
requests.exceptions.ConnectionError,
|
||||
socket.error,
|
||||
http.client.HTTPException,
|
||||
ValueError, # JSON decoding raises a ValueError.
|
||||
|
||||
@@ -117,8 +117,7 @@ def flatten(obj, fields):
|
||||
# Possibly filter field names.
|
||||
if fields:
|
||||
return {k: v for k, v in d.items() if k in fields}
|
||||
else:
|
||||
return d
|
||||
return d
|
||||
|
||||
|
||||
def apply_(obj, data):
|
||||
@@ -259,8 +258,7 @@ class EditPlugin(plugins.BeetsPlugin):
|
||||
ui.print_(f"Could not read data: {e}")
|
||||
if ui.input_yn("Edit again to fix? (Y/n)", True):
|
||||
continue
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
# Show the changes.
|
||||
# If the objects are not on the DB yet, we need a copy of their
|
||||
@@ -281,10 +279,10 @@ class EditPlugin(plugins.BeetsPlugin):
|
||||
)
|
||||
if choice == "a": # Apply.
|
||||
return True
|
||||
elif choice == "c": # Cancel.
|
||||
if choice == "c": # Cancel.
|
||||
self.apply_data(objs, new_data, old_data)
|
||||
return False
|
||||
elif choice == "e": # Keep editing.
|
||||
if choice == "e": # Keep editing.
|
||||
self.apply_data(objs, new_data, old_data)
|
||||
continue
|
||||
|
||||
@@ -373,8 +371,7 @@ class EditPlugin(plugins.BeetsPlugin):
|
||||
# Return Action.RETAG, which makes the importer write the tags
|
||||
# to the files if needed without re-applying metadata.
|
||||
return Action.RETAG
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
def importer_edit_candidate(self, session, task):
|
||||
"""Callback for invoking the functionality during an interactive
|
||||
|
||||
@@ -32,7 +32,7 @@ def api_url(host, port, endpoint):
|
||||
"""
|
||||
# check if http or https is defined as host and create hostname
|
||||
hostname_list = [host]
|
||||
if host.startswith("http://") or host.startswith("https://"):
|
||||
if host.startswith(("http://", "https://")):
|
||||
hostname = "".join(hostname_list)
|
||||
else:
|
||||
hostname_list.insert(0, "http://")
|
||||
@@ -128,9 +128,7 @@ def get_user(host, port, username):
|
||||
"""
|
||||
url = api_url(host, port, "/Users/Public")
|
||||
r = requests.get(url, timeout=10)
|
||||
user = [i for i in r.json() if i["Name"] == username]
|
||||
|
||||
return user
|
||||
return [i for i in r.json() if i["Name"] == username]
|
||||
|
||||
|
||||
class EmbyUpdate(BeetsPlugin):
|
||||
|
||||
@@ -128,8 +128,7 @@ class ExportPlugin(BeetsPlugin):
|
||||
format_options = self.config[file_format]["formatting"].get(dict)
|
||||
|
||||
export_format = ExportFormat.factory(
|
||||
file_type=file_format,
|
||||
**{"file_path": file_path, "file_mode": file_mode},
|
||||
file_type=file_format, file_path=file_path, file_mode=file_mode
|
||||
)
|
||||
|
||||
if opts.library or opts.album:
|
||||
@@ -180,12 +179,11 @@ class ExportFormat:
|
||||
def factory(cls, file_type, **kwargs):
|
||||
if file_type in ["json", "jsonlines"]:
|
||||
return JsonFormat(**kwargs)
|
||||
elif file_type == "csv":
|
||||
if file_type == "csv":
|
||||
return CSVFormat(**kwargs)
|
||||
elif file_type == "xml":
|
||||
if file_type == "xml":
|
||||
return XMLFormat(**kwargs)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError()
|
||||
|
||||
def export(self, data, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -414,7 +414,6 @@ class ArtSource(RequestMixin, ABC):
|
||||
After calling this, `Candidate.path` is set to the image path if
|
||||
successful, or to `None` otherwise.
|
||||
"""
|
||||
pass
|
||||
|
||||
def cleanup(self, candidate: Candidate) -> None:
|
||||
pass
|
||||
|
||||
@@ -73,6 +73,5 @@ class FileFilterPlugin(BeetsPlugin):
|
||||
if "singletons" not in import_config or not import_config["singletons"]:
|
||||
# Album
|
||||
return self.path_album_regex.match(full_path) is not None
|
||||
else:
|
||||
# Singleton
|
||||
return self.path_singleton_regex.match(full_path) is not None
|
||||
# Singleton
|
||||
return self.path_singleton_regex.match(full_path) is not None
|
||||
|
||||
@@ -179,7 +179,7 @@ def get_set_of_values_for_field(lib, fields):
|
||||
|
||||
|
||||
def get_basic_beet_options():
|
||||
word = (
|
||||
return (
|
||||
BL_NEED2.format("-l format-item", "-f -d 'print with custom format'")
|
||||
+ BL_NEED2.format("-l format-album", "-f -d 'print with custom format'")
|
||||
+ BL_NEED2.format(
|
||||
@@ -198,7 +198,6 @@ def get_basic_beet_options():
|
||||
"-s h -l help", "-f -d 'print this help message and exit'"
|
||||
)
|
||||
)
|
||||
return word
|
||||
|
||||
|
||||
def get_subcommands(cmd_name_and_help, nobasicfields, extravalues):
|
||||
|
||||
@@ -84,9 +84,8 @@ def split_on_feat(
|
||||
|
||||
if len(parts) == 1:
|
||||
return parts[0], None
|
||||
else:
|
||||
assert len(parts) == 2 # help mypy out
|
||||
return parts
|
||||
assert len(parts) == 2 # help mypy out
|
||||
return parts
|
||||
|
||||
|
||||
def contains_feat(title: str, custom_words: list[str] | None = None) -> bool:
|
||||
@@ -123,12 +122,9 @@ def find_feat_part(
|
||||
|
||||
# Otherwise, if there's nothing on the right-hand side,
|
||||
# look for a featuring artist on the left-hand side.
|
||||
else:
|
||||
lhs, _ = split_on_feat(
|
||||
albumartist_split[0], custom_words=custom_words
|
||||
)
|
||||
if lhs:
|
||||
return lhs
|
||||
lhs, _ = split_on_feat(albumartist_split[0], custom_words=custom_words)
|
||||
if lhs:
|
||||
return lhs
|
||||
|
||||
# Fall back to conservative handling of the track artist without relying
|
||||
# on albumartist, which covers compilations using a 'Various Artists'
|
||||
|
||||
@@ -29,8 +29,7 @@ def summary(task):
|
||||
"""
|
||||
if task.is_album:
|
||||
return f"{task.cur_artist} - {task.cur_album}"
|
||||
else:
|
||||
return f"{task.item.artist} - {task.item.title}"
|
||||
return f"{task.item.artist} - {task.item.title}"
|
||||
|
||||
|
||||
class IHatePlugin(BeetsPlugin):
|
||||
|
||||
@@ -34,12 +34,11 @@ def _build_m3u_session_filename(basename):
|
||||
basename and file ending."""
|
||||
date = datetime.datetime.now().strftime("%Y%m%d_%Hh%M")
|
||||
basename = re.sub(r"(\.m3u|\.M3U)", "", basename)
|
||||
path = normpath(
|
||||
return normpath(
|
||||
os.path.join(
|
||||
config["importfeeds"]["dir"].as_filename(), f"{basename}_{date}.m3u"
|
||||
)
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
def _build_m3u_filename(basename):
|
||||
@@ -47,12 +46,11 @@ def _build_m3u_filename(basename):
|
||||
date."""
|
||||
basename = re.sub(r"[\s,/\\'\"]", "_", basename)
|
||||
date = datetime.datetime.now().strftime("%Y%m%d_%Hh%M")
|
||||
path = normpath(
|
||||
return normpath(
|
||||
os.path.join(
|
||||
config["importfeeds"]["dir"].as_filename(), f"{date}_{basename}.m3u"
|
||||
)
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
def _write_m3u(m3u_path, items_paths):
|
||||
|
||||
@@ -90,7 +90,7 @@ class InlinePlugin(BeetsPlugin):
|
||||
"syntax error in inline field definition:\n{}",
|
||||
traceback.format_exc(),
|
||||
)
|
||||
return
|
||||
return None
|
||||
else:
|
||||
is_expr = False
|
||||
else:
|
||||
@@ -118,18 +118,18 @@ class InlinePlugin(BeetsPlugin):
|
||||
raise InlineError(python_code, exc)
|
||||
|
||||
return _expr_func
|
||||
else:
|
||||
# For function bodies, invoke the function with values as global
|
||||
# variables.
|
||||
def _func_func(obj):
|
||||
old_globals = dict(func.__globals__)
|
||||
func.__globals__.update(_dict_for(obj))
|
||||
try:
|
||||
return func(obj)
|
||||
except Exception as exc:
|
||||
raise InlineError(python_code, exc)
|
||||
finally:
|
||||
func.__globals__.clear()
|
||||
func.__globals__.update(old_globals)
|
||||
|
||||
return _func_func
|
||||
# For function bodies, invoke the function with values as global
|
||||
# variables.
|
||||
def _func_func(obj):
|
||||
old_globals = dict(func.__globals__)
|
||||
func.__globals__.update(_dict_for(obj))
|
||||
try:
|
||||
return func(obj)
|
||||
except Exception as exc:
|
||||
raise InlineError(python_code, exc)
|
||||
finally:
|
||||
func.__globals__.clear()
|
||||
func.__globals__.update(old_globals)
|
||||
|
||||
return _func_func
|
||||
|
||||
@@ -188,6 +188,7 @@ class IPFSPlugin(BeetsPlugin):
|
||||
# prefix=True). However, that should be fine since the hash will not
|
||||
# exceed MAX_PATH.
|
||||
shutil.rmtree(util.syspath(_hash, prefix=False))
|
||||
return None
|
||||
|
||||
def ipfs_publish(self, lib):
|
||||
with tempfile.NamedTemporaryFile() as tmp:
|
||||
@@ -204,6 +205,7 @@ class IPFSPlugin(BeetsPlugin):
|
||||
self._log.error(msg)
|
||||
return False
|
||||
self._log.info("hash of library: {}", output)
|
||||
return None
|
||||
|
||||
def ipfs_import(self, lib, args):
|
||||
_hash = args[0]
|
||||
@@ -243,6 +245,7 @@ class IPFSPlugin(BeetsPlugin):
|
||||
added_album = jlib.add_album(new_album)
|
||||
added_album.ipfs = album.ipfs
|
||||
added_album.store()
|
||||
return None
|
||||
|
||||
def already_added(self, check, jlib):
|
||||
for jalbum in jlib.albums():
|
||||
@@ -263,8 +266,7 @@ class IPFSPlugin(BeetsPlugin):
|
||||
|
||||
def query(self, lib, args):
|
||||
rlib = self.get_remote_lib(lib)
|
||||
albums = rlib.albums(args)
|
||||
return albums
|
||||
return rlib.albums(args)
|
||||
|
||||
def get_remote_lib(self, lib):
|
||||
lib_root = os.path.dirname(lib.path)
|
||||
@@ -308,3 +310,4 @@ class IPFSPlugin(BeetsPlugin):
|
||||
new_album = tmplib.add_album(items)
|
||||
new_album.ipfs = album.ipfs
|
||||
new_album.store(inherit=False)
|
||||
return None
|
||||
|
||||
@@ -39,12 +39,10 @@ def update_kodi(host, port, user, password):
|
||||
|
||||
# Create the payload. Id seems to be mandatory.
|
||||
payload = {"jsonrpc": "2.0", "method": "AudioLibrary.Scan", "id": 1}
|
||||
r = requests.post(
|
||||
return requests.post(
|
||||
url, auth=(user, password), json=payload, headers=headers, timeout=10
|
||||
)
|
||||
|
||||
return r
|
||||
|
||||
|
||||
class KodiUpdate(BeetsPlugin):
|
||||
def __init__(self):
|
||||
|
||||
@@ -365,8 +365,7 @@ class LastGenrePlugin(plugins.BeetsPlugin):
|
||||
"""Format to title case if configured."""
|
||||
if self.config["title_case"]:
|
||||
return [tag.title() for tag in tags]
|
||||
else:
|
||||
return tags
|
||||
return tags
|
||||
|
||||
def _artist_for_filter(self, obj: LibModel) -> str | None:
|
||||
"""Return the representative artist for genre resolution and filtering."""
|
||||
|
||||
@@ -132,7 +132,7 @@ def import_lastfm(lib, log):
|
||||
f"/{page_total}" if page_total > 1 else "",
|
||||
)
|
||||
|
||||
for retry in range(0, retry_limit):
|
||||
for retry in range(retry_limit):
|
||||
tracks, page_total = fetch_tracks(user, page_current + 1, per_page)
|
||||
if page_total < 1:
|
||||
# It means nothing to us!
|
||||
@@ -143,22 +143,21 @@ def import_lastfm(lib, log):
|
||||
found_total += found
|
||||
unknown_total += unknown
|
||||
break
|
||||
log.error("ERROR: unable to read page #{}", page_current + 1)
|
||||
if retry < retry_limit:
|
||||
log.info(
|
||||
"Retrying page #{}... ({}/{} retry)",
|
||||
page_current + 1,
|
||||
retry + 1,
|
||||
retry_limit,
|
||||
)
|
||||
else:
|
||||
log.error("ERROR: unable to read page #{}", page_current + 1)
|
||||
if retry < retry_limit:
|
||||
log.info(
|
||||
"Retrying page #{}... ({}/{} retry)",
|
||||
page_current + 1,
|
||||
retry + 1,
|
||||
retry_limit,
|
||||
)
|
||||
else:
|
||||
log.error(
|
||||
"FAIL: unable to fetch page #{}, ",
|
||||
"tried {} times",
|
||||
page_current,
|
||||
retry + 1,
|
||||
)
|
||||
log.error(
|
||||
"FAIL: unable to fetch page #{}, ",
|
||||
"tried {} times",
|
||||
page_current,
|
||||
retry + 1,
|
||||
)
|
||||
page_current += 1
|
||||
|
||||
log.info("... done!")
|
||||
|
||||
@@ -351,9 +351,9 @@ class ListenBrainzPlugin(MusicBrainzAPIMixin, BeetsPlugin):
|
||||
else:
|
||||
continue
|
||||
identifier = playlist_info.get("identifier")
|
||||
id = identifier.split("/")[-1]
|
||||
id_ = identifier.split("/")[-1]
|
||||
listenbrainz_playlists.append(
|
||||
{"type": playlist_type, "date": date, "identifier": id}
|
||||
{"type": playlist_type, "date": date, "identifier": id_}
|
||||
)
|
||||
listenbrainz_playlists = sorted(
|
||||
listenbrainz_playlists, key=lambda x: x["type"]
|
||||
|
||||
@@ -939,9 +939,9 @@ class RestFiles:
|
||||
|
||||
@cached_property
|
||||
def artists_dir(self) -> Path:
|
||||
dir = self.directory / "artists"
|
||||
dir.mkdir(parents=True, exist_ok=True)
|
||||
return dir
|
||||
dir_ = self.directory / "artists"
|
||||
dir_.mkdir(parents=True, exist_ok=True)
|
||||
return dir_
|
||||
|
||||
def write_indexes(self) -> None:
|
||||
"""Write conf.py and index.rst files necessary for Sphinx
|
||||
|
||||
@@ -150,15 +150,13 @@ class MusicBrainzPseudoReleasePlugin(MusicBrainzPlugin):
|
||||
)
|
||||
self._add_custom_tags(official_release, pseudo_release)
|
||||
return official_release
|
||||
else:
|
||||
return PseudoAlbumInfo(
|
||||
pseudo_release=_merge_pseudo_and_actual_album(
|
||||
pseudo_release, official_release
|
||||
),
|
||||
official_release=official_release,
|
||||
)
|
||||
else:
|
||||
return official_release
|
||||
return PseudoAlbumInfo(
|
||||
pseudo_release=_merge_pseudo_and_actual_album(
|
||||
pseudo_release, official_release
|
||||
),
|
||||
official_release=official_release,
|
||||
)
|
||||
return official_release
|
||||
|
||||
def _intercept_mb_release(self, data: Release) -> list[str]:
|
||||
album_id = data["id"] if "id" in data else None
|
||||
@@ -177,10 +175,9 @@ class MusicBrainzPseudoReleasePlugin(MusicBrainzPlugin):
|
||||
) -> bool:
|
||||
if len(self._scripts) == 0:
|
||||
return False
|
||||
elif script := release.get("text_representation", {}).get("script"):
|
||||
if script := release.get("text_representation", {}).get("script"):
|
||||
return script in self._scripts
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
def _wanted_pseudo_release_id(
|
||||
self, album_id: str, relation: ReleaseRelation
|
||||
@@ -201,8 +198,7 @@ class MusicBrainzPseudoReleasePlugin(MusicBrainzPlugin):
|
||||
album_id,
|
||||
)
|
||||
return release["id"]
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
def _replace_artist_with_alias(
|
||||
self, raw_pseudo_release: Release, pseudo_release: AlbumInfo
|
||||
@@ -316,9 +312,8 @@ class PseudoAlbumInfo(AlbumInfo):
|
||||
if official_dist < pseudo_dist:
|
||||
self.use_official_as_ref()
|
||||
return "official"
|
||||
else:
|
||||
self.use_pseudo_as_ref()
|
||||
return "pseudo"
|
||||
self.use_pseudo_as_ref()
|
||||
return "pseudo"
|
||||
|
||||
def _compute_distance(self, items: Sequence[Item]) -> Distance:
|
||||
mapping, _, _ = assign_items(items, self.tracks)
|
||||
@@ -334,8 +329,7 @@ class PseudoAlbumInfo(AlbumInfo):
|
||||
# ensure we don't duplicate an official release's id, always return pseudo's
|
||||
if self.__dict__["_pseudo_source"] or attr == "album_id":
|
||||
return super().__getattr__(attr)
|
||||
else:
|
||||
return self.__dict__["_official_release"].__getattr__(attr)
|
||||
return self.__dict__["_official_release"].__getattr__(attr)
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
cls = self.__class__
|
||||
|
||||
@@ -62,6 +62,7 @@ class MBSubmitPlugin(BeetsPlugin):
|
||||
PromptChoice("p", "Print tracks", self.print_tracks),
|
||||
PromptChoice("o", "Open files with Picard", self.picard),
|
||||
]
|
||||
return None
|
||||
|
||||
def picard(self, session, task):
|
||||
paths = []
|
||||
|
||||
@@ -74,41 +74,39 @@ def _item(track_info, album_info, album_id):
|
||||
a = album_info
|
||||
|
||||
return Item(
|
||||
**{
|
||||
"album_id": album_id,
|
||||
"album": a.album,
|
||||
"albumartist": a.artist,
|
||||
"albumartist_credit": a.artist_credit,
|
||||
"albumartist_sort": a.artist_sort,
|
||||
"albumdisambig": a.albumdisambig,
|
||||
"albumstatus": a.albumstatus,
|
||||
"albumtype": a.albumtype,
|
||||
"artist": t.artist,
|
||||
"artist_credit": t.artist_credit,
|
||||
"artist_sort": t.artist_sort,
|
||||
"asin": a.asin,
|
||||
"catalognum": a.catalognum,
|
||||
"comp": a.va,
|
||||
"country": a.country,
|
||||
"day": a.day,
|
||||
"disc": t.medium,
|
||||
"disctitle": t.disctitle,
|
||||
"disctotal": a.mediums,
|
||||
"label": a.label,
|
||||
"language": a.language,
|
||||
"length": t.length,
|
||||
"mb_albumid": a.album_id,
|
||||
"mb_artistid": t.artist_id,
|
||||
"mb_releasegroupid": a.releasegroup_id,
|
||||
"mb_trackid": t.track_id,
|
||||
"media": t.media,
|
||||
"month": a.month,
|
||||
"script": a.script,
|
||||
"title": t.title,
|
||||
"track": t.index,
|
||||
"tracktotal": len(a.tracks),
|
||||
"year": a.year,
|
||||
}
|
||||
album_id=album_id,
|
||||
album=a.album,
|
||||
albumartist=a.artist,
|
||||
albumartist_credit=a.artist_credit,
|
||||
albumartist_sort=a.artist_sort,
|
||||
albumdisambig=a.albumdisambig,
|
||||
albumstatus=a.albumstatus,
|
||||
albumtype=a.albumtype,
|
||||
artist=t.artist,
|
||||
artist_credit=t.artist_credit,
|
||||
artist_sort=t.artist_sort,
|
||||
asin=a.asin,
|
||||
catalognum=a.catalognum,
|
||||
comp=a.va,
|
||||
country=a.country,
|
||||
day=a.day,
|
||||
disc=t.medium,
|
||||
disctitle=t.disctitle,
|
||||
disctotal=a.mediums,
|
||||
label=a.label,
|
||||
language=a.language,
|
||||
length=t.length,
|
||||
mb_albumid=a.album_id,
|
||||
mb_artistid=t.artist_id,
|
||||
mb_releasegroupid=a.releasegroup_id,
|
||||
mb_trackid=t.track_id,
|
||||
media=t.media,
|
||||
month=a.month,
|
||||
script=a.script,
|
||||
title=t.title,
|
||||
track=t.index,
|
||||
tracktotal=len(a.tracks),
|
||||
year=a.year,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -170,8 +170,8 @@ class MPDStats:
|
||||
item = self.lib.items(query).get()
|
||||
if item:
|
||||
return item
|
||||
else:
|
||||
self._log.info("item not found: {}", displayable_path(path))
|
||||
self._log.info("item not found: {}", displayable_path(path))
|
||||
return None
|
||||
|
||||
def update_item(self, item, attribute, value=None, increment=None):
|
||||
"""Update the beets item. Set attribute to value or increment the value
|
||||
|
||||
@@ -53,8 +53,7 @@ class BufferedSocket:
|
||||
if self.sep in self.buf:
|
||||
res, self.buf = self.buf.split(self.sep, 1)
|
||||
return res + self.sep
|
||||
else:
|
||||
return b""
|
||||
return b""
|
||||
|
||||
def send(self, data):
|
||||
self.sock.send(data)
|
||||
|
||||
@@ -137,7 +137,7 @@ class ParentWorkPlugin(MusicBrainzAPIMixin, BeetsPlugin):
|
||||
"No work for {0}, add one at https://musicbrainz.org/recording/{0.mb_trackid}",
|
||||
item,
|
||||
)
|
||||
return
|
||||
return None
|
||||
|
||||
hasparent = hasattr(item, "parentwork")
|
||||
work_changed = True
|
||||
@@ -148,7 +148,7 @@ class ParentWorkPlugin(MusicBrainzAPIMixin, BeetsPlugin):
|
||||
work_info, work_date = self.find_parentwork_info(item.mb_workid)
|
||||
except requests.exceptions.RequestException:
|
||||
self._log.debug("error fetching work", item, exc_info=True)
|
||||
return
|
||||
return None
|
||||
parent_info = self.get_info(item, work_info)
|
||||
parent_info["parentwork_workid_current"] = item.mb_workid
|
||||
if "parent_composer" in parent_info:
|
||||
@@ -165,7 +165,7 @@ class ParentWorkPlugin(MusicBrainzAPIMixin, BeetsPlugin):
|
||||
|
||||
elif hasparent:
|
||||
self._log.debug("{}: Work present, skipping", item)
|
||||
return
|
||||
return None
|
||||
|
||||
# apply all non-null values to the item
|
||||
for key, value in parent_info.items():
|
||||
@@ -188,6 +188,7 @@ class ParentWorkPlugin(MusicBrainzAPIMixin, BeetsPlugin):
|
||||
"parentwork_date",
|
||||
],
|
||||
)
|
||||
return None
|
||||
|
||||
def find_parentwork_info(self, mb_workid: str) -> tuple[Work, str | None]:
|
||||
"""Get the MusicBrainz information dict about a parent work, including
|
||||
|
||||
@@ -181,19 +181,15 @@ class PlayPlugin(BeetsPlugin):
|
||||
if args:
|
||||
if ARGS_MARKER in command_str:
|
||||
return command_str.replace(ARGS_MARKER, args)
|
||||
else:
|
||||
return f"{command_str} {args}"
|
||||
else:
|
||||
# Don't include the marker in the command.
|
||||
return command_str.replace(f" {ARGS_MARKER}", "")
|
||||
return f"{command_str} {args}"
|
||||
# Don't include the marker in the command.
|
||||
return command_str.replace(f" {ARGS_MARKER}", "")
|
||||
|
||||
def _playlist_or_paths(self, paths):
|
||||
"""Return either the raw paths of items or a playlist of the items."""
|
||||
if config["play"]["raw"]:
|
||||
return paths
|
||||
else:
|
||||
return [self._create_tmp_playlist(paths)]
|
||||
return [shlex.quote(self._create_tmp_playlist(paths))]
|
||||
return [self._create_tmp_playlist(paths)]
|
||||
|
||||
def _exceeds_threshold(
|
||||
self, selection, command_str, open_args, item_type="track"
|
||||
|
||||
@@ -32,6 +32,7 @@ def get_music_section(
|
||||
for child in tree.findall("Directory"):
|
||||
if child.get("title") == library_name:
|
||||
return child.get("key")
|
||||
return None
|
||||
|
||||
|
||||
def update_plex(host, port, token, library_name, secure, ignore_cert_errors):
|
||||
@@ -51,8 +52,7 @@ def update_plex(host, port, token, library_name, secure, ignore_cert_errors):
|
||||
url = urljoin(f"{get_protocol(secure)}://{host}:{port}", api_endpoint)
|
||||
|
||||
# Sends request and returns requests object.
|
||||
r = requests.get(url, verify=not ignore_cert_errors, timeout=10)
|
||||
return r
|
||||
return requests.get(url, verify=not ignore_cert_errors, timeout=10)
|
||||
|
||||
|
||||
def append_token(url, token):
|
||||
@@ -65,8 +65,7 @@ def append_token(url, token):
|
||||
def get_protocol(secure):
|
||||
if secure:
|
||||
return "https"
|
||||
else:
|
||||
return "http"
|
||||
return "http"
|
||||
|
||||
|
||||
class PlexUpdate(BeetsPlugin):
|
||||
|
||||
@@ -109,13 +109,13 @@ def _equal_chance_permutation(
|
||||
del groups[group]
|
||||
|
||||
|
||||
def _take_time(iter: Iterable[LibModel], secs: float) -> Iterable[LibModel]:
|
||||
def _take_time(iter_: Iterable[LibModel], secs: float) -> Iterable[LibModel]:
|
||||
"""Return a list containing the first values in `iter`, which should
|
||||
be Item or Album objects, that add up to the given amount of time in
|
||||
seconds.
|
||||
"""
|
||||
total_time = 0.0
|
||||
for obj in iter:
|
||||
for obj in iter_:
|
||||
length = obj.length
|
||||
if total_time + length <= secs:
|
||||
yield obj
|
||||
@@ -151,5 +151,4 @@ def random_objs(
|
||||
# Select objects by time our count.
|
||||
if time_minutes:
|
||||
return _take_time(perm, time_minutes * 60)
|
||||
else:
|
||||
return islice(perm, number)
|
||||
return islice(perm, number)
|
||||
|
||||
@@ -1026,7 +1026,6 @@ class AudioToolsBackend(Backend):
|
||||
rg = self._mod_replaygain.ReplayGain(audiofile.sample_rate())
|
||||
except ValueError:
|
||||
raise ReplayGainError(f"Unsupported sample rate {item.samplerate}")
|
||||
return
|
||||
return rg
|
||||
|
||||
def compute_track_gain(self, task: AnyRgTask) -> AnyRgTask:
|
||||
@@ -1296,15 +1295,14 @@ class ReplayGainPlugin(BeetsPlugin):
|
||||
self.backend_instance.NAME,
|
||||
self._log,
|
||||
)
|
||||
else:
|
||||
return RgTask(
|
||||
items,
|
||||
album,
|
||||
self.config["targetlevel"].as_number(),
|
||||
self.peak_method,
|
||||
self.backend_instance.NAME,
|
||||
self._log,
|
||||
)
|
||||
return RgTask(
|
||||
items,
|
||||
album,
|
||||
self.config["targetlevel"].as_number(),
|
||||
self.peak_method,
|
||||
self.backend_instance.NAME,
|
||||
self._log,
|
||||
)
|
||||
|
||||
def handle_album(self, album: Album, write: bool, force: bool = False):
|
||||
"""Compute album and track replay gain store it in all of the
|
||||
|
||||
@@ -113,8 +113,6 @@ class APIError(Exception):
|
||||
class AudioFeaturesUnavailableError(Exception):
|
||||
"""Raised when audio features API returns 403 (deprecated)."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class SpotifyPlugin(
|
||||
SearchApiMetadataSourcePlugin[SearchResponseAlbums | SearchResponseTracks]
|
||||
@@ -279,12 +277,12 @@ class SpotifyPlugin(
|
||||
return self._handle_response(
|
||||
method, url, params=params, retry_count=retry_count + 1
|
||||
)
|
||||
elif e.response.status_code == 404:
|
||||
if e.response.status_code == 404:
|
||||
raise APIError(
|
||||
f"API Error: {e.response.status_code}\n"
|
||||
f"URL: {url}\nparams: {params}"
|
||||
)
|
||||
elif e.response.status_code == 403:
|
||||
if e.response.status_code == 403:
|
||||
# Check if this is the audio features endpoint
|
||||
if url.startswith(self.audio_features_url):
|
||||
raise AudioFeaturesUnavailableError(
|
||||
@@ -295,7 +293,7 @@ class SpotifyPlugin(
|
||||
f"API Error: {e.response.status_code}\n"
|
||||
f"URL: {url}\nparams: {params}"
|
||||
)
|
||||
elif e.response.status_code == 429:
|
||||
if e.response.status_code == 429:
|
||||
seconds = e.response.headers.get(
|
||||
"Retry-After", DEFAULT_WAITING_TIME
|
||||
)
|
||||
@@ -306,21 +304,20 @@ class SpotifyPlugin(
|
||||
return self._handle_response(
|
||||
method, url, params=params, retry_count=retry_count + 1
|
||||
)
|
||||
elif e.response.status_code == 503:
|
||||
if e.response.status_code == 503:
|
||||
self._log.error("Service Unavailable.")
|
||||
raise APIError("Service Unavailable.")
|
||||
elif e.response.status_code == 502:
|
||||
if e.response.status_code == 502:
|
||||
self._log.error("Bad Gateway.")
|
||||
raise APIError("Bad Gateway.")
|
||||
elif e.response is not None:
|
||||
if e.response is not None:
|
||||
raise APIError(
|
||||
f"{self.data_source} API error:\n"
|
||||
f"{e.response.text}\n"
|
||||
f"URL:\n{url}\nparams:\n{params}"
|
||||
)
|
||||
else:
|
||||
self._log.error("Request failed. Error: {}", e)
|
||||
raise APIError("Request failed.")
|
||||
self._log.error("Request failed. Error: {}", e)
|
||||
raise APIError("Request failed.")
|
||||
|
||||
def _multi_artist_credit(
|
||||
self, artists: list[dict[str | int, str]]
|
||||
@@ -628,7 +625,7 @@ class SpotifyPlugin(
|
||||
"Your beets query returned no items, skipping {.data_source}.",
|
||||
self,
|
||||
)
|
||||
return
|
||||
return None
|
||||
|
||||
self._log.info("Processing {} tracks...", len(items))
|
||||
|
||||
|
||||
@@ -41,19 +41,18 @@ def filter_to_be_removed(items, keys):
|
||||
):
|
||||
dont_remove.append(item)
|
||||
return [item for item in items if item not in dont_remove]
|
||||
else:
|
||||
|
||||
def to_be_removed(item):
|
||||
for artist, album, title in keys:
|
||||
if (
|
||||
artist == item["artist"]
|
||||
and album == item["album"]
|
||||
and title == item["title"]
|
||||
):
|
||||
return False
|
||||
return True
|
||||
def to_be_removed(item):
|
||||
for artist, album, title in keys:
|
||||
if (
|
||||
artist == item["artist"]
|
||||
and album == item["album"]
|
||||
and title == item["title"]
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
return [item for item in items if to_be_removed(item)]
|
||||
return [item for item in items if to_be_removed(item)]
|
||||
|
||||
|
||||
class SubsonicPlaylistPlugin(BeetsPlugin):
|
||||
@@ -96,7 +95,7 @@ class SubsonicPlaylistPlugin(BeetsPlugin):
|
||||
if playlist.attrib.get("code", "200") != "200":
|
||||
alt_error = "error getting playlist, but no error message found"
|
||||
self._log.warn(playlist.attrib.get("message", alt_error))
|
||||
return
|
||||
return None
|
||||
|
||||
name = playlist.attrib.get("name", "undefined")
|
||||
tracks = [
|
||||
@@ -167,11 +166,10 @@ class SubsonicPlaylistPlugin(BeetsPlugin):
|
||||
params["s"] = b
|
||||
params["v"] = "1.12.0"
|
||||
params["c"] = "beets"
|
||||
resp = requests.get(
|
||||
return requests.get(
|
||||
f"{self.config['base_url'].get()}/rest/{endpoint}?{urlencode(params)}",
|
||||
timeout=10,
|
||||
)
|
||||
return resp
|
||||
|
||||
def get_playlists(self, ids):
|
||||
output = {}
|
||||
|
||||
@@ -36,8 +36,7 @@ class Substitute(BeetsPlugin):
|
||||
for pattern, replacement in self.substitute_rules:
|
||||
text = pattern.sub(replacement, text)
|
||||
return text
|
||||
else:
|
||||
return ""
|
||||
return ""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the substitute plugin.
|
||||
|
||||
@@ -81,9 +81,8 @@ class ThePlugin(BeetsPlugin):
|
||||
r = re.sub(r, "", text).strip()
|
||||
if self.config["strip"]:
|
||||
return r
|
||||
else:
|
||||
fmt = self.config["format"].as_str()
|
||||
return fmt.format(r, t.strip()).strip()
|
||||
fmt = self.config["format"].as_str()
|
||||
return fmt.format(r, t.strip()).strip()
|
||||
else:
|
||||
return ""
|
||||
|
||||
@@ -97,5 +96,4 @@ class ThePlugin(BeetsPlugin):
|
||||
self._log.debug('"{}" -> "{}"', text, r)
|
||||
break
|
||||
return r
|
||||
else:
|
||||
return ""
|
||||
return ""
|
||||
|
||||
@@ -89,9 +89,9 @@ class ThumbnailsPlugin(BeetsPlugin):
|
||||
)
|
||||
return False
|
||||
|
||||
for dir in (NORMAL_DIR, LARGE_DIR):
|
||||
if not os.path.exists(syspath(dir)):
|
||||
os.makedirs(syspath(dir))
|
||||
for dir_ in (NORMAL_DIR, LARGE_DIR):
|
||||
if not os.path.exists(syspath(dir_)):
|
||||
os.makedirs(syspath(dir_))
|
||||
|
||||
if not ArtResizer.shared.can_write_metadata:
|
||||
raise RuntimeError(
|
||||
@@ -170,8 +170,8 @@ class ThumbnailsPlugin(BeetsPlugin):
|
||||
See https://standards.freedesktop.org/thumbnail-spec/latest/x227.html
|
||||
"""
|
||||
uri = self.get_uri(path)
|
||||
hash = md5(uri.encode("utf-8")).hexdigest()
|
||||
return bytestring_path(f"{hash}.png")
|
||||
hash_ = md5(uri.encode("utf-8")).hexdigest()
|
||||
return bytestring_path(f"{hash_}.png")
|
||||
|
||||
def add_tags(self, album, image_path):
|
||||
"""Write required metadata to the thumbnail
|
||||
|
||||
@@ -432,8 +432,7 @@ class TidalPlugin(MetadataSourcePlugin):
|
||||
"""
|
||||
if version := attributes.get("version"):
|
||||
return f"{attributes['title']} ({version})"
|
||||
else:
|
||||
return attributes["title"]
|
||||
return attributes["title"]
|
||||
|
||||
@staticmethod
|
||||
def _parse_data_url(
|
||||
@@ -457,8 +456,8 @@ class TidalPlugin(MetadataSourcePlugin):
|
||||
def _parse_label(
|
||||
attributes: AlbumAttributes | TrackAttributes,
|
||||
) -> str | None:
|
||||
if copyright := attributes.get("copyright"):
|
||||
return copyright["text"]
|
||||
if copyright_ := attributes.get("copyright"):
|
||||
return copyright_["text"]
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -214,9 +214,9 @@ class TidalAPI(RequestHandler):
|
||||
"links": {"next": url},
|
||||
}
|
||||
|
||||
while next := doc.get("links", {}).get("next"):
|
||||
while next_ := doc.get("links", {}).get("next"):
|
||||
page_doc = self.get_json(
|
||||
url=next, params={**params, "include": include}, **kwargs
|
||||
url=next_, params={**params, "include": include}, **kwargs
|
||||
)
|
||||
doc = self.merge_multiresource_pagination(doc, page_doc)
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ class TitlecasePlugin(BeetsPlugin):
|
||||
# Check if A-Z is all uppercase or all lowercase
|
||||
if self.all_lowercase and text.islower():
|
||||
return text
|
||||
elif self.all_caps and text.isupper():
|
||||
if self.all_caps and text.isupper():
|
||||
return text
|
||||
# Any necessary replacements go first, mainly punctuation.
|
||||
titlecased = text.lower() if self.force_lowercase else text
|
||||
|
||||
@@ -70,7 +70,7 @@ def _rep(obj, expand=False):
|
||||
|
||||
return out
|
||||
|
||||
elif isinstance(obj, beets.library.Album):
|
||||
if isinstance(obj, beets.library.Album):
|
||||
if app.config.get("INCLUDE_PATHS", False):
|
||||
out["artpath"] = util.displayable_path(out["artpath"])
|
||||
else:
|
||||
@@ -78,6 +78,7 @@ def _rep(obj, expand=False):
|
||||
if expand:
|
||||
out["items"] = [_rep(item) for item in obj.items()]
|
||||
return out
|
||||
return None
|
||||
|
||||
|
||||
def json_generator(items, root, expand=False):
|
||||
@@ -124,7 +125,7 @@ def resource(name, patchable=False):
|
||||
|
||||
def make_responder(retriever):
|
||||
def responder(ids):
|
||||
entities = [retriever(id) for id in ids]
|
||||
entities = [retriever(id_) for id_ in ids]
|
||||
entities = [entity for entity in entities if entity]
|
||||
|
||||
if get_method() == "DELETE":
|
||||
@@ -136,7 +137,7 @@ def resource(name, patchable=False):
|
||||
|
||||
return flask.make_response(jsonify({"deleted": True}), 200)
|
||||
|
||||
elif get_method() == "PATCH" and patchable:
|
||||
if get_method() == "PATCH" and patchable:
|
||||
if app.config.get("READONLY", True):
|
||||
return flask.abort(405)
|
||||
|
||||
@@ -146,7 +147,7 @@ def resource(name, patchable=False):
|
||||
|
||||
if len(entities) == 1:
|
||||
return flask.jsonify(_rep(entities[0], expand=is_expand()))
|
||||
elif entities:
|
||||
if entities:
|
||||
return app.response_class(
|
||||
json_generator(entities, root=name),
|
||||
mimetype="application/json",
|
||||
@@ -155,16 +156,14 @@ def resource(name, patchable=False):
|
||||
elif get_method() == "GET":
|
||||
if len(entities) == 1:
|
||||
return flask.jsonify(_rep(entities[0], expand=is_expand()))
|
||||
elif entities:
|
||||
if entities:
|
||||
return app.response_class(
|
||||
json_generator(entities, root=name),
|
||||
mimetype="application/json",
|
||||
)
|
||||
else:
|
||||
return flask.abort(404)
|
||||
return flask.abort(404)
|
||||
|
||||
else:
|
||||
return flask.abort(405)
|
||||
return flask.abort(405)
|
||||
|
||||
responder.__name__ = f"get_{name}"
|
||||
|
||||
@@ -189,7 +188,7 @@ def resource_query(name, patchable=False):
|
||||
|
||||
return flask.make_response(jsonify({"deleted": True}), 200)
|
||||
|
||||
elif get_method() == "PATCH" and patchable:
|
||||
if get_method() == "PATCH" and patchable:
|
||||
if app.config.get("READONLY", True):
|
||||
return flask.abort(405)
|
||||
|
||||
@@ -202,7 +201,7 @@ def resource_query(name, patchable=False):
|
||||
mimetype="application/json",
|
||||
)
|
||||
|
||||
elif get_method() == "GET":
|
||||
if get_method() == "GET":
|
||||
return app.response_class(
|
||||
json_generator(
|
||||
entities, root="results", expand=is_expand()
|
||||
@@ -210,8 +209,7 @@ def resource_query(name, patchable=False):
|
||||
mimetype="application/json",
|
||||
)
|
||||
|
||||
else:
|
||||
return flask.abort(405)
|
||||
return flask.abort(405)
|
||||
|
||||
responder.__name__ = f"query_{name}"
|
||||
|
||||
@@ -254,9 +252,9 @@ class IdListConverter(BaseConverter):
|
||||
|
||||
def to_python(self, value):
|
||||
ids = []
|
||||
for id in value.split(","):
|
||||
for id_ in value.split(","):
|
||||
try:
|
||||
ids.append(int(id))
|
||||
ids.append(int(id_))
|
||||
except ValueError:
|
||||
pass
|
||||
return ids
|
||||
@@ -303,8 +301,8 @@ def before_request():
|
||||
|
||||
@app.route("/item/<idlist:ids>", methods=["GET", "DELETE", "PATCH"])
|
||||
@resource("items", patchable=True)
|
||||
def get_item(id):
|
||||
return g.lib.get_item(id)
|
||||
def get_item(id_):
|
||||
return g.lib.get_item(id_)
|
||||
|
||||
|
||||
@app.route("/item/")
|
||||
@@ -329,10 +327,9 @@ def item_file(item_id):
|
||||
else:
|
||||
safe_filename = base_filename
|
||||
|
||||
response = flask.send_file(
|
||||
return flask.send_file(
|
||||
item_path, as_attachment=True, download_name=safe_filename
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
@app.route("/item/query/<query:queries>", methods=["GET", "DELETE", "PATCH"])
|
||||
@@ -347,8 +344,7 @@ def item_at_path(path):
|
||||
item = g.lib.items(query).get()
|
||||
if item:
|
||||
return flask.jsonify(_rep(item))
|
||||
else:
|
||||
return flask.abort(404)
|
||||
return flask.abort(404)
|
||||
|
||||
|
||||
@app.route("/item/values/<string:key>")
|
||||
@@ -368,8 +364,8 @@ def item_unique_field_values(key):
|
||||
|
||||
@app.route("/album/<idlist:ids>", methods=["GET", "DELETE"])
|
||||
@resource("albums")
|
||||
def get_album(id):
|
||||
return g.lib.get_album(id)
|
||||
def get_album(id_):
|
||||
return g.lib.get_album(id_)
|
||||
|
||||
|
||||
@app.route("/album/")
|
||||
@@ -390,8 +386,7 @@ def album_art(album_id):
|
||||
album = g.lib.get_album(album_id)
|
||||
if album and album.artpath:
|
||||
return flask.send_file(album.artpath.decode())
|
||||
else:
|
||||
return flask.abort(404)
|
||||
return flask.abort(404)
|
||||
|
||||
|
||||
@app.route("/album/values/<string:key>")
|
||||
|
||||
@@ -14,7 +14,7 @@ sys.path.insert(0, str(Path(__file__).parent / "extensions"))
|
||||
|
||||
project = "beets"
|
||||
AUTHOR = "Adrian Sampson"
|
||||
copyright = "2016, Adrian Sampson"
|
||||
copyright = "2016, Adrian Sampson" # noqa: A001
|
||||
|
||||
master_doc = "index"
|
||||
language = "en"
|
||||
|
||||
@@ -54,7 +54,7 @@ class Ref(NamedTuple):
|
||||
if len(line_parts := line.split(" ", 1)) == 1:
|
||||
return cls(line, None, None)
|
||||
|
||||
id, path_with_name = line_parts
|
||||
id_, path_with_name = line_parts
|
||||
parts = [p.strip() for p in path_with_name.split(":", 1)]
|
||||
|
||||
if len(parts) == 1:
|
||||
@@ -62,7 +62,7 @@ class Ref(NamedTuple):
|
||||
else:
|
||||
name, path = parts
|
||||
|
||||
return cls(id, path, name)
|
||||
return cls(id_, path, name)
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
|
||||
@@ -311,6 +311,7 @@ skip-magic-trailing-comma = true
|
||||
[tool.ruff.lint]
|
||||
future-annotations = true
|
||||
select = [
|
||||
"A", # flake8-builtins
|
||||
# "ARG", # flake8-unused-arguments
|
||||
# "C4", # flake8-comprehensions
|
||||
"E", # pycodestyle
|
||||
@@ -320,7 +321,9 @@ select = [
|
||||
"I", # isort
|
||||
"ISC", # flake8-implicit-str-concat
|
||||
"N", # pep8-naming
|
||||
"PIE", # flake8-pie
|
||||
"PT", # flake8-pytest-style
|
||||
"RET", # flake8-return
|
||||
"RUF", # ruff
|
||||
"UP", # pyupgrade
|
||||
"TC", # flake8-type-checking
|
||||
|
||||
@@ -95,6 +95,7 @@ def pytest_make_parametrize_id(config, val, argname):
|
||||
def pytest_assertrepr_compare(op, left, right):
|
||||
if isinstance(left, Distance) or isinstance(right, Distance):
|
||||
return [f"Comparing Distance: {float(left)} {op} {float(right)}"]
|
||||
return None
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
||||
@@ -31,7 +31,7 @@ class BeatportTest(BeetsTestCase):
|
||||
The list of elements on the returned dict is incomplete, including just
|
||||
those required for the tests on this class.
|
||||
"""
|
||||
results = {
|
||||
return {
|
||||
"id": 1742984,
|
||||
"type": "release",
|
||||
"name": "Charade",
|
||||
@@ -61,7 +61,6 @@ class BeatportTest(BeetsTestCase):
|
||||
{"id": 9, "name": "Breaks", "slug": "breaks", "type": "genre"}
|
||||
],
|
||||
}
|
||||
return results
|
||||
|
||||
def _make_tracks_response(self):
|
||||
"""Return a list that mimics a response from the beatport API.
|
||||
@@ -71,7 +70,7 @@ class BeatportTest(BeetsTestCase):
|
||||
The list of elements on the returned list is incomplete, including just
|
||||
those required for the tests on this class.
|
||||
"""
|
||||
results = [
|
||||
return [
|
||||
{
|
||||
"id": 7817567,
|
||||
"type": "track",
|
||||
@@ -445,7 +444,6 @@ class BeatportTest(BeetsTestCase):
|
||||
},
|
||||
},
|
||||
]
|
||||
return results
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
@@ -561,9 +559,10 @@ class BeatportTest(BeetsTestCase):
|
||||
# Specify beatport ids here because an 'item.id' is beets-internal.
|
||||
ids = [7817567, 7817568, 7817569, 7817570, 7817571, 7817572]
|
||||
# Concatenate with 'id' to pass strict equality test.
|
||||
for track, test_track, id in zip(self.tracks, self.test_tracks, ids):
|
||||
for track, test_track, id_ in zip(self.tracks, self.test_tracks, ids):
|
||||
assert (
|
||||
track.url == f"https://beatport.com/track/{test_track.url}/{id}"
|
||||
track.url
|
||||
== f"https://beatport.com/track/{test_track.url}/{id_}"
|
||||
)
|
||||
|
||||
def test_bpm_applied(self):
|
||||
@@ -581,7 +580,7 @@ class BeatportTest(BeetsTestCase):
|
||||
|
||||
class BeatportResponseEmptyTest(unittest.TestCase):
|
||||
def _make_tracks_response(self):
|
||||
results = [
|
||||
return [
|
||||
{
|
||||
"id": 7817567,
|
||||
"name": "Mirage a Trois",
|
||||
@@ -603,7 +602,6 @@ class BeatportResponseEmptyTest(unittest.TestCase):
|
||||
],
|
||||
}
|
||||
]
|
||||
return results
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
@@ -93,15 +93,14 @@ class MPCResponse:
|
||||
|
||||
def _parse_status(self, status):
|
||||
"""Parses the first response line, which contains the status."""
|
||||
if status.startswith("OK") or status.startswith("list_OK"):
|
||||
if status.startswith(("OK", "list_OK")):
|
||||
return True, None
|
||||
elif status.startswith("ACK"):
|
||||
if status.startswith("ACK"):
|
||||
code, rest = status[5:].split("@", 1)
|
||||
pos, rest = rest.split("]", 1)
|
||||
cmd, rest = rest[2:].split("}")
|
||||
return False, (int(code), int(pos), cmd, rest[1:])
|
||||
else:
|
||||
raise RuntimeError(f"Unexpected status: {status!r}")
|
||||
raise RuntimeError(f"Unexpected status: {status!r}")
|
||||
|
||||
def _parse_body(self, body):
|
||||
"""Messages are generally in the format "header: content".
|
||||
@@ -147,15 +146,14 @@ class MPCClient:
|
||||
while True:
|
||||
line = self.readline()
|
||||
response += line
|
||||
if line.startswith(b"OK") or line.startswith(b"ACK"):
|
||||
if line.startswith((b"OK", b"ACK")):
|
||||
if force_multi or any(responses):
|
||||
if line.startswith(b"ACK"):
|
||||
responses.append(MPCResponse(response))
|
||||
n_remaining = force_multi - len(responses)
|
||||
responses.extend([None] * n_remaining)
|
||||
return responses
|
||||
else:
|
||||
return MPCResponse(response)
|
||||
return MPCResponse(response)
|
||||
if line.startswith(b"list_OK"):
|
||||
responses.append(MPCResponse(response))
|
||||
response = b""
|
||||
|
||||
@@ -49,9 +49,9 @@ class ChromaTest(IOMixin, PluginMixin, ImportTestCase):
|
||||
def run_search(self, fp):
|
||||
return self.run_with_output("chromasearch", "-s", fp, "-f", "$title")
|
||||
|
||||
def line_count(self, str):
|
||||
def line_count(self, str_):
|
||||
return len(
|
||||
[line for line in str.split("\n") if line.strip(" \n") != ""]
|
||||
[line for line in str_.split("\n") if line.strip(" \n") != ""]
|
||||
)
|
||||
|
||||
def compare_fingerprints(self, *args, **kwargs):
|
||||
|
||||
@@ -31,10 +31,9 @@ class ExportPluginTest(IOMixin, PluginTestCase):
|
||||
|
||||
def execute_command(self, format_type, artist):
|
||||
query = ",".join(self.test_values.keys())
|
||||
out = self.run_with_output(
|
||||
return self.run_with_output(
|
||||
"export", "-f", format_type, "-i", query, artist
|
||||
)
|
||||
return out
|
||||
|
||||
def create_item(self):
|
||||
(item,) = self.add_item_fixtures()
|
||||
|
||||
@@ -49,7 +49,7 @@ class FetchartCliTest(IOMixin, PluginTestCase):
|
||||
self.skipTest("unable to set file attributes")
|
||||
|
||||
def test_set_art_from_folder(self):
|
||||
self.touch(b"c\xc3\xb6ver.jpg", dir=self.album.path, content="IMAGE")
|
||||
self.touch(b"c\xc3\xb6ver.jpg", dir_=self.album.path, content="IMAGE")
|
||||
|
||||
self.run_command("fetchart")
|
||||
|
||||
@@ -63,21 +63,21 @@ class FetchartCliTest(IOMixin, PluginTestCase):
|
||||
assert self.album["artpath"] is None
|
||||
|
||||
def test_filesystem_does_not_pick_up_ignored_file(self):
|
||||
self.touch(b"co_ver.jpg", dir=self.album.path, content="IMAGE")
|
||||
self.touch(b"co_ver.jpg", dir_=self.album.path, content="IMAGE")
|
||||
self.config["ignore"] = ["*_*"]
|
||||
self.run_command("fetchart")
|
||||
self.album.load()
|
||||
assert self.album["artpath"] is None
|
||||
|
||||
def test_filesystem_picks_up_non_ignored_file(self):
|
||||
self.touch(b"cover.jpg", dir=self.album.path, content="IMAGE")
|
||||
self.touch(b"cover.jpg", dir_=self.album.path, content="IMAGE")
|
||||
self.config["ignore"] = ["*_*"]
|
||||
self.run_command("fetchart")
|
||||
self.album.load()
|
||||
self.check_cover_is_stored()
|
||||
|
||||
def test_filesystem_does_not_pick_up_hidden_file(self):
|
||||
self.touch(b".cover.jpg", dir=self.album.path, content="IMAGE")
|
||||
self.touch(b".cover.jpg", dir_=self.album.path, content="IMAGE")
|
||||
if sys.platform == "win32":
|
||||
self.hide_file_windows()
|
||||
self.config["ignore"] = [] # By default, ignore includes '.*'.
|
||||
@@ -87,14 +87,14 @@ class FetchartCliTest(IOMixin, PluginTestCase):
|
||||
assert self.album["artpath"] is None
|
||||
|
||||
def test_filesystem_picks_up_non_hidden_file(self):
|
||||
self.touch(b"cover.jpg", dir=self.album.path, content="IMAGE")
|
||||
self.touch(b"cover.jpg", dir_=self.album.path, content="IMAGE")
|
||||
self.config["ignore_hidden"] = True
|
||||
self.run_command("fetchart")
|
||||
self.album.load()
|
||||
self.check_cover_is_stored()
|
||||
|
||||
def test_filesystem_picks_up_hidden_file(self):
|
||||
self.touch(b".cover.jpg", dir=self.album.path, content="IMAGE")
|
||||
self.touch(b".cover.jpg", dir_=self.album.path, content="IMAGE")
|
||||
if sys.platform == "win32":
|
||||
self.hide_file_windows()
|
||||
self.config["ignore"] = [] # By default, ignore includes '.*'.
|
||||
@@ -104,13 +104,13 @@ class FetchartCliTest(IOMixin, PluginTestCase):
|
||||
self.check_cover_is_stored()
|
||||
|
||||
def test_filesystem_picks_up_webp_file(self):
|
||||
self.touch(b"cover.webp", dir=self.album.path, content="IMAGE")
|
||||
self.touch(b"cover.webp", dir_=self.album.path, content="IMAGE")
|
||||
self.run_command("fetchart")
|
||||
self.album.load()
|
||||
self.check_cover_is_stored("webp")
|
||||
|
||||
def test_filesystem_picks_up_png_file(self):
|
||||
self.touch(b"cover.png", dir=self.album.path, content="IMAGE")
|
||||
self.touch(b"cover.png", dir_=self.album.path, content="IMAGE")
|
||||
self.run_command("fetchart")
|
||||
self.album.load()
|
||||
self.check_cover_is_stored("png")
|
||||
@@ -124,7 +124,7 @@ class FetchartCliTest(IOMixin, PluginTestCase):
|
||||
"""OSError (e.g. PermissionError) in set_art is logged as a warning,
|
||||
not an unhandled crash. Regression test for #6193.
|
||||
"""
|
||||
self.touch(b"c\xc3\xb6ver.jpg", dir=self.album.path, content="IMAGE")
|
||||
self.touch(b"c\xc3\xb6ver.jpg", dir_=self.album.path, content="IMAGE")
|
||||
with mock.patch(
|
||||
"beets.library.Album.set_art",
|
||||
side_effect=PermissionError("[WinError 32] file in use"),
|
||||
|
||||
@@ -155,7 +155,7 @@ class TestListenBrainzPlugin(ConfigMixin):
|
||||
call_count += 1
|
||||
if call_count == 1:
|
||||
return {"payload": {"listens": page1}}
|
||||
elif call_count == 2:
|
||||
if call_count == 2:
|
||||
return {"payload": {"listens": page2}}
|
||||
return {"payload": {"listens": []}}
|
||||
|
||||
|
||||
@@ -922,7 +922,7 @@ class TestMusicBrainzPlugin(MusicBrainzPluginTestMixin):
|
||||
assert excinfo.value is error
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,expected",
|
||||
"input_,expected",
|
||||
[
|
||||
("??-??-??", (None, None, None)),
|
||||
("??-01-??", (None, 1, None)),
|
||||
@@ -935,5 +935,5 @@ class TestMusicBrainzPlugin(MusicBrainzPluginTestMixin):
|
||||
("2010-01-02", (2010, 1, 2)),
|
||||
],
|
||||
)
|
||||
def test_get_date(self, input, expected):
|
||||
assert musicbrainz._get_date(input) == expected
|
||||
def test_get_date(self, input_, expected):
|
||||
assert musicbrainz._get_date(input_) == expected
|
||||
|
||||
@@ -188,22 +188,22 @@ class SmartPlaylistTest(BeetsTestCase):
|
||||
pl = b"$title-my<playlist>.m3u", (q, None), (a_q, None)
|
||||
spl._matched_playlists = {pl}
|
||||
|
||||
dir = mkdtemp()
|
||||
dir_ = mkdtemp()
|
||||
config["smartplaylist"]["relative_to"] = False
|
||||
config["smartplaylist"]["playlist_dir"] = str(dir)
|
||||
config["smartplaylist"]["playlist_dir"] = str(dir_)
|
||||
try:
|
||||
spl.update_playlists(lib)
|
||||
except Exception:
|
||||
rmtree(syspath(dir))
|
||||
rmtree(syspath(dir_))
|
||||
raise
|
||||
|
||||
lib.items.assert_called_once_with(q, None)
|
||||
lib.albums.assert_called_once_with(a_q, None)
|
||||
|
||||
m3u_filepath = Path(dir, "ta_ga_da-my_playlist_.m3u")
|
||||
m3u_filepath = Path(dir_, "ta_ga_da-my_playlist_.m3u")
|
||||
assert m3u_filepath.exists()
|
||||
content = m3u_filepath.read_bytes()
|
||||
rmtree(syspath(dir))
|
||||
rmtree(syspath(dir_))
|
||||
|
||||
assert content == b"/tagada.mp3\n"
|
||||
|
||||
@@ -229,24 +229,24 @@ class SmartPlaylistTest(BeetsTestCase):
|
||||
pl = b"$title-my<playlist>.m3u", (q, None), (a_q, None)
|
||||
spl._matched_playlists = {pl}
|
||||
|
||||
dir = mkdtemp()
|
||||
dir_ = mkdtemp()
|
||||
config["smartplaylist"]["output"] = "extm3u"
|
||||
config["smartplaylist"]["prefix"] = "http://beets:8337/files"
|
||||
config["smartplaylist"]["relative_to"] = False
|
||||
config["smartplaylist"]["playlist_dir"] = str(dir)
|
||||
config["smartplaylist"]["playlist_dir"] = str(dir_)
|
||||
try:
|
||||
spl.update_playlists(lib)
|
||||
except Exception:
|
||||
rmtree(syspath(dir))
|
||||
rmtree(syspath(dir_))
|
||||
raise
|
||||
|
||||
lib.items.assert_called_once_with(q, None)
|
||||
lib.albums.assert_called_once_with(a_q, None)
|
||||
|
||||
m3u_filepath = Path(dir, "ta_ga_da-my_playlist_.m3u")
|
||||
m3u_filepath = Path(dir_, "ta_ga_da-my_playlist_.m3u")
|
||||
assert m3u_filepath.exists()
|
||||
content = m3u_filepath.read_bytes()
|
||||
rmtree(syspath(dir))
|
||||
rmtree(syspath(dir_))
|
||||
|
||||
assert content == (
|
||||
b"#EXTM3U\n"
|
||||
@@ -278,24 +278,24 @@ class SmartPlaylistTest(BeetsTestCase):
|
||||
pl = b"$title-my<playlist>.m3u", (q, None), (a_q, None)
|
||||
spl._matched_playlists = {pl}
|
||||
|
||||
dir = mkdtemp()
|
||||
dir_ = mkdtemp()
|
||||
config["smartplaylist"]["output"] = "extm3u"
|
||||
config["smartplaylist"]["relative_to"] = False
|
||||
config["smartplaylist"]["playlist_dir"] = str(dir)
|
||||
config["smartplaylist"]["playlist_dir"] = str(dir_)
|
||||
config["smartplaylist"]["fields"] = ["id", "genres"]
|
||||
try:
|
||||
spl.update_playlists(lib)
|
||||
except Exception:
|
||||
rmtree(syspath(dir))
|
||||
rmtree(syspath(dir_))
|
||||
raise
|
||||
|
||||
lib.items.assert_called_once_with(q, None)
|
||||
lib.albums.assert_called_once_with(a_q, None)
|
||||
|
||||
m3u_filepath = Path(dir, "ta_ga_da-my_playlist_.m3u")
|
||||
m3u_filepath = Path(dir_, "ta_ga_da-my_playlist_.m3u")
|
||||
assert m3u_filepath.exists()
|
||||
content = m3u_filepath.read_bytes()
|
||||
rmtree(syspath(dir))
|
||||
rmtree(syspath(dir_))
|
||||
|
||||
assert content == (
|
||||
b"#EXTM3U\n"
|
||||
|
||||
@@ -24,8 +24,8 @@ class SubstitutePluginTest(PluginTestCase):
|
||||
|
||||
def run_substitute(self, config, cases):
|
||||
with self.configure_plugin(config):
|
||||
for input, expected in cases:
|
||||
assert Substitute().tmpl_substitute(input) == expected
|
||||
for input_, expected in cases:
|
||||
assert Substitute().tmpl_substitute(input_) == expected
|
||||
|
||||
def test_simple_substitute(self):
|
||||
self.run_substitute(
|
||||
|
||||
@@ -119,10 +119,9 @@ class ThumbnailsTest(BeetsTestCase):
|
||||
def os_stat(target):
|
||||
if target == syspath(md5_file):
|
||||
return Mock(st_mtime=1)
|
||||
elif target == syspath(path_to_art):
|
||||
if target == syspath(path_to_art):
|
||||
return Mock(st_mtime=2)
|
||||
else:
|
||||
raise ValueError(f"invalid target {target}")
|
||||
raise ValueError(f"invalid target {target}")
|
||||
|
||||
mock_os.stat.side_effect = os_stat
|
||||
|
||||
@@ -147,10 +146,9 @@ class ThumbnailsTest(BeetsTestCase):
|
||||
def os_stat(target):
|
||||
if target == syspath(md5_file):
|
||||
return Mock(st_mtime=3)
|
||||
elif target == syspath(path_to_art):
|
||||
if target == syspath(path_to_art):
|
||||
return Mock(st_mtime=2)
|
||||
else:
|
||||
raise ValueError(f"invalid target {target}")
|
||||
raise ValueError(f"invalid target {target}")
|
||||
|
||||
mock_os.stat.side_effect = os_stat
|
||||
|
||||
|
||||
@@ -22,16 +22,16 @@ if TYPE_CHECKING:
|
||||
)
|
||||
|
||||
|
||||
def _make_artist(id: str, name: str) -> TidalArtist:
|
||||
def _make_artist(id_: str, name: str) -> TidalArtist:
|
||||
return {
|
||||
"id": id,
|
||||
"id": id_,
|
||||
"type": "artists",
|
||||
"attributes": {"name": name, "popularity": 0.5},
|
||||
}
|
||||
|
||||
|
||||
def _make_album(
|
||||
id: str,
|
||||
id_: str,
|
||||
title: str,
|
||||
tracks: list[TidalTrack],
|
||||
artist_ids: list[str],
|
||||
@@ -59,7 +59,7 @@ def _make_album(
|
||||
attrs["version"] = version
|
||||
|
||||
album: TidalAlbum = {
|
||||
"id": id,
|
||||
"id": id_,
|
||||
"type": "albums",
|
||||
"attributes": attrs,
|
||||
"relationships": {
|
||||
@@ -77,7 +77,7 @@ def _make_album(
|
||||
|
||||
|
||||
def _make_track(
|
||||
id: str,
|
||||
id_: str,
|
||||
title: str,
|
||||
duration: str = "PT3M30S",
|
||||
isrc: str = "ISRC123",
|
||||
@@ -97,7 +97,7 @@ def _make_track(
|
||||
if version:
|
||||
attrs["version"] = version
|
||||
return {
|
||||
"id": id,
|
||||
"id": id_,
|
||||
"type": "tracks",
|
||||
"attributes": attrs,
|
||||
"relationships": {
|
||||
|
||||
@@ -45,7 +45,6 @@ class DummyPILBackend(PILBackend):
|
||||
|
||||
def __init__(self):
|
||||
"""Init a dummy backend class for mocked PIL tests."""
|
||||
pass
|
||||
|
||||
|
||||
class ArtResizerFileSizeTest(CleanupModulesMixin, BeetsTestCase):
|
||||
|
||||
@@ -165,8 +165,7 @@ def create_archive(session):
|
||||
archive = ZipFile(os.fsdecode(path), mode="w")
|
||||
archive.write(syspath(os.path.join(_common.RSRC, b"full.mp3")), "full.mp3")
|
||||
archive.close()
|
||||
path = bytestring_path(path)
|
||||
return path
|
||||
return bytestring_path(path)
|
||||
|
||||
|
||||
class RmTempTest(BeetsTestCase):
|
||||
@@ -519,7 +518,7 @@ class ImportTest(PathsMixin, AutotagImportTestCase):
|
||||
|
||||
def test_skip_non_album_dirs(self):
|
||||
assert (self.import_path / "album").exists()
|
||||
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.run()
|
||||
|
||||
@@ -534,7 +533,7 @@ class ImportTest(PathsMixin, AutotagImportTestCase):
|
||||
|
||||
def test_empty_directory_warning(self):
|
||||
import_dir = os.path.join(self.temp_dir, b"empty")
|
||||
self.touch(b"non-audio", dir=import_dir)
|
||||
self.touch(b"non-audio", dir_=import_dir)
|
||||
self.setup_importer(import_dir=import_dir)
|
||||
with capture_log() as logs:
|
||||
self.importer.run()
|
||||
@@ -544,7 +543,7 @@ class ImportTest(PathsMixin, AutotagImportTestCase):
|
||||
|
||||
def test_empty_directory_singleton_warning(self):
|
||||
import_dir = os.path.join(self.temp_dir, b"empty")
|
||||
self.touch(b"non-audio", dir=import_dir)
|
||||
self.touch(b"non-audio", dir_=import_dir)
|
||||
self.setup_singleton_importer(import_dir=import_dir)
|
||||
with capture_log() as logs:
|
||||
self.importer.run()
|
||||
@@ -1442,18 +1441,18 @@ class AlbumsInDirTest(BeetsTestCase):
|
||||
|
||||
|
||||
class MultiDiscAlbumsInDirTest(BeetsTestCase):
|
||||
def create_music(self, files=True, ascii=True):
|
||||
def create_music(self, files=True, ascii_=True):
|
||||
"""Create some music in multiple album directories.
|
||||
|
||||
`files` indicates whether to create the files (otherwise, only
|
||||
directories are made). `ascii` indicates ACII-only filenames;
|
||||
directories are made). `ascii_` indicates ACII-only filenames;
|
||||
otherwise, we use Unicode names.
|
||||
"""
|
||||
self.base = os.path.abspath(os.path.join(self.temp_dir, b"tempdir"))
|
||||
os.mkdir(syspath(self.base))
|
||||
|
||||
name = b"CAT" if ascii else util.bytestring_path("C\xc1T")
|
||||
name_alt_case = b"CAt" if ascii else util.bytestring_path("C\xc1t")
|
||||
name = b"CAT" if ascii_ else util.bytestring_path("C\xc1T")
|
||||
name_alt_case = b"CAt" if ascii_ else util.bytestring_path("C\xc1t")
|
||||
|
||||
self.dirs = [
|
||||
# Nested album, multiple subdirs.
|
||||
@@ -1492,7 +1491,7 @@ class MultiDiscAlbumsInDirTest(BeetsTestCase):
|
||||
os.path.join(self.base, b"artist [CD5]", name + b"S", b"song7.mp3"),
|
||||
]
|
||||
|
||||
if not ascii:
|
||||
if not ascii_:
|
||||
self.dirs = [self._normalize_path(p) for p in self.dirs]
|
||||
self.files = [self._normalize_path(p) for p in self.files]
|
||||
|
||||
@@ -1546,14 +1545,14 @@ class MultiDiscAlbumsInDirTest(BeetsTestCase):
|
||||
assert len(albums) == 0
|
||||
|
||||
def test_single_disc_unicode(self):
|
||||
self.create_music(ascii=False)
|
||||
self.create_music(ascii_=False)
|
||||
albums = list(albums_in_dir(self.base))
|
||||
root, items = albums[3]
|
||||
assert root == self.dirs[8:]
|
||||
assert len(items) == 1
|
||||
|
||||
def test_coalesce_multiple_unicode(self):
|
||||
self.create_music(ascii=False)
|
||||
self.create_music(ascii_=False)
|
||||
albums = list(albums_in_dir(self.base))
|
||||
assert len(albums) == 4
|
||||
root, items = albums[0]
|
||||
@@ -1736,7 +1735,6 @@ class ImportPretendTest(IOMixin, AutotagImportTestCase):
|
||||
assert len(self.lib.albums()) == 0
|
||||
|
||||
return [line for line in logs if not line.startswith("Sending event:")]
|
||||
assert self._album().data_source == "original_source"
|
||||
|
||||
def test_import_singletons_pretend(self):
|
||||
assert self.__run(self.setup_singleton_importer(pretend=True)) == [
|
||||
|
||||
@@ -103,8 +103,8 @@ class ConfigCommandTest(IOMixin, BeetsTestCase):
|
||||
execlp.assert_called_once_with("myvisual", "myvisual", self.config_path)
|
||||
|
||||
def test_edit_config_with_automatic_open(self):
|
||||
with patch("beets.util.open_anything") as open:
|
||||
open.return_value = "please_open"
|
||||
with patch("beets.util.open_anything") as open_:
|
||||
open_.return_value = "please_open"
|
||||
with patch("os.execlp") as execlp:
|
||||
self.run_command("config", "-e")
|
||||
execlp.assert_called_once_with(
|
||||
|
||||
@@ -94,7 +94,7 @@ class ModifyTest(IOMixin, BeetsTestCase):
|
||||
album = "album"
|
||||
original_artist = "composer"
|
||||
new_artist = "coverArtist"
|
||||
for i in range(0, 10):
|
||||
for i in range(10):
|
||||
self.add_item_fixture(
|
||||
title=f"{title}{i}", artist=original_artist, album=album
|
||||
)
|
||||
@@ -109,7 +109,7 @@ class ModifyTest(IOMixin, BeetsTestCase):
|
||||
assert len(list(new_items)) == 7
|
||||
|
||||
def test_modify_formatted(self):
|
||||
for i in range(0, 3):
|
||||
for i in range(3):
|
||||
self.add_item_fixture(
|
||||
title=f"title{i}", artist="artist", album="album"
|
||||
)
|
||||
|
||||
@@ -22,8 +22,7 @@ class QueryTest(BeetsTestCase):
|
||||
return item
|
||||
|
||||
def add_album(self, items):
|
||||
album = self.lib.add_album(items)
|
||||
return album
|
||||
return self.lib.add_album(items)
|
||||
|
||||
def check_do_query(
|
||||
self, num_items, num_albums, q=(), album=False, also_items=True
|
||||
|
||||
Reference in New Issue
Block a user