Skip to content

Commit 0b0ffc0

Browse files
authored
Fix type guard handling for classes (#365)
1 parent 8107a80 commit 0b0ffc0

File tree

4 files changed

+46
-28
lines changed

4 files changed

+46
-28
lines changed

src/sphinx_autodoc_typehints/__init__.py

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -404,29 +404,32 @@ def get_all_type_hints(autodoc_mock_imports: list[str], obj: Any, name: str) ->
404404
_TYPE_GUARD_IMPORTS_RESOLVED_GLOBALS_ID = set()
405405

406406

407-
def _resolve_type_guarded_imports(autodoc_mock_imports: list[str], obj: Any) -> None:
408-
if (hasattr(obj, "__module__") and obj.__module__ not in _TYPE_GUARD_IMPORTS_RESOLVED) or (
409-
hasattr(obj, "__globals__") and id(obj.__globals__) not in _TYPE_GUARD_IMPORTS_RESOLVED_GLOBALS_ID
410-
):
411-
_TYPE_GUARD_IMPORTS_RESOLVED.add(obj.__module__)
412-
if obj.__module__ not in sys.builtin_module_names:
413-
if hasattr(obj, "__globals__"):
414-
_TYPE_GUARD_IMPORTS_RESOLVED_GLOBALS_ID.add(id(obj.__globals__))
415-
416-
module = inspect.getmodule(obj)
417-
if module:
418-
try:
419-
module_code = inspect.getsource(module)
420-
except (TypeError, OSError):
421-
... # no source code => no type guards
422-
else:
423-
for _, part in _TYPE_GUARD_IMPORT_RE.findall(module_code):
424-
guarded_code = textwrap.dedent(part)
425-
try:
426-
with mock(autodoc_mock_imports):
427-
exec(guarded_code, obj.__globals__) # noqa: S102
428-
except Exception as exc: # noqa: BLE001
429-
_LOGGER.warning(f"Failed guarded type import with {exc!r}")
407+
def _resolve_type_guarded_imports(autodoc_mock_imports: list[str], obj: Any) -> None: # noqa: C901
408+
if hasattr(obj, "__module__") and obj.__module__ in _TYPE_GUARD_IMPORTS_RESOLVED:
409+
return # already processed module
410+
if not hasattr(obj, "__globals__"): # classes with __slots__ do not have this
411+
return # if lacks globals nothing we can do
412+
if id(obj.__globals__) in _TYPE_GUARD_IMPORTS_RESOLVED_GLOBALS_ID:
413+
return # already processed object
414+
_TYPE_GUARD_IMPORTS_RESOLVED.add(obj.__module__)
415+
if obj.__module__ not in sys.builtin_module_names:
416+
if hasattr(obj, "__globals__"):
417+
_TYPE_GUARD_IMPORTS_RESOLVED_GLOBALS_ID.add(id(obj.__globals__))
418+
419+
module = inspect.getmodule(obj)
420+
if module:
421+
try:
422+
module_code = inspect.getsource(module)
423+
except (TypeError, OSError):
424+
... # no source code => no type guards
425+
else:
426+
for _, part in _TYPE_GUARD_IMPORT_RE.findall(module_code):
427+
guarded_code = textwrap.dedent(part)
428+
try:
429+
with mock(autodoc_mock_imports):
430+
exec(guarded_code, obj.__globals__) # noqa: S102
431+
except Exception as exc: # noqa: BLE001
432+
_LOGGER.warning(f"Failed guarded type import with {exc!r}")
430433

431434

432435
def _get_type_hint(autodoc_mock_imports: list[str], name: str, obj: Any) -> dict[str, Any]:

tests/roots/test-resolve-typing-guard/demo_typing_guard.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
from functools import cmp_to_key # has __module__ but cannot get module as is builtin
77
from typing import TYPE_CHECKING
88

9+
from demo_typing_guard_dummy import AnotherClass
10+
911
if TYPE_CHECKING:
1012
from decimal import Decimal
1113
from typing import Sequence
1214

13-
from demo_typing_guard_dummy import AnotherClass # module contains mocked import # noqa: F401
1415

1516
if typing.TYPE_CHECKING:
1617
from typing import AnyStr
@@ -34,14 +35,27 @@ def a(f: Decimal, s: AnyStr) -> Sequence[AnyStr | Decimal]:
3435
class SomeClass:
3536
"""This class do something."""
3637

38+
def create(self, item: Decimal) -> None:
39+
"""
40+
Create something.
41+
42+
:param item: the item in question
43+
"""
44+
3745
if TYPE_CHECKING: # Classes doesn't have `__globals__` attribute
3846

39-
def __getattr__(self, item: str): # noqa: ANN204
40-
"""This method do something."""
47+
def guarded(self, item: Decimal) -> None:
48+
"""
49+
Guarded method.
50+
51+
:param item: some item
52+
"""
4153

4254

4355
__all__ = [
4456
"a",
4557
"ValueError",
4658
"cmp_to_key",
59+
"SomeClass",
60+
"AnotherClass",
4761
]

tests/roots/test-resolve-typing-guard/demo_typing_guard_dummy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44

55

66
class AnotherClass:
7-
...
7+
"""Another class is here"""

tests/test_sphinx_autodoc_typehints.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,8 @@ def test_resolve_typing_guard_imports(app: SphinxTestApp, status: StringIO, warn
764764
set_python_path()
765765
app.config.autodoc_mock_imports = ["viktor"] # type: ignore[attr-defined] # create flag
766766
app.build()
767-
assert "build succeeded" in status.getvalue()
767+
out = status.getvalue()
768+
assert "build succeeded" in out
768769
err = warning.getvalue()
769770
r = re.compile("WARNING: Failed guarded type import")
770771
assert len(r.findall(err)) == 1

0 commit comments

Comments
 (0)