From 444e27865dffadea25fda24516f168849a364597 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 27 Jan 2022 02:40:37 +0900 Subject: [PATCH] Fix #10133: autodoc: Crashed when mocked module is used for type annotation --- CHANGES | 2 ++ sphinx/ext/autodoc/mock.py | 5 +++++ sphinx/util/typing.py | 10 ++++++++++ tests/test_util_typing.py | 2 ++ 4 files changed, 19 insertions(+) diff --git a/CHANGES b/CHANGES index 931c0e0dcdd..dc2e2dfe8f2 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,8 @@ Features added Bugs fixed ---------- +* #10133: autodoc: Crashed when mocked module is used for type annotation + Testing -------- diff --git a/sphinx/ext/autodoc/mock.py b/sphinx/ext/autodoc/mock.py index 36b2836f3e2..7bd0b1ea017 100644 --- a/sphinx/ext/autodoc/mock.py +++ b/sphinx/ext/autodoc/mock.py @@ -154,6 +154,11 @@ def mock(modnames: List[str]) -> Generator[None, None, None]: finder.invalidate_caches() +def ismockmodule(subject: Any) -> bool: + """Check if the object is a mocked module.""" + return isinstance(subject, _MockModule) + + def ismock(subject: Any) -> bool: """Check if the object is mocked.""" # check the object has '__sphinx_mock__' attribute diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index d9b63e046e3..5cd0c230ef2 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -116,6 +116,7 @@ def restify(cls: Optional[Type], mode: str = 'fully-qualified-except-typing') -> 'smart' Show the name of the annotation. """ + from sphinx.ext.autodoc.mock import ismock, ismockmodule # lazy loading from sphinx.util import inspect # lazy loading if mode == 'smart': @@ -130,6 +131,10 @@ def restify(cls: Optional[Type], mode: str = 'fully-qualified-except-typing') -> return '...' elif isinstance(cls, str): return cls + elif ismockmodule(cls): + return ':py:class:`%s%s`' % (modprefix, cls.__name__) + elif ismock(cls): + return ':py:class:`%s%s.%s`' % (modprefix, cls.__module__, cls.__name__) elif cls in INVALID_BUILTIN_CLASSES: return ':py:class:`%s%s`' % (modprefix, INVALID_BUILTIN_CLASSES[cls]) elif inspect.isNewType(cls): @@ -335,6 +340,7 @@ def stringify(annotation: Any, mode: str = 'fully-qualified-except-typing') -> s 'fully-qualified' Show the module name and qualified name of the annotation. """ + from sphinx.ext.autodoc.mock import ismock, ismockmodule # lazy loading from sphinx.util import inspect # lazy loading if mode == 'smart': @@ -364,6 +370,10 @@ def stringify(annotation: Any, mode: str = 'fully-qualified-except-typing') -> s return repr(annotation) elif annotation is NoneType: return 'None' + elif ismockmodule(annotation): + return modprefix + annotation.__name__ + elif ismock(annotation): + return modprefix + '%s.%s' % (annotation.__module__, annotation.__name__) elif annotation in INVALID_BUILTIN_CLASSES: return modprefix + INVALID_BUILTIN_CLASSES[annotation] elif str(annotation).startswith('typing.Annotated'): # for py310+ diff --git a/tests/test_util_typing.py b/tests/test_util_typing.py index c061fa08518..7d81fee5d7b 100644 --- a/tests/test_util_typing.py +++ b/tests/test_util_typing.py @@ -213,6 +213,7 @@ def test_restify_broken_type_hints(): def test_restify_mock(): with mock(['unknown']): import unknown + assert restify(unknown) == ':py:class:`unknown`' assert restify(unknown.secret.Class) == ':py:class:`unknown.secret.Class`' assert restify(unknown.secret.Class, "smart") == ':py:class:`~unknown.secret.Class`' @@ -480,5 +481,6 @@ def test_stringify_broken_type_hints(): def test_stringify_mock(): with mock(['unknown']): import unknown + assert stringify(unknown) == 'unknown' assert stringify(unknown.secret.Class) == 'unknown.secret.Class' assert stringify(unknown.secret.Class, "smart") == 'unknown.secret.Class'