Skip to content

Commit

Permalink
Fix type guard handling for classes
Browse files Browse the repository at this point in the history
Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net>
  • Loading branch information
gaborbernat committed Jun 27, 2023
1 parent 8107a80 commit c16e722
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 28 deletions.
49 changes: 26 additions & 23 deletions src/sphinx_autodoc_typehints/__init__.py
Expand Up @@ -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]:
Expand Down
20 changes: 17 additions & 3 deletions tests/roots/test-resolve-typing-guard/demo_typing_guard.py
Expand Up @@ -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
Expand All @@ -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",
]
Expand Up @@ -4,4 +4,4 @@


class AnotherClass:
...
"""Another class is here"""
3 changes: 2 additions & 1 deletion tests/test_sphinx_autodoc_typehints.py
Expand Up @@ -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
Expand Down

0 comments on commit c16e722

Please sign in to comment.