Skip to content

Commit 73aa9b6

Browse files
Fix mock imports on guarded imports (#225)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 4d5867d commit 73aa9b6

File tree

5 files changed

+37
-12
lines changed

5 files changed

+37
-12
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 1.18.1
4+
5+
- Fix mocked module import not working when used as guarded import
6+
37
## 1.18.0
48

59
- Support and require `nptyping>=2`

src/sphinx_autodoc_typehints/__init__.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from sphinx.config import Config
1313
from sphinx.environment import BuildEnvironment
1414
from sphinx.ext.autodoc import Options
15+
from sphinx.ext.autodoc.mock import mock
1516
from sphinx.util import logging
1617
from sphinx.util.inspect import signature as sphinx_signature
1718
from sphinx.util.inspect import stringify_signature
@@ -305,24 +306,24 @@ def _future_annotations_imported(obj: Any) -> bool:
305306
return bool(_annotations.compiler_flag == future_annotations)
306307

307308

308-
def get_all_type_hints(obj: Any, name: str) -> dict[str, Any]:
309-
result = _get_type_hint(name, obj)
309+
def get_all_type_hints(autodoc_mock_imports: list[str], obj: Any, name: str) -> dict[str, Any]:
310+
result = _get_type_hint(autodoc_mock_imports, name, obj)
310311
if not result:
311312
result = backfill_type_hints(obj, name)
312313
try:
313314
obj.__annotations__ = result
314315
except (AttributeError, TypeError):
315316
pass
316317
else:
317-
result = _get_type_hint(name, obj)
318+
result = _get_type_hint(autodoc_mock_imports, name, obj)
318319
return result
319320

320321

321322
_TYPE_GUARD_IMPORT_RE = re.compile(r"\nif (typing.)?TYPE_CHECKING:[^\n]*([\s\S]*?)(?=\n\S)")
322323
_TYPE_GUARD_IMPORTS_RESOLVED = set()
323324

324325

325-
def _resolve_type_guarded_imports(obj: Any) -> None:
326+
def _resolve_type_guarded_imports(autodoc_mock_imports: list[str], obj: Any) -> None:
326327
if hasattr(obj, "__module__") and obj.__module__ not in _TYPE_GUARD_IMPORTS_RESOLVED:
327328
_TYPE_GUARD_IMPORTS_RESOLVED.add(obj.__module__)
328329
if obj.__module__ not in sys.builtin_module_names:
@@ -336,13 +337,14 @@ def _resolve_type_guarded_imports(obj: Any) -> None:
336337
for (_, part) in _TYPE_GUARD_IMPORT_RE.findall(module_code):
337338
guarded_code = textwrap.dedent(part)
338339
try:
339-
exec(guarded_code, obj.__globals__)
340+
with mock(autodoc_mock_imports):
341+
exec(guarded_code, obj.__globals__)
340342
except Exception as exc:
341343
_LOGGER.warning(f"Failed guarded type import with {exc!r}")
342344

343345

344-
def _get_type_hint(name: str, obj: Any) -> dict[str, Any]:
345-
_resolve_type_guarded_imports(obj)
346+
def _get_type_hint(autodoc_mock_imports: list[str], name: str, obj: Any) -> dict[str, Any]:
347+
_resolve_type_guarded_imports(autodoc_mock_imports, obj)
346348
try:
347349
result = get_type_hints(obj)
348350
except (AttributeError, TypeError, RecursionError) as exc:
@@ -497,7 +499,7 @@ def process_docstring(
497499
signature = sphinx_signature(obj)
498500
except (ValueError, TypeError):
499501
signature = None
500-
type_hints = get_all_type_hints(obj, name)
502+
type_hints = get_all_type_hints(app.config.autodoc_mock_imports, obj, name)
501503
app.config._annotation_globals = getattr(obj, "__globals__", {}) # type: ignore # config has no such attribute
502504
try:
503505
_inject_types_to_docstring(type_hints, signature, original_obj, app, what, name, lines)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from decimal import Decimal
1111
from typing import Sequence
1212

13+
from demo_typing_guard_dummy import AnotherClass # module contains mocked import # noqa: F401
14+
1315
if typing.TYPE_CHECKING:
1416
from typing import AnyStr
1517

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from viktor import AI # module part of autodoc_mock_imports # noqa: F401,SC100,SC200
2+
3+
4+
class AnotherClass:
5+
...

tests/test_sphinx_autodoc_typehints.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,11 @@ def test_format_annotation_both_libs(library: ModuleType, annotation: str, param
353353
def test_process_docstring_slot_wrapper() -> None:
354354
lines: list[str] = []
355355
config = create_autospec(
356-
Config, typehints_fully_qualified=False, simplify_optional_unions=False, typehints_formatter=None
356+
Config,
357+
typehints_fully_qualified=False,
358+
simplify_optional_unions=False,
359+
typehints_formatter=None,
360+
autodoc_mock_imports=[],
357361
)
358362
app: Sphinx = create_autospec(Sphinx, config=config)
359363
process_docstring(app, "class", "SlotWrapper", Slotted, None, lines)
@@ -870,7 +874,11 @@ def __init__(bound_args): # noqa: N805
870874
@pytest.mark.parametrize("obj", [cmp_to_key, 1])
871875
def test_default_no_signature(obj: Any) -> None:
872876
config = create_autospec(
873-
Config, typehints_fully_qualified=False, simplify_optional_unions=False, typehints_formatter=None
877+
Config,
878+
typehints_fully_qualified=False,
879+
simplify_optional_unions=False,
880+
typehints_formatter=None,
881+
autodoc_mock_imports=[],
874882
)
875883
app: Sphinx = create_autospec(Sphinx, config=config)
876884
lines: list[str] = []
@@ -888,6 +896,7 @@ def test_bound_class_method(method: FunctionType) -> None:
888896
always_document_param_types=True,
889897
typehints_defaults=True,
890898
typehints_formatter=None,
899+
autodoc_mock_imports=[],
891900
)
892901
app: Sphinx = create_autospec(Sphinx, config=config)
893902
process_docstring(app, "class", method.__qualname__, method, None, [])
@@ -905,17 +914,20 @@ def test_syntax_error_backfill() -> None:
905914
@pytest.mark.sphinx("text", testroot="resolve-typing-guard")
906915
def test_resolve_typing_guard_imports(app: SphinxTestApp, status: StringIO, warning: StringIO) -> None:
907916
set_python_path()
917+
app.config.autodoc_mock_imports = ["viktor"] # type: ignore # create flag
908918
app.build()
909919
assert "build succeeded" in status.getvalue()
910-
pat = r'WARNING: Failed guarded type import with ImportError\("cannot import name \'missing\' from \'functools\''
911920
err = warning.getvalue()
921+
r = re.compile("WARNING: Failed guarded type import")
922+
assert len(r.findall(err)) == 1
923+
pat = r'WARNING: Failed guarded type import with ImportError\("cannot import name \'missing\' from \'functools\''
912924
assert re.search(pat, err)
913925

914926

915927
def test_no_source_code_type_guard() -> None:
916928
from csv import Error
917929

918-
_resolve_type_guarded_imports(Error)
930+
_resolve_type_guarded_imports([], Error)
919931

920932

921933
@pytest.mark.sphinx("text", testroot="dummy")

0 commit comments

Comments
 (0)