mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-07-31 16:47:59 -05:00
Merge branch '3.x'
This commit is contained in:
@@ -8,7 +8,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
name: [py36, py37, py38]
|
||||
name: [py36, py37, py38, py39]
|
||||
include:
|
||||
- name: py36
|
||||
python: 3.6
|
||||
@@ -19,6 +19,9 @@ jobs:
|
||||
- name: py38
|
||||
python: 3.8
|
||||
docutils: du15
|
||||
- name: py39
|
||||
python: 3.9
|
||||
docutils: du16
|
||||
coverage: "--cov ./ --cov-append --cov-config setup.cfg"
|
||||
env:
|
||||
PYTEST_ADDOPTS: ${{ matrix.coverage }}
|
||||
|
||||
@@ -58,12 +58,20 @@ Dependencies
|
||||
Incompatible changes
|
||||
--------------------
|
||||
|
||||
* #8105: autodoc: the signature of class constructor will be shown for decorated
|
||||
classes, not a signature of decorator
|
||||
|
||||
Deprecated
|
||||
----------
|
||||
|
||||
* The ``follow_wrapped`` argument of ``sphinx.util.inspect.signature()``
|
||||
|
||||
Features added
|
||||
--------------
|
||||
|
||||
* #8119: autodoc: Allow to determine whether a member not included in
|
||||
``__all__`` attribute of the module should be documented or not via
|
||||
:event:`autodoc-skip-member` event
|
||||
* #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
|
||||
@@ -72,6 +80,9 @@ Bugs fixed
|
||||
----------
|
||||
|
||||
* #7613: autodoc: autodoc does not respect __signature__ of the class
|
||||
* #4606: autodoc: the location of the warning is incorrect for inherited method
|
||||
* #8105: autodoc: the signature of class constructor is incorrect if the class
|
||||
is decorated
|
||||
|
||||
Testing
|
||||
--------
|
||||
@@ -94,6 +105,9 @@ Features added
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #8219: autodoc: Parameters for generic class are not shown when super class is
|
||||
a generic class and show-inheritance option is given (in Python 3.7 or above)
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
|
||||
@@ -56,6 +56,11 @@ The following is a list of deprecated interfaces.
|
||||
- 6.0
|
||||
- ``docutils.utils.smartyquotes``
|
||||
|
||||
* - The ``follow_wrapped`` argument of ``sphinx.util.inspect.signature()``
|
||||
- 3.4
|
||||
- 5.0
|
||||
- N/A
|
||||
|
||||
* - ``sphinx.builders.latex.LaTeXBuilder.usepackages``
|
||||
- 3.3
|
||||
- 5.0
|
||||
|
||||
@@ -272,9 +272,9 @@ identifier and put ``sphinx.po`` in there. Don't forget to update the possible
|
||||
values for :confval:`language` in ``doc/usage/configuration.rst``.
|
||||
|
||||
The Sphinx core messages can also be translated on `Transifex
|
||||
<https://www.transifex.com/sphinx-doc/>`_. There ``tx`` client tool, which is
|
||||
provided by the ``transifex_client`` Python package, can be used to pull
|
||||
translations in ``.po`` format from Transifex. To do this, go to
|
||||
<https://www.transifex.com/sphinx-doc/sphinx-1/>`_. There ``tx`` client tool,
|
||||
which is provided by the ``transifex_client`` Python package, can be used to
|
||||
pull translations in ``.po`` format from Transifex. To do this, go to
|
||||
``sphinx/locale`` and then run ``tx pull -f -l LANG`` where ``LANG`` is an
|
||||
existing language identifier. It is good practice to run ``python setup.py
|
||||
update_catalog`` afterwards to make sure the ``.po`` file has the canonical
|
||||
|
||||
@@ -37,7 +37,7 @@ from sphinx.util.docstrings import extract_metadata, prepare_docstring
|
||||
from sphinx.util.inspect import (
|
||||
evaluate_signature, getdoc, object_description, safe_getattr, stringify_signature
|
||||
)
|
||||
from sphinx.util.typing import stringify as stringify_typehint
|
||||
from sphinx.util.typing import restify, stringify as stringify_typehint
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sphinx.ext.autodoc.directive import DocumenterBridge
|
||||
@@ -256,6 +256,32 @@ class Options(dict):
|
||||
return None
|
||||
|
||||
|
||||
class ObjectMember(tuple):
|
||||
"""A member of object.
|
||||
|
||||
This is used for the result of `Documenter.get_object_members()` to
|
||||
represent each member of the object.
|
||||
|
||||
.. Note::
|
||||
|
||||
An instance of this class behaves as a tuple of (name, object)
|
||||
for compatibility to old Sphinx. The behavior will be dropped
|
||||
in the future. Therefore extensions should not use the tuple
|
||||
interface.
|
||||
"""
|
||||
|
||||
def __new__(cls, name: str, obj: Any, **kwargs: Any) -> Any:
|
||||
return super().__new__(cls, (name, obj)) # type: ignore
|
||||
|
||||
def __init__(self, name: str, obj: Any, skipped: bool = False) -> None:
|
||||
self.__name__ = name
|
||||
self.object = obj
|
||||
self.skipped = skipped
|
||||
|
||||
|
||||
ObjectMembers = Union[List[ObjectMember], List[Tuple[str, Any]]]
|
||||
|
||||
|
||||
class Documenter:
|
||||
"""
|
||||
A Documenter knows how to autodocument a single object type. When
|
||||
@@ -537,9 +563,18 @@ class Documenter:
|
||||
yield from docstringlines
|
||||
|
||||
def get_sourcename(self) -> str:
|
||||
if (getattr(self.object, '__module__', None) and
|
||||
getattr(self.object, '__qualname__', None)):
|
||||
# Get the correct location of docstring from self.object
|
||||
# to support inherited methods
|
||||
fullname = '%s.%s' % (self.object.__module__, self.object.__qualname__)
|
||||
else:
|
||||
fullname = self.fullname
|
||||
|
||||
if self.analyzer:
|
||||
return '%s:docstring of %s' % (self.analyzer.srcname, self.fullname)
|
||||
return 'docstring of %s' % self.fullname
|
||||
return '%s:docstring of %s' % (self.analyzer.srcname, fullname)
|
||||
else:
|
||||
return 'docstring of %s' % fullname
|
||||
|
||||
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
|
||||
"""Add content from docstrings, attribute documentation and user."""
|
||||
@@ -574,7 +609,7 @@ class Documenter:
|
||||
for line, src in zip(more_content.data, more_content.items):
|
||||
self.add_line(line, src[0], src[1])
|
||||
|
||||
def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]:
|
||||
def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]:
|
||||
"""Return `(members_check_module, members)` where `members` is a
|
||||
list of `(membername, member)` pairs of the members of *self.object*.
|
||||
|
||||
@@ -584,10 +619,10 @@ class Documenter:
|
||||
members = get_object_members(self.object, self.objpath, self.get_attr, self.analyzer)
|
||||
if not want_all:
|
||||
if not self.options.members:
|
||||
return False, []
|
||||
return False, [] # type: ignore
|
||||
# specific members given
|
||||
selected = []
|
||||
for name in self.options.members:
|
||||
for name in self.options.members: # type: str
|
||||
if name in members:
|
||||
selected.append((name, members[name].value))
|
||||
else:
|
||||
@@ -600,7 +635,7 @@ class Documenter:
|
||||
return False, [(m.name, m.value) for m in members.values()
|
||||
if m.directly_defined]
|
||||
|
||||
def filter_members(self, members: List[Tuple[str, Any]], want_all: bool
|
||||
def filter_members(self, members: ObjectMembers, want_all: bool
|
||||
) -> List[Tuple[str, Any, bool]]:
|
||||
"""Filter the given member list.
|
||||
|
||||
@@ -639,7 +674,8 @@ class Documenter:
|
||||
attr_docs = {}
|
||||
|
||||
# process members and determine which to skip
|
||||
for (membername, member) in members:
|
||||
for obj in members:
|
||||
membername, member = obj
|
||||
# if isattr is True, the member is documented as an attribute
|
||||
if member is INSTANCEATTR:
|
||||
isattr = True
|
||||
@@ -716,6 +752,10 @@ class Documenter:
|
||||
# ignore undocumented members if :undoc-members: is not given
|
||||
keep = has_doc or self.options.undoc_members
|
||||
|
||||
if isinstance(obj, ObjectMember) and obj.skipped:
|
||||
# forcedly skipped member (ex. a module attribute not defined in __all__)
|
||||
keep = False
|
||||
|
||||
# give the user a chance to decide whether this member
|
||||
# should be skipped
|
||||
if self.env.app:
|
||||
@@ -977,28 +1017,35 @@ class ModuleDocumenter(Documenter):
|
||||
if self.options.deprecated:
|
||||
self.add_line(' :deprecated:', sourcename)
|
||||
|
||||
def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]:
|
||||
def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]:
|
||||
if want_all:
|
||||
if self.__all__:
|
||||
memberlist = self.__all__
|
||||
else:
|
||||
members = get_module_members(self.object)
|
||||
if not self.__all__:
|
||||
# for implicit module members, check __module__ to avoid
|
||||
# documenting imported objects
|
||||
return True, get_module_members(self.object)
|
||||
return True, members
|
||||
else:
|
||||
ret = []
|
||||
for name, value in members:
|
||||
if name in self.__all__:
|
||||
ret.append(ObjectMember(name, value))
|
||||
else:
|
||||
ret.append(ObjectMember(name, value, skipped=True))
|
||||
|
||||
return False, ret
|
||||
else:
|
||||
memberlist = self.options.members or []
|
||||
ret = []
|
||||
for mname in memberlist:
|
||||
try:
|
||||
ret.append((mname, safe_getattr(self.object, mname)))
|
||||
except AttributeError:
|
||||
logger.warning(
|
||||
__('missing attribute mentioned in :members: or __all__: '
|
||||
'module %s, attribute %s') %
|
||||
(safe_getattr(self.object, '__name__', '???'), mname),
|
||||
type='autodoc'
|
||||
)
|
||||
return False, ret
|
||||
ret = []
|
||||
for name in memberlist:
|
||||
try:
|
||||
value = safe_getattr(self.object, name)
|
||||
ret.append(ObjectMember(name, value))
|
||||
except AttributeError:
|
||||
logger.warning(__('missing attribute mentioned in :members: option: '
|
||||
'module %s, attribute %s') %
|
||||
(safe_getattr(self.object, '__name__', '???'), name),
|
||||
type='autodoc')
|
||||
return False, ret
|
||||
|
||||
def sort_members(self, documenters: List[Tuple["Documenter", bool]],
|
||||
order: str) -> List[Tuple["Documenter", bool]]:
|
||||
@@ -1198,7 +1245,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
|
||||
try:
|
||||
self.env.app.emit('autodoc-before-process-signature', self.object, False)
|
||||
sig = inspect.signature(self.object, follow_wrapped=True,
|
||||
sig = inspect.signature(self.object,
|
||||
type_aliases=self.env.config.autodoc_type_aliases)
|
||||
args = stringify_signature(sig, **kwargs)
|
||||
except TypeError as exc:
|
||||
@@ -1512,13 +1559,16 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
if not self.doc_as_attr and self.options.show_inheritance:
|
||||
sourcename = self.get_sourcename()
|
||||
self.add_line('', sourcename)
|
||||
if hasattr(self.object, '__bases__') and len(self.object.__bases__):
|
||||
bases = [':class:`%s`' % b.__name__
|
||||
if b.__module__ in ('__builtin__', 'builtins')
|
||||
else ':class:`%s.%s`' % (b.__module__, b.__qualname__)
|
||||
for b in self.object.__bases__]
|
||||
self.add_line(' ' + _('Bases: %s') % ', '.join(bases),
|
||||
sourcename)
|
||||
|
||||
if hasattr(self.object, '__orig_bases__') and len(self.object.__orig_bases__):
|
||||
# A subclass of generic types
|
||||
# refs: PEP-560 <https://www.python.org/dev/peps/pep-0560/>
|
||||
bases = [restify(cls) for cls in self.object.__orig_bases__]
|
||||
self.add_line(' ' + _('Bases: %s') % ', '.join(bases), sourcename)
|
||||
elif hasattr(self.object, '__bases__') and len(self.object.__bases__):
|
||||
# A normal class
|
||||
bases = [restify(cls) for cls in self.object.__bases__]
|
||||
self.add_line(' ' + _('Bases: %s') % ', '.join(bases), sourcename)
|
||||
|
||||
def get_doc(self, ignore: int = None) -> List[List[str]]:
|
||||
lines = getattr(self, '_new_docstrings', None)
|
||||
@@ -1831,7 +1881,6 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
else:
|
||||
self.env.app.emit('autodoc-before-process-signature', self.object, True)
|
||||
sig = inspect.signature(self.object, bound_method=True,
|
||||
follow_wrapped=True,
|
||||
type_aliases=self.env.config.autodoc_type_aliases)
|
||||
args = stringify_signature(sig, **kwargs)
|
||||
except TypeError as exc:
|
||||
|
||||
+11
-10
@@ -62,14 +62,6 @@ def getargspec(func: Callable) -> Any:
|
||||
methods."""
|
||||
warnings.warn('sphinx.ext.inspect.getargspec() is deprecated',
|
||||
RemovedInSphinx50Warning, stacklevel=2)
|
||||
# On 3.5+, signature(int) or similar raises ValueError. On 3.4, it
|
||||
# succeeds with a bogus signature. We want a TypeError uniformly, to
|
||||
# match historical behavior.
|
||||
if (isinstance(func, type) and
|
||||
is_builtin_class_method(func, "__new__") and
|
||||
is_builtin_class_method(func, "__init__")):
|
||||
raise TypeError(
|
||||
"can't compute signature for built-in type {}".format(func))
|
||||
|
||||
sig = inspect.signature(func)
|
||||
|
||||
@@ -320,6 +312,9 @@ def isgenericalias(obj: Any) -> bool:
|
||||
elif (hasattr(types, 'GenericAlias') and # only for py39+
|
||||
isinstance(obj, types.GenericAlias)): # type: ignore
|
||||
return True
|
||||
elif (hasattr(typing, '_SpecialGenericAlias') and # for py39+
|
||||
isinstance(obj, typing._SpecialGenericAlias)): # type: ignore
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -422,14 +417,20 @@ def _should_unwrap(subject: Callable) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def signature(subject: Callable, bound_method: bool = False, follow_wrapped: bool = False,
|
||||
def signature(subject: Callable, bound_method: bool = False, follow_wrapped: bool = None,
|
||||
type_aliases: Dict = {}) -> inspect.Signature:
|
||||
"""Return a Signature object for the given *subject*.
|
||||
|
||||
:param bound_method: Specify *subject* is a bound method or not
|
||||
:param follow_wrapped: Same as ``inspect.signature()``.
|
||||
Defaults to ``False`` (get a signature of *subject*).
|
||||
"""
|
||||
|
||||
if follow_wrapped is None:
|
||||
follow_wrapped = True
|
||||
else:
|
||||
warnings.warn('The follow_wrapped argument of sphinx.util.inspect.signature() is '
|
||||
'deprecated', RemovedInSphinx50Warning, stacklevel=2)
|
||||
|
||||
try:
|
||||
try:
|
||||
if _should_unwrap(subject):
|
||||
|
||||
+194
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import sys
|
||||
import typing
|
||||
from typing import Any, Callable, Dict, Generator, List, Tuple, TypeVar, Union
|
||||
from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, TypeVar, Union
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst.states import Inliner
|
||||
@@ -30,6 +30,10 @@ else:
|
||||
ref = _ForwardRef(self.arg)
|
||||
return ref._eval_type(globalns, localns)
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Type # NOQA # for python3.5.1
|
||||
|
||||
|
||||
# An entry of Directive.option_spec
|
||||
DirectiveOption = Callable[[str], Any]
|
||||
@@ -60,6 +64,195 @@ def is_system_TypeVar(typ: Any) -> bool:
|
||||
return modname == 'typing' and isinstance(typ, TypeVar)
|
||||
|
||||
|
||||
def restify(cls: Optional["Type"]) -> str:
|
||||
"""Convert python class to a reST reference."""
|
||||
if cls is None or cls is NoneType:
|
||||
return ':obj:`None`'
|
||||
elif cls is Ellipsis:
|
||||
return '...'
|
||||
elif cls.__module__ in ('__builtin__', 'builtins'):
|
||||
return ':class:`%s`' % cls.__name__
|
||||
else:
|
||||
if sys.version_info >= (3, 7): # py37+
|
||||
return _restify_py37(cls)
|
||||
else:
|
||||
return _restify_py36(cls)
|
||||
|
||||
|
||||
def _restify_py37(cls: Optional["Type"]) -> str:
|
||||
"""Convert python class to a reST reference."""
|
||||
from sphinx.util import inspect # lazy loading
|
||||
|
||||
if (inspect.isgenericalias(cls) and
|
||||
cls.__module__ == 'typing' and cls.__origin__ is Union):
|
||||
# Union
|
||||
if len(cls.__args__) > 1 and cls.__args__[-1] is NoneType:
|
||||
if len(cls.__args__) > 2:
|
||||
args = ', '.join(restify(a) for a in cls.__args__[:-1])
|
||||
return ':obj:`Optional`\\ [:obj:`Union`\\ [%s]]' % args
|
||||
else:
|
||||
return ':obj:`Optional`\\ [%s]' % restify(cls.__args__[0])
|
||||
else:
|
||||
args = ', '.join(restify(a) for a in cls.__args__)
|
||||
return ':obj:`Union`\\ [%s]' % args
|
||||
elif inspect.isgenericalias(cls):
|
||||
if getattr(cls, '_name', None):
|
||||
if cls.__module__ == 'typing':
|
||||
text = ':class:`%s`' % cls._name
|
||||
else:
|
||||
text = ':class:`%s.%s`' % (cls.__module__, cls._name)
|
||||
else:
|
||||
text = restify(cls.__origin__)
|
||||
|
||||
if not hasattr(cls, '__args__'):
|
||||
pass
|
||||
elif all(is_system_TypeVar(a) for a in cls.__args__):
|
||||
# Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT])
|
||||
pass
|
||||
elif cls.__module__ == 'typing' and cls._name == 'Callable':
|
||||
args = ', '.join(restify(a) for a in cls.__args__[:-1])
|
||||
text += r"\ [[%s], %s]" % (args, restify(cls.__args__[-1]))
|
||||
elif cls.__args__:
|
||||
text += r"\ [%s]" % ", ".join(restify(a) for a in cls.__args__)
|
||||
|
||||
return text
|
||||
elif hasattr(cls, '__qualname__'):
|
||||
if cls.__module__ == 'typing':
|
||||
return ':class:`%s`' % cls.__qualname__
|
||||
else:
|
||||
return ':class:`%s.%s`' % (cls.__module__, cls.__qualname__)
|
||||
elif hasattr(cls, '_name'):
|
||||
# SpecialForm
|
||||
if cls.__module__ == 'typing':
|
||||
return ':obj:`%s`' % cls._name
|
||||
else:
|
||||
return ':obj:`%s.%s`' % (cls.__module__, cls._name)
|
||||
else:
|
||||
# not a class (ex. TypeVar)
|
||||
return ':obj:`%s.%s`' % (cls.__module__, cls.__name__)
|
||||
|
||||
|
||||
def _restify_py36(cls: Optional["Type"]) -> str:
|
||||
module = getattr(cls, '__module__', None)
|
||||
if module == 'typing':
|
||||
if getattr(cls, '_name', None):
|
||||
qualname = cls._name
|
||||
elif getattr(cls, '__qualname__', None):
|
||||
qualname = cls.__qualname__
|
||||
elif getattr(cls, '__forward_arg__', None):
|
||||
qualname = cls.__forward_arg__
|
||||
elif getattr(cls, '__origin__', None):
|
||||
qualname = stringify(cls.__origin__) # ex. Union
|
||||
else:
|
||||
qualname = repr(cls).replace('typing.', '')
|
||||
elif hasattr(cls, '__qualname__'):
|
||||
qualname = '%s.%s' % (module, cls.__qualname__)
|
||||
else:
|
||||
qualname = repr(cls)
|
||||
|
||||
if (isinstance(cls, typing.TupleMeta) and # type: ignore
|
||||
not hasattr(cls, '__tuple_params__')): # for Python 3.6
|
||||
params = cls.__args__
|
||||
if params:
|
||||
param_str = ', '.join(restify(p) for p in params)
|
||||
return ':class:`%s`\\ [%s]' % (qualname, param_str)
|
||||
else:
|
||||
return ':class:`%s`' % qualname
|
||||
elif isinstance(cls, typing.GenericMeta):
|
||||
params = None
|
||||
if hasattr(cls, '__args__'):
|
||||
# for Python 3.5.2+
|
||||
if cls.__args__ is None or len(cls.__args__) <= 2: # type: ignore # NOQA
|
||||
params = cls.__args__ # type: ignore
|
||||
elif cls.__origin__ == Generator: # type: ignore
|
||||
params = cls.__args__ # type: ignore
|
||||
else: # typing.Callable
|
||||
args = ', '.join(restify(arg) for arg in cls.__args__[:-1]) # type: ignore
|
||||
result = restify(cls.__args__[-1]) # type: ignore
|
||||
return ':class:`%s`\\ [[%s], %s]' % (qualname, args, result)
|
||||
elif hasattr(cls, '__parameters__'):
|
||||
# for Python 3.5.0 and 3.5.1
|
||||
params = cls.__parameters__ # type: ignore
|
||||
|
||||
if params:
|
||||
param_str = ', '.join(restify(p) for p in params)
|
||||
return ':class:`%s`\\ [%s]' % (qualname, param_str)
|
||||
else:
|
||||
return ':class:`%s`' % qualname
|
||||
elif (hasattr(typing, 'UnionMeta') and
|
||||
isinstance(cls, typing.UnionMeta) and # type: ignore
|
||||
hasattr(cls, '__union_params__')): # for Python 3.5
|
||||
params = cls.__union_params__
|
||||
if params is not None:
|
||||
if len(params) == 2 and params[1] is NoneType:
|
||||
return ':obj:`Optional`\\ [%s]' % restify(params[0])
|
||||
else:
|
||||
param_str = ', '.join(restify(p) for p in params)
|
||||
return ':obj:`%s`\\ [%s]' % (qualname, param_str)
|
||||
else:
|
||||
return ':obj:`%s`' % qualname
|
||||
elif (hasattr(cls, '__origin__') and
|
||||
cls.__origin__ is typing.Union): # for Python 3.5.2+
|
||||
params = cls.__args__
|
||||
if params is not None:
|
||||
if len(params) > 1 and params[-1] is NoneType:
|
||||
if len(params) > 2:
|
||||
param_str = ", ".join(restify(p) for p in params[:-1])
|
||||
return ':obj:`Optional`\\ [:obj:`Union`\\ [%s]]' % param_str
|
||||
else:
|
||||
return ':obj:`Optional`\\ [%s]' % restify(params[0])
|
||||
else:
|
||||
param_str = ', '.join(restify(p) for p in params)
|
||||
return ':obj:`Union`\\ [%s]' % param_str
|
||||
else:
|
||||
return ':obj:`Union`'
|
||||
elif (isinstance(cls, typing.CallableMeta) and # type: ignore
|
||||
getattr(cls, '__args__', None) is not None and
|
||||
hasattr(cls, '__result__')): # for Python 3.5
|
||||
# Skipped in the case of plain typing.Callable
|
||||
args = cls.__args__
|
||||
if args is None:
|
||||
return qualname
|
||||
elif args is Ellipsis:
|
||||
args_str = '...'
|
||||
else:
|
||||
formatted_args = (restify(a) for a in args) # type: ignore
|
||||
args_str = '[%s]' % ', '.join(formatted_args)
|
||||
|
||||
return ':class:`%s`\\ [%s, %s]' % (qualname, args_str, stringify(cls.__result__))
|
||||
elif (isinstance(cls, typing.TupleMeta) and # type: ignore
|
||||
hasattr(cls, '__tuple_params__') and
|
||||
hasattr(cls, '__tuple_use_ellipsis__')): # for Python 3.5
|
||||
params = cls.__tuple_params__
|
||||
if params is not None:
|
||||
param_strings = [restify(p) for p in params]
|
||||
if cls.__tuple_use_ellipsis__:
|
||||
param_strings.append('...')
|
||||
return ':class:`%s`\\ [%s]' % (qualname, ', '.join(param_strings))
|
||||
else:
|
||||
return ':class:`%s`' % qualname
|
||||
elif hasattr(cls, '__qualname__'):
|
||||
if cls.__module__ == 'typing':
|
||||
return ':class:`%s`' % cls.__qualname__
|
||||
else:
|
||||
return ':class:`%s.%s`' % (cls.__module__, cls.__qualname__)
|
||||
elif hasattr(cls, '_name'):
|
||||
# SpecialForm
|
||||
if cls.__module__ == 'typing':
|
||||
return ':obj:`%s`' % cls._name
|
||||
else:
|
||||
return ':obj:`%s.%s`' % (cls.__module__, cls._name)
|
||||
elif hasattr(cls, '__name__'):
|
||||
# not a class (ex. TypeVar)
|
||||
return ':obj:`%s.%s`' % (cls.__module__, cls.__name__)
|
||||
else:
|
||||
# others (ex. Any)
|
||||
if cls.__module__ == 'typing':
|
||||
return ':obj:`%s`' % qualname
|
||||
else:
|
||||
return ':obj:`%s.%s`' % (cls.__module__, qualname)
|
||||
|
||||
|
||||
def stringify(annotation: Any) -> str:
|
||||
"""Stringify type annotation object."""
|
||||
if isinstance(annotation, str):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from inspect import Parameter, Signature
|
||||
from typing import List, Union
|
||||
|
||||
|
||||
class Foo:
|
||||
@@ -21,3 +22,8 @@ class Qux:
|
||||
|
||||
def __init__(self, x, y):
|
||||
pass
|
||||
|
||||
|
||||
class Quux(List[Union[int, float]]):
|
||||
"""A subclass of List[Union[int, float]]"""
|
||||
pass
|
||||
|
||||
@@ -29,3 +29,25 @@ class Bar:
|
||||
@deco1
|
||||
def meth(self, name=None, age=None):
|
||||
pass
|
||||
|
||||
|
||||
class Baz:
|
||||
@deco1
|
||||
def __init__(self, name=None, age=None):
|
||||
pass
|
||||
|
||||
|
||||
class Qux:
|
||||
@deco1
|
||||
def __new__(self, name=None, age=None):
|
||||
pass
|
||||
|
||||
|
||||
class _Metaclass(type):
|
||||
@deco1
|
||||
def __call__(self, name=None, age=None):
|
||||
pass
|
||||
|
||||
|
||||
class Quux(metaclass=_Metaclass):
|
||||
pass
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Tuple, Union
|
||||
from typing import Any, Tuple, Union
|
||||
|
||||
|
||||
def incr(a: int, b: int = 1) -> int:
|
||||
@@ -11,7 +11,7 @@ def decr(a, b = 1):
|
||||
|
||||
|
||||
class Math:
|
||||
def __init__(self, s: str, o: object = None) -> None:
|
||||
def __init__(self, s: str, o: Any = None) -> None:
|
||||
pass
|
||||
|
||||
def incr(self, a: int, b: int = 1) -> int:
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from test_ext_autodoc import do_autodoc
|
||||
@@ -48,3 +50,45 @@ def test_classes(app):
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
def test_decorators(app):
|
||||
actual = do_autodoc(app, 'class', 'target.decorator.Baz')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Baz(name=None, age=None)',
|
||||
' :module: target.decorator',
|
||||
'',
|
||||
]
|
||||
|
||||
actual = do_autodoc(app, 'class', 'target.decorator.Qux')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Qux(name=None, age=None)',
|
||||
' :module: target.decorator',
|
||||
'',
|
||||
]
|
||||
|
||||
actual = do_autodoc(app, 'class', 'target.decorator.Quux')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Quux(name=None, age=None)',
|
||||
' :module: target.decorator',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_show_inheritance_for_subclass_of_generic_type(app):
|
||||
options = {'show-inheritance': True}
|
||||
actual = do_autodoc(app, 'class', 'target.classes.Quux', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Quux(iterable=(), /)',
|
||||
' :module: target.classes',
|
||||
'',
|
||||
' Bases: :class:`List`\\ [:obj:`Union`\\ [:class:`int`, :class:`float`]]',
|
||||
'',
|
||||
' A subclass of List[Union[int, float]]',
|
||||
'',
|
||||
]
|
||||
|
||||
@@ -490,7 +490,7 @@ def test_autodoc_typehints_signature(app):
|
||||
'.. py:module:: target.typehints',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Math(s: str, o: object = None)',
|
||||
'.. py:class:: Math(s: str, o: Any = None)',
|
||||
' :module: target.typehints',
|
||||
'',
|
||||
'',
|
||||
|
||||
@@ -80,3 +80,28 @@ def test_between_exclude(app):
|
||||
' third line',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_skip_module_member(app):
|
||||
def autodoc_skip_member(app, what, name, obj, skip, options):
|
||||
if name == "Class":
|
||||
return True # Skip "Class" class in __all__
|
||||
elif name == "raises":
|
||||
return False # Show "raises()" function (not in __all__)
|
||||
|
||||
app.connect('autodoc-skip-member', autodoc_skip_member)
|
||||
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'module', 'target', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: raises(exc, func, *args, **kwds)',
|
||||
' :module: target',
|
||||
'',
|
||||
' Raise AssertionError if ``func(*args, **kwds)`` does not raise *exc*.',
|
||||
'',
|
||||
]
|
||||
|
||||
@@ -100,7 +100,7 @@ def test_signature_methods():
|
||||
|
||||
# wrapped bound method
|
||||
sig = inspect.signature(wrapped_bound_method)
|
||||
assert stringify_signature(sig) == '(*args, **kwargs)'
|
||||
assert stringify_signature(sig) == '(arg1, **kwargs)'
|
||||
|
||||
|
||||
def test_signature_partialmethod():
|
||||
|
||||
@@ -16,7 +16,7 @@ from typing import (
|
||||
|
||||
import pytest
|
||||
|
||||
from sphinx.util.typing import stringify
|
||||
from sphinx.util.typing import restify, stringify
|
||||
|
||||
|
||||
class MyClass1:
|
||||
@@ -36,6 +36,80 @@ class BrokenType:
|
||||
__args__ = int
|
||||
|
||||
|
||||
def test_restify():
|
||||
assert restify(int) == ":class:`int`"
|
||||
assert restify(str) == ":class:`str`"
|
||||
assert restify(None) == ":obj:`None`"
|
||||
assert restify(Integral) == ":class:`numbers.Integral`"
|
||||
assert restify(Any) == ":obj:`Any`"
|
||||
|
||||
|
||||
def test_restify_type_hints_containers():
|
||||
assert restify(List) == ":class:`List`"
|
||||
assert restify(Dict) == ":class:`Dict`"
|
||||
assert restify(List[int]) == ":class:`List`\\ [:class:`int`]"
|
||||
assert restify(List[str]) == ":class:`List`\\ [:class:`str`]"
|
||||
assert restify(Dict[str, float]) == ":class:`Dict`\\ [:class:`str`, :class:`float`]"
|
||||
assert restify(Tuple[str, str, str]) == ":class:`Tuple`\\ [:class:`str`, :class:`str`, :class:`str`]"
|
||||
assert restify(Tuple[str, ...]) == ":class:`Tuple`\\ [:class:`str`, ...]"
|
||||
assert restify(List[Dict[str, Tuple]]) == ":class:`List`\\ [:class:`Dict`\\ [:class:`str`, :class:`Tuple`]]"
|
||||
assert restify(MyList[Tuple[int, int]]) == ":class:`test_util_typing.MyList`\\ [:class:`Tuple`\\ [:class:`int`, :class:`int`]]"
|
||||
assert restify(Generator[None, None, None]) == ":class:`Generator`\\ [:obj:`None`, :obj:`None`, :obj:`None`]"
|
||||
|
||||
|
||||
def test_restify_type_hints_Callable():
|
||||
assert restify(Callable) == ":class:`Callable`"
|
||||
|
||||
if sys.version_info >= (3, 7):
|
||||
assert restify(Callable[[str], int]) == ":class:`Callable`\\ [[:class:`str`], :class:`int`]"
|
||||
assert restify(Callable[..., int]) == ":class:`Callable`\\ [[...], :class:`int`]"
|
||||
else:
|
||||
assert restify(Callable[[str], int]) == ":class:`Callable`\\ [:class:`str`, :class:`int`]"
|
||||
assert restify(Callable[..., int]) == ":class:`Callable`\\ [..., :class:`int`]"
|
||||
|
||||
|
||||
def test_restify_type_hints_Union():
|
||||
assert restify(Optional[int]) == ":obj:`Optional`\\ [:class:`int`]"
|
||||
assert restify(Union[str, None]) == ":obj:`Optional`\\ [:class:`str`]"
|
||||
assert restify(Union[int, str]) == ":obj:`Union`\\ [:class:`int`, :class:`str`]"
|
||||
|
||||
if sys.version_info >= (3, 7):
|
||||
assert restify(Union[int, Integral]) == ":obj:`Union`\\ [:class:`int`, :class:`numbers.Integral`]"
|
||||
assert (restify(Union[MyClass1, MyClass2]) ==
|
||||
":obj:`Union`\\ [:class:`test_util_typing.MyClass1`, :class:`test_util_typing.<MyClass2>`]")
|
||||
else:
|
||||
assert restify(Union[int, Integral]) == ":class:`numbers.Integral`"
|
||||
assert restify(Union[MyClass1, MyClass2]) == ":class:`test_util_typing.MyClass1`"
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
|
||||
def test_restify_type_hints_typevars():
|
||||
T = TypeVar('T')
|
||||
T_co = TypeVar('T_co', covariant=True)
|
||||
T_contra = TypeVar('T_contra', contravariant=True)
|
||||
|
||||
assert restify(T) == ":obj:`test_util_typing.T`"
|
||||
assert restify(T_co) == ":obj:`test_util_typing.T_co`"
|
||||
assert restify(T_contra) == ":obj:`test_util_typing.T_contra`"
|
||||
assert restify(List[T]) == ":class:`List`\\ [:obj:`test_util_typing.T`]"
|
||||
|
||||
|
||||
def test_restify_type_hints_custom_class():
|
||||
assert restify(MyClass1) == ":class:`test_util_typing.MyClass1`"
|
||||
assert restify(MyClass2) == ":class:`test_util_typing.<MyClass2>`"
|
||||
|
||||
|
||||
def test_restify_type_hints_alias():
|
||||
MyStr = str
|
||||
MyTuple = Tuple[str, str]
|
||||
assert restify(MyStr) == ":class:`str`"
|
||||
assert restify(MyTuple) == ":class:`Tuple`\\ [:class:`str`, :class:`str`]" # type: ignore
|
||||
|
||||
|
||||
def test_restify_broken_type_hints():
|
||||
assert restify(BrokenType) == ':class:`test_util_typing.BrokenType`'
|
||||
|
||||
|
||||
def test_stringify():
|
||||
assert stringify(int) == "int"
|
||||
assert stringify(str) == "str"
|
||||
|
||||
Reference in New Issue
Block a user