diff --git a/beets/util/__init__.py b/beets/util/__init__.py index ea08bb65d..e6da8e96a 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -62,6 +62,7 @@ if TYPE_CHECKING: MAX_FILENAME_LENGTH = 200 WINDOWS_MAGIC_PREFIX = "\\\\?\\" T = TypeVar("T") +T2 = TypeVar("T2") StrPath = str | Path PathLike = StrPath | bytes Replacements = Sequence[tuple[Pattern[str], str]] @@ -1060,7 +1061,7 @@ def par_map(transform: Callable[[T], Any], items: Sequence[T]) -> None: pool.join() -class cached_classproperty(Generic[T]): +class cached_classproperty(Generic[T, T2]): """Descriptor implementing cached class properties. Provides class-level dynamic property behavior where the getter function is @@ -1068,7 +1069,7 @@ class cached_classproperty(Generic[T]): instance properties, this operates on the class rather than instances. """ - cache: ClassVar[dict[tuple[type[object], str], object]] = {} + _cache: ClassVar[dict[tuple[type[object], str], object]] = {} name: str = "" @@ -1086,21 +1087,32 @@ class cached_classproperty(Generic[T]): # "Callable[[Album], ...]"; expected "Callable[[type[Album]], ...]" # # Therefore, we just use `Any` here, which is not ideal, but works. - def __init__(self, getter: Callable[..., T]) -> None: + def __init__(self, getter: Callable[..., T2]) -> None: """Initialize the descriptor with the property getter function.""" - self.getter: Callable[..., T] = getter + self.getter: Callable[[type[T]], T2] = getter - def __set_name__(self, owner: object, name: str) -> None: + def __set_name__(self, owner: T, name: str) -> None: """Capture the attribute name this descriptor is assigned to.""" self.name = name - def __get__(self, instance: object, owner: type[object]) -> T: + # For some reason, if we use T instead of object here, + # mypy complains when accessing a cached_property, e. g.: + # error: Argument 1 to "__get__" of "cached_classproperty" has incompatible type + # "MetadataSourcePlugin"; expected "Never" + # error: Argument 2 to "__get__" of "cached_classproperty" has incompatible type + # "type[MetadataSourcePlugin]"; expected "type[Never]" + def __get__(self, instance: object, owner: type[object]) -> T2: """Compute and cache if needed, and return the property value.""" - key: tuple[type[object], str] = owner, self.name - if key not in self.cache: - self.cache[key] = self.getter(owner) + owner = cast(type[T], owner) + key: tuple[type[T], str] = owner, self.name + if key not in self._cache: + cached_classproperty._cache[key] = self.getter(owner) - return cast(T, self.cache[key]) + return cast(T2, cached_classproperty._cache[key]) + + @classmethod + def clear_cache(cls) -> None: + cls._cache.clear() class LazySharedInstance(Generic[T]): diff --git a/test/conftest.py b/test/conftest.py index 059526d2f..34fbe88a6 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -53,7 +53,7 @@ def pytest_assertrepr_compare(op, left, right): @pytest.fixture(autouse=True) def clear_cached_classproperty(): - cached_classproperty.cache.clear() + cached_classproperty.clear_cache() @pytest.fixture(scope="module")