Skip to content

Commit

Permalink
Fix sphinx-doc#8460: autodoc: Support custom types defined by typing.…
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
tk0miya committed Nov 21, 2020
1 parent 68c91b1 commit c363cc4
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions sphinx/ext/autodoc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions sphinx/ext/autosummary/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions sphinx/util/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.<locals>.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)
Expand Down
5 changes: 4 additions & 1 deletion tests/roots/test-ext-autodoc/target/typevar.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TypeVar
from typing import NewType, TypeVar

#: T1
T1 = TypeVar("T1")
Expand All @@ -13,3 +13,6 @@

#: T5
T5 = TypeVar("T5", contravariant=True)

#: T6
T6 = NewType("T6", int)
7 changes: 7 additions & 0 deletions tests/test_ext_autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]


Expand Down
15 changes: 15 additions & 0 deletions tests/test_ext_autodoc_autodata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
'',
]

0 comments on commit c363cc4

Please sign in to comment.