Use PEP585 lowercase collections typing annotations

This commit is contained in:
Šarūnas Nejus 2024-11-17 05:57:54 +00:00
parent 7be8f9c97a
commit 51f9dd229e
No known key found for this signature in database
GPG key ID: DD28F6704DBE3435
15 changed files with 168 additions and 198 deletions

View file

@ -21,13 +21,10 @@ from functools import total_ordering
from typing import ( from typing import (
Any, Any,
Callable, Callable,
Dict,
Iterable, Iterable,
Iterator, Iterator,
List,
NamedTuple, NamedTuple,
Optional, Optional,
Tuple,
TypeVar, TypeVar,
Union, Union,
cast, cast,
@ -47,7 +44,7 @@ V = TypeVar("V")
# Classes used to represent candidate options. # Classes used to represent candidate options.
class AttrDict(Dict[str, V]): class AttrDict(dict[str, V]):
"""A dictionary that supports attribute ("dot") access, so `d.field` """A dictionary that supports attribute ("dot") access, so `d.field`
is equivalent to `d['field']`. is equivalent to `d['field']`.
""" """
@ -82,16 +79,16 @@ class AlbumInfo(AttrDict):
# TYPING: are all of these correct? I've assumed optional strings # TYPING: are all of these correct? I've assumed optional strings
def __init__( def __init__(
self, self,
tracks: List[TrackInfo], tracks: list[TrackInfo],
album: Optional[str] = None, album: Optional[str] = None,
album_id: Optional[str] = None, album_id: Optional[str] = None,
artist: Optional[str] = None, artist: Optional[str] = None,
artist_id: Optional[str] = None, artist_id: Optional[str] = None,
artists: Optional[List[str]] = None, artists: Optional[list[str]] = None,
artists_ids: Optional[List[str]] = None, artists_ids: Optional[list[str]] = None,
asin: Optional[str] = None, asin: Optional[str] = None,
albumtype: Optional[str] = None, albumtype: Optional[str] = None,
albumtypes: Optional[List[str]] = None, albumtypes: Optional[list[str]] = None,
va: bool = False, va: bool = False,
year: Optional[int] = None, year: Optional[int] = None,
month: Optional[int] = None, month: Optional[int] = None,
@ -100,7 +97,7 @@ class AlbumInfo(AttrDict):
barcode: Optional[str] = None, barcode: Optional[str] = None,
mediums: Optional[int] = None, mediums: Optional[int] = None,
artist_sort: Optional[str] = None, artist_sort: Optional[str] = None,
artists_sort: Optional[List[str]] = None, artists_sort: Optional[list[str]] = None,
releasegroup_id: Optional[str] = None, releasegroup_id: Optional[str] = None,
release_group_title: Optional[str] = None, release_group_title: Optional[str] = None,
catalognum: Optional[str] = None, catalognum: Optional[str] = None,
@ -114,7 +111,7 @@ class AlbumInfo(AttrDict):
albumdisambig: Optional[str] = None, albumdisambig: Optional[str] = None,
releasegroupdisambig: Optional[str] = None, releasegroupdisambig: Optional[str] = None,
artist_credit: Optional[str] = None, artist_credit: Optional[str] = None,
artists_credit: Optional[List[str]] = None, artists_credit: Optional[list[str]] = None,
original_year: Optional[int] = None, original_year: Optional[int] = None,
original_month: Optional[int] = None, original_month: Optional[int] = None,
original_day: Optional[int] = None, original_day: Optional[int] = None,
@ -195,18 +192,18 @@ class TrackInfo(AttrDict):
release_track_id: Optional[str] = None, release_track_id: Optional[str] = None,
artist: Optional[str] = None, artist: Optional[str] = None,
artist_id: Optional[str] = None, artist_id: Optional[str] = None,
artists: Optional[List[str]] = None, artists: Optional[list[str]] = None,
artists_ids: Optional[List[str]] = None, artists_ids: Optional[list[str]] = None,
length: Optional[float] = None, length: Optional[float] = None,
index: Optional[int] = None, index: Optional[int] = None,
medium: Optional[int] = None, medium: Optional[int] = None,
medium_index: Optional[int] = None, medium_index: Optional[int] = None,
medium_total: Optional[int] = None, medium_total: Optional[int] = None,
artist_sort: Optional[str] = None, artist_sort: Optional[str] = None,
artists_sort: Optional[List[str]] = None, artists_sort: Optional[list[str]] = None,
disctitle: Optional[str] = None, disctitle: Optional[str] = None,
artist_credit: Optional[str] = None, artist_credit: Optional[str] = None,
artists_credit: Optional[List[str]] = None, artists_credit: Optional[list[str]] = None,
data_source: Optional[str] = None, data_source: Optional[str] = None,
data_url: Optional[str] = None, data_url: Optional[str] = None,
media: Optional[str] = None, media: Optional[str] = None,
@ -368,10 +365,10 @@ class Distance:
def __init__(self): def __init__(self):
self._penalties = {} self._penalties = {}
self.tracks: Dict[TrackInfo, Distance] = {} self.tracks: dict[TrackInfo, Distance] = {}
@cached_classproperty @cached_classproperty
def _weights(cls) -> Dict[str, float]: def _weights(cls) -> dict[str, float]:
"""A dictionary from keys to floating-point weights.""" """A dictionary from keys to floating-point weights."""
weights_view = config["match"]["distance_weights"] weights_view = config["match"]["distance_weights"]
weights = {} weights = {}
@ -407,7 +404,7 @@ class Distance:
dist_raw += sum(penalty) * self._weights[key] dist_raw += sum(penalty) * self._weights[key]
return dist_raw return dist_raw
def items(self) -> List[Tuple[str, float]]: def items(self) -> list[tuple[str, float]]:
"""Return a list of (key, dist) pairs, with `dist` being the """Return a list of (key, dist) pairs, with `dist` being the
weighted distance, sorted from highest to lowest. Does not weighted distance, sorted from highest to lowest. Does not
include penalties with a zero value. include penalties with a zero value.
@ -457,13 +454,13 @@ class Distance:
return dist / dist_max return dist / dist_max
return 0.0 return 0.0
def __iter__(self) -> Iterator[Tuple[str, float]]: def __iter__(self) -> Iterator[tuple[str, float]]:
return iter(self.items()) return iter(self.items())
def __len__(self) -> int: def __len__(self) -> int:
return len(self.items()) return len(self.items())
def keys(self) -> List[str]: def keys(self) -> list[str]:
return [key for key, _ in self.items()] return [key for key, _ in self.items()]
def update(self, dist: "Distance"): def update(self, dist: "Distance"):
@ -501,7 +498,7 @@ class Distance:
self, self,
key: str, key: str,
value: Any, value: Any,
options: Union[List[Any], Tuple[Any, ...], Any], options: Union[list[Any], tuple[Any, ...], Any],
): ):
"""Adds a distance penalty of 1.0 if `value` doesn't match any """Adds a distance penalty of 1.0 if `value` doesn't match any
of the values in `options`. If an option is a compiled regular of the values in `options`. If an option is a compiled regular
@ -544,7 +541,7 @@ class Distance:
self, self,
key: str, key: str,
value: Any, value: Any,
options: Union[List[Any], Tuple[Any, ...], Any], options: Union[list[Any], tuple[Any, ...], Any],
): ):
"""Adds a distance penalty that corresponds to the position at """Adds a distance penalty that corresponds to the position at
which `value` appears in `options`. A distance penalty of 0.0 which `value` appears in `options`. A distance penalty of 0.0
@ -593,9 +590,9 @@ class Distance:
class AlbumMatch(NamedTuple): class AlbumMatch(NamedTuple):
distance: Distance distance: Distance
info: AlbumInfo info: AlbumInfo
mapping: Dict[Item, TrackInfo] mapping: dict[Item, TrackInfo]
extra_items: List[Item] extra_items: list[Item]
extra_tracks: List[TrackInfo] extra_tracks: list[TrackInfo]
class TrackMatch(NamedTuple): class TrackMatch(NamedTuple):
@ -666,12 +663,12 @@ def invoke_mb(call_func: Callable, *args):
@plugins.notify_info_yielded("albuminfo_received") @plugins.notify_info_yielded("albuminfo_received")
def album_candidates( def album_candidates(
items: List[Item], items: list[Item],
artist: str, artist: str,
album: str, album: str,
va_likely: bool, va_likely: bool,
extra_tags: Dict, extra_tags: dict,
) -> Iterable[Tuple]: ) -> Iterable[tuple]:
"""Search for album matches. ``items`` is a list of Item objects """Search for album matches. ``items`` is a list of Item objects
that make up the album. ``artist`` and ``album`` are the respective that make up the album. ``artist`` and ``album`` are the respective
names (strings), which may be derived from the item list or may be names (strings), which may be derived from the item list or may be
@ -699,7 +696,7 @@ def album_candidates(
@plugins.notify_info_yielded("trackinfo_received") @plugins.notify_info_yielded("trackinfo_received")
def item_candidates(item: Item, artist: str, title: str) -> Iterable[Tuple]: def item_candidates(item: Item, artist: str, title: str) -> Iterable[tuple]:
"""Search for item matches. ``item`` is the Item to be matched. """Search for item matches. ``item`` is the Item to be matched.
``artist`` and ``title`` are strings and either reflect the item or ``artist`` and ``title`` are strings and either reflect the item or
are specified by the user. are specified by the user.

View file

@ -23,13 +23,10 @@ import re
from enum import IntEnum from enum import IntEnum
from typing import ( from typing import (
Any, Any,
Dict,
Iterable, Iterable,
List,
NamedTuple, NamedTuple,
Optional, Optional,
Sequence, Sequence,
Tuple,
TypeVar, TypeVar,
Union, Union,
cast, cast,
@ -88,7 +85,7 @@ class Proposal(NamedTuple):
def current_metadata( def current_metadata(
items: Iterable[Item], items: Iterable[Item],
) -> Tuple[Dict[str, Any], Dict[str, Any]]: ) -> tuple[dict[str, Any], dict[str, Any]]:
"""Extract the likely current metadata for an album given a list of its """Extract the likely current metadata for an album given a list of its
items. Return two dictionaries: items. Return two dictionaries:
- The most common value for each field. - The most common value for each field.
@ -127,7 +124,7 @@ def current_metadata(
def assign_items( def assign_items(
items: Sequence[Item], items: Sequence[Item],
tracks: Sequence[TrackInfo], tracks: Sequence[TrackInfo],
) -> Tuple[Dict[Item, TrackInfo], List[Item], List[TrackInfo]]: ) -> tuple[dict[Item, TrackInfo], list[Item], list[TrackInfo]]:
"""Given a list of Items and a list of TrackInfo objects, find the """Given a list of Items and a list of TrackInfo objects, find the
best mapping between them. Returns a mapping from Items to TrackInfo best mapping between them. Returns a mapping from Items to TrackInfo
objects, a set of extra Items, and a set of extra TrackInfo objects, a set of extra Items, and a set of extra TrackInfo
@ -135,7 +132,7 @@ def assign_items(
of objects of the two types. of objects of the two types.
""" """
# Construct the cost matrix. # Construct the cost matrix.
costs: List[List[Distance]] = [] costs: list[list[Distance]] = []
for item in items: for item in items:
row = [] row = []
for track in tracks: for track in tracks:
@ -221,7 +218,7 @@ def track_distance(
def distance( def distance(
items: Sequence[Item], items: Sequence[Item],
album_info: AlbumInfo, album_info: AlbumInfo,
mapping: Dict[Item, TrackInfo], mapping: dict[Item, TrackInfo],
) -> Distance: ) -> Distance:
"""Determines how "significant" an album metadata change would be. """Determines how "significant" an album metadata change would be.
Returns a Distance object. `album_info` is an AlbumInfo object Returns a Distance object. `album_info` is an AlbumInfo object
@ -425,7 +422,7 @@ def _sort_candidates(candidates: Iterable[AnyMatch]) -> Sequence[AnyMatch]:
def _add_candidate( def _add_candidate(
items: Sequence[Item], items: Sequence[Item],
results: Dict[Any, AlbumMatch], results: dict[Any, AlbumMatch],
info: AlbumInfo, info: AlbumInfo,
): ):
"""Given a candidate AlbumInfo object, attempt to add the candidate """Given a candidate AlbumInfo object, attempt to add the candidate
@ -479,8 +476,8 @@ def tag_album(
items, items,
search_artist: Optional[str] = None, search_artist: Optional[str] = None,
search_album: Optional[str] = None, search_album: Optional[str] = None,
search_ids: List[str] = [], search_ids: list[str] = [],
) -> Tuple[str, str, Proposal]: ) -> tuple[str, str, Proposal]:
"""Return a tuple of the current artist name, the current album """Return a tuple of the current artist name, the current album
name, and a `Proposal` containing `AlbumMatch` candidates. name, and a `Proposal` containing `AlbumMatch` candidates.
@ -505,7 +502,7 @@ def tag_album(
log.debug("Tagging {0} - {1}", cur_artist, cur_album) log.debug("Tagging {0} - {1}", cur_artist, cur_album)
# The output result, keys are the MB album ID. # The output result, keys are the MB album ID.
candidates: Dict[Any, AlbumMatch] = {} candidates: dict[Any, AlbumMatch] = {}
# Search by explicit ID. # Search by explicit ID.
if search_ids: if search_ids:
@ -571,7 +568,7 @@ def tag_item(
item, item,
search_artist: Optional[str] = None, search_artist: Optional[str] = None,
search_title: Optional[str] = None, search_title: Optional[str] = None,
search_ids: Optional[List[str]] = None, search_ids: Optional[list[str]] = None,
) -> Proposal: ) -> Proposal:
"""Find metadata for a single track. Return a `Proposal` consisting """Find metadata for a single track. Return a `Proposal` consisting
of `TrackMatch` objects. of `TrackMatch` objects.

View file

@ -20,7 +20,7 @@ import re
import traceback import traceback
from collections import Counter from collections import Counter
from itertools import product from itertools import product
from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple, cast from typing import Any, Iterator, Optional, Sequence, cast
from urllib.parse import urljoin from urllib.parse import urljoin
import musicbrainzngs import musicbrainzngs
@ -131,7 +131,7 @@ def configure():
) )
def _preferred_alias(aliases: List): def _preferred_alias(aliases: list):
"""Given an list of alias structures for an artist credit, select """Given an list of alias structures for an artist credit, select
and return the user's preferred alias alias or None if no matching and return the user's preferred alias alias or None if no matching
alias is found. alias is found.
@ -166,7 +166,7 @@ def _preferred_alias(aliases: List):
return matches[0] return matches[0]
def _preferred_release_event(release: Dict[str, Any]) -> Tuple[str, str]: def _preferred_release_event(release: dict[str, Any]) -> tuple[str, str]:
"""Given a release, select and return the user's preferred release """Given a release, select and return the user's preferred release
event as a tuple of (country, release_date). Fall back to the event as a tuple of (country, release_date). Fall back to the
default release event if a preferred event is not found. default release event if a preferred event is not found.
@ -186,8 +186,8 @@ def _preferred_release_event(release: Dict[str, Any]) -> Tuple[str, str]:
def _multi_artist_credit( def _multi_artist_credit(
credit: List[Dict], include_join_phrase: bool credit: list[dict], include_join_phrase: bool
) -> Tuple[List[str], List[str], List[str]]: ) -> tuple[list[str], list[str], list[str]]:
"""Given a list representing an ``artist-credit`` block, accumulate """Given a list representing an ``artist-credit`` block, accumulate
data into a triple of joined artist name lists: canonical, sort, and data into a triple of joined artist name lists: canonical, sort, and
credit. credit.
@ -234,7 +234,7 @@ def _multi_artist_credit(
) )
def _flatten_artist_credit(credit: List[Dict]) -> Tuple[str, str, str]: def _flatten_artist_credit(credit: list[dict]) -> tuple[str, str, str]:
"""Given a list representing an ``artist-credit`` block, flatten the """Given a list representing an ``artist-credit`` block, flatten the
data into a triple of joined artist name strings: canonical, sort, and data into a triple of joined artist name strings: canonical, sort, and
credit. credit.
@ -249,12 +249,12 @@ def _flatten_artist_credit(credit: List[Dict]) -> Tuple[str, str, str]:
) )
def _artist_ids(credit: List[Dict]) -> List[str]: def _artist_ids(credit: list[dict]) -> list[str]:
""" """
Given a list representing an ``artist-credit``, Given a list representing an ``artist-credit``,
return a list of artist IDs return a list of artist IDs
""" """
artist_ids: List[str] = [] artist_ids: list[str] = []
for el in credit: for el in credit:
if isinstance(el, dict): if isinstance(el, dict):
artist_ids.append(el["artist"]["id"]) artist_ids.append(el["artist"]["id"])
@ -276,7 +276,7 @@ def _get_related_artist_names(relations, relation_type):
def track_info( def track_info(
recording: Dict, recording: dict,
index: Optional[int] = None, index: Optional[int] = None,
medium: Optional[int] = None, medium: Optional[int] = None,
medium_index: Optional[int] = None, medium_index: Optional[int] = None,
@ -400,7 +400,7 @@ def _set_date_str(
setattr(info, key, date_num) setattr(info, key, date_num)
def album_info(release: Dict) -> beets.autotag.hooks.AlbumInfo: def album_info(release: dict) -> beets.autotag.hooks.AlbumInfo:
"""Takes a MusicBrainz release result dictionary and returns a beets """Takes a MusicBrainz release result dictionary and returns a beets
AlbumInfo object containing the interesting data about that release. AlbumInfo object containing the interesting data about that release.
""" """
@ -662,7 +662,7 @@ def match_album(
artist: str, artist: str,
album: str, album: str,
tracks: Optional[int] = None, tracks: Optional[int] = None,
extra_tags: Optional[Dict[str, Any]] = None, extra_tags: Optional[dict[str, Any]] = None,
) -> Iterator[beets.autotag.hooks.AlbumInfo]: ) -> Iterator[beets.autotag.hooks.AlbumInfo]:
"""Searches for a single album ("release" in MusicBrainz parlance) """Searches for a single album ("release" in MusicBrainz parlance)
and returns an iterator over AlbumInfo objects. May raise a and returns an iterator over AlbumInfo objects. May raise a
@ -756,8 +756,8 @@ def _is_translation(r):
def _find_actual_release_from_pseudo_release( def _find_actual_release_from_pseudo_release(
pseudo_rel: Dict, pseudo_rel: dict,
) -> Optional[Dict]: ) -> Optional[dict]:
try: try:
relations = pseudo_rel["release"]["release-relation-list"] relations = pseudo_rel["release"]["release-relation-list"]
except KeyError: except KeyError:

View file

@ -30,18 +30,13 @@ from typing import (
Any, Any,
AnyStr, AnyStr,
Callable, Callable,
DefaultDict,
Dict,
Generator, Generator,
Generic, Generic,
Iterable, Iterable,
Iterator, Iterator,
List,
Mapping, Mapping,
Optional, Optional,
Sequence, Sequence,
Tuple,
Type,
TypeVar, TypeVar,
Union, Union,
cast, cast,
@ -161,11 +156,11 @@ class LazyConvertDict:
def __init__(self, model_cls: "Model"): def __init__(self, model_cls: "Model"):
"""Initialize the object empty""" """Initialize the object empty"""
# FIXME: Dict[str, SQLiteType] # FIXME: Dict[str, SQLiteType]
self._data: Dict[str, Any] = {} self._data: dict[str, Any] = {}
self.model_cls = model_cls self.model_cls = model_cls
self._converted: Dict[str, Any] = {} self._converted: dict[str, Any] = {}
def init(self, data: Dict[str, Any]): def init(self, data: dict[str, Any]):
"""Set the base data that should be lazily converted""" """Set the base data that should be lazily converted"""
self._data = data self._data = data
@ -195,7 +190,7 @@ class LazyConvertDict:
if key in self._data: if key in self._data:
del self._data[key] del self._data[key]
def keys(self) -> List[str]: def keys(self) -> list[str]:
"""Get a list of available field names for this object.""" """Get a list of available field names for this object."""
return list(self._converted.keys()) + list(self._data.keys()) return list(self._converted.keys()) + list(self._data.keys())
@ -213,7 +208,7 @@ class LazyConvertDict:
for key, value in values.items(): for key, value in values.items():
self[key] = value self[key] = value
def items(self) -> Iterable[Tuple[str, Any]]: def items(self) -> Iterable[tuple[str, Any]]:
"""Iterate over (key, value) pairs that this object contains. """Iterate over (key, value) pairs that this object contains.
Computed fields are not included. Computed fields are not included.
""" """
@ -286,7 +281,7 @@ class Model(ABC):
"""The flex field SQLite table name. """The flex field SQLite table name.
""" """
_fields: Dict[str, types.Type] = {} _fields: dict[str, types.Type] = {}
"""A mapping indicating available "fixed" fields on this type. The """A mapping indicating available "fixed" fields on this type. The
keys are field names and the values are `Type` objects. keys are field names and the values are `Type` objects.
""" """
@ -296,16 +291,16 @@ class Model(ABC):
terms. terms.
""" """
_types: Dict[str, types.Type] = {} _types: dict[str, types.Type] = {}
"""Optional Types for non-fixed (i.e., flexible and computed) fields. """Optional Types for non-fixed (i.e., flexible and computed) fields.
""" """
_sorts: Dict[str, Type[Sort]] = {} _sorts: dict[str, type[Sort]] = {}
"""Optional named sort criteria. The keys are strings and the values """Optional named sort criteria. The keys are strings and the values
are subclasses of `Sort`. are subclasses of `Sort`.
""" """
_queries: Dict[str, Type[FieldQuery]] = {} _queries: dict[str, type[FieldQuery]] = {}
"""Named queries that use a field-like `name:value` syntax but which """Named queries that use a field-like `name:value` syntax but which
do not relate to any specific field. do not relate to any specific field.
""" """
@ -348,7 +343,7 @@ class Model(ABC):
return cls._relation._fields.keys() - cls.shared_db_fields return cls._relation._fields.keys() - cls.shared_db_fields
@classmethod @classmethod
def _getters(cls: Type["Model"]): def _getters(cls: type["Model"]):
"""Return a mapping from field names to getter functions.""" """Return a mapping from field names to getter functions."""
# We could cache this if it becomes a performance problem to # We could cache this if it becomes a performance problem to
# gather the getter mapping every time. # gather the getter mapping every time.
@ -378,10 +373,10 @@ class Model(ABC):
@classmethod @classmethod
def _awaken( def _awaken(
cls: Type[AnyModel], cls: type[AnyModel],
db: Optional[Database] = None, db: Optional[Database] = None,
fixed_values: Dict[str, Any] = {}, fixed_values: dict[str, Any] = {},
flex_values: Dict[str, Any] = {}, flex_values: dict[str, Any] = {},
) -> AnyModel: ) -> AnyModel:
"""Create an object with values drawn from the database. """Create an object with values drawn from the database.
@ -537,7 +532,7 @@ class Model(ABC):
for key, value in values.items(): for key, value in values.items():
self[key] = value self[key] = value
def items(self) -> Iterator[Tuple[str, Any]]: def items(self) -> Iterator[tuple[str, Any]]:
"""Iterate over (key, value) pairs that this object contains. """Iterate over (key, value) pairs that this object contains.
Computed fields are not included. Computed fields are not included.
""" """
@ -730,16 +725,16 @@ class Model(ABC):
cls, cls,
field, field,
pattern, pattern,
query_cls: Type[FieldQuery] = MatchQuery, query_cls: type[FieldQuery] = MatchQuery,
) -> FieldQuery: ) -> FieldQuery:
"""Get a `FieldQuery` for this model.""" """Get a `FieldQuery` for this model."""
return query_cls(field, pattern, field in cls._fields) return query_cls(field, pattern, field in cls._fields)
@classmethod @classmethod
def all_fields_query( def all_fields_query(
cls: Type["Model"], cls: type["Model"],
pats: Mapping, pats: Mapping,
query_cls: Type[FieldQuery] = MatchQuery, query_cls: type[FieldQuery] = MatchQuery,
): ):
"""Get a query that matches many fields with different patterns. """Get a query that matches many fields with different patterns.
@ -764,8 +759,8 @@ class Results(Generic[AnyModel]):
def __init__( def __init__(
self, self,
model_class: Type[AnyModel], model_class: type[AnyModel],
rows: List[Mapping], rows: list[Mapping],
db: "Database", db: "Database",
flex_rows, flex_rows,
query: Optional[Query] = None, query: Optional[Query] = None,
@ -800,7 +795,7 @@ class Results(Generic[AnyModel]):
# The materialized objects corresponding to rows that have been # The materialized objects corresponding to rows that have been
# consumed. # consumed.
self._objects: List[AnyModel] = [] self._objects: list[AnyModel] = []
def _get_objects(self) -> Iterator[AnyModel]: def _get_objects(self) -> Iterator[AnyModel]:
"""Construct and generate Model objects for they query. The """Construct and generate Model objects for they query. The
@ -852,7 +847,7 @@ class Results(Generic[AnyModel]):
def _get_indexed_flex_attrs(self) -> Mapping: def _get_indexed_flex_attrs(self) -> Mapping:
"""Index flexible attributes by the entity id they belong to""" """Index flexible attributes by the entity id they belong to"""
flex_values: Dict[int, Dict[str, Any]] = {} flex_values: dict[int, dict[str, Any]] = {}
for row in self.flex_rows: for row in self.flex_rows:
if row["entity_id"] not in flex_values: if row["entity_id"] not in flex_values:
flex_values[row["entity_id"]] = {} flex_values[row["entity_id"]] = {}
@ -861,7 +856,7 @@ class Results(Generic[AnyModel]):
return flex_values return flex_values
def _make_model(self, row, flex_values: Dict = {}) -> AnyModel: def _make_model(self, row, flex_values: dict = {}) -> AnyModel:
"""Create a Model object for the given row""" """Create a Model object for the given row"""
cols = dict(row) cols = dict(row)
values = {k: v for (k, v) in cols.items() if not k[:4] == "flex"} values = {k: v for (k, v) in cols.items() if not k[:4] == "flex"}
@ -951,7 +946,7 @@ class Transaction:
def __exit__( def __exit__(
self, self,
exc_type: Type[Exception], exc_type: type[Exception],
exc_value: Exception, exc_value: Exception,
traceback: TracebackType, traceback: TracebackType,
): ):
@ -970,7 +965,7 @@ class Transaction:
self._mutated = False self._mutated = False
self.db._db_lock.release() self.db._db_lock.release()
def query(self, statement: str, subvals: Sequence = ()) -> List: def query(self, statement: str, subvals: Sequence = ()) -> list:
"""Execute an SQL statement with substitution values and return """Execute an SQL statement with substitution values and return
a list of rows from the database. a list of rows from the database.
""" """
@ -1010,7 +1005,7 @@ class Database:
the backend. the backend.
""" """
_models: Sequence[Type[Model]] = () _models: Sequence[type[Model]] = ()
"""The Model subclasses representing tables in this database. """The Model subclasses representing tables in this database.
""" """
@ -1031,9 +1026,9 @@ class Database:
self.path = path self.path = path
self.timeout = timeout self.timeout = timeout
self._connections: Dict[int, sqlite3.Connection] = {} self._connections: dict[int, sqlite3.Connection] = {}
self._tx_stacks: DefaultDict[int, List[Transaction]] = defaultdict(list) self._tx_stacks: defaultdict[int, list[Transaction]] = defaultdict(list)
self._extensions: List[str] = [] self._extensions: list[str] = []
# A lock to protect the _connections and _tx_stacks maps, which # A lock to protect the _connections and _tx_stacks maps, which
# both map thread IDs to private resources. # both map thread IDs to private resources.
@ -1138,7 +1133,7 @@ class Database:
conn.close() conn.close()
@contextlib.contextmanager @contextlib.contextmanager
def _tx_stack(self) -> Generator[List, None, None]: def _tx_stack(self) -> Generator[list, None, None]:
"""A context manager providing access to the current thread's """A context manager providing access to the current thread's
transaction stack. The context manager synchronizes access to transaction stack. The context manager synchronizes access to
the stack map. Transactions should never migrate across threads. the stack map. Transactions should never migrate across threads.
@ -1231,7 +1226,7 @@ class Database:
def _fetch( def _fetch(
self, self,
model_cls: Type[AnyModel], model_cls: type[AnyModel],
query: Optional[Query] = None, query: Optional[Query] = None,
sort: Optional[Sort] = None, sort: Optional[Sort] = None,
) -> Results[AnyModel]: ) -> Results[AnyModel]:
@ -1289,7 +1284,7 @@ class Database:
def _get( def _get(
self, self,
model_cls: Type[AnyModel], model_cls: type[AnyModel],
id, id,
) -> Optional[AnyModel]: ) -> Optional[AnyModel]:
"""Get a Model object by its id or None if the id does not """Get a Model object by its id or None if the id does not

View file

@ -28,14 +28,10 @@ from typing import (
Collection, Collection,
Generic, Generic,
Iterator, Iterator,
List,
MutableSequence, MutableSequence,
Optional, Optional,
Pattern, Pattern,
Sequence, Sequence,
Set,
Tuple,
Type,
TypeVar, TypeVar,
Union, Union,
) )
@ -83,11 +79,11 @@ class Query(ABC):
"""An abstract class representing a query into the database.""" """An abstract class representing a query into the database."""
@property @property
def field_names(self) -> Set[str]: def field_names(self) -> set[str]:
"""Return a set with field names that this query operates on.""" """Return a set with field names that this query operates on."""
return set() return set()
def clause(self) -> Tuple[Optional[str], Sequence[Any]]: def clause(self) -> tuple[Optional[str], Sequence[Any]]:
"""Generate an SQLite expression implementing the query. """Generate an SQLite expression implementing the query.
Return (clause, subvals) where clause is a valid sqlite Return (clause, subvals) where clause is a valid sqlite
@ -141,7 +137,7 @@ class FieldQuery(Query, Generic[P]):
) )
@property @property
def field_names(self) -> Set[str]: def field_names(self) -> set[str]:
"""Return a set with field names that this query operates on.""" """Return a set with field names that this query operates on."""
return {self.field_name} return {self.field_name}
@ -150,10 +146,10 @@ class FieldQuery(Query, Generic[P]):
self.pattern = pattern self.pattern = pattern
self.fast = fast self.fast = fast
def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: def col_clause(self) -> tuple[str, Sequence[SQLiteType]]:
return self.field, () return self.field, ()
def clause(self) -> Tuple[Optional[str], Sequence[SQLiteType]]: def clause(self) -> tuple[Optional[str], Sequence[SQLiteType]]:
if self.fast: if self.fast:
return self.col_clause() return self.col_clause()
else: else:
@ -188,7 +184,7 @@ class FieldQuery(Query, Generic[P]):
class MatchQuery(FieldQuery[AnySQLiteType]): class MatchQuery(FieldQuery[AnySQLiteType]):
"""A query that looks for exact matches in an Model field.""" """A query that looks for exact matches in an Model field."""
def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: def col_clause(self) -> tuple[str, Sequence[SQLiteType]]:
return self.field + " = ?", [self.pattern] return self.field + " = ?", [self.pattern]
@classmethod @classmethod
@ -202,7 +198,7 @@ class NoneQuery(FieldQuery[None]):
def __init__(self, field, fast: bool = True): def __init__(self, field, fast: bool = True):
super().__init__(field, None, fast) super().__init__(field, None, fast)
def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: def col_clause(self) -> tuple[str, Sequence[SQLiteType]]:
return self.field + " IS NULL", () return self.field + " IS NULL", ()
def match(self, obj: Model) -> bool: def match(self, obj: Model) -> bool:
@ -239,7 +235,7 @@ class StringFieldQuery(FieldQuery[P]):
class StringQuery(StringFieldQuery[str]): class StringQuery(StringFieldQuery[str]):
"""A query that matches a whole string in a specific Model field.""" """A query that matches a whole string in a specific Model field."""
def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: def col_clause(self) -> tuple[str, Sequence[SQLiteType]]:
search = ( search = (
self.pattern.replace("\\", "\\\\") self.pattern.replace("\\", "\\\\")
.replace("%", "\\%") .replace("%", "\\%")
@ -257,7 +253,7 @@ class StringQuery(StringFieldQuery[str]):
class SubstringQuery(StringFieldQuery[str]): class SubstringQuery(StringFieldQuery[str]):
"""A query that matches a substring in a specific Model field.""" """A query that matches a substring in a specific Model field."""
def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: def col_clause(self) -> tuple[str, Sequence[SQLiteType]]:
pattern = ( pattern = (
self.pattern.replace("\\", "\\\\") self.pattern.replace("\\", "\\\\")
.replace("%", "\\%") .replace("%", "\\%")
@ -292,7 +288,7 @@ class RegexpQuery(StringFieldQuery[Pattern[str]]):
super().__init__(field_name, pattern_re, fast) super().__init__(field_name, pattern_re, fast)
def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: def col_clause(self) -> tuple[str, Sequence[SQLiteType]]:
return f" regexp({self.field}, ?)", [self.pattern.pattern] return f" regexp({self.field}, ?)", [self.pattern.pattern]
@staticmethod @staticmethod
@ -351,7 +347,7 @@ class BytesQuery(FieldQuery[bytes]):
super().__init__(field_name, bytes_pattern) super().__init__(field_name, bytes_pattern)
def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: def col_clause(self) -> tuple[str, Sequence[SQLiteType]]:
return self.field + " = ?", [self.buf_pattern] return self.field + " = ?", [self.buf_pattern]
@classmethod @classmethod
@ -416,7 +412,7 @@ class NumericQuery(FieldQuery[str]):
return False return False
return True return True
def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: def col_clause(self) -> tuple[str, Sequence[SQLiteType]]:
if self.point is not None: if self.point is not None:
return self.field + "=?", (self.point,) return self.field + "=?", (self.point,)
else: else:
@ -444,7 +440,7 @@ class InQuery(Generic[AnySQLiteType], FieldQuery[Sequence[AnySQLiteType]]):
def subvals(self) -> Sequence[SQLiteType]: def subvals(self) -> Sequence[SQLiteType]:
return self.pattern return self.pattern
def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: def col_clause(self) -> tuple[str, Sequence[SQLiteType]]:
placeholders = ", ".join(["?"] * len(self.subvals)) placeholders = ", ".join(["?"] * len(self.subvals))
return f"{self.field_name} IN ({placeholders})", self.subvals return f"{self.field_name} IN ({placeholders})", self.subvals
@ -461,7 +457,7 @@ class CollectionQuery(Query):
""" """
@property @property
def field_names(self) -> Set[str]: def field_names(self) -> set[str]:
"""Return a set with field names that this query operates on.""" """Return a set with field names that this query operates on."""
return reduce(or_, (sq.field_names for sq in self.subqueries)) return reduce(or_, (sq.field_names for sq in self.subqueries))
@ -485,7 +481,7 @@ class CollectionQuery(Query):
def clause_with_joiner( def clause_with_joiner(
self, self,
joiner: str, joiner: str,
) -> Tuple[Optional[str], Sequence[SQLiteType]]: ) -> tuple[Optional[str], Sequence[SQLiteType]]:
"""Return a clause created by joining together the clauses of """Return a clause created by joining together the clauses of
all subqueries with the string joiner (padded by spaces). all subqueries with the string joiner (padded by spaces).
""" """
@ -521,11 +517,11 @@ class AnyFieldQuery(CollectionQuery):
""" """
@property @property
def field_names(self) -> Set[str]: def field_names(self) -> set[str]:
"""Return a set with field names that this query operates on.""" """Return a set with field names that this query operates on."""
return set(self.fields) return set(self.fields)
def __init__(self, pattern, fields, cls: Type[FieldQuery]): def __init__(self, pattern, fields, cls: type[FieldQuery]):
self.pattern = pattern self.pattern = pattern
self.fields = fields self.fields = fields
self.query_class = cls self.query_class = cls
@ -536,7 +532,7 @@ class AnyFieldQuery(CollectionQuery):
# TYPING ERROR # TYPING ERROR
super().__init__(subqueries) super().__init__(subqueries)
def clause(self) -> Tuple[Optional[str], Sequence[SQLiteType]]: def clause(self) -> tuple[Optional[str], Sequence[SQLiteType]]:
return self.clause_with_joiner("or") return self.clause_with_joiner("or")
def match(self, obj: Model) -> bool: def match(self, obj: Model) -> bool:
@ -575,7 +571,7 @@ class MutableCollectionQuery(CollectionQuery):
class AndQuery(MutableCollectionQuery): class AndQuery(MutableCollectionQuery):
"""A conjunction of a list of other queries.""" """A conjunction of a list of other queries."""
def clause(self) -> Tuple[Optional[str], Sequence[SQLiteType]]: def clause(self) -> tuple[Optional[str], Sequence[SQLiteType]]:
return self.clause_with_joiner("and") return self.clause_with_joiner("and")
def match(self, obj: Model) -> bool: def match(self, obj: Model) -> bool:
@ -585,7 +581,7 @@ class AndQuery(MutableCollectionQuery):
class OrQuery(MutableCollectionQuery): class OrQuery(MutableCollectionQuery):
"""A conjunction of a list of other queries.""" """A conjunction of a list of other queries."""
def clause(self) -> Tuple[Optional[str], Sequence[SQLiteType]]: def clause(self) -> tuple[Optional[str], Sequence[SQLiteType]]:
return self.clause_with_joiner("or") return self.clause_with_joiner("or")
def match(self, obj: Model) -> bool: def match(self, obj: Model) -> bool:
@ -598,14 +594,14 @@ class NotQuery(Query):
""" """
@property @property
def field_names(self) -> Set[str]: def field_names(self) -> set[str]:
"""Return a set with field names that this query operates on.""" """Return a set with field names that this query operates on."""
return self.subquery.field_names return self.subquery.field_names
def __init__(self, subquery): def __init__(self, subquery):
self.subquery = subquery self.subquery = subquery
def clause(self) -> Tuple[Optional[str], Sequence[SQLiteType]]: def clause(self) -> tuple[Optional[str], Sequence[SQLiteType]]:
clause, subvals = self.subquery.clause() clause, subvals = self.subquery.clause()
if clause: if clause:
return f"not ({clause})", subvals return f"not ({clause})", subvals
@ -630,7 +626,7 @@ class NotQuery(Query):
class TrueQuery(Query): class TrueQuery(Query):
"""A query that always matches.""" """A query that always matches."""
def clause(self) -> Tuple[str, Sequence[SQLiteType]]: def clause(self) -> tuple[str, Sequence[SQLiteType]]:
return "1", () return "1", ()
def match(self, obj: Model) -> bool: def match(self, obj: Model) -> bool:
@ -640,7 +636,7 @@ class TrueQuery(Query):
class FalseQuery(Query): class FalseQuery(Query):
"""A query that never matches.""" """A query that never matches."""
def clause(self) -> Tuple[str, Sequence[SQLiteType]]: def clause(self) -> tuple[str, Sequence[SQLiteType]]:
return "0", () return "0", ()
def match(self, obj: Model) -> bool: def match(self, obj: Model) -> bool:
@ -650,7 +646,7 @@ class FalseQuery(Query):
# Time/date queries. # Time/date queries.
def _parse_periods(pattern: str) -> Tuple[Optional[Period], Optional[Period]]: def _parse_periods(pattern: str) -> tuple[Optional[Period], Optional[Period]]:
"""Parse a string containing two dates separated by two dots (..). """Parse a string containing two dates separated by two dots (..).
Return a pair of `Period` objects. Return a pair of `Period` objects.
""" """
@ -696,7 +692,7 @@ class Period:
self.precision = precision self.precision = precision
@classmethod @classmethod
def parse(cls: Type["Period"], string: str) -> Optional["Period"]: def parse(cls: type["Period"], string: str) -> Optional["Period"]:
"""Parse a date and return a `Period` object or `None` if the """Parse a date and return a `Period` object or `None` if the
string is empty, or raise an InvalidQueryArgumentValueError if string is empty, or raise an InvalidQueryArgumentValueError if
the string cannot be parsed to a date. the string cannot be parsed to a date.
@ -715,7 +711,7 @@ class Period:
def find_date_and_format( def find_date_and_format(
string: str, string: str,
) -> Union[Tuple[None, None], Tuple[datetime, int]]: ) -> Union[tuple[None, None], tuple[datetime, int]]:
for ord, format in enumerate(cls.date_formats): for ord, format in enumerate(cls.date_formats):
for format_option in format: for format_option in format:
try: try:
@ -843,7 +839,7 @@ class DateQuery(FieldQuery[str]):
_clause_tmpl = "{0} {1} ?" _clause_tmpl = "{0} {1} ?"
def col_clause(self) -> Tuple[str, Sequence[SQLiteType]]: def col_clause(self) -> tuple[str, Sequence[SQLiteType]]:
clause_parts = [] clause_parts = []
subvals = [] subvals = []
@ -908,7 +904,7 @@ class Sort:
""" """
return None return None
def sort(self, items: List) -> List: def sort(self, items: list) -> list:
"""Sort the list of objects and return a list.""" """Sort the list of objects and return a list."""
return sorted(items) return sorted(items)
@ -931,7 +927,7 @@ class Sort:
class MultipleSort(Sort): class MultipleSort(Sort):
"""Sort that encapsulates multiple sub-sorts.""" """Sort that encapsulates multiple sub-sorts."""
def __init__(self, sorts: Optional[List[Sort]] = None): def __init__(self, sorts: Optional[list[Sort]] = None):
self.sorts = sorts or [] self.sorts = sorts or []
def add_sort(self, sort: Sort): def add_sort(self, sort: Sort):
@ -1061,7 +1057,7 @@ class SlowFieldSort(FieldSort):
class NullSort(Sort): class NullSort(Sort):
"""No sorting. Leave results unsorted.""" """No sorting. Leave results unsorted."""
def sort(self, items: List) -> List: def sort(self, items: list) -> list:
return items return items
def __nonzero__(self) -> bool: def __nonzero__(self) -> bool:

View file

@ -16,7 +16,7 @@
import itertools import itertools
import re import re
from typing import Collection, Dict, List, Optional, Sequence, Tuple, Type from typing import Collection, Optional, Sequence
from . import Model, query from . import Model, query
from .query import Sort from .query import Sort
@ -35,10 +35,10 @@ PARSE_QUERY_PART_REGEX = re.compile(
def parse_query_part( def parse_query_part(
part: str, part: str,
query_classes: Dict[str, Type[query.FieldQuery]] = {}, query_classes: dict[str, type[query.FieldQuery]] = {},
prefixes: Dict = {}, prefixes: dict = {},
default_class: Type[query.SubstringQuery] = query.SubstringQuery, default_class: type[query.SubstringQuery] = query.SubstringQuery,
) -> Tuple[Optional[str], str, Type[query.FieldQuery], bool]: ) -> tuple[Optional[str], str, type[query.FieldQuery], bool]:
"""Parse a single *query part*, which is a chunk of a complete query """Parse a single *query part*, which is a chunk of a complete query
string representing a single criterion. string representing a single criterion.
@ -104,8 +104,8 @@ def parse_query_part(
def construct_query_part( def construct_query_part(
model_cls: Type[Model], model_cls: type[Model],
prefixes: Dict, prefixes: dict,
query_part: str, query_part: str,
) -> query.Query: ) -> query.Query:
"""Parse a *query part* string and return a :class:`Query` object. """Parse a *query part* string and return a :class:`Query` object.
@ -127,7 +127,7 @@ def construct_query_part(
# Use `model_cls` to build up a map from field (or query) names to # Use `model_cls` to build up a map from field (or query) names to
# `Query` classes. # `Query` classes.
query_classes: Dict[str, Type[query.FieldQuery]] = {} query_classes: dict[str, type[query.FieldQuery]] = {}
for k, t in itertools.chain( for k, t in itertools.chain(
model_cls._fields.items(), model_cls._types.items() model_cls._fields.items(), model_cls._types.items()
): ):
@ -171,9 +171,9 @@ def construct_query_part(
# TYPING ERROR # TYPING ERROR
def query_from_strings( def query_from_strings(
query_cls: Type[query.CollectionQuery], query_cls: type[query.CollectionQuery],
model_cls: Type[Model], model_cls: type[Model],
prefixes: Dict, prefixes: dict,
query_parts: Collection[str], query_parts: Collection[str],
) -> query.Query: ) -> query.Query:
"""Creates a collection query of type `query_cls` from a list of """Creates a collection query of type `query_cls` from a list of
@ -189,7 +189,7 @@ def query_from_strings(
def construct_sort_part( def construct_sort_part(
model_cls: Type[Model], model_cls: type[Model],
part: str, part: str,
case_insensitive: bool = True, case_insensitive: bool = True,
) -> Sort: ) -> Sort:
@ -220,7 +220,7 @@ def construct_sort_part(
def sort_from_strings( def sort_from_strings(
model_cls: Type[Model], model_cls: type[Model],
sort_parts: Sequence[str], sort_parts: Sequence[str],
case_insensitive: bool = True, case_insensitive: bool = True,
) -> Sort: ) -> Sort:
@ -239,11 +239,11 @@ def sort_from_strings(
def parse_sorted_query( def parse_sorted_query(
model_cls: Type[Model], model_cls: type[Model],
parts: List[str], parts: list[str],
prefixes: Dict = {}, prefixes: dict = {},
case_insensitive: bool = True, case_insensitive: bool = True,
) -> Tuple[query.Query, Sort]: ) -> tuple[query.Query, Sort]:
"""Given a list of strings, create the `Query` and `Sort` that they """Given a list of strings, create the `Query` and `Sort` that they
represent. represent.
""" """

View file

@ -16,7 +16,7 @@
import typing import typing
from abc import ABC from abc import ABC
from typing import Any, Generic, List, TypeVar, Union, cast from typing import Any, Generic, TypeVar, Union, cast
from beets.util import str2bool from beets.util import str2bool
@ -49,11 +49,11 @@ class Type(ABC, Generic[T, N]):
"""The SQLite column type for the value. """The SQLite column type for the value.
""" """
query: typing.Type[FieldQuery] = SubstringQuery query: type[FieldQuery] = SubstringQuery
"""The `Query` subclass to be used when querying the field. """The `Query` subclass to be used when querying the field.
""" """
model_type: typing.Type[T] model_type: type[T]
"""The Python type that is used to represent the value in the model. """The Python type that is used to represent the value in the model.
The model is guaranteed to return a value of this type if the field The model is guaranteed to return a value of this type if the field
@ -232,7 +232,7 @@ class BaseFloat(Type[float, N]):
""" """
sql = "REAL" sql = "REAL"
query: typing.Type[FieldQuery[Any]] = NumericQuery query: type[FieldQuery[Any]] = NumericQuery
model_type = float model_type = float
def __init__(self, digits: int = 1): def __init__(self, digits: int = 1):
@ -277,7 +277,7 @@ class String(BaseString[str, Any]):
model_type = str model_type = str
class DelimitedString(BaseString[List[str], List[str]]): class DelimitedString(BaseString[list[str], list[str]]):
"""A list of Unicode strings, represented in-database by a single string """A list of Unicode strings, represented in-database by a single string
containing delimiter-separated values. containing delimiter-separated values.
""" """
@ -287,7 +287,7 @@ class DelimitedString(BaseString[List[str], List[str]]):
def __init__(self, delimiter: str): def __init__(self, delimiter: str):
self.delimiter = delimiter self.delimiter = delimiter
def format(self, value: List[str]): def format(self, value: list[str]):
return self.delimiter.join(value) return self.delimiter.join(value)
def parse(self, string: str): def parse(self, string: str):
@ -295,7 +295,7 @@ class DelimitedString(BaseString[List[str], List[str]]):
return [] return []
return string.split(self.delimiter) return string.split(self.delimiter)
def to_sql(self, model_value: List[str]): def to_sql(self, model_value: list[str]):
return self.delimiter.join(model_value) return self.delimiter.join(model_value)

View file

@ -27,7 +27,7 @@ import sys
import textwrap import textwrap
import traceback import traceback
from difflib import SequenceMatcher from difflib import SequenceMatcher
from typing import Any, Callable, List from typing import Any, Callable
import confuse import confuse
@ -1450,7 +1450,7 @@ class Subcommand:
invoked by a SubcommandOptionParser. invoked by a SubcommandOptionParser.
""" """
func: Callable[[library.Library, optparse.Values, List[str]], Any] 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):
"""Creates a new subcommand. name is the primary way to invoke """Creates a new subcommand. name is the primary way to invoke

View file

@ -16,14 +16,14 @@
libraries. libraries.
""" """
from typing import Any, Dict, NamedTuple from typing import Any, NamedTuple
from beets import util from beets import util
class Node(NamedTuple): class Node(NamedTuple):
files: Dict[str, Any] files: dict[str, Any]
dirs: Dict[str, Any] dirs: dict[str, Any]
def _insert(node, path, itemid): def _insert(node, path, itemid):

View file

@ -19,7 +19,7 @@ import re
import sys import sys
from dataclasses import dataclass from dataclasses import dataclass
from mimetypes import guess_type from mimetypes import guess_type
from typing import ClassVar, Mapping, Type from typing import ClassVar, Mapping
from flask import ( from flask import (
Blueprint, Blueprint,
@ -127,7 +127,7 @@ ARTIST_ATTR_MAP = {
class AURADocument: class AURADocument:
"""Base class for building AURA documents.""" """Base class for building AURA documents."""
model_cls: ClassVar[Type[LibModel]] model_cls: ClassVar[type[LibModel]]
lib: Library lib: Library
args: Mapping[str, str] args: Mapping[str, str]
@ -153,7 +153,7 @@ class AURADocument:
return make_response(document, status) return make_response(document, status)
@classmethod @classmethod
def get_attribute_converter(cls, beets_attr: str) -> Type[SQLiteType]: def get_attribute_converter(cls, beets_attr: str) -> type[SQLiteType]:
"""Work out what data type an attribute should be for beets. """Work out what data type an attribute should be for beets.
Args: Args:
@ -374,7 +374,7 @@ class TrackDocument(AURADocument):
return self.lib.items(query, sort) return self.lib.items(query, sort)
@classmethod @classmethod
def get_attribute_converter(cls, beets_attr: str) -> Type[SQLiteType]: def get_attribute_converter(cls, beets_attr: str) -> type[SQLiteType]:
"""Work out what data type an attribute should be for beets. """Work out what data type an attribute should be for beets.
Args: Args:

View file

@ -26,7 +26,6 @@ import sys
import time import time
import traceback import traceback
from string import Template from string import Template
from typing import List
from mediafile import MediaFile from mediafile import MediaFile
@ -1059,7 +1058,7 @@ class Command:
raise BPDError(ERROR_SYSTEM, "server error", self.name) raise BPDError(ERROR_SYSTEM, "server error", self.name)
class CommandList(List[Command]): class CommandList(list[Command]):
"""A list of commands issued by the client for processing by the """A list of commands issued by the client for processing by the
server. May be verbose, in which case the response is delimited, or server. May be verbose, in which case the response is delimited, or
not. Should be a list of `Command` objects. not. Should be a list of `Command` objects.

View file

@ -28,20 +28,7 @@ from dataclasses import dataclass
from logging import Logger from logging import Logger
from multiprocessing.pool import ThreadPool from multiprocessing.pool import ThreadPool
from threading import Event, Thread from threading import Event, Thread
from typing import ( from typing import Any, Callable, Optional, Sequence, TypeVar, Union, cast
Any,
Callable,
DefaultDict,
Dict,
List,
Optional,
Sequence,
Tuple,
Type,
TypeVar,
Union,
cast,
)
from confuse import ConfigView from confuse import ConfigView
@ -69,7 +56,7 @@ class FatalGstreamerPluginReplayGainError(FatalReplayGainError):
loading the required plugins.""" loading the required plugins."""
def call(args: List[Any], log: Logger, **kwargs: Any): def call(args: list[Any], log: Logger, **kwargs: Any):
"""Execute the command and return its output or raise a """Execute the command and return its output or raise a
ReplayGainError on failure. ReplayGainError on failure.
""" """
@ -147,7 +134,7 @@ class RgTask:
self.backend_name = backend_name self.backend_name = backend_name
self._log = log self._log = log
self.album_gain: Optional[Gain] = None self.album_gain: Optional[Gain] = None
self.track_gains: Optional[List[Gain]] = None self.track_gains: Optional[list[Gain]] = None
def _store_track_gain(self, item: Item, track_gain: Gain): def _store_track_gain(self, item: Item, track_gain: Gain):
"""Store track gain for a single item in the database.""" """Store track gain for a single item in the database."""
@ -348,7 +335,7 @@ class FfmpegBackend(Backend):
# analyse tracks # analyse tracks
# Gives a list of tuples (track_gain, track_n_blocks) # Gives a list of tuples (track_gain, track_n_blocks)
track_results: List[Tuple[Gain, int]] = [ track_results: list[tuple[Gain, int]] = [
self._analyse_item( self._analyse_item(
item, item,
task.target_level, task.target_level,
@ -358,7 +345,7 @@ class FfmpegBackend(Backend):
for item in task.items for item in task.items
] ]
track_gains: List[Gain] = [tg for tg, _nb in track_results] track_gains: list[Gain] = [tg for tg, _nb in track_results]
# Album peak is maximum track peak # Album peak is maximum track peak
album_peak = max(tg.peak for tg in track_gains) album_peak = max(tg.peak for tg in track_gains)
@ -410,7 +397,7 @@ class FfmpegBackend(Backend):
def _construct_cmd( def _construct_cmd(
self, item: Item, peak_method: Optional[PeakMethod] self, item: Item, peak_method: Optional[PeakMethod]
) -> List[Union[str, bytes]]: ) -> list[Union[str, bytes]]:
"""Construct the shell command to analyse items.""" """Construct the shell command to analyse items."""
return [ return [
self._ffmpeg_path, self._ffmpeg_path,
@ -435,7 +422,7 @@ class FfmpegBackend(Backend):
target_level: float, target_level: float,
peak_method: Optional[PeakMethod], peak_method: Optional[PeakMethod],
count_blocks: bool = True, count_blocks: bool = True,
) -> Tuple[Gain, int]: ) -> tuple[Gain, int]:
"""Analyse item. Return a pair of a Gain object and the number """Analyse item. Return a pair of a Gain object and the number
of gating blocks above the threshold. of gating blocks above the threshold.
@ -647,7 +634,7 @@ class CommandBackend(Backend):
items: Sequence[Item], items: Sequence[Item],
target_level: float, target_level: float,
is_album: bool, is_album: bool,
) -> List[Gain]: ) -> list[Gain]:
"""Computes the track or album gain of a list of items, returns """Computes the track or album gain of a list of items, returns
a list of TrackGain objects. a list of TrackGain objects.
@ -667,7 +654,7 @@ class CommandBackend(Backend):
# tag-writing; this turns the mp3gain/aacgain tool into a gain # tag-writing; this turns the mp3gain/aacgain tool into a gain
# calculator rather than a tag manipulator because we take care # calculator rather than a tag manipulator because we take care
# of changing tags ourselves. # of changing tags ourselves.
cmd: List[Union[bytes, str]] = [self.command, "-o", "-s", "s"] cmd: list[Union[bytes, str]] = [self.command, "-o", "-s", "s"]
if self.noclip: if self.noclip:
# Adjust to avoid clipping. # Adjust to avoid clipping.
cmd = cmd + ["-k"] cmd = cmd + ["-k"]
@ -685,7 +672,7 @@ class CommandBackend(Backend):
output, len(items) + (1 if is_album else 0) output, len(items) + (1 if is_album else 0)
) )
def parse_tool_output(self, text: bytes, num_lines: int) -> List[Gain]: def parse_tool_output(self, text: bytes, num_lines: int) -> list[Gain]:
"""Given the tab-delimited output from an invocation of mp3gain """Given the tab-delimited output from an invocation of mp3gain
or aacgain, parse the text and return a list of dictionaries or aacgain, parse the text and return a list of dictionaries
containing information about each analyzed file. containing information about each analyzed file.
@ -771,7 +758,7 @@ class GStreamerBackend(Backend):
self._main_loop = self.GLib.MainLoop() self._main_loop = self.GLib.MainLoop()
self._files: List[bytes] = [] self._files: list[bytes] = []
def _import_gst(self): def _import_gst(self):
"""Import the necessary GObject-related modules and assign `Gst` """Import the necessary GObject-related modules and assign `Gst`
@ -811,7 +798,7 @@ class GStreamerBackend(Backend):
self._files = [i.path for i in items] self._files = [i.path for i in items]
# FIXME: Turn this into DefaultDict[bytes, Gain] # FIXME: Turn this into DefaultDict[bytes, Gain]
self._file_tags: DefaultDict[bytes, Dict[str, float]] = ( self._file_tags: collections.defaultdict[bytes, dict[str, float]] = (
collections.defaultdict(dict) collections.defaultdict(dict)
) )
@ -1199,13 +1186,13 @@ class ExceptionWatcher(Thread):
# Main plugin logic. # Main plugin logic.
BACKEND_CLASSES: List[Type[Backend]] = [ BACKEND_CLASSES: list[type[Backend]] = [
CommandBackend, CommandBackend,
GStreamerBackend, GStreamerBackend,
AudioToolsBackend, AudioToolsBackend,
FfmpegBackend, FfmpegBackend,
] ]
BACKENDS: Dict[str, Type[Backend]] = {b.NAME: b for b in BACKEND_CLASSES} BACKENDS: dict[str, type[Backend]] = {b.NAME: b for b in BACKEND_CLASSES}
class ReplayGainPlugin(BeetsPlugin): class ReplayGainPlugin(BeetsPlugin):
@ -1375,7 +1362,7 @@ class ReplayGainPlugin(BeetsPlugin):
self._log.info("analyzing {0}", album) self._log.info("analyzing {0}", album)
discs: Dict[int, List[Item]] = {} discs: dict[int, list[Item]] = {}
if self.config["per_disc"].get(bool): if self.config["per_disc"].get(bool):
for item in album.items(): for item in album.items():
if discs.get(item.disc) is None: if discs.get(item.disc) is None:
@ -1447,8 +1434,8 @@ class ReplayGainPlugin(BeetsPlugin):
def _apply( def _apply(
self, self,
func: Callable[..., AnyRgTask], func: Callable[..., AnyRgTask],
args: List[Any], args: list[Any],
kwds: Dict[str, Any], kwds: dict[str, Any],
callback: Callable[[AnyRgTask], Any], callback: Callable[[AnyRgTask], Any],
): ):
if self.pool is not None: if self.pool is not None:
@ -1525,7 +1512,7 @@ class ReplayGainPlugin(BeetsPlugin):
self, self,
lib: Library, lib: Library,
opts: optparse.Values, opts: optparse.Values,
args: List[str], args: list[str],
): ):
try: try:
write = ui.should_write(opts.write) write = ui.should_write(opts.write)
@ -1562,7 +1549,7 @@ class ReplayGainPlugin(BeetsPlugin):
# Silence interrupt exceptions # Silence interrupt exceptions
pass pass
def commands(self) -> List[ui.Subcommand]: def commands(self) -> list[ui.Subcommand]:
"""Return the "replaygain" ui subcommand.""" """Return the "replaygain" ui subcommand."""
cmd = ui.Subcommand("replaygain", help="analyze for ReplayGain") cmd = ui.Subcommand("replaygain", help="analyze for ReplayGain")
cmd.parser.add_album_option() cmd.parser.add_album_option()

View file

@ -15,7 +15,6 @@
"""Moves patterns in path formats (suitable for moving articles).""" """Moves patterns in path formats (suitable for moving articles)."""
import re import re
from typing import List
from beets.plugins import BeetsPlugin from beets.plugins import BeetsPlugin
@ -28,7 +27,7 @@ FORMAT = "{0}, {1}"
class ThePlugin(BeetsPlugin): class ThePlugin(BeetsPlugin):
patterns: List[str] = [] patterns: list[str] = []
def __init__(self): def __init__(self):
super().__init__() super().__init__()

View file

@ -14,7 +14,7 @@
"""Tests for the 'albumtypes' plugin.""" """Tests for the 'albumtypes' plugin."""
from typing import Sequence, Tuple from typing import Sequence
from beets.autotag.mb import VARIOUS_ARTISTS_ID from beets.autotag.mb import VARIOUS_ARTISTS_ID
from beets.test.helper import PluginTestCase from beets.test.helper import PluginTestCase
@ -91,7 +91,7 @@ class AlbumTypesPluginTest(PluginTestCase):
def _set_config( def _set_config(
self, self,
types: Sequence[Tuple[str, str]], types: Sequence[tuple[str, str]],
ignore_va: Sequence[str], ignore_va: Sequence[str],
bracket: str, bracket: str,
): ):

View file

@ -1,7 +1,7 @@
import os import os
from http import HTTPStatus from http import HTTPStatus
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Optional from typing import Any, Optional
import pytest import pytest
from flask.testing import Client from flask.testing import Client
@ -59,8 +59,8 @@ class TestAuraResponse:
"""Return a callback accepting `endpoint` and `params` parameters.""" """Return a callback accepting `endpoint` and `params` parameters."""
def get( def get(
endpoint: str, params: Dict[str, str] endpoint: str, params: dict[str, str]
) -> Optional[Dict[str, Any]]: ) -> Optional[dict[str, Any]]:
"""Add additional `params` and GET the given endpoint. """Add additional `params` and GET the given endpoint.
`include` parameter is added to every call to check that the `include` parameter is added to every call to check that the