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.
This commit is contained in:
Takeshi KOMIYA
2020-11-18 22:02:04 +09:00
parent 68aa4fb29e
commit 93d6c212f7
7 changed files with 77 additions and 10 deletions

View File

@@ -30,6 +30,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

View File

@@ -1714,7 +1714,12 @@ class DataDocumenter(ModuleLevelDocumenter):
def add_directive_header(self, sig: str) -> None:
super().add_directive_header(sig)
sourcename = self.get_sourcename()
if not self.options.annotation:
if self.options.annotation is SUPPRESS or inspect.isNewType(self.object):
pass
elif self.options.annotation:
self.add_line(' :annotation: %s' % self.options.annotation,
sourcename)
else:
# obtain annotation for this data
annotations = get_type_hints(self.parent, None, self.config.autodoc_type_aliases)
if self.objpath[-1] in annotations:
@@ -1734,11 +1739,6 @@ class DataDocumenter(ModuleLevelDocumenter):
self.add_line(' :value: ' + objrepr, sourcename)
except ValueError:
pass
elif self.options.annotation is SUPPRESS:
pass
else:
self.add_line(' :annotation: %s' % self.options.annotation,
sourcename)
def document_members(self, all_members: bool = False) -> None:
pass
@@ -1753,8 +1753,18 @@ class DataDocumenter(ModuleLevelDocumenter):
# suppress docstring of the value
super().add_content(more_content, no_docstring=True)
else:
if not more_content:
more_content = StringList()
if inspect.isNewType(self.object):
self.update_content_for_NewType(more_content)
super().add_content(more_content, no_docstring=no_docstring)
def update_content_for_NewType(self, more_content: StringList) -> None:
supertype = restify(self.object.__supertype__)
more_content.append(_('alias of %s') % supertype, '')
more_content.append('', '')
class DataDeclarationDocumenter(DataDocumenter):
"""
@@ -1800,6 +1810,24 @@ class GenericAliasDocumenter(DataDocumenter):
super().add_content(content)
class NewTypeDataDocumenter(DataDocumenter):
"""
Specialized Documenter subclass for NewTypes.
Note: This must be invoked before FunctionDocumenter because NewType is a kind of
function object.
"""
objtype = 'newtypedata'
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.
@@ -2307,6 +2335,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_autodocumenter(ExceptionDocumenter)
app.add_autodocumenter(DataDocumenter)
app.add_autodocumenter(GenericAliasDocumenter)
app.add_autodocumenter(NewTypeDataDocumenter)
app.add_autodocumenter(TypeVarDocumenter)
app.add_autodocumenter(FunctionDocumenter)
app.add_autodocumenter(DecoratorDocumenter)

View File

@@ -89,11 +89,12 @@ def setup_documenters(app: Any) -> None:
DecoratorDocumenter, ExceptionDocumenter,
FunctionDocumenter, GenericAliasDocumenter,
InstanceAttributeDocumenter, MethodDocumenter,
ModuleDocumenter, PropertyDocumenter,
SingledispatchFunctionDocumenter, SlotsAttributeDocumenter)
ModuleDocumenter, NewTypeDataDocumenter,
PropertyDocumenter, SingledispatchFunctionDocumenter,
SlotsAttributeDocumenter)
documenters = [
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
FunctionDocumenter, MethodDocumenter, NewTypeDataDocumenter, AttributeDocumenter,
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
SlotsAttributeDocumenter, GenericAliasDocumenter, SingledispatchFunctionDocumenter,
] # type: List[Type[Documenter]]

View File

@@ -174,6 +174,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)

View File

@@ -1,4 +1,4 @@
from typing import TypeVar
from typing import NewType, TypeVar
#: T1
T1 = TypeVar("T1")
@@ -13,3 +13,6 @@ T4 = TypeVar("T4", covariant=True)
#: T5
T5 = TypeVar("T5", contravariant=True)
#: T6
T6 = NewType("T6", int)

View File

@@ -1758,6 +1758,14 @@ def test_autodoc_TypeVar(app):
' T5',
'',
" alias of TypeVar('T5', contravariant=True)",
'',
'.. py:data:: T6',
' :module: target.typevar',
'',
' T6',
'',
' alias of :class:`int`',
'',
]

View File

@@ -73,3 +73,18 @@ def test_autodata_type_comment(app):
' attr3',
'',
]
@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 :class:`int`',
'',
]