mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #8465 from tk0miya/8460_NewType
Fix #8460: autodoc: Support custom types defined by typing.NewType
This commit is contained in:
commit
24a329eebe
1
CHANGES
1
CHANGES
@ -31,6 +31,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
|
||||
|
@ -1677,7 +1677,25 @@ class ExceptionDocumenter(ClassDocumenter):
|
||||
return isinstance(member, type) and issubclass(member, BaseException)
|
||||
|
||||
|
||||
class DataDocumenter(ModuleLevelDocumenter):
|
||||
class NewTypeMixin:
|
||||
"""
|
||||
Mixin for DataDocumenter and AttributeDocumenter to provide the feature for
|
||||
supporting NewTypes.
|
||||
"""
|
||||
|
||||
def should_suppress_directive_header(self) -> bool:
|
||||
"""Check directive header should be suppressed."""
|
||||
return inspect.isNewType(self.object) # type: ignore
|
||||
|
||||
def update_content(self, more_content: StringList) -> None:
|
||||
"""Update docstring for the NewType object."""
|
||||
if inspect.isNewType(self.object): # type: ignore
|
||||
supertype = restify(self.object.__supertype__) # type: ignore
|
||||
more_content.append(_('alias of %s') % supertype, '')
|
||||
more_content.append('', '')
|
||||
|
||||
|
||||
class DataDocumenter(ModuleLevelDocumenter, NewTypeMixin):
|
||||
"""
|
||||
Specialized Documenter subclass for data items.
|
||||
"""
|
||||
@ -1718,7 +1736,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 self.should_suppress_directive_header():
|
||||
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:
|
||||
@ -1738,11 +1761,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
|
||||
@ -1757,6 +1775,10 @@ class DataDocumenter(ModuleLevelDocumenter):
|
||||
# suppress docstring of the value
|
||||
super().add_content(more_content, no_docstring=True)
|
||||
else:
|
||||
if not more_content:
|
||||
more_content = StringList()
|
||||
|
||||
self.update_content(more_content)
|
||||
super().add_content(more_content, no_docstring=no_docstring)
|
||||
|
||||
|
||||
@ -1804,6 +1826,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.
|
||||
@ -2009,7 +2049,7 @@ class SingledispatchMethodDocumenter(MethodDocumenter):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore
|
||||
class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter, NewTypeMixin): # type: ignore # NOQA
|
||||
"""
|
||||
Specialized Documenter subclass for attributes.
|
||||
"""
|
||||
@ -2105,7 +2145,11 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
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 self.should_suppress_directive_header():
|
||||
pass
|
||||
elif self.options.annotation:
|
||||
self.add_line(' :annotation: %s' % self.options.annotation, sourcename)
|
||||
else:
|
||||
# obtain type annotation for this attribute
|
||||
annotations = get_type_hints(self.parent, None, self.config.autodoc_type_aliases)
|
||||
if self.objpath[-1] in annotations:
|
||||
@ -2127,10 +2171,6 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
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 get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]:
|
||||
try:
|
||||
@ -2149,6 +2189,10 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
# if it's not a data descriptor, its docstring is very probably the
|
||||
# wrong thing to display
|
||||
no_docstring = True
|
||||
|
||||
if more_content is None:
|
||||
more_content = StringList()
|
||||
self.update_content(more_content)
|
||||
super().add_content(more_content, no_docstring)
|
||||
|
||||
|
||||
@ -2288,6 +2332,24 @@ class SlotsAttributeDocumenter(AttributeDocumenter):
|
||||
return []
|
||||
|
||||
|
||||
class NewTypeAttributeDocumenter(AttributeDocumenter):
|
||||
"""
|
||||
Specialized Documenter subclass for NewTypes.
|
||||
|
||||
Note: This must be invoked before MethodDocumenter because NewType is a kind of
|
||||
function object.
|
||||
"""
|
||||
|
||||
objtype = 'newvarattribute'
|
||||
directivetype = 'attribute'
|
||||
priority = MethodDocumenter.priority + 1
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
|
||||
) -> bool:
|
||||
return not isinstance(parent, ModuleDocumenter) and inspect.isNewType(member)
|
||||
|
||||
|
||||
def get_documenters(app: Sphinx) -> Dict[str, "Type[Documenter]"]:
|
||||
"""Returns registered Documenter classes"""
|
||||
warnings.warn("get_documenters() is deprecated.", RemovedInSphinx50Warning, stacklevel=2)
|
||||
@ -2317,6 +2379,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)
|
||||
@ -2325,6 +2388,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.add_autodocumenter(PropertyDocumenter)
|
||||
app.add_autodocumenter(InstanceAttributeDocumenter)
|
||||
app.add_autodocumenter(SlotsAttributeDocumenter)
|
||||
app.add_autodocumenter(NewTypeAttributeDocumenter)
|
||||
|
||||
app.add_config_value('autoclass_content', 'class', True, ENUM('both', 'class', 'init'))
|
||||
app.add_config_value('autodoc_member_order', 'alphabetical', True,
|
||||
|
@ -89,13 +89,15 @@ def setup_documenters(app: Any) -> None:
|
||||
DecoratorDocumenter, ExceptionDocumenter,
|
||||
FunctionDocumenter, GenericAliasDocumenter,
|
||||
InstanceAttributeDocumenter, MethodDocumenter,
|
||||
ModuleDocumenter, PropertyDocumenter,
|
||||
ModuleDocumenter, NewTypeAttributeDocumenter,
|
||||
NewTypeDataDocumenter, PropertyDocumenter,
|
||||
SingledispatchFunctionDocumenter, SlotsAttributeDocumenter)
|
||||
documenters = [
|
||||
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
|
||||
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
|
||||
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
|
||||
SlotsAttributeDocumenter, GenericAliasDocumenter, SingledispatchFunctionDocumenter,
|
||||
FunctionDocumenter, MethodDocumenter, NewTypeAttributeDocumenter,
|
||||
NewTypeDataDocumenter, AttributeDocumenter, InstanceAttributeDocumenter,
|
||||
DecoratorDocumenter, PropertyDocumenter, SlotsAttributeDocumenter,
|
||||
GenericAliasDocumenter, SingledispatchFunctionDocumenter,
|
||||
] # type: List[Type[Documenter]]
|
||||
for documenter in documenters:
|
||||
app.registry.add_documenter(documenter.objtype, documenter)
|
||||
|
@ -177,6 +177,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)
|
||||
|
@ -88,10 +88,14 @@ def is_system_TypeVar(typ: Any) -> bool:
|
||||
|
||||
def restify(cls: Optional["Type"]) -> str:
|
||||
"""Convert python class to a reST reference."""
|
||||
from sphinx.util import inspect # lazy loading
|
||||
|
||||
if cls is None or cls is NoneType:
|
||||
return ':obj:`None`'
|
||||
elif cls is Ellipsis:
|
||||
return '...'
|
||||
elif inspect.isNewType(cls):
|
||||
return ':class:`%s`' % cls.__name__
|
||||
elif cls.__module__ in ('__builtin__', 'builtins'):
|
||||
return ':class:`%s`' % cls.__name__
|
||||
else:
|
||||
@ -277,6 +281,8 @@ def _restify_py36(cls: Optional["Type"]) -> str:
|
||||
|
||||
def stringify(annotation: Any) -> str:
|
||||
"""Stringify type annotation object."""
|
||||
from sphinx.util import inspect # lazy loading
|
||||
|
||||
if isinstance(annotation, str):
|
||||
if annotation.startswith("'") and annotation.endswith("'"):
|
||||
# might be a double Forward-ref'ed type. Go unquoting.
|
||||
@ -285,6 +291,9 @@ def stringify(annotation: Any) -> str:
|
||||
return annotation
|
||||
elif isinstance(annotation, TypeVar):
|
||||
return annotation.__name__
|
||||
elif inspect.isNewType(annotation):
|
||||
# Could not get the module where it defiend
|
||||
return annotation.__name__
|
||||
elif not annotation:
|
||||
return repr(annotation)
|
||||
elif annotation is NoneType:
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import TypeVar
|
||||
from typing import NewType, TypeVar
|
||||
|
||||
#: T1
|
||||
T1 = TypeVar("T1")
|
||||
@ -13,3 +13,13 @@ T4 = TypeVar("T4", covariant=True)
|
||||
|
||||
#: T5
|
||||
T5 = TypeVar("T5", contravariant=True)
|
||||
|
||||
#: T6
|
||||
T6 = NewType("T6", int)
|
||||
|
||||
|
||||
class Class:
|
||||
# TODO: TypeVar
|
||||
|
||||
#: T6
|
||||
T6 = NewType("T6", int)
|
||||
|
@ -1731,6 +1731,18 @@ def test_autodoc_TypeVar(app):
|
||||
'.. py:module:: target.typevar',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Class()',
|
||||
' :module: target.typevar',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Class.T6',
|
||||
' :module: target.typevar',
|
||||
'',
|
||||
' T6',
|
||||
'',
|
||||
' alias of :class:`int`',
|
||||
'',
|
||||
'',
|
||||
'.. py:data:: T1',
|
||||
' :module: target.typevar',
|
||||
'',
|
||||
@ -1758,6 +1770,14 @@ def test_autodoc_TypeVar(app):
|
||||
' T5',
|
||||
'',
|
||||
" alias of TypeVar('T5', contravariant=True)",
|
||||
'',
|
||||
'.. py:data:: T6',
|
||||
' :module: target.typevar',
|
||||
'',
|
||||
' T6',
|
||||
'',
|
||||
' alias of :class:`int`',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
|
@ -70,3 +70,18 @@ def test_autoattribute_instance_variable(app):
|
||||
' attr4',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoattribute_NewType(app):
|
||||
actual = do_autodoc(app, 'attribute', 'target.typevar.Class.T6')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:attribute:: Class.T6',
|
||||
' :module: target.typevar',
|
||||
'',
|
||||
' T6',
|
||||
'',
|
||||
' alias of :class:`int`',
|
||||
'',
|
||||
]
|
||||
|
@ -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`',
|
||||
'',
|
||||
]
|
||||
|
@ -10,7 +10,8 @@
|
||||
|
||||
import sys
|
||||
from numbers import Integral
|
||||
from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, TypeVar, Union
|
||||
from typing import (Any, Callable, Dict, Generator, List, NewType, Optional, Tuple, TypeVar,
|
||||
Union)
|
||||
|
||||
import pytest
|
||||
|
||||
@ -26,6 +27,7 @@ class MyClass2(MyClass1):
|
||||
|
||||
|
||||
T = TypeVar('T')
|
||||
MyInt = NewType('MyInt', int)
|
||||
|
||||
|
||||
class MyList(List[T]):
|
||||
@ -92,6 +94,7 @@ def test_restify_type_hints_typevars():
|
||||
assert restify(T_co) == ":obj:`tests.test_util_typing.T_co`"
|
||||
assert restify(T_contra) == ":obj:`tests.test_util_typing.T_contra`"
|
||||
assert restify(List[T]) == ":class:`List`\\ [:obj:`tests.test_util_typing.T`]"
|
||||
assert restify(MyInt) == ":class:`MyInt`"
|
||||
|
||||
|
||||
def test_restify_type_hints_custom_class():
|
||||
@ -179,6 +182,7 @@ def test_stringify_type_hints_typevars():
|
||||
assert stringify(T_co) == "T_co"
|
||||
assert stringify(T_contra) == "T_contra"
|
||||
assert stringify(List[T]) == "List[T]"
|
||||
assert stringify(MyInt) == "MyInt"
|
||||
|
||||
|
||||
def test_stringify_type_hints_custom_class():
|
||||
|
Loading…
Reference in New Issue
Block a user