mirror of
https://github.com/beetbox/beets.git
synced 2026-01-06 16:02:53 +01:00
Resolve some 'mypy' errors (#5282)
## Description I noticed that some development work was waiting on cleaner `mypy` runs, so I thought I would help out a bit. This PR just contains a number of miscellaneous fixes across the codebase that I hit. I'm happy to add more, or remove any problematic changes (though I don't think I've broken anything, only type annotations have changed). ## To Do - [x] ~Documentation~ - [ ] Changelog - [x] ~Tests~
This commit is contained in:
commit
b583fb7dec
12 changed files with 45 additions and 33 deletions
|
|
@ -28,6 +28,7 @@ from typing import (
|
|||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
|
@ -42,20 +43,22 @@ from beets.util import as_string
|
|||
|
||||
log = logging.getLogger("beets")
|
||||
|
||||
V = TypeVar("V")
|
||||
|
||||
|
||||
# Classes used to represent candidate options.
|
||||
class AttrDict(dict):
|
||||
class AttrDict(Dict[str, V]):
|
||||
"""A dictionary that supports attribute ("dot") access, so `d.field`
|
||||
is equivalent to `d['field']`.
|
||||
"""
|
||||
|
||||
def __getattr__(self, attr):
|
||||
def __getattr__(self, attr: str) -> V:
|
||||
if attr in self:
|
||||
return self.get(attr)
|
||||
return self[attr]
|
||||
else:
|
||||
raise AttributeError
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
def __setattr__(self, key: str, value: V):
|
||||
self.__setitem__(key, value)
|
||||
|
||||
def __hash__(self):
|
||||
|
|
@ -79,7 +82,7 @@ class AlbumInfo(AttrDict):
|
|||
# TYPING: are all of these correct? I've assumed optional strings
|
||||
def __init__(
|
||||
self,
|
||||
tracks: List["TrackInfo"],
|
||||
tracks: List[TrackInfo],
|
||||
album: Optional[str] = None,
|
||||
album_id: Optional[str] = None,
|
||||
artist: Optional[str] = None,
|
||||
|
|
@ -201,7 +204,7 @@ class AlbumInfo(AttrDict):
|
|||
for track in self.tracks:
|
||||
track.decode(codec)
|
||||
|
||||
def copy(self) -> "AlbumInfo":
|
||||
def copy(self) -> AlbumInfo:
|
||||
dupe = AlbumInfo([])
|
||||
dupe.update(self)
|
||||
dupe.tracks = [track.copy() for track in self.tracks]
|
||||
|
|
@ -309,7 +312,7 @@ class TrackInfo(AttrDict):
|
|||
if isinstance(value, bytes):
|
||||
setattr(self, fld, value.decode(codec, "ignore"))
|
||||
|
||||
def copy(self) -> "TrackInfo":
|
||||
def copy(self) -> TrackInfo:
|
||||
dupe = TrackInfo()
|
||||
dupe.update(self)
|
||||
return dupe
|
||||
|
|
@ -545,7 +548,7 @@ class Distance:
|
|||
|
||||
# Adding components.
|
||||
|
||||
def _eq(self, value1: Union[re.Pattern, Any], value2: Any) -> bool:
|
||||
def _eq(self, value1: Union[re.Pattern[str], Any], value2: Any) -> bool:
|
||||
"""Returns True if `value1` is equal to `value2`. `value1` may
|
||||
be a compiled regular expression, in which case it will be
|
||||
matched against `value2`.
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ def distance(
|
|||
if album_info.media:
|
||||
# Preferred media options.
|
||||
patterns = config["match"]["preferred"]["media"].as_str_seq()
|
||||
patterns = cast(Sequence, patterns)
|
||||
patterns = cast(Sequence[str], patterns)
|
||||
options = [re.compile(r"(\d+x)?(%s)" % pat, re.I) for pat in patterns]
|
||||
if options:
|
||||
dist.add_priority("media", album_info.media, options)
|
||||
|
|
@ -276,7 +276,7 @@ def distance(
|
|||
|
||||
# Preferred countries.
|
||||
patterns = config["match"]["preferred"]["countries"].as_str_seq()
|
||||
patterns = cast(Sequence, patterns)
|
||||
patterns = cast(Sequence[str], patterns)
|
||||
options = [re.compile(pat, re.I) for pat in patterns]
|
||||
if album_info.country and options:
|
||||
dist.add_priority("country", album_info.country, options)
|
||||
|
|
@ -440,7 +440,9 @@ def _add_candidate(
|
|||
return
|
||||
|
||||
# Discard matches without required tags.
|
||||
for req_tag in cast(Sequence, config["match"]["required"].as_str_seq()):
|
||||
for req_tag in cast(
|
||||
Sequence[str], config["match"]["required"].as_str_seq()
|
||||
):
|
||||
if getattr(info, req_tag) is None:
|
||||
log.debug("Ignored. Missing required tag: {0}", req_tag)
|
||||
return
|
||||
|
|
@ -469,7 +471,7 @@ def tag_album(
|
|||
items,
|
||||
search_artist: Optional[str] = None,
|
||||
search_album: Optional[str] = None,
|
||||
search_ids: List = [],
|
||||
search_ids: List[str] = [],
|
||||
) -> Tuple[str, str, Proposal]:
|
||||
"""Return a tuple of the current artist name, the current album
|
||||
name, and a `Proposal` containing `AlbumMatch` candidates.
|
||||
|
|
@ -561,7 +563,7 @@ def tag_item(
|
|||
item,
|
||||
search_artist: Optional[str] = None,
|
||||
search_title: Optional[str] = None,
|
||||
search_ids: List = [],
|
||||
search_ids: Optional[List[str]] = None,
|
||||
) -> Proposal:
|
||||
"""Find metadata for a single track. Return a `Proposal` consisting
|
||||
of `TrackMatch` objects.
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ from . import types
|
|||
from .query import (
|
||||
AndQuery,
|
||||
FieldQuery,
|
||||
FieldSort,
|
||||
MatchQuery,
|
||||
NullSort,
|
||||
Query,
|
||||
|
|
@ -303,7 +302,7 @@ class Model(ABC):
|
|||
"""Optional Types for non-fixed (i.e., flexible and computed) fields.
|
||||
"""
|
||||
|
||||
_sorts: Dict[str, Type[FieldSort]] = {}
|
||||
_sorts: Dict[str, Type[Sort]] = {}
|
||||
"""Optional named sort criteria. The keys are strings and the values
|
||||
are subclasses of `Sort`.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ class SubstringQuery(StringFieldQuery[str]):
|
|||
return pattern.lower() in value.lower()
|
||||
|
||||
|
||||
class RegexpQuery(StringFieldQuery[Pattern]):
|
||||
class RegexpQuery(StringFieldQuery[Pattern[str]]):
|
||||
"""A query that matches a regular expression in a specific Model field.
|
||||
|
||||
Raises InvalidQueryError when the pattern is not a valid regular
|
||||
|
|
@ -342,7 +342,7 @@ class BytesQuery(FieldQuery[bytes]):
|
|||
return pattern == value
|
||||
|
||||
|
||||
class NumericQuery(FieldQuery):
|
||||
class NumericQuery(FieldQuery[str]):
|
||||
"""Matches numeric fields. A syntax using Ruby-style range ellipses
|
||||
(``..``) lets users specify one- or two-sided ranges. For example,
|
||||
``year:2001..`` finds music released since the turn of the century.
|
||||
|
|
@ -787,7 +787,7 @@ class DateInterval:
|
|||
return f"[{self.start}, {self.end})"
|
||||
|
||||
|
||||
class DateQuery(FieldQuery):
|
||||
class DateQuery(FieldQuery[str]):
|
||||
"""Matches date fields stored as seconds since Unix epoch time.
|
||||
|
||||
Dates can be specified as ``year-month-day`` strings where only year
|
||||
|
|
@ -797,7 +797,7 @@ class DateQuery(FieldQuery):
|
|||
using an ellipsis interval syntax similar to that of NumericQuery.
|
||||
"""
|
||||
|
||||
def __init__(self, field, pattern, fast: bool = True):
|
||||
def __init__(self, field: str, pattern: str, fast: bool = True):
|
||||
super().__init__(field, pattern, fast)
|
||||
start, end = _parse_periods(pattern)
|
||||
self.interval = DateInterval.from_periods(start, end)
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ class BaseFloat(Type[float, N]):
|
|||
"""
|
||||
|
||||
sql = "REAL"
|
||||
query = NumericQuery
|
||||
query: typing.Type[FieldQuery[Any]] = NumericQuery
|
||||
model_type = float
|
||||
|
||||
def __init__(self, digits: int = 1):
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ log = logging.getLogger("beets")
|
|||
# Library-specific query types.
|
||||
|
||||
|
||||
class SingletonQuery(dbcore.FieldQuery):
|
||||
class SingletonQuery(dbcore.FieldQuery[str]):
|
||||
"""This query is responsible for the 'singleton' lookup.
|
||||
|
||||
It is based on the FieldQuery and constructs a SQL clause
|
||||
|
|
@ -60,14 +60,14 @@ class SingletonQuery(dbcore.FieldQuery):
|
|||
and singleton:false, singleton:0 are handled consistently.
|
||||
"""
|
||||
|
||||
def __new__(cls, field, value, *args, **kwargs):
|
||||
def __new__(cls, field: str, value: str, *args, **kwargs):
|
||||
query = dbcore.query.NoneQuery("album_id")
|
||||
if util.str2bool(value):
|
||||
return query
|
||||
return dbcore.query.NotQuery(query)
|
||||
|
||||
|
||||
class PathQuery(dbcore.FieldQuery):
|
||||
class PathQuery(dbcore.FieldQuery[bytes]):
|
||||
"""A query that matches all items under a given path.
|
||||
|
||||
Matching can either be case-insensitive or case-sensitive. By
|
||||
|
|
@ -185,7 +185,7 @@ class DateType(types.Float):
|
|||
return self.null
|
||||
|
||||
|
||||
class PathType(types.Type):
|
||||
class PathType(types.Type[bytes, bytes]):
|
||||
"""A dbcore type for filesystem paths.
|
||||
|
||||
These are represented as `bytes` objects, in keeping with
|
||||
|
|
@ -384,7 +384,7 @@ class LibModel(dbcore.Model):
|
|||
"""Shared concrete functionality for Items and Albums."""
|
||||
|
||||
# Config key that specifies how an instance should be formatted.
|
||||
_format_config_key = None
|
||||
_format_config_key: str
|
||||
|
||||
def _template_funcs(self):
|
||||
funcs = DefaultTemplateFunctions(self, self._db).functions()
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ from beets.plugins import BeetsPlugin
|
|||
from beets.ui import decargs, print_
|
||||
|
||||
|
||||
class BareascQuery(StringFieldQuery):
|
||||
class BareascQuery(StringFieldQuery[str]):
|
||||
"""Compare items using bare ASCII, without accents etc."""
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import sys
|
|||
import time
|
||||
import traceback
|
||||
from string import Template
|
||||
from typing import List
|
||||
|
||||
from mediafile import MediaFile
|
||||
|
||||
|
|
@ -1059,7 +1060,7 @@ class Command:
|
|||
raise BPDError(ERROR_SYSTEM, "server error", self.name)
|
||||
|
||||
|
||||
class CommandList(list):
|
||||
class CommandList(List[Command]):
|
||||
"""A list of commands issued by the client for processing by the
|
||||
server. May be verbose, in which case the response is delimited, or
|
||||
not. Should be a list of `Command` objects.
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ from beets.dbcore.query import StringFieldQuery
|
|||
from beets.plugins import BeetsPlugin
|
||||
|
||||
|
||||
class FuzzyQuery(StringFieldQuery):
|
||||
class FuzzyQuery(StringFieldQuery[str]):
|
||||
@classmethod
|
||||
def string_match(cls, pattern, val):
|
||||
def string_match(cls, pattern: str, val: str):
|
||||
# smartcase
|
||||
if pattern.islower():
|
||||
val = val.lower()
|
||||
|
|
|
|||
|
|
@ -1440,7 +1440,7 @@ class ReplayGainPlugin(BeetsPlugin):
|
|||
"""Open a `ThreadPool` instance in `self.pool`"""
|
||||
if self.pool is None and self.backend_instance.do_parallel:
|
||||
self.pool = ThreadPool(threads)
|
||||
self.exc_queue: queue.Queue[Exception] = queue.Queue()
|
||||
self.exc_queue: queue.Queue = queue.Queue()
|
||||
|
||||
signal.signal(signal.SIGINT, self._interrupt)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
|
||||
import re
|
||||
from typing import List
|
||||
|
||||
from beets.plugins import BeetsPlugin
|
||||
|
||||
|
|
@ -28,7 +29,7 @@ FORMAT = "{0}, {1}"
|
|||
|
||||
|
||||
class ThePlugin(BeetsPlugin):
|
||||
patterns = []
|
||||
patterns: List[str] = []
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
|
||||
import unittest
|
||||
from typing import Sequence, Tuple
|
||||
|
||||
from beets.autotag.mb import VARIOUS_ARTISTS_ID
|
||||
from beets.test.helper import TestHelper
|
||||
|
|
@ -98,12 +99,17 @@ class AlbumTypesPluginTest(unittest.TestCase, TestHelper):
|
|||
result = subject._atypes(album)
|
||||
self.assertEqual("[EP][Single][OST][Live][Remix]", result)
|
||||
|
||||
def _set_config(self, types: [(str, str)], ignore_va: [str], bracket: str):
|
||||
def _set_config(
|
||||
self,
|
||||
types: Sequence[Tuple[str, str]],
|
||||
ignore_va: Sequence[str],
|
||||
bracket: str,
|
||||
):
|
||||
self.config["albumtypes"]["types"] = types
|
||||
self.config["albumtypes"]["ignore_va"] = ignore_va
|
||||
self.config["albumtypes"]["bracket"] = bracket
|
||||
|
||||
def _create_album(self, album_types: [str], artist_id: str = 0):
|
||||
def _create_album(self, album_types: Sequence[str], artist_id: str = "0"):
|
||||
return self.add_album(
|
||||
albumtypes=album_types, mb_albumartistid=artist_id
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue