From 2504ad0b9971dfc1820a60156db8c52e74dd0b1f Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 4 Nov 2022 14:56:36 +1000 Subject: [PATCH] Fix mypy not picking up on missing attributes Behaviour changed in recent releases: https://github.com/python/mypy/issues/13319 --- pylib/anki/_legacy.py | 58 ++++++++++++++++++++--------------------- pylib/anki/consts.py | 9 ++++--- pylib/anki/decks.py | 29 ++++++++++++--------- pylib/anki/lang.py | 9 ++++--- pylib/anki/stdmodels.py | 9 ++++--- pylib/anki/utils.py | 9 ++++--- qt/aqt/utils.py | 9 ++++--- 7 files changed, 70 insertions(+), 62 deletions(-) diff --git a/pylib/anki/_legacy.py b/pylib/anki/_legacy.py index e4209aebb..3e6d3c97e 100644 --- a/pylib/anki/_legacy.py +++ b/pylib/anki/_legacy.py @@ -7,7 +7,7 @@ import functools import os import pathlib import traceback -from typing import Any, Callable, Union, no_type_check +from typing import TYPE_CHECKING, Any, Callable, Union from anki._vendor import stringcase @@ -66,21 +66,20 @@ class DeprecatedNamesMixin: # deprecated name -> [new internal name, new name shown to user] _deprecated_attributes: dict[str, tuple[str, str | None]] = {} - # the @no_type_check lines are required to prevent mypy allowing arbitrary - # attributes on the consuming class + # TYPE_CHECKING check is required for https://github.com/python/mypy/issues/13319 + if not TYPE_CHECKING: - @no_type_check - def __getattr__(self, name: str) -> Any: - try: - remapped, replacement = _get_remapped_and_replacement(self, name) - out = getattr(self, remapped) - except AttributeError: - raise AttributeError( - f"'{self.__class__.__name__}' object has no attribute '{name}'" - ) from None + def __getattr__(self, name: str) -> Any: + try: + remapped, replacement = _get_remapped_and_replacement(self, name) + out = getattr(self, remapped) + except AttributeError: + raise AttributeError( + f"'{self.__class__.__name__}' object has no attribute '{name}'" + ) from None - _print_replacement_warning(name, replacement) - return out + _print_replacement_warning(name, replacement) + return out @classmethod def register_deprecated_aliases(cls, **kwargs: DeprecatedAliasTarget) -> None: @@ -123,9 +122,9 @@ class DeprecatedNamesMixinForModule: _deprecated_names.register_deprecated_aliases(... _deprecated_names.register_deprecated_attributes(... - @no_type_check - def __getattr__(name: str) -> Any: - return _deprecated_names.__getattr__(name) + if not TYPE_CHECKING: + def __getattr__(name: str) -> Any: + return _deprecated_names.__getattr__(name) ``` See DeprecatedNamesMixin for more documentation. """ @@ -135,19 +134,20 @@ class DeprecatedNamesMixinForModule: self._deprecated_aliases: dict[str, str] = {} self._deprecated_attributes: dict[str, tuple[str, str | None]] = {} - @no_type_check - def __getattr__(self, name: str) -> Any: - try: - remapped, replacement = _get_remapped_and_replacement(self, name) - out = self.module_globals[remapped] - except (AttributeError, KeyError): - raise AttributeError( - f"Module '{self.module_globals['__name__']}' has no attribute '{name}'" - ) from None + if not TYPE_CHECKING: - # skip an additional frame as we are called from the module `__getattr__` - _print_replacement_warning(name, replacement, frame=2) - return out + def __getattr__(self, name: str) -> Any: + try: + remapped, replacement = _get_remapped_and_replacement(self, name) + out = self.module_globals[remapped] + except (AttributeError, KeyError): + raise AttributeError( + f"Module '{self.module_globals['__name__']}' has no attribute '{name}'" + ) from None + + # skip an additional frame as we are called from the module `__getattr__` + _print_replacement_warning(name, replacement, frame=2) + return out def register_deprecated_aliases(self, **kwargs: DeprecatedAliasTarget) -> None: self._deprecated_aliases = {k: _target_to_string(v) for k, v in kwargs.items()} diff --git a/pylib/anki/consts.py b/pylib/anki/consts.py index 2de007e98..5b6e552fc 100644 --- a/pylib/anki/consts.py +++ b/pylib/anki/consts.py @@ -4,7 +4,7 @@ from __future__ import annotations import sys -from typing import Any, NewType, no_type_check +from typing import TYPE_CHECKING, Any, NewType from anki._legacy import DeprecatedNamesMixinForModule @@ -132,6 +132,7 @@ def new_card_scheduling_labels( _deprecated_names = DeprecatedNamesMixinForModule(globals()) -@no_type_check -def __getattr__(name: str) -> Any: - return _deprecated_names.__getattr__(name) +if not TYPE_CHECKING: + + def __getattr__(name: str) -> Any: + return _deprecated_names.__getattr__(name) diff --git a/pylib/anki/decks.py b/pylib/anki/decks.py index a1bb8283c..dd81e2926 100644 --- a/pylib/anki/decks.py +++ b/pylib/anki/decks.py @@ -4,7 +4,7 @@ from __future__ import annotations import copy -from typing import TYPE_CHECKING, Any, Iterable, NewType, Sequence, no_type_check +from typing import TYPE_CHECKING, Any, Iterable, NewType, Sequence if TYPE_CHECKING: import anki @@ -590,15 +590,18 @@ DeckManager.register_deprecated_aliases( ) -@no_type_check -def __getattr__(name): - if name == "defaultDeck": - print_deprecation_warning( - "defaultDeck is deprecated; call decks.id() without it" - ) - return 0 - elif name == "defaultDynamicDeck": - print_deprecation_warning("defaultDynamicDeck is replaced with new_filtered()") - return 1 - else: - raise AttributeError(f"module {__name__} has no attribute {name}") +if not TYPE_CHECKING: + + def __getattr__(name): + if name == "defaultDeck": + print_deprecation_warning( + "defaultDeck is deprecated; call decks.id() without it" + ) + return 0 + elif name == "defaultDynamicDeck": + print_deprecation_warning( + "defaultDynamicDeck is replaced with new_filtered()" + ) + return 1 + else: + raise AttributeError(f"module {__name__} has no attribute {name}") diff --git a/pylib/anki/lang.py b/pylib/anki/lang.py index a76da5133..d3e2d9291 100644 --- a/pylib/anki/lang.py +++ b/pylib/anki/lang.py @@ -6,7 +6,7 @@ from __future__ import annotations import locale import re import weakref -from typing import Any, no_type_check +from typing import TYPE_CHECKING, Any import anki import anki._backend @@ -224,6 +224,7 @@ def with_collapsed_whitespace(string: str) -> str: _deprecated_names = DeprecatedNamesMixinForModule(globals()) -@no_type_check -def __getattr__(name: str) -> Any: - return _deprecated_names.__getattr__(name) +if not TYPE_CHECKING: + + def __getattr__(name: str) -> Any: + return _deprecated_names.__getattr__(name) diff --git a/pylib/anki/stdmodels.py b/pylib/anki/stdmodels.py index fff7f0702..e17fc8767 100644 --- a/pylib/anki/stdmodels.py +++ b/pylib/anki/stdmodels.py @@ -3,7 +3,7 @@ from __future__ import annotations -from typing import Any, Callable, no_type_check +from typing import TYPE_CHECKING, Any, Callable import anki.collection import anki.models @@ -115,6 +115,7 @@ _deprecated_names.register_deprecated_attributes( ) -@no_type_check -def __getattr__(name: str) -> Any: - return _deprecated_names.__getattr__(name) +if not TYPE_CHECKING: + + def __getattr__(name: str) -> Any: + return _deprecated_names.__getattr__(name) diff --git a/pylib/anki/utils.py b/pylib/anki/utils.py index 730bb7301..489ca5536 100644 --- a/pylib/anki/utils.py +++ b/pylib/anki/utils.py @@ -16,7 +16,7 @@ import tempfile import time from contextlib import contextmanager from hashlib import sha1 -from typing import Any, Callable, Iterable, Iterator, no_type_check +from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator from anki._legacy import DeprecatedNamesMixinForModule from anki.dbproxy import DBProxy @@ -324,6 +324,7 @@ _deprecated_names.register_deprecated_aliases( _deprecated_names.register_deprecated_attributes(json=((_json, "_json"), None)) -@no_type_check -def __getattr__(name: str) -> Any: - return _deprecated_names.__getattr__(name) +if not TYPE_CHECKING: + + def __getattr__(name: str) -> Any: + return _deprecated_names.__getattr__(name) diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index 8ba782913..cf8fa083a 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -9,7 +9,7 @@ import subprocess import sys from functools import partial, wraps from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, Literal, Sequence, Union, no_type_check +from typing import TYPE_CHECKING, Any, Callable, Literal, Sequence, Union from send2trash import send2trash @@ -1243,6 +1243,7 @@ class KeyboardModifiersPressed: _deprecated_names = DeprecatedNamesMixinForModule(globals()) -@no_type_check -def __getattr__(name: str) -> Any: - return _deprecated_names.__getattr__(name) +if not TYPE_CHECKING: + + def __getattr__(name: str) -> Any: + return _deprecated_names.__getattr__(name)