Address RUF012

This commit is contained in:
Šarūnas Nejus 2025-12-28 03:11:39 +00:00
parent c52656fb0a
commit 1c20e4bd4e
No known key found for this signature in database
28 changed files with 118 additions and 78 deletions

View file

@ -29,7 +29,7 @@ from collections import defaultdict
from collections.abc import Mapping
from functools import cached_property
from sqlite3 import sqlite_version_info
from typing import TYPE_CHECKING, Any, AnyStr, Generic
from typing import TYPE_CHECKING, Any, AnyStr, ClassVar, Generic
from typing_extensions import (
Self,
@ -299,7 +299,7 @@ class Model(ABC, Generic[D]):
"""The flex field SQLite table name.
"""
_fields: dict[str, types.Type] = {}
_fields: ClassVar[dict[str, types.Type]] = {}
"""A mapping indicating available "fixed" fields on this type. The
keys are field names and the values are `Type` objects.
"""
@ -314,7 +314,7 @@ class Model(ABC, Generic[D]):
"""Optional types for non-fixed (flexible and computed) fields."""
return {}
_sorts: dict[str, type[FieldSort]] = {}
_sorts: ClassVar[dict[str, type[FieldSort]]] = {}
"""Optional named sort criteria. The keys are strings and the values
are subclasses of `Sort`.
"""

View file

@ -25,7 +25,7 @@ from datetime import datetime, timedelta
from functools import cached_property, reduce
from operator import mul, or_
from re import Pattern
from typing import TYPE_CHECKING, Any, Generic, TypeVar
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar
from beets import util
from beets.util.units import raw_seconds_short
@ -691,7 +691,12 @@ class Period:
("%Y-%m-%dT%H:%M", "%Y-%m-%d %H:%M"), # minute
("%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"), # second
)
relative_units = {"y": 365, "m": 30, "w": 7, "d": 1}
relative_units: ClassVar[dict[str, int]] = {
"y": 365,
"m": 30,
"w": 7,
"d": 1,
}
relative_re = "(?P<sign>[+|-]?)(?P<quantity>[0-9]+)(?P<timespan>[y|m|w|d])"
def __init__(self, date: datetime, precision: str):

View file

@ -20,7 +20,7 @@ import re
import time
import typing
from abc import ABC
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar, cast
import beets
from beets import util
@ -406,7 +406,7 @@ class MusicalKey(String):
The standard format is C, Cm, C#, C#m, etc.
"""
ENHARMONIC = {
ENHARMONIC: ClassVar[dict[str, str]] = {
r"db": "c#",
r"eb": "d#",
r"gb": "f#",

View file

@ -7,7 +7,7 @@ import time
import unicodedata
from functools import cached_property
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, ClassVar
from mediafile import MediaFile, UnreadableFileError
@ -229,7 +229,7 @@ class Album(LibModel):
_table = "albums"
_flex_table = "album_attributes"
_always_dirty = True
_fields = {
_fields: ClassVar[dict[str, types.Type]] = {
"id": types.PRIMARY_ID,
"artpath": types.NullPathType(),
"added": types.DATE,
@ -281,13 +281,13 @@ class Album(LibModel):
def _types(cls) -> dict[str, types.Type]:
return {**super()._types, "path": types.PathType()}
_sorts = {
_sorts: ClassVar[dict[str, type[dbcore.query.FieldSort]]] = {
"albumartist": dbcore.query.SmartArtistSort,
"artist": dbcore.query.SmartArtistSort,
}
# List of keys that are set on an album's items.
item_keys = [
item_keys: ClassVar[list[str]] = [
"added",
"albumartist",
"albumartists",
@ -624,7 +624,7 @@ class Item(LibModel):
_table = "items"
_flex_table = "item_attributes"
_fields = {
_fields: ClassVar[dict[str, types.Type]] = {
"id": types.PRIMARY_ID,
"path": types.PathType(),
"album_id": types.FOREIGN_ID,
@ -744,7 +744,9 @@ class Item(LibModel):
_formatter = FormattedItemMapping
_sorts = {"artist": dbcore.query.SmartArtistSort}
_sorts: ClassVar[dict[str, type[dbcore.query.FieldSort]]] = {
"artist": dbcore.query.SmartArtistSort
}
@cached_classproperty
def _queries(cls) -> dict[str, FieldQueryType]:

View file

@ -141,7 +141,13 @@ class PluginLogFilter(logging.Filter):
# Managing the plugins themselves.
class BeetsPlugin(metaclass=abc.ABCMeta):
class BeetsPluginMeta(abc.ABCMeta):
template_funcs: ClassVar[TFuncMap[str]] = {}
template_fields: ClassVar[TFuncMap[Item]] = {}
album_template_fields: ClassVar[TFuncMap[Album]] = {}
class BeetsPlugin(metaclass=BeetsPluginMeta):
"""The base class for all beets plugins. Plugins provide
functionality by defining a subclass of BeetsPlugin and overriding
the abstract methods defined here.
@ -151,9 +157,10 @@ class BeetsPlugin(metaclass=abc.ABCMeta):
list
)
listeners: ClassVar[dict[EventType, list[Listener]]] = defaultdict(list)
template_funcs: ClassVar[TFuncMap[str]] | TFuncMap[str] = {} # type: ignore[valid-type]
template_fields: ClassVar[TFuncMap[Item]] | TFuncMap[Item] = {} # type: ignore[valid-type]
album_template_fields: ClassVar[TFuncMap[Album]] | TFuncMap[Album] = {} # type: ignore[valid-type]
template_funcs: TFuncMap[str]
template_fields: TFuncMap[Item]
album_template_fields: TFuncMap[Album]
name: str
config: ConfigView
@ -220,14 +227,10 @@ class BeetsPlugin(metaclass=abc.ABCMeta):
self.name = name or self.__module__.split(".")[-1]
self.config = beets.config[self.name]
# If the class attributes are not set, initialize as instance attributes.
# TODO: Revise with v3.0.0, see also type: ignore[valid-type] above
if not self.template_funcs:
self.template_funcs = {}
if not self.template_fields:
self.template_fields = {}
if not self.album_template_fields:
self.album_template_fields = {}
# create per-instance storage for template fields and functions
self.template_funcs = {}
self.template_fields = {}
self.album_template_fields = {}
self.early_import_stages = []
self.import_stages = []

View file

@ -524,7 +524,7 @@ class ImportHelper(TestHelper):
autotagging library and several assertions for the library.
"""
default_import_config = {
default_import_config: ClassVar[dict[str, bool]] = {
"autotag": True,
"copy": True,
"hardlink": False,
@ -880,7 +880,7 @@ class FetchImageHelper:
def run(self, *args, **kwargs):
super().run(*args, **kwargs)
IMAGEHEADER: dict[str, bytes] = {
IMAGEHEADER: ClassVar[dict[str, bytes]] = {
"image/jpeg": b"\xff\xd8\xff\x00\x00\x00JFIF",
"image/png": b"\211PNG\r\n\032\n",
"image/gif": b"GIF89a",

View file

@ -15,6 +15,7 @@
"""Fetch various AcousticBrainz metadata using MBID."""
from collections import defaultdict
from typing import ClassVar
import requests
@ -55,7 +56,7 @@ ABSCHEME = {
class AcousticPlugin(plugins.BeetsPlugin):
item_types = {
item_types: ClassVar[dict[str, types.Type]] = {
"average_loudness": types.Float(6),
"chords_changes_rate": types.Float(6),
"chords_key": types.STRING,

View file

@ -26,7 +26,7 @@ import sys
import time
import traceback
from string import Template
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, ClassVar
import beets
import beets.ui
@ -1344,7 +1344,7 @@ class Server(BaseServer):
# Searching.
tagtype_map = {
tagtype_map: ClassVar[dict[str, str]] = {
"Artist": "artist",
"ArtistSort": "artist_sort",
"Album": "album",

View file

@ -18,7 +18,7 @@ from __future__ import annotations
import collections
import time
from typing import TYPE_CHECKING, Literal
from typing import TYPE_CHECKING, ClassVar, Literal
import requests
@ -37,7 +37,7 @@ if TYPE_CHECKING:
class DeezerPlugin(SearchApiMetadataSourcePlugin[IDResponse]):
item_types = {
item_types: ClassVar[dict[str, types.Type]] = {
"deezer_track_rank": types.INTEGER,
"deezer_track_id": types.INTEGER,
"deezer_updated": types.DATE,

View file

@ -355,7 +355,7 @@ class ArtSource(RequestMixin, ABC):
# Specify whether this source fetches local or remote images
LOC: ClassVar[SourceLocation]
# A list of methods to match metadata, sorted by descending accuracy
VALID_MATCHING_CRITERIA: list[str] = ["default"]
VALID_MATCHING_CRITERIA: ClassVar[list[str]] = ["default"]
# A human-readable name for the art source
NAME: ClassVar[str]
# The key to select the art source in the config. This value will also be
@ -518,8 +518,8 @@ class RemoteArtSource(ArtSource):
class CoverArtArchive(RemoteArtSource):
NAME = "Cover Art Archive"
ID = "coverart"
VALID_MATCHING_CRITERIA = ["release", "releasegroup"]
VALID_THUMBNAIL_SIZES = [250, 500, 1200]
VALID_MATCHING_CRITERIA: ClassVar[list[str]] = ["release", "releasegroup"]
VALID_THUMBNAIL_SIZES: ClassVar[list[int]] = [250, 500, 1200]
URL = "https://coverartarchive.org/release/{mbid}"
GROUP_URL = "https://coverartarchive.org/release-group/{mbid}"
@ -1128,7 +1128,7 @@ class LastFM(RemoteArtSource):
ID = "lastfm"
# Sizes in priority order.
SIZES = OrderedDict(
SIZES: ClassVar[dict[str, tuple[int, int]]] = OrderedDict(
[
("mega", (300, 300)),
("extralarge", (300, 300)),

View file

@ -26,7 +26,7 @@ from functools import cached_property, partial, total_ordering
from html import unescape
from itertools import groupby
from pathlib import Path
from typing import TYPE_CHECKING, NamedTuple
from typing import TYPE_CHECKING, ClassVar, NamedTuple
from urllib.parse import quote, quote_plus, urlencode, urlparse
import langdetect
@ -367,7 +367,7 @@ class LRCLib(Backend):
class MusiXmatch(Backend):
URL_TEMPLATE = "https://www.musixmatch.com/lyrics/{}/{}"
REPLACEMENTS = {
REPLACEMENTS: ClassVar[dict[str, str]] = {
r"\s+": "-",
"<": "Less_Than",
">": "Greater_Than",
@ -600,7 +600,7 @@ class Google(SearchBackend):
SEARCH_URL = "https://www.googleapis.com/customsearch/v1"
#: Exclude some letras.mus.br pages which do not contain lyrics.
EXCLUDE_PAGES = [
EXCLUDE_PAGES: ClassVar[list[str]] = [
"significado.html",
"traduccion.html",
"traducao.html",
@ -630,9 +630,12 @@ class Google(SearchBackend):
#: Split cleaned up URL title into artist and title parts.
URL_TITLE_PARTS_RE = re.compile(r" +(?:[ :|-]+|par|by) +|, ")
SOURCE_DIST_FACTOR = {"www.azlyrics.com": 0.5, "www.songlyrics.com": 0.6}
SOURCE_DIST_FACTOR: ClassVar[dict[str, float]] = {
"www.azlyrics.com": 0.5,
"www.songlyrics.com": 0.6,
}
ignored_domains: set[str] = set()
ignored_domains: ClassVar[set[str]] = set()
@classmethod
def pre_process_html(cls, html: str) -> str:
@ -937,7 +940,7 @@ class RestFiles:
class LyricsPlugin(LyricsRequestHandler, plugins.BeetsPlugin):
BACKEND_BY_NAME = {
BACKEND_BY_NAME: ClassVar[dict[str, type[Backend]]] = {
b.name: b for b in [LRCLib, Google, Genius, Tekstowo, MusiXmatch]
}

View file

@ -14,14 +14,20 @@
"""Synchronize information from music player libraries"""
from __future__ import annotations
from abc import ABCMeta, abstractmethod
from importlib import import_module
from typing import TYPE_CHECKING, ClassVar
from confuse import ConfigValueError
from beets import ui
from beets.plugins import BeetsPlugin
if TYPE_CHECKING:
from beets.dbcore import types
METASYNC_MODULE = "beetsplug.metasync"
# Dictionary to map the MODULE and the CLASS NAME of meta sources
@ -32,8 +38,9 @@ SOURCES = {
class MetaSource(metaclass=ABCMeta):
item_types: ClassVar[dict[str, types.Type]]
def __init__(self, config, log):
self.item_types = {}
self.config = config
self._log = log

View file

@ -17,6 +17,7 @@
from datetime import datetime
from os.path import basename
from time import mktime
from typing import ClassVar
from xml.sax.saxutils import quoteattr
from beets.dbcore import types
@ -35,7 +36,7 @@ dbus = import_dbus()
class Amarok(MetaSource):
item_types = {
item_types: ClassVar[dict[str, types.Type]] = {
"amarok_rating": types.INTEGER,
"amarok_score": types.FLOAT,
"amarok_uid": types.STRING,

View file

@ -20,6 +20,7 @@ import shutil
import tempfile
from contextlib import contextmanager
from time import mktime
from typing import ClassVar
from urllib.parse import unquote, urlparse
from confuse import ConfigValueError
@ -58,7 +59,7 @@ def _norm_itunes_path(path):
class Itunes(MetaSource):
item_types = {
item_types: ClassVar[dict[str, types.Type]] = {
"itunes_rating": types.INTEGER, # 0..100 scale
"itunes_playcount": types.INTEGER,
"itunes_skipcount": types.INTEGER,

View file

@ -18,7 +18,7 @@
from __future__ import annotations
from collections import defaultdict
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, ClassVar
import requests
@ -96,7 +96,7 @@ def _item(track_info, album_info, album_id):
class MissingPlugin(MusicBrainzAPIMixin, BeetsPlugin):
"""List missing tracks"""
album_types = {
album_types: ClassVar[dict[str, types.Type]] = {
"missing": types.INTEGER,
}

View file

@ -15,6 +15,7 @@
import os
import time
from typing import ClassVar
import mpd
@ -318,7 +319,7 @@ class MPDStats:
class MPDStatsPlugin(plugins.BeetsPlugin):
item_types = {
item_types: ClassVar[dict[str, types.Type]] = {
"play_count": types.INTEGER,
"skip_count": types.INTEGER,
"last_played": types.DATE,

View file

@ -15,7 +15,7 @@ from __future__ import annotations
import os
import tempfile
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, ClassVar
import beets
from beets.dbcore.query import BLOB_TYPE, InQuery
@ -24,6 +24,8 @@ from beets.util import path_as_posix
if TYPE_CHECKING:
from collections.abc import Sequence
from beets.dbcore.query import FieldQueryType
def is_m3u_file(path: str) -> bool:
return Path(path).suffix.lower() in {".m3u", ".m3u8"}
@ -85,7 +87,9 @@ class PlaylistQuery(InQuery[bytes]):
class PlaylistPlugin(beets.plugins.BeetsPlugin):
item_queries = {"playlist": PlaylistQuery}
item_queries: ClassVar[dict[str, FieldQueryType]] = {
"playlist": PlaylistQuery
}
def __init__(self):
super().__init__()

View file

@ -27,7 +27,7 @@ import re
import threading
import time
import webbrowser
from typing import TYPE_CHECKING, Any, Literal
from typing import TYPE_CHECKING, Any, ClassVar, Literal
import confuse
import requests
@ -88,7 +88,7 @@ class AudioFeaturesUnavailableError(Exception):
class SpotifyPlugin(
SearchApiMetadataSourcePlugin[SearchResponseAlbums | SearchResponseTracks]
):
item_types = {
item_types: ClassVar[dict[str, types.Type]] = {
"spotify_track_popularity": types.INTEGER,
"spotify_acousticness": types.FLOAT,
"spotify_danceability": types.FLOAT,
@ -114,7 +114,7 @@ class SpotifyPlugin(
track_url = "https://api.spotify.com/v1/tracks/"
audio_features_url = "https://api.spotify.com/v1/audio-features/"
spotify_audio_features = {
spotify_audio_features: ClassVar[dict[str, str]] = {
"acousticness": "spotify_acousticness",
"danceability": "spotify_danceability",
"energy": "spotify_energy",

View file

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

View file

@ -72,10 +72,10 @@ class ConfDomain(Domain):
name = "conf"
label = "Simple Configuration"
object_types = {"conf": ObjType("conf", "conf")}
directives = {"conf": Conf}
roles = {"conf": XRefRole()}
initial_data: dict[str, Any] = {"objects": {}}
object_types = {"conf": ObjType("conf", "conf")} # noqa: RUF012
directives = {"conf": Conf} # noqa: RUF012
roles = {"conf": XRefRole()} # noqa: RUF012
initial_data: dict[str, Any] = {"objects": {}} # noqa: RUF012
def get_objects(self) -> Iterable[tuple[str, str, str, str, str, int]]:
"""Return an iterable of object tuples for the inventory."""

View file

@ -24,7 +24,7 @@ class LyricsPage(NamedTuple):
artist: str = "The Beatles"
track_title: str = "Lady Madonna"
url_title: str | None = None # only relevant to the Google backend
marks: list[str] = [] # markers for pytest.param
marks: list[str] = [] # markers for pytest.param # noqa: RUF012
def __str__(self) -> str:
"""Return name of this test case."""

View file

@ -22,6 +22,7 @@ import threading
import time
import unittest
from contextlib import contextmanager
from typing import ClassVar
from unittest.mock import MagicMock, patch
import confuse
@ -837,7 +838,7 @@ class BPDQueueTest(BPDTestHelper):
fail=True,
)
METADATA = {"Pos", "Time", "Id", "file", "duration"}
METADATA: ClassVar[set[str]] = {"Pos", "Time", "Id", "file", "duration"}
def test_cmd_add(self):
with self.run_bpd() as client:
@ -1032,7 +1033,7 @@ class BPDConnectionTest(BPDTestHelper):
}
)
ALL_MPD_TAGTYPES = {
ALL_MPD_TAGTYPES: ClassVar[set[str]] = {
"Artist",
"ArtistSort",
"Album",
@ -1057,7 +1058,7 @@ class BPDConnectionTest(BPDTestHelper):
"MUSICBRAINZ_RELEASETRACKID",
"MUSICBRAINZ_WORKID",
}
UNSUPPORTED_TAGTYPES = {
UNSUPPORTED_TAGTYPES: ClassVar[set[str]] = {
"MUSICBRAINZ_WORKID", # not tracked by beets
"Performer", # not tracked by beets
"AlbumSort", # not tracked by beets

View file

@ -13,6 +13,7 @@
# included in all copies or substantial portions of the Software.
import codecs
from typing import ClassVar
from unittest.mock import patch
from beets.dbcore.query import TrueQuery
@ -319,7 +320,7 @@ class EditDuringImporterTestCase(
matching = AutotagStub.GOOD
IGNORED = ["added", "album_id", "id", "mtime", "path"]
IGNORED: ClassVar[list[str]] = ["added", "album_id", "id", "mtime", "path"]
def setUp(self):
super().setUp()

View file

@ -19,7 +19,7 @@ import os
import sys
import unittest
from contextlib import contextmanager
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, ClassVar
from beets import plugins
from beets.test.helper import PluginTestCase, capture_log
@ -70,7 +70,7 @@ class HookLogsTest(HookTestCase):
class HookCommandTest(HookTestCase):
EVENTS: list[plugins.EventType] = ["write", "after_write"]
EVENTS: ClassVar[list[plugins.EventType]] = ["write", "after_write"]
def setUp(self):
super().setUp()

View file

@ -13,6 +13,7 @@
# included in all copies or substantial portions of the Software.
from typing import Any, ClassVar
from unittest.mock import ANY, Mock, call, patch
from beets import util
@ -46,9 +47,8 @@ class MPDStatsTest(PluginTestCase):
assert mpdstats.get_item("/some/non-existing/path") is None
assert "item not found:" in log.info.call_args[0][0]
FAKE_UNKNOWN_STATE = "some-unknown-one"
STATUSES = [
{"state": FAKE_UNKNOWN_STATE},
STATUSES: ClassVar[list[dict[str, Any]]] = [
{"state": "some-unknown-one"},
{"state": "pause"},
{"state": "play", "songid": 1, "time": "0:1"},
{"state": "stop"},

View file

@ -15,6 +15,7 @@
"""Tests for MusicBrainz API wrapper."""
import unittest
from typing import ClassVar
from unittest import mock
import pytest
@ -1017,7 +1018,11 @@ class TestMusicBrainzPlugin(PluginMixin):
plugin = "musicbrainz"
mbid = "d2a6f856-b553-40a0-ac54-a321e8e2da99"
RECORDING = {"title": "foo", "id": "bar", "length": 42}
RECORDING: ClassVar[dict[str, int | str]] = {
"title": "foo",
"id": "bar",
"length": 42,
}
@pytest.fixture
def plugin_config(self):

View file

@ -19,6 +19,7 @@ import shutil
import sqlite3
import unittest
from tempfile import mkstemp
from typing import ClassVar
import pytest
@ -57,13 +58,13 @@ class QueryFixture(dbcore.query.FieldQuery):
class ModelFixture1(LibModel):
_table = "test"
_flex_table = "testflex"
_fields = {
_fields: ClassVar[dict[str, dbcore.types.Type]] = {
"id": dbcore.types.PRIMARY_ID,
"field_one": dbcore.types.INTEGER,
"field_two": dbcore.types.STRING,
}
_sorts = {
_sorts: ClassVar[dict[str, type[dbcore.query.FieldSort]]] = {
"some_sort": SortFixture,
}
@ -92,7 +93,7 @@ class DatabaseFixture1(dbcore.Database):
class ModelFixture2(ModelFixture1):
_fields = {
_fields: ClassVar[dict[str, dbcore.types.Type]] = {
"id": dbcore.types.PRIMARY_ID,
"field_one": dbcore.types.INTEGER,
"field_two": dbcore.types.INTEGER,
@ -104,7 +105,7 @@ class DatabaseFixture2(dbcore.Database):
class ModelFixture3(ModelFixture1):
_fields = {
_fields: ClassVar[dict[str, dbcore.types.Type]] = {
"id": dbcore.types.PRIMARY_ID,
"field_one": dbcore.types.INTEGER,
"field_two": dbcore.types.INTEGER,
@ -117,7 +118,7 @@ class DatabaseFixture3(dbcore.Database):
class ModelFixture4(ModelFixture1):
_fields = {
_fields: ClassVar[dict[str, dbcore.types.Type]] = {
"id": dbcore.types.PRIMARY_ID,
"field_one": dbcore.types.INTEGER,
"field_two": dbcore.types.INTEGER,
@ -133,14 +134,14 @@ class DatabaseFixture4(dbcore.Database):
class AnotherModelFixture(ModelFixture1):
_table = "another"
_flex_table = "anotherflex"
_fields = {
_fields: ClassVar[dict[str, dbcore.types.Type]] = {
"id": dbcore.types.PRIMARY_ID,
"foo": dbcore.types.INTEGER,
}
class ModelFixture5(ModelFixture1):
_fields = {
_fields: ClassVar[dict[str, dbcore.types.Type]] = {
"some_string_field": dbcore.types.STRING,
"some_float_field": dbcore.types.FLOAT,
"some_boolean_field": dbcore.types.BOOLEAN,

View file

@ -19,6 +19,7 @@ import logging
import os
import pkgutil
import sys
from typing import ClassVar
from unittest.mock import ANY, Mock, patch
import pytest
@ -46,7 +47,7 @@ from beets.util import PromptChoice, displayable_path, syspath
class TestPluginRegistration(PluginTestCase):
class RatingPlugin(plugins.BeetsPlugin):
item_types = {
item_types: ClassVar[dict[str, types.Type]] = {
"rating": types.Float(),
"multi_value": types.MULTI_VALUE_DSV,
}
@ -70,7 +71,9 @@ class TestPluginRegistration(PluginTestCase):
def test_duplicate_type(self):
class DuplicateTypePlugin(plugins.BeetsPlugin):
item_types = {"rating": types.INTEGER}
item_types: ClassVar[dict[str, types.Type]] = {
"rating": types.INTEGER
}
self.register_plugin(DuplicateTypePlugin)
with pytest.raises(