Skip to content

Commit 01b3a1a

Browse files
authored
More robust handling of type guard imports (#208)
1 parent d770c69 commit 01b3a1a

File tree

5 files changed

+32
-6
lines changed

5 files changed

+32
-6
lines changed

CHANGELOG.md

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

3+
## 1.15.2
4+
5+
- Log a warning instead of crashing when a type guard import fails to resolve
6+
- 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
7+
38
## 1.15.1
49

510
- Fix `fully_qualified` should be `typehints_fully_qualified`

src/sphinx_autodoc_typehints/__init__.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ def get_all_type_hints(obj: Any, name: str) -> dict[str, Any]:
271271
return _get_type_hint(name, obj)
272272

273273

274-
_TYPE_GUARD_IMPORT_RE = re.compile(r"if (typing.)?TYPE_CHECKING:([\s\S]*?)(?=\n\S)")
274+
_TYPE_GUARD_IMPORT_RE = re.compile(r"if (typing.)?TYPE_CHECKING:[^\n]*([\s\S]*?)(?=\n\S)")
275275
_TYPE_GUARD_IMPORTS_RESOLVED = set()
276276

277277

@@ -281,10 +281,17 @@ def _resolve_type_guarded_imports(obj: Any) -> None:
281281
if obj.__module__ not in sys.builtin_module_names:
282282
module = inspect.getmodule(obj)
283283
if module:
284-
module_code = inspect.getsource(module)
285-
for (_, part) in _TYPE_GUARD_IMPORT_RE.findall(module_code):
286-
module_code = textwrap.dedent(part)
287-
exec(module_code, obj.__globals__)
284+
try:
285+
module_code = inspect.getsource(module)
286+
except OSError:
287+
... # no source code => no type guards
288+
else:
289+
for (_, part) in _TYPE_GUARD_IMPORT_RE.findall(module_code):
290+
guarded_code = textwrap.dedent(part)
291+
try:
292+
exec(guarded_code, obj.__globals__)
293+
except Exception as exc:
294+
_LOGGER.warning(f"Failed guarded type import with {exc!r}")
288295

289296

290297
def _get_type_hint(name: str, obj: Any) -> dict[str, Any]:

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
from typing import AnyStr
1515

1616

17+
if TYPE_CHECKING: # bad import
18+
from functools import missing # noqa: F401
19+
20+
1721
def a(f: Decimal, s: AnyStr) -> Sequence[AnyStr | Decimal]:
1822
"""
1923
Do.

tests/test_sphinx_autodoc_typehints.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from sphobjinv import Inventory
3636

3737
from sphinx_autodoc_typehints import (
38+
_resolve_type_guarded_imports,
3839
backfill_type_hints,
3940
format_annotation,
4041
get_annotation_args,
@@ -822,4 +823,12 @@ def test_resolve_typing_guard_imports(app: SphinxTestApp, status: StringIO, warn
822823
set_python_path()
823824
app.build()
824825
assert "build succeeded" in status.getvalue()
825-
assert not warning.getvalue()
826+
pat = r'WARNING: Failed guarded type import with ImportError\("cannot import name \'missing\' from \'functools\''
827+
err = warning.getvalue()
828+
assert re.search(pat, err)
829+
830+
831+
def test_no_source_code_type_guard() -> None:
832+
from csv import Error
833+
834+
_resolve_type_guarded_imports(Error)

whitelist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ backfill
55
conf
66
contravariant
77
cpython
8+
csv
89
dedent
910
dirname
1011
docnames

0 commit comments

Comments
 (0)