diff --git a/CHANGELOG.md b/CHANGELOG.md index a75b3fc5..0a1e3de5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 1.15.2 + +- Log a warning instead of crashing when a type guard import fails to resolve +- When resolving type guard imports if the target module does not have source code (such is the case for C-extension modules) do nothing instead of crashing + ## 1.15.1 - Fix `fully_qualified` should be `typehints_fully_qualified` diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index 5ecff688..d973331a 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -271,7 +271,7 @@ def get_all_type_hints(obj: Any, name: str) -> dict[str, Any]: return _get_type_hint(name, obj) -_TYPE_GUARD_IMPORT_RE = re.compile(r"if (typing.)?TYPE_CHECKING:([\s\S]*?)(?=\n\S)") +_TYPE_GUARD_IMPORT_RE = re.compile(r"if (typing.)?TYPE_CHECKING:[^\n]*([\s\S]*?)(?=\n\S)") _TYPE_GUARD_IMPORTS_RESOLVED = set() @@ -281,10 +281,17 @@ def _resolve_type_guarded_imports(obj: Any) -> None: if obj.__module__ not in sys.builtin_module_names: module = inspect.getmodule(obj) if module: - module_code = inspect.getsource(module) - for (_, part) in _TYPE_GUARD_IMPORT_RE.findall(module_code): - module_code = textwrap.dedent(part) - exec(module_code, obj.__globals__) + try: + module_code = inspect.getsource(module) + except OSError: + ... # no source code => no type guards + else: + for (_, part) in _TYPE_GUARD_IMPORT_RE.findall(module_code): + guarded_code = textwrap.dedent(part) + try: + exec(guarded_code, obj.__globals__) + except Exception as exc: + _LOGGER.warning(f"Failed guarded type import with {exc!r}") def _get_type_hint(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 03ad07e0..281de406 100644 --- a/tests/roots/test-resolve-typing-guard/demo_typing_guard.py +++ b/tests/roots/test-resolve-typing-guard/demo_typing_guard.py @@ -14,6 +14,10 @@ from typing import AnyStr +if TYPE_CHECKING: # bad import + from functools import missing # noqa: F401 + + def a(f: Decimal, s: AnyStr) -> Sequence[AnyStr | Decimal]: """ Do. diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py index aa366636..d5a4c4c8 100644 --- a/tests/test_sphinx_autodoc_typehints.py +++ b/tests/test_sphinx_autodoc_typehints.py @@ -35,6 +35,7 @@ from sphobjinv import Inventory from sphinx_autodoc_typehints import ( + _resolve_type_guarded_imports, backfill_type_hints, format_annotation, get_annotation_args, @@ -822,4 +823,12 @@ def test_resolve_typing_guard_imports(app: SphinxTestApp, status: StringIO, warn set_python_path() app.build() assert "build succeeded" in status.getvalue() - assert not warning.getvalue() + pat = r'WARNING: Failed guarded type import with ImportError\("cannot import name \'missing\' from \'functools\'' + err = warning.getvalue() + assert re.search(pat, err) + + +def test_no_source_code_type_guard() -> None: + from csv import Error + + _resolve_type_guarded_imports(Error) diff --git a/whitelist.txt b/whitelist.txt index 6329db95..7255ccf4 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -5,6 +5,7 @@ backfill conf contravariant cpython +csv dedent dirname docnames