Skip to content

Commit b3c42a3

Browse files
authored
Enable ruff's future-annotations and RUF* rules (#6245)
## Summary This PR updates typing and linting across the codebase and enables stricter `ruff` checks for Python 3.10: 1. Enable `tool.ruff.lint.future-annotations` Very handy feature released in `0.13.0`: if required, it _automatically_ adds `from __future__ import annotations` and moves relevant imports under `if TYPE_CHECKING`: ```py # before (runtime import) from beets.library import Library # after from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: from beets.library import Library ``` 2. Set `tool.ruff.target-version = "py310"` This enforced PEP 604 unions in the codebase: ```py # before SQLiteType = Union[str, bytes, float, int, memoryview, None] # after SQLiteType = str | bytes | float | int | memoryview | None ``` 3. Enable `RUF*` family of checks - Remove unused `# noqa`s - Ignore unused unpacked variables ```py # before likelies, consensus = util.get_most_common_tags(self.items) # after likelies, _ = util.get_most_common_tags(self.items) ``` - Avoid list materialization ```py # before for part in parts + [","]: # after for part in [*parts, ","]: ``` - And, most importantly, **RUF012**: use `ClassVar` for mutable class attributes - This underlined our messy `BeetsPlugin.template_*` attributes design, where I have now defined `BeetsPluginMeta` to make a clear distinction between class and instance attributes. @semohr and @asardaes I saw you had a discussion regarding these earlier - we will need to revisit this at some point to sort it out for good. - It also revealed a legitimate issue in `metasync.MetaSource` where `item_types` were initialised as an instance attribute (but luckily never used).
2 parents bd319c2 + c9625f8 commit b3c42a3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+414
-294
lines changed

.git-blame-ignore-revs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,17 @@ d93ddf8dd43e4f9ed072a03829e287c78d2570a2
8181
59c93e70139f70e9fd1c6f3c1bceb005945bec33
8282
# Moved ui.commands._utils into ui.commands.utils
8383
25ae330044abf04045e3f378f72bbaed739fb30d
84-
# Refactor test_ui_command.py into multiple modules
84+
# Refactor test_ui_command.py into multiple modules
8585
a59e41a88365e414db3282658d2aa456e0b3468a
8686
# pyupgrade Python 3.10
8787
301637a1609831947cb5dd90270ed46c24b1ab1b
8888
# Fix changelog formatting
8989
658b184c59388635787b447983ecd3a575f4fe56
90+
# Configure future-annotations
91+
ac7f3d9da95c2d0a32e5c908ea68480518a1582d
92+
# Configure ruff for py310
93+
c46069654628040316dea9db85d01b263db3ba9e
94+
# Enable RUF rules
95+
4749599913a42e02e66b37db9190de11d6be2cdf
96+
# Address RUF012
97+
bc71ec308eb938df1d349f6857634ddf2a82e339

beets/autotag/match.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@
2525
import numpy as np
2626

2727
from beets import config, logging, metadata_plugins, plugins
28-
from beets.autotag import AlbumInfo, AlbumMatch, TrackInfo, TrackMatch, hooks
28+
from beets.autotag import AlbumMatch, TrackMatch, hooks
2929
from beets.util import get_most_common_tags
3030

3131
from .distance import VA_ARTISTS, distance, track_distance
3232

3333
if TYPE_CHECKING:
3434
from collections.abc import Iterable, Sequence
3535

36+
from beets.autotag import AlbumInfo, TrackInfo
3637
from beets.library import Item
3738

3839
# Global logger.

beets/dbcore/db.py

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,10 @@
2626
import time
2727
from abc import ABC
2828
from collections import defaultdict
29-
from collections.abc import (
30-
Callable,
31-
Generator,
32-
Iterable,
33-
Iterator,
34-
Mapping,
35-
Sequence,
36-
)
29+
from collections.abc import Mapping
3730
from functools import cached_property
38-
from sqlite3 import Connection, sqlite_version_info
39-
from typing import TYPE_CHECKING, Any, AnyStr, Generic
31+
from sqlite3 import sqlite_version_info
32+
from typing import TYPE_CHECKING, Any, AnyStr, ClassVar, Generic
4033

4134
from typing_extensions import (
4235
Self,
@@ -48,20 +41,20 @@
4841

4942
from ..util import cached_classproperty, functemplate
5043
from . import types
51-
from .query import (
52-
FieldQueryType,
53-
FieldSort,
54-
MatchQuery,
55-
NullSort,
56-
Query,
57-
Sort,
58-
TrueQuery,
59-
)
44+
from .query import MatchQuery, NullSort, TrueQuery
6045

6146
if TYPE_CHECKING:
47+
from collections.abc import (
48+
Callable,
49+
Generator,
50+
Iterable,
51+
Iterator,
52+
Sequence,
53+
)
54+
from sqlite3 import Connection
6255
from types import TracebackType
6356

64-
from .query import SQLiteType
57+
from .query import FieldQueryType, FieldSort, Query, Sort, SQLiteType
6558

6659
D = TypeVar("D", bound="Database", default=Any)
6760

@@ -306,7 +299,7 @@ class Model(ABC, Generic[D]):
306299
"""The flex field SQLite table name.
307300
"""
308301

309-
_fields: dict[str, types.Type] = {}
302+
_fields: ClassVar[dict[str, types.Type]] = {}
310303
"""A mapping indicating available "fixed" fields on this type. The
311304
keys are field names and the values are `Type` objects.
312305
"""
@@ -321,7 +314,7 @@ def _types(cls) -> dict[str, types.Type]:
321314
"""Optional types for non-fixed (flexible and computed) fields."""
322315
return {}
323316

324-
_sorts: dict[str, type[FieldSort]] = {}
317+
_sorts: ClassVar[dict[str, type[FieldSort]]] = {}
325318
"""Optional named sort criteria. The keys are strings and the values
326319
are subclasses of `Sort`.
327320
"""

beets/dbcore/query.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,19 @@
2020
import re
2121
import unicodedata
2222
from abc import ABC, abstractmethod
23-
from collections.abc import Iterator, MutableSequence, Sequence
23+
from collections.abc import Sequence
2424
from datetime import datetime, timedelta
2525
from functools import cached_property, reduce
2626
from operator import mul, or_
2727
from re import Pattern
28-
from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union
28+
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar
2929

3030
from beets import util
3131
from beets.util.units import raw_seconds_short
3232

3333
if TYPE_CHECKING:
34+
from collections.abc import Iterator, MutableSequence
35+
3436
from beets.dbcore.db import AnyModel, Model
3537

3638
P = TypeVar("P", default=Any)
@@ -122,7 +124,7 @@ def __hash__(self) -> int:
122124
return hash(type(self))
123125

124126

125-
SQLiteType = Union[str, bytes, float, int, memoryview, None]
127+
SQLiteType = str | bytes | float | int | memoryview | None
126128
AnySQLiteType = TypeVar("AnySQLiteType", bound=SQLiteType)
127129
FieldQueryType = type["FieldQuery"]
128130

@@ -689,7 +691,12 @@ class Period:
689691
("%Y-%m-%dT%H:%M", "%Y-%m-%d %H:%M"), # minute
690692
("%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"), # second
691693
)
692-
relative_units = {"y": 365, "m": 30, "w": 7, "d": 1}
694+
relative_units: ClassVar[dict[str, int]] = {
695+
"y": 365,
696+
"m": 30,
697+
"w": 7,
698+
"d": 1,
699+
}
693700
relative_re = "(?P<sign>[+|-]?)(?P<quantity>[0-9]+)(?P<timespan>[y|m|w|d])"
694701

695702
def __init__(self, date: datetime, precision: str):

beets/dbcore/queryparse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ def parse_sorted_query(
250250
# Split up query in to comma-separated subqueries, each representing
251251
# an AndQuery, which need to be joined together in one OrQuery
252252
subquery_parts = []
253-
for part in parts + [","]:
253+
for part in [*parts, ","]:
254254
if part.endswith(","):
255255
# Ensure we can catch "foo, bar" as well as "foo , bar"
256256
last_subquery_part = part[:-1]

beets/dbcore/types.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import time
2121
import typing
2222
from abc import ABC
23-
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast
23+
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar, cast
2424

2525
import beets
2626
from beets import util
@@ -406,7 +406,7 @@ class MusicalKey(String):
406406
The standard format is C, Cm, C#, C#m, etc.
407407
"""
408408

409-
ENHARMONIC = {
409+
ENHARMONIC: ClassVar[dict[str, str]] = {
410410
r"db": "c#",
411411
r"eb": "d#",
412412
r"gb": "f#",

beets/importer/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@
2828
# Note: Stages are not exposed to the public API
2929

3030
__all__ = [
31-
"ImportSession",
32-
"ImportAbortError",
3331
"Action",
34-
"ImportTask",
3532
"ArchiveImportTask",
33+
"ImportAbortError",
34+
"ImportSession",
35+
"ImportTask",
3636
"SentinelImportTask",
3737
"SingletonImportTask",
3838
]

beets/importer/session.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import time
1818
from typing import TYPE_CHECKING
1919

20-
from beets import config, dbcore, library, logging, plugins, util
20+
from beets import config, logging, plugins, util
2121
from beets.importer.tasks import Action
2222
from beets.util import displayable_path, normpath, pipeline, syspath
2323

@@ -27,6 +27,7 @@
2727
if TYPE_CHECKING:
2828
from collections.abc import Sequence
2929

30+
from beets import dbcore, library
3031
from beets.util import PathBytes
3132

3233
from .tasks import ImportTask

beets/importer/stages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,5 +388,5 @@ def _extend_pipeline(tasks, *stages):
388388
else:
389389
task_iter = tasks
390390

391-
ipl = pipeline.Pipeline([task_iter] + list(stages))
391+
ipl = pipeline.Pipeline([task_iter, *list(stages)])
392392
return pipeline.multiple(ipl.pull())

beets/importer/tasks.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import shutil
2121
import time
2222
from collections import defaultdict
23-
from collections.abc import Callable, Iterable, Sequence
23+
from collections.abc import Callable
2424
from enum import Enum
2525
from tempfile import mkdtemp
2626
from typing import TYPE_CHECKING, Any
@@ -33,6 +33,8 @@
3333
from .state import ImportState
3434

3535
if TYPE_CHECKING:
36+
from collections.abc import Iterable, Sequence
37+
3638
from beets.autotag.match import Recommendation
3739

3840
from .session import ImportSession
@@ -232,7 +234,7 @@ def chosen_info(self):
232234
or APPLY (in which case the data comes from the choice).
233235
"""
234236
if self.choice_flag in (Action.ASIS, Action.RETAG):
235-
likelies, consensus = util.get_most_common_tags(self.items)
237+
likelies, _ = util.get_most_common_tags(self.items)
236238
return likelies
237239
elif self.choice_flag is Action.APPLY and self.match:
238240
return self.match.info.copy()
@@ -890,7 +892,7 @@ def extract(self):
890892
# The (0, 0, -1) is added to date_time because the
891893
# function time.mktime expects a 9-element tuple.
892894
# The -1 indicates that the DST flag is unknown.
893-
date_time = time.mktime(f.date_time + (0, 0, -1))
895+
date_time = time.mktime((*f.date_time, 0, 0, -1))
894896
fullpath = os.path.join(extract_to, f.filename)
895897
os.utime(fullpath, (date_time, date_time))
896898

0 commit comments

Comments
 (0)