diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index 83448536..177b738b 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -404,29 +404,32 @@ def get_all_type_hints(autodoc_mock_imports: list[str], obj: Any, name: str) -> _TYPE_GUARD_IMPORTS_RESOLVED_GLOBALS_ID = set() -def _resolve_type_guarded_imports(autodoc_mock_imports: list[str], obj: Any) -> None: - if (hasattr(obj, "__module__") and obj.__module__ not in _TYPE_GUARD_IMPORTS_RESOLVED) or ( - hasattr(obj, "__globals__") and id(obj.__globals__) not in _TYPE_GUARD_IMPORTS_RESOLVED_GLOBALS_ID - ): - _TYPE_GUARD_IMPORTS_RESOLVED.add(obj.__module__) - if obj.__module__ not in sys.builtin_module_names: - if hasattr(obj, "__globals__"): - _TYPE_GUARD_IMPORTS_RESOLVED_GLOBALS_ID.add(id(obj.__globals__)) - - module = inspect.getmodule(obj) - if module: - try: - module_code = inspect.getsource(module) - except (TypeError, OSError): - ... # no source code => no type guards - else: - for _, part in _TYPE_GUARD_IMPORT_RE.findall(module_code): - guarded_code = textwrap.dedent(part) - try: - with mock(autodoc_mock_imports): - exec(guarded_code, obj.__globals__) # noqa: S102 - except Exception as exc: # noqa: BLE001 - _LOGGER.warning(f"Failed guarded type import with {exc!r}") +def _resolve_type_guarded_imports(autodoc_mock_imports: list[str], obj: Any) -> None: # noqa: C901 + if hasattr(obj, "__module__") and obj.__module__ in _TYPE_GUARD_IMPORTS_RESOLVED: + return # already processed module + if not hasattr(obj, "__globals__"): # classes with __slots__ do not have this + return # if lacks globals nothing we can do + if id(obj.__globals__) in _TYPE_GUARD_IMPORTS_RESOLVED_GLOBALS_ID: + return # already processed object + _TYPE_GUARD_IMPORTS_RESOLVED.add(obj.__module__) + if obj.__module__ not in sys.builtin_module_names: + if hasattr(obj, "__globals__"): + _TYPE_GUARD_IMPORTS_RESOLVED_GLOBALS_ID.add(id(obj.__globals__)) + + module = inspect.getmodule(obj) + if module: + try: + module_code = inspect.getsource(module) + except (TypeError, OSError): + ... # no source code => no type guards + else: + for _, part in _TYPE_GUARD_IMPORT_RE.findall(module_code): + guarded_code = textwrap.dedent(part) + try: + with mock(autodoc_mock_imports): + exec(guarded_code, obj.__globals__) # noqa: S102 + except Exception as exc: # noqa: BLE001 + _LOGGER.warning(f"Failed guarded type import with {exc!r}") def _get_type_hint(autodoc_mock_imports: list[str], name: str, obj: Any) -> dict[str, Any]: diff --git a/tests/roots/test-resolve-typing-guard/demo_typing_guard.py b/tests/roots/test-resolve-typing-guard/demo_typing_guard.py index 70324466..d24d6024 100644 --- a/tests/roots/test-resolve-typing-guard/demo_typing_guard.py +++ b/tests/roots/test-resolve-typing-guard/demo_typing_guard.py @@ -6,11 +6,12 @@ from functools import cmp_to_key # has __module__ but cannot get module as is builtin from typing import TYPE_CHECKING +from demo_typing_guard_dummy import AnotherClass + if TYPE_CHECKING: from decimal import Decimal from typing import Sequence - from demo_typing_guard_dummy import AnotherClass # module contains mocked import # noqa: F401 if typing.TYPE_CHECKING: from typing import AnyStr @@ -34,14 +35,27 @@ def a(f: Decimal, s: AnyStr) -> Sequence[AnyStr | Decimal]: class SomeClass: """This class do something.""" + def create(self, item: Decimal) -> None: + """ + Create something. + + :param item: the item in question + """ + if TYPE_CHECKING: # Classes doesn't have `__globals__` attribute - def __getattr__(self, item: str): # noqa: ANN204 - """This method do something.""" + def guarded(self, item: Decimal) -> None: + """ + Guarded method. + + :param item: some item + """ __all__ = [ "a", "ValueError", "cmp_to_key", + "SomeClass", + "AnotherClass", ] diff --git a/tests/roots/test-resolve-typing-guard/demo_typing_guard_dummy.py b/tests/roots/test-resolve-typing-guard/demo_typing_guard_dummy.py index f06dfbc7..9a61ebd3 100644 --- a/tests/roots/test-resolve-typing-guard/demo_typing_guard_dummy.py +++ b/tests/roots/test-resolve-typing-guard/demo_typing_guard_dummy.py @@ -4,4 +4,4 @@ class AnotherClass: - ... + """Another class is here""" diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py index 5ff472d5..bd0c6c29 100644 --- a/tests/test_sphinx_autodoc_typehints.py +++ b/tests/test_sphinx_autodoc_typehints.py @@ -764,7 +764,8 @@ def test_resolve_typing_guard_imports(app: SphinxTestApp, status: StringIO, warn set_python_path() app.config.autodoc_mock_imports = ["viktor"] # type: ignore[attr-defined] # create flag app.build() - assert "build succeeded" in status.getvalue() + out = status.getvalue() + assert "build succeeded" in out err = warning.getvalue() r = re.compile("WARNING: Failed guarded type import") assert len(r.findall(err)) == 1