Skip to content

Commit

Permalink
More robust handling of type guard imports (#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaborbernat authored Jan 11, 2022
1 parent d770c69 commit 01b3a1a
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 6 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`
Expand Down
17 changes: 12 additions & 5 deletions src/sphinx_autodoc_typehints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()


Expand All @@ -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]:
Expand Down
4 changes: 4 additions & 0 deletions tests/roots/test-resolve-typing-guard/demo_typing_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 10 additions & 1 deletion tests/test_sphinx_autodoc_typehints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
1 change: 1 addition & 0 deletions whitelist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ backfill
conf
contravariant
cpython
csv
dedent
dirname
docnames
Expand Down

0 comments on commit 01b3a1a

Please sign in to comment.