From c363cc4fa0f2fe2d6d33958afafc0fd7d66657e7 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 18 Nov 2020 22:02:04 +0900 Subject: [PATCH] Fix #8460: autodoc: Support custom types defined by typing.NewType A custom type defined by typing.NewType was rendered as a function because the generated type is a function having special attributes. This renders it as a variable. Note: The module name where the NewType object defined is lost on generating it. So it is hard to make cross-reference for these custom types. --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 35 +++++++++++++++++++ sphinx/ext/autosummary/generate.py | 4 +-- sphinx/util/inspect.py | 10 ++++++ .../roots/test-ext-autodoc/target/typevar.py | 5 ++- tests/test_ext_autodoc.py | 7 ++++ tests/test_ext_autodoc_autodata.py | 15 ++++++++ 7 files changed, 74 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index b6bfa0e9368..d67912b6ead 100644 --- a/CHANGES +++ b/CHANGES @@ -29,6 +29,7 @@ Features added value equal to None is set. * #8209: autodoc: Add ``:no-value:`` option to :rst:dir:`autoattribute` and :rst:dir:`autodata` directive to suppress the default value of the variable +* #8460: autodoc: Support custom types defined by typing.NewType * #6914: Add a new event :event:`warn-missing-reference` to custom warning messages when failed to resolve a cross-reference * #6914: Emit a detailed warning when failed to resolve a ``:ref:`` reference diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 1bd49fc38cc..963cbe90dd3 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1697,6 +1697,14 @@ def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: ) -> bool: return isinstance(parent, ModuleDocumenter) and isattr + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) + if inspect.isNewType(self.object): + self.options = Options(self.options) + self.options['annotation'] = SUPPRESS + + return ret + def add_directive_header(self, sig: str) -> None: super().add_directive_header(sig) sourcename = self.get_sourcename() @@ -1733,6 +1741,17 @@ def get_real_modname(self) -> str: return self.get_attr(self.parent or self.object, '__module__', None) \ or self.modname + def add_content(self, more_content: Any, no_docstring: bool = False) -> None: + if inspect.isNewType(self.object): + supertype = stringify_typehint(self.object.__supertype__) + if more_content: + more_content.append('', '') # insert a blank line after specified content + else: + more_content = StringList() + more_content.append(_('alias of %s') % supertype, '') + + super().add_content(more_content) + class DataDeclarationDocumenter(DataDocumenter): """ @@ -1797,6 +1816,21 @@ def add_content(self, more_content: Any, no_docstring: bool = False) -> None: super().add_content(content) +class NewTypeDocumenter(DataDocumenter): + """ + Specialized Documenter subclass for NewTypes. + """ + + objtype = 'newvar' + directivetype = 'data' + priority = FunctionDocumenter.priority + 1 + + @classmethod + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + return inspect.isNewType(member) and isattr + + class TypeVarDocumenter(DataDocumenter): """ Specialized Documenter subclass for TypeVars. @@ -2284,6 +2318,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_autodocumenter(DataDocumenter) app.add_autodocumenter(DataDeclarationDocumenter) app.add_autodocumenter(GenericAliasDocumenter) + app.add_autodocumenter(NewTypeDocumenter) app.add_autodocumenter(TypeVarDocumenter) app.add_autodocumenter(FunctionDocumenter) app.add_autodocumenter(DecoratorDocumenter) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 7e187e36a5d..1d60a767cc9 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -90,11 +90,11 @@ def setup_documenters(app: Any) -> None: DecoratorDocumenter, ExceptionDocumenter, FunctionDocumenter, GenericAliasDocumenter, InstanceAttributeDocumenter, MethodDocumenter, - ModuleDocumenter, PropertyDocumenter, + ModuleDocumenter, NewTypeDocumenter, PropertyDocumenter, SingledispatchFunctionDocumenter, SlotsAttributeDocumenter) documenters = [ ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, - FunctionDocumenter, MethodDocumenter, AttributeDocumenter, + FunctionDocumenter, MethodDocumenter, NewTypeDocumenter, AttributeDocumenter, InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, SlotsAttributeDocumenter, DataDeclarationDocumenter, GenericAliasDocumenter, SingledispatchFunctionDocumenter, diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 4f4f12e1d0b..f4d7df58b64 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -158,6 +158,16 @@ def getslots(obj: Any) -> Optional[Dict]: raise ValueError +def isNewType(obj: Any) -> bool: + """Check the if object is a kind of NewType.""" + __module__ = safe_getattr(obj, '__module__', None) + __qualname__ = safe_getattr(obj, '__qualname__', None) + if __module__ == 'typing' and __qualname__ == 'NewType..new_type': + return True + else: + return False + + def isenumclass(x: Any) -> bool: """Check if the object is subclass of enum.""" return inspect.isclass(x) and issubclass(x, enum.Enum) diff --git a/tests/roots/test-ext-autodoc/target/typevar.py b/tests/roots/test-ext-autodoc/target/typevar.py index 9c6b0eab0bb..f71efd4c245 100644 --- a/tests/roots/test-ext-autodoc/target/typevar.py +++ b/tests/roots/test-ext-autodoc/target/typevar.py @@ -1,4 +1,4 @@ -from typing import TypeVar +from typing import NewType, TypeVar #: T1 T1 = TypeVar("T1") @@ -13,3 +13,6 @@ #: T5 T5 = TypeVar("T5", contravariant=True) + +#: T6 +T6 = NewType("T6", int) diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index d8e1f730e87..eca900b3395 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -1758,6 +1758,13 @@ def test_autodoc_TypeVar(app): ' T5', '', " alias of TypeVar('T5', contravariant=True)", + '', + '.. py:data:: T6', + ' :module: target.typevar', + '', + ' T6', + '', + ' alias of int', ] diff --git a/tests/test_ext_autodoc_autodata.py b/tests/test_ext_autodoc_autodata.py index 7e4471db9ec..42b4d3ad68b 100644 --- a/tests/test_ext_autodoc_autodata.py +++ b/tests/test_ext_autodoc_autodata.py @@ -39,3 +39,18 @@ def test_autodata_novalue(app): ' documentation for the integer', '', ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodata_NewType(app): + actual = do_autodoc(app, 'data', 'target.typevar.T6') + assert list(actual) == [ + '', + '.. py:data:: T6', + ' :module: target.typevar', + '', + ' T6', + '', + ' alias of int', + '', + ]