Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #9362: autodoc: AttributeError is raised for a subclass of Tuple[()] #9415

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES
Expand Up @@ -88,6 +88,7 @@ Bugs fixed
attribute not having any comment
* #9364: autodoc: single element tuple on the default argument value is wrongly
rendered
* #9362: autodoc: AttributeError is raised on processing a subclass of Tuple[()]
* #9317: html: Pushing left key causes visiting the next page at the first page
* #9381: html: URL for html_favicon and html_log does not work
* #9270: html theme : pyramid theme generates incorrect logo links
Expand Down
43 changes: 23 additions & 20 deletions sphinx/util/typing.py
Expand Up @@ -105,27 +105,30 @@ 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 cls in INVALID_BUILTIN_CLASSES:
return ':class:`%s`' % INVALID_BUILTIN_CLASSES[cls]
elif inspect.isNewType(cls):
return ':class:`%s`' % cls.__name__
elif types_Union and isinstance(cls, types_Union):
if len(cls.__args__) > 1 and None in cls.__args__:
args = ' | '.join(restify(a) for a in cls.__args__ if a)
return 'Optional[%s]' % args
else:
return ' | '.join(restify(a) for a in cls.__args__)
elif cls.__module__ in ('__builtin__', 'builtins'):
return ':class:`%s`' % cls.__name__
else:
if sys.version_info >= (3, 7): # py37+
return _restify_py37(cls)
try:
if cls is None or cls is NoneType:
return ':obj:`None`'
elif cls is Ellipsis:
return '...'
elif cls in INVALID_BUILTIN_CLASSES:
return ':class:`%s`' % INVALID_BUILTIN_CLASSES[cls]
elif inspect.isNewType(cls):
return ':class:`%s`' % cls.__name__
elif types_Union and isinstance(cls, types_Union):
if len(cls.__args__) > 1 and None in cls.__args__:
args = ' | '.join(restify(a) for a in cls.__args__ if a)
return 'Optional[%s]' % args
else:
return ' | '.join(restify(a) for a in cls.__args__)
elif cls.__module__ in ('__builtin__', 'builtins'):
return ':class:`%s`' % cls.__name__
else:
return _restify_py36(cls)
if sys.version_info >= (3, 7): # py37+
return _restify_py37(cls)
else:
return _restify_py36(cls)
except AttributeError:
return repr(cls)


def _restify_py37(cls: Optional[Type]) -> str:
Expand Down
2 changes: 2 additions & 0 deletions tests/test_util_typing.py
Expand Up @@ -60,6 +60,7 @@ def test_restify_type_hints_containers():
assert restify(Tuple[str, str, str]) == (":class:`~typing.Tuple`\\ "
"[:class:`str`, :class:`str`, :class:`str`]")
assert restify(Tuple[str, ...]) == ":class:`~typing.Tuple`\\ [:class:`str`, ...]"
assert restify(Tuple[()]) == ":class:`~typing.Tuple`\\ [()]"
assert restify(List[Dict[str, Tuple]]) == (":class:`~typing.List`\\ "
"[:class:`~typing.Dict`\\ "
"[:class:`str`, :class:`~typing.Tuple`]]")
Expand Down Expand Up @@ -168,6 +169,7 @@ def test_stringify_type_hints_containers():
assert stringify(Dict[str, float]) == "Dict[str, float]"
assert stringify(Tuple[str, str, str]) == "Tuple[str, str, str]"
assert stringify(Tuple[str, ...]) == "Tuple[str, ...]"
assert stringify(Tuple[()]) == "Tuple[()]"
assert stringify(List[Dict[str, Tuple]]) == "List[Dict[str, Tuple]]"
assert stringify(MyList[Tuple[int, int]]) == "tests.test_util_typing.MyList[Tuple[int, int]]"
assert stringify(Generator[None, None, None]) == "Generator[None, None, None]"
Expand Down