Merge branch '3.x' into 7701_anonymous_indirect_target

This commit is contained in:
Takeshi KOMIYA 2020-05-30 01:22:39 +09:00 committed by GitHub
commit 28fe0d6399
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 438 additions and 121 deletions

21
CHANGES
View File

@ -45,6 +45,8 @@ Features added
* #7487: autodoc: Allow to generate docs for singledispatch functions by * #7487: autodoc: Allow to generate docs for singledispatch functions by
py:autofunction py:autofunction
* #7143: autodoc: Support final classes and methods * #7143: autodoc: Support final classes and methods
* #7384: autodoc: Support signatures defined by ``__new__()``, metaclasses and
builtin base classes
* #7466: autosummary: headings in generated documents are not translated * #7466: autosummary: headings in generated documents are not translated
* #7490: autosummary: Add ``:caption:`` option to autosummary directive to set a * #7490: autosummary: Add ``:caption:`` option to autosummary directive to set a
caption to the toctree caption to the toctree
@ -75,6 +77,7 @@ Features added
:rst:dir:`py:exception:` and :rst:dir:`py:method:` directives :rst:dir:`py:exception:` and :rst:dir:`py:method:` directives
* #7596: py domain: Change a type annotation for variables to a hyperlink * #7596: py domain: Change a type annotation for variables to a hyperlink
* #7582: napoleon: a type for attribute are represented like type annotation * #7582: napoleon: a type for attribute are represented like type annotation
* #7734: napoleon: overescaped trailing underscore on attribute
* #7683: Add ``allowed_exceptions`` parameter to ``Sphinx.emit()`` to allow * #7683: Add ``allowed_exceptions`` parameter to ``Sphinx.emit()`` to allow
handlers to raise specified exceptions handlers to raise specified exceptions
@ -100,8 +103,11 @@ Bugs fixed
* #7676: autodoc: typo in the default value of autodoc_member_order * #7676: autodoc: typo in the default value of autodoc_member_order
* #7676: autodoc: wrong value for :member-order: option is ignored silently * #7676: autodoc: wrong value for :member-order: option is ignored silently
* #7676: autodoc: member-order="bysource" does not work for C module * #7676: autodoc: member-order="bysource" does not work for C module
* #3673: autodoc: member-order="bysource" does not work for a module having
__all__
* #7668: autodoc: wrong retann value is passed to a handler of * #7668: autodoc: wrong retann value is passed to a handler of
autodoc-proccess-signature autodoc-proccess-signature
* #7711: autodoc: fails with ValueError when processing numpy objects
* #7551: autosummary: a nested class is indexed as non-nested class * #7551: autosummary: a nested class is indexed as non-nested class
* #7661: autosummary: autosummary directive emits warnings twices if failed to * #7661: autosummary: autosummary directive emits warnings twices if failed to
import the target module import the target module
@ -117,6 +123,7 @@ Bugs fixed
supporting images supporting images
* #7610: incorrectly renders consecutive backslashes for docutils-0.16 * #7610: incorrectly renders consecutive backslashes for docutils-0.16
* #7646: handle errors on event handlers * #7646: handle errors on event handlers
* #4187: LaTeX: EN DASH disappears from PDF bookmarks in Japanese documents
* #7701: LaTeX: Anonymous indirect hyperlink target causes duplicated labels * #7701: LaTeX: Anonymous indirect hyperlink target causes duplicated labels
* C++, fix rendering and xrefs in nested names explicitly starting * C++, fix rendering and xrefs in nested names explicitly starting
in global scope, e.g., ``::A::B``. in global scope, e.g., ``::A::B``.
@ -126,7 +133,7 @@ Bugs fixed
Testing Testing
-------- --------
Release 3.0.4 (in development) Release 3.0.5 (in development)
============================== ==============================
Dependencies Dependencies
@ -144,15 +151,21 @@ Features added
Bugs fixed Bugs fixed
---------- ----------
Testing
--------
Release 3.0.4 (released May 27, 2020)
=====================================
Bugs fixed
----------
* #7567: autodoc: parametrized types are shown twice for generic types * #7567: autodoc: parametrized types are shown twice for generic types
* #7637: autodoc: system defined TypeVars are shown in Python 3.9 * #7637: autodoc: system defined TypeVars are shown in Python 3.9
* #7696: html: Updated jQuery version from 3.4.1 to 3.5.1 for security reasons * #7696: html: Updated jQuery version from 3.4.1 to 3.5.1 for security reasons
* #7611: md5 fails when OpenSSL FIPS is enabled * #7611: md5 fails when OpenSSL FIPS is enabled
* #7626: release package does not contain ``CODE_OF_CONDUCT`` * #7626: release package does not contain ``CODE_OF_CONDUCT``
Testing
--------
Release 3.0.3 (released Apr 26, 2020) Release 3.0.3 (released Apr 26, 2020)
===================================== =====================================

View File

@ -575,7 +575,7 @@ General configuration
A dictionary of options that modify how the lexer specified by A dictionary of options that modify how the lexer specified by
:confval:`highlight_language` generates highlighted source code. These are :confval:`highlight_language` generates highlighted source code. These are
lexer-specific; for the options understood by each, see the lexer-specific; for the options understood by each, see the
`Pygments documentation <https://pygments.org/docs/lexers.html>`_. `Pygments documentation <https://pygments.org/docs/lexers>`_.
.. versionadded:: 1.3 .. versionadded:: 1.3

View File

@ -202,6 +202,7 @@ setup(
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy', 'Programming Language :: Python :: Implementation :: PyPy',
'Framework :: Setuptools Plugin', 'Framework :: Setuptools Plugin',

View File

@ -968,12 +968,14 @@ class Sphinx:
self.add_css_file(filename, **attributes) self.add_css_file(filename, **attributes)
def add_latex_package(self, packagename: str, options: str = None) -> None: def add_latex_package(self, packagename: str, options: str = None,
after_hyperref: bool = False) -> None:
r"""Register a package to include in the LaTeX source code. r"""Register a package to include in the LaTeX source code.
Add *packagename* to the list of packages that LaTeX source code will Add *packagename* to the list of packages that LaTeX source code will
include. If you provide *options*, it will be taken to `\usepackage` include. If you provide *options*, it will be taken to `\usepackage`
declaration. declaration. If you set *after_hyperref* truthy, the package will be
loaded after ``hyperref`` package.
.. code-block:: python .. code-block:: python
@ -983,8 +985,11 @@ class Sphinx:
# => \usepackage[foo,bar]{mypackage} # => \usepackage[foo,bar]{mypackage}
.. versionadded:: 1.3 .. versionadded:: 1.3
.. versionadded:: 3.1
*after_hyperref* option.
""" """
self.registry.add_latex_package(packagename, options) self.registry.add_latex_package(packagename, options, after_hyperref)
def add_lexer(self, alias: str, lexer: Union[Lexer, "Type[Lexer]"]) -> None: def add_lexer(self, alias: str, lexer: Union[Lexer, "Type[Lexer]"]) -> None:
"""Register a new lexer for source code. """Register a new lexer for source code.

View File

@ -129,6 +129,7 @@ class LaTeXBuilder(Builder):
self.document_data = [] # type: List[Tuple[str, str, str, str, str, bool]] self.document_data = [] # type: List[Tuple[str, str, str, str, str, bool]]
self.themes = ThemeFactory(self.app) self.themes = ThemeFactory(self.app)
self.usepackages = self.app.registry.latex_packages self.usepackages = self.app.registry.latex_packages
self.usepackages_after_hyperref = self.app.registry.latex_packages_after_hyperref
texescape.init() texescape.init()
self.init_context() self.init_context()
@ -180,6 +181,7 @@ class LaTeXBuilder(Builder):
# Apply extension settings to context # Apply extension settings to context
self.context['packages'] = self.usepackages self.context['packages'] = self.usepackages
self.context['packages_after_hyperref'] = self.usepackages_after_hyperref
# Apply user settings to context # Apply user settings to context
self.context.update(self.config.latex_elements) self.context.update(self.config.latex_elements)
@ -501,6 +503,12 @@ def validate_latex_theme_options(app: Sphinx, config: Config) -> None:
config.latex_theme_options.pop(key) config.latex_theme_options.pop(key)
def install_pakcages_for_ja(app: Sphinx) -> None:
"""Install packages for Japanese."""
if app.config.language == 'ja':
app.add_latex_package('pxjahyper', after_hyperref=True)
def default_latex_engine(config: Config) -> str: def default_latex_engine(config: Config) -> str:
""" Better default latex_engine settings for specific languages. """ """ Better default latex_engine settings for specific languages. """
if config.language == 'ja': if config.language == 'ja':
@ -548,6 +556,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_builder(LaTeXBuilder) app.add_builder(LaTeXBuilder)
app.connect('config-inited', validate_config_values, priority=800) app.connect('config-inited', validate_config_values, priority=800)
app.connect('config-inited', validate_latex_theme_options, priority=800) app.connect('config-inited', validate_latex_theme_options, priority=800)
app.connect('builder-inited', install_pakcages_for_ja)
app.add_config_value('latex_engine', default_latex_engine, None, app.add_config_value('latex_engine', default_latex_engine, None,
ENUM('pdflatex', 'xelatex', 'lualatex', 'platex', 'uplatex')) ENUM('pdflatex', 'xelatex', 'lualatex', 'platex', 'uplatex'))

View File

@ -13,7 +13,7 @@
import importlib import importlib
import re import re
import warnings import warnings
from inspect import Parameter from inspect import Parameter, Signature
from types import ModuleType from types import ModuleType
from typing import ( from typing import (
Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union
@ -392,6 +392,17 @@ class Documenter:
# directives of course) # directives of course)
return '.'.join(self.objpath) or self.modname return '.'.join(self.objpath) or self.modname
def _call_format_args(self, **kwargs: Any) -> str:
if kwargs:
try:
return self.format_args(**kwargs)
except TypeError:
# avoid chaining exceptions, by putting nothing here
pass
# retry without arguments for old documenters
return self.format_args()
def format_signature(self, **kwargs: Any) -> str: def format_signature(self, **kwargs: Any) -> str:
"""Format the signature (arguments and return annotation) of the object. """Format the signature (arguments and return annotation) of the object.
@ -405,12 +416,7 @@ class Documenter:
# try to introspect the signature # try to introspect the signature
try: try:
retann = None retann = None
try: args = self._call_format_args(**kwargs)
args = self.format_args(**kwargs)
except TypeError:
# retry without arguments for old documenters
args = self.format_args()
if args: if args:
matched = re.match(r'^(\(.*\))\s+->\s+(.*)$', args) matched = re.match(r'^(\(.*\))\s+->\s+(.*)$', args)
if matched: if matched:
@ -714,29 +720,9 @@ class Documenter:
'.'.join(self.objpath + [mname]) '.'.join(self.objpath + [mname])
documenter = classes[-1](self.directive, full_mname, self.indent) documenter = classes[-1](self.directive, full_mname, self.indent)
memberdocumenters.append((documenter, isattr)) memberdocumenters.append((documenter, isattr))
member_order = self.options.member_order or \
self.env.config.autodoc_member_order
if member_order == 'groupwise':
# sort by group; alphabetically within groups
memberdocumenters.sort(key=lambda e: (e[0].member_order, e[0].name))
elif member_order == 'bysource':
if self.analyzer:
# sort by source order, by virtue of the module analyzer
tagorder = self.analyzer.tagorder
def keyfunc(entry: Tuple[Documenter, bool]) -> int: member_order = self.options.member_order or self.env.config.autodoc_member_order
fullname = entry[0].name.split('::')[1] memberdocumenters = self.sort_members(memberdocumenters, member_order)
return tagorder.get(fullname, len(tagorder))
memberdocumenters.sort(key=keyfunc)
else:
# Assume that member discovery order matches source order.
# This is a reasonable assumption in Python 3.6 and up, where
# module.__dict__ is insertion-ordered.
pass
elif member_order == 'alphabetical':
memberdocumenters.sort(key=lambda e: e[0].name)
else:
raise ValueError("Illegal member order {}".format(member_order))
for documenter, isattr in memberdocumenters: for documenter, isattr in memberdocumenters:
documenter.generate( documenter.generate(
@ -747,6 +733,31 @@ class Documenter:
self.env.temp_data['autodoc:module'] = None self.env.temp_data['autodoc:module'] = None
self.env.temp_data['autodoc:class'] = None self.env.temp_data['autodoc:class'] = None
def sort_members(self, documenters: List[Tuple["Documenter", bool]],
order: str) -> List[Tuple["Documenter", bool]]:
"""Sort the given member list."""
if order == 'groupwise':
# sort by group; alphabetically within groups
documenters.sort(key=lambda e: (e[0].member_order, e[0].name))
elif order == 'bysource':
if self.analyzer:
# sort by source order, by virtue of the module analyzer
tagorder = self.analyzer.tagorder
def keyfunc(entry: Tuple[Documenter, bool]) -> int:
fullname = entry[0].name.split('::')[1]
return tagorder.get(fullname, len(tagorder))
documenters.sort(key=keyfunc)
else:
# Assume that member discovery order matches source order.
# This is a reasonable assumption in Python 3.6 and up, where
# module.__dict__ is insertion-ordered.
pass
else: # alphabetical
documenters.sort(key=lambda e: e[0].name)
return documenters
def generate(self, more_content: Any = None, real_modname: str = None, def generate(self, more_content: Any = None, real_modname: str = None,
check_module: bool = False, all_members: bool = False) -> None: check_module: bool = False, all_members: bool = False) -> None:
"""Generate reST for the object given by *self.name*, and possibly for """Generate reST for the object given by *self.name*, and possibly for
@ -852,6 +863,7 @@ class ModuleDocumenter(Documenter):
def __init__(self, *args: Any) -> None: def __init__(self, *args: Any) -> None:
super().__init__(*args) super().__init__(*args)
merge_special_members_option(self.options) merge_special_members_option(self.options)
self.__all__ = None
@classmethod @classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
@ -874,6 +886,30 @@ class ModuleDocumenter(Documenter):
type='autodoc') type='autodoc')
return ret return ret
def import_object(self) -> Any:
def is_valid_module_all(__all__: Any) -> bool:
"""Check the given *__all__* is valid for a module."""
if (isinstance(__all__, (list, tuple)) and
all(isinstance(e, str) for e in __all__)):
return True
else:
return False
ret = super().import_object()
if not self.options.ignore_module_all:
__all__ = getattr(self.object, '__all__', None)
if is_valid_module_all(__all__):
# valid __all__ found. copy it to self.__all__
self.__all__ = __all__
elif __all__:
# invalid __all__ found.
logger.warning(__('__all__ should be a list of strings, not %r '
'(in module %s) -- ignoring __all__') %
(__all__, self.fullname), type='autodoc')
return ret
def add_directive_header(self, sig: str) -> None: def add_directive_header(self, sig: str) -> None:
Documenter.add_directive_header(self, sig) Documenter.add_directive_header(self, sig)
@ -889,24 +925,12 @@ class ModuleDocumenter(Documenter):
def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]: def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]:
if want_all: if want_all:
if (self.options.ignore_module_all or not if self.__all__:
hasattr(self.object, '__all__')): memberlist = self.__all__
else:
# for implicit module members, check __module__ to avoid # for implicit module members, check __module__ to avoid
# documenting imported objects # documenting imported objects
return True, get_module_members(self.object) return True, get_module_members(self.object)
else:
memberlist = self.object.__all__
# Sometimes __all__ is broken...
if not isinstance(memberlist, (list, tuple)) or not \
all(isinstance(entry, str) for entry in memberlist):
logger.warning(
__('__all__ should be a list of strings, not %r '
'(in module %s) -- ignoring __all__') %
(memberlist, self.fullname),
type='autodoc'
)
# fall back to all members
return True, get_module_members(self.object)
else: else:
memberlist = self.options.members or [] memberlist = self.options.members or []
ret = [] ret = []
@ -922,6 +946,25 @@ class ModuleDocumenter(Documenter):
) )
return False, ret return False, ret
def sort_members(self, documenters: List[Tuple["Documenter", bool]],
order: str) -> List[Tuple["Documenter", bool]]:
if order == 'bysource' and self.__all__:
# Sort alphabetically first (for members not listed on the __all__)
documenters.sort(key=lambda e: e[0].name)
# Sort by __all__
def keyfunc(entry: Tuple[Documenter, bool]) -> int:
name = entry[0].name.split('::')[1]
if name in self.__all__:
return self.__all__.index(name)
else:
return len(self.__all__)
documenters.sort(key=keyfunc)
return documenters
else:
return super().sort_members(documenters, order)
class ModuleLevelDocumenter(Documenter): class ModuleLevelDocumenter(Documenter):
""" """
@ -1168,6 +1211,14 @@ class DecoratorDocumenter(FunctionDocumenter):
return None return None
# Types which have confusing metaclass signatures it would be best not to show.
# These are listed by name, rather than storing the objects themselves, to avoid
# needing to import the modules.
_METACLASS_CALL_BLACKLIST = [
'enum.EnumMeta.__call__',
]
class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore
""" """
Specialized Documenter subclass for classes. Specialized Documenter subclass for classes.
@ -1202,27 +1253,83 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
self.doc_as_attr = True self.doc_as_attr = True
return ret return ret
def _get_signature(self) -> Optional[Signature]:
def get_user_defined_function_or_method(obj: Any, attr: str) -> Any:
""" Get the `attr` function or method from `obj`, if it is user-defined. """
if inspect.is_builtin_class_method(obj, attr):
return None
attr = self.get_attr(obj, attr, None)
if not (inspect.ismethod(attr) or inspect.isfunction(attr)):
return None
return attr
# This sequence is copied from inspect._signature_from_callable.
# ValueError means that no signature could be found, so we keep going.
# First, let's see if it has an overloaded __call__ defined
# in its metaclass
call = get_user_defined_function_or_method(type(self.object), '__call__')
if call is not None:
if "{0.__module__}.{0.__qualname__}".format(call) in _METACLASS_CALL_BLACKLIST:
call = None
if call is not None:
self.env.app.emit('autodoc-before-process-signature', call, True)
try:
return inspect.signature(call, bound_method=True)
except ValueError:
pass
# Now we check if the 'obj' class has a '__new__' method
new = get_user_defined_function_or_method(self.object, '__new__')
if new is not None:
self.env.app.emit('autodoc-before-process-signature', new, True)
try:
return inspect.signature(new, bound_method=True)
except ValueError:
pass
# Finally, we should have at least __init__ implemented
init = get_user_defined_function_or_method(self.object, '__init__')
if init is not None:
self.env.app.emit('autodoc-before-process-signature', init, True)
try:
return inspect.signature(init, bound_method=True)
except ValueError:
pass
# None of the attributes are user-defined, so fall back to let inspect
# handle it.
# We don't know the exact method that inspect.signature will read
# the signature from, so just pass the object itself to our hook.
self.env.app.emit('autodoc-before-process-signature', self.object, False)
try:
return inspect.signature(self.object, bound_method=False)
except ValueError:
pass
# Still no signature: happens e.g. for old-style classes
# with __init__ in C and no `__text_signature__`.
return None
def format_args(self, **kwargs: Any) -> str: def format_args(self, **kwargs: Any) -> str:
if self.env.config.autodoc_typehints in ('none', 'description'): if self.env.config.autodoc_typehints in ('none', 'description'):
kwargs.setdefault('show_annotation', False) kwargs.setdefault('show_annotation', False)
# for classes, the relevant signature is the __init__ method's
initmeth = self.get_attr(self.object, '__init__', None)
# classes without __init__ method, default __init__ or
# __init__ written in C?
if initmeth is None or \
inspect.is_builtin_class_method(self.object, '__init__') or \
not(inspect.ismethod(initmeth) or inspect.isfunction(initmeth)):
return None
try: try:
self.env.app.emit('autodoc-before-process-signature', initmeth, True) sig = self._get_signature()
sig = inspect.signature(initmeth, bound_method=True) except TypeError as exc:
return stringify_signature(sig, show_return_annotation=False, **kwargs) # __signature__ attribute contained junk
except TypeError: logger.warning(__("Failed to get a constructor signature for %s: %s"),
# still not possible: happens e.g. for old-style classes self.fullname, exc)
# with __init__ in C
return None return None
if sig is None:
return None
return stringify_signature(sig, show_return_annotation=False, **kwargs)
def format_signature(self, **kwargs: Any) -> str: def format_signature(self, **kwargs: Any) -> str:
if self.doc_as_attr: if self.doc_as_attr:
return '' return ''

View File

@ -318,7 +318,7 @@ class GoogleDocstring:
return [line[min_indent:] for line in lines] return [line[min_indent:] for line in lines]
def _escape_args_and_kwargs(self, name: str) -> str: def _escape_args_and_kwargs(self, name: str) -> str:
if name.endswith('_'): if name.endswith('_') and getattr(self._config, 'strip_signature_backslash', False):
name = name[:-1] + r'\_' name = name[:-1] + r'\_'
if name[:2] == '**': if name[:2] == '**':

View File

@ -98,6 +98,8 @@ class SphinxComponentRegistry:
#: LaTeX packages; list of package names and its options #: LaTeX packages; list of package names and its options
self.latex_packages = [] # type: List[Tuple[str, str]] self.latex_packages = [] # type: List[Tuple[str, str]]
self.latex_packages_after_hyperref = [] # type: List[Tuple[str, str]]
#: post transforms; list of transforms #: post transforms; list of transforms
self.post_transforms = [] # type: List[Type[Transform]] self.post_transforms = [] # type: List[Type[Transform]]
@ -363,8 +365,11 @@ class SphinxComponentRegistry:
logger.debug('[app] adding js_file: %r, %r', filename, attributes) logger.debug('[app] adding js_file: %r, %r', filename, attributes)
self.js_files.append((filename, attributes)) self.js_files.append((filename, attributes))
def add_latex_package(self, name: str, options: str) -> None: def add_latex_package(self, name: str, options: str, after_hyperref: bool = False) -> None:
logger.debug('[app] adding latex package: %r', name) logger.debug('[app] adding latex package: %r', name)
if after_hyperref:
self.latex_packages_after_hyperref.append((name, options))
else:
self.latex_packages.append((name, options)) self.latex_packages.append((name, options))
def add_enumerable_node(self, node: "Type[Node]", figtype: str, def add_enumerable_node(self, node: "Type[Node]", figtype: str,

View File

@ -46,6 +46,14 @@
<%- endfor %> <%- endfor %>
<%= hyperref %> <%= hyperref %>
<%- for name, option in packages_after_hyperref %>
<%- if option %>
\usepackage[<%= option %>]{<%= name %>}
<%- else %>
\usepackage{<%= name %>}
<%- endif %>
<%- endfor %>
<%= contentsname %> <%= contentsname %>
\usepackage{sphinxmessages} \usepackage{sphinxmessages}
<%= tocdepth %> <%= tocdepth %>

View File

@ -726,7 +726,7 @@ def getdoc(obj: Any, attrgetter: Callable = safe_getattr,
# This tries to obtain the docstring from super classes. # This tries to obtain the docstring from super classes.
for basecls in getattr(cls, '__mro__', []): for basecls in getattr(cls, '__mro__', []):
meth = safe_getattr(basecls, name, None) meth = safe_getattr(basecls, name, None)
if meth: if meth is not None:
doc = inspect.getdoc(meth) doc = inspect.getdoc(meth)
if doc: if doc:
break break

View File

@ -0,0 +1,25 @@
__all__ = ['baz', 'foo', 'Bar']
def foo():
pass
class Bar:
pass
def baz():
pass
def qux():
pass
class Quux:
pass
def foobar():
pass

View File

@ -37,6 +37,26 @@ def tuple_args(x: Tuple[int, Union[int, str]]) -> Tuple[int, int]:
pass pass
class NewAnnotation:
def __new__(cls, i: int) -> 'NewAnnotation':
pass
class NewComment:
def __new__(cls, i):
# type: (int) -> NewComment
pass
class _MetaclassWithCall(type):
def __call__(cls, a: int):
pass
class SignatureFromMetaclass(metaclass=_MetaclassWithCall):
pass
def complex_func(arg1, arg2, arg3=None, *args, **kwargs): def complex_func(arg1, arg2, arg3=None, *args, **kwargs):
# type: (str, List[int], Tuple[int, Union[str, Unknown]], *str, **str) -> None # type: (str, List[int], Tuple[int, Union[str, Unknown]], *str, **str) -> None
pass pass
@ -48,4 +68,3 @@ def missing_attr(c,
): ):
# type: (...) -> str # type: (...) -> str
return a + (b or "") return a + (b or "")

View File

@ -169,21 +169,64 @@ def test_format_signature(app):
pass pass
class E: class E:
def __init__(self):
pass pass
# no signature for classes without __init__
for C in (D, E):
assert formatsig('class', 'D', C, None, None) == ''
# an empty init and no init are the same
for C in (D, E):
assert formatsig('class', 'D', C, None, None) == '()'
class SomeMeta(type):
def __call__(cls, a, b=None):
return type.__call__(cls, a, b)
# these three are all equivalent
class F: class F:
def __init__(self, a, b=None): def __init__(self, a, b=None):
pass pass
class FNew:
def __new__(cls, a, b=None):
return super().__new__(cls)
class FMeta(metaclass=SomeMeta):
pass
# and subclasses should always inherit
class G(F): class G(F):
pass pass
for C in (F, G):
class GNew(FNew):
pass
class GMeta(FMeta):
pass
# subclasses inherit
for C in (F, FNew, FMeta, G, GNew, GMeta):
assert formatsig('class', 'C', C, None, None) == '(a, b=None)' assert formatsig('class', 'C', C, None, None) == '(a, b=None)'
assert formatsig('class', 'C', D, 'a, b', 'X') == '(a, b) -> X' assert formatsig('class', 'C', D, 'a, b', 'X') == '(a, b) -> X'
class ListSubclass(list):
pass
# only supported if the python implementation decides to document it
if getattr(list, '__text_signature__', None) is not None:
assert formatsig('class', 'C', ListSubclass, None, None) == '(iterable=(), /)'
else:
assert formatsig('class', 'C', ListSubclass, None, None) == ''
class ExceptionSubclass(Exception):
pass
# Exception has no __text_signature__ at least in Python 3.8
if getattr(Exception, '__text_signature__', None) is None:
assert formatsig('class', 'C', ExceptionSubclass, None, None) == ''
# __init__ have signature at first line of docstring # __init__ have signature at first line of docstring
directive.env.config.autoclass_content = 'both' directive.env.config.autoclass_content = 'both'
@ -497,14 +540,14 @@ def test_autodoc_members(app):
# default (no-members) # default (no-members)
actual = do_autodoc(app, 'class', 'target.inheritance.Base') actual = do_autodoc(app, 'class', 'target.inheritance.Base')
assert list(filter(lambda l: '::' in l, actual)) == [ assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base', '.. py:class:: Base()',
] ]
# default ALL-members # default ALL-members
options = {"members": None} options = {"members": None}
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [ assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base', '.. py:class:: Base()',
' .. py:method:: Base.inheritedclassmeth()', ' .. py:method:: Base.inheritedclassmeth()',
' .. py:method:: Base.inheritedmeth()', ' .. py:method:: Base.inheritedmeth()',
' .. py:method:: Base.inheritedstaticmeth(cls)' ' .. py:method:: Base.inheritedstaticmeth(cls)'
@ -514,7 +557,7 @@ def test_autodoc_members(app):
options = {"members": "inheritedmeth,inheritedstaticmeth"} options = {"members": "inheritedmeth,inheritedstaticmeth"}
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [ assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base', '.. py:class:: Base()',
' .. py:method:: Base.inheritedmeth()', ' .. py:method:: Base.inheritedmeth()',
' .. py:method:: Base.inheritedstaticmeth(cls)' ' .. py:method:: Base.inheritedstaticmeth(cls)'
] ]
@ -526,7 +569,7 @@ def test_autodoc_exclude_members(app):
"exclude-members": "inheritedmeth,inheritedstaticmeth"} "exclude-members": "inheritedmeth,inheritedstaticmeth"}
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [ assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base', '.. py:class:: Base()',
' .. py:method:: Base.inheritedclassmeth()' ' .. py:method:: Base.inheritedclassmeth()'
] ]
@ -535,7 +578,7 @@ def test_autodoc_exclude_members(app):
"exclude-members": "inheritedmeth"} "exclude-members": "inheritedmeth"}
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [ assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base', '.. py:class:: Base()',
] ]
@ -679,10 +722,10 @@ def test_autodoc_ignore_module_all(app):
assert list(filter(lambda l: 'class::' in l, actual)) == [ assert list(filter(lambda l: 'class::' in l, actual)) == [
'.. py:class:: Class(arg)', '.. py:class:: Class(arg)',
'.. py:class:: CustomDict', '.. py:class:: CustomDict',
'.. py:class:: InnerChild', '.. py:class:: InnerChild()',
'.. py:class:: InstAttCls()', '.. py:class:: InstAttCls()',
'.. py:class:: Outer', '.. py:class:: Outer()',
' .. py:class:: Outer.Inner', ' .. py:class:: Outer.Inner()',
'.. py:class:: StrRepr' '.. py:class:: StrRepr'
] ]
@ -703,7 +746,7 @@ def test_autodoc_noindex(app):
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(actual) == [ assert list(actual) == [
'', '',
'.. py:class:: Base', '.. py:class:: Base()',
' :noindex:', ' :noindex:',
' :module: target.inheritance', ' :module: target.inheritance',
'' ''
@ -730,13 +773,13 @@ def test_autodoc_inner_class(app):
actual = do_autodoc(app, 'class', 'target.Outer', options) actual = do_autodoc(app, 'class', 'target.Outer', options)
assert list(actual) == [ assert list(actual) == [
'', '',
'.. py:class:: Outer', '.. py:class:: Outer()',
' :module: target', ' :module: target',
'', '',
' Foo', ' Foo',
'', '',
'', '',
' .. py:class:: Outer.Inner', ' .. py:class:: Outer.Inner()',
' :module: target', ' :module: target',
'', '',
' Foo', ' Foo',
@ -757,7 +800,7 @@ def test_autodoc_inner_class(app):
actual = do_autodoc(app, 'class', 'target.Outer.Inner', options) actual = do_autodoc(app, 'class', 'target.Outer.Inner', options)
assert list(actual) == [ assert list(actual) == [
'', '',
'.. py:class:: Outer.Inner', '.. py:class:: Outer.Inner()',
' :module: target', ' :module: target',
'', '',
' Foo', ' Foo',
@ -774,7 +817,7 @@ def test_autodoc_inner_class(app):
actual = do_autodoc(app, 'class', 'target.InnerChild', options) actual = do_autodoc(app, 'class', 'target.InnerChild', options)
assert list(actual) == [ assert list(actual) == [
'', '',
'.. py:class:: InnerChild', '.. py:class:: InnerChild()',
' :module: target', '', ' :module: target', '',
' Bases: :class:`target.Outer.Inner`', ' Bases: :class:`target.Outer.Inner`',
'', '',
@ -818,7 +861,7 @@ def test_autodoc_descriptor(app):
actual = do_autodoc(app, 'class', 'target.descriptor.Class', options) actual = do_autodoc(app, 'class', 'target.descriptor.Class', options)
assert list(actual) == [ assert list(actual) == [
'', '',
'.. py:class:: Class', '.. py:class:: Class()',
' :module: target.descriptor', ' :module: target.descriptor',
'', '',
'', '',
@ -914,6 +957,40 @@ def test_autodoc_member_order(app):
] ]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_module_member_order(app):
# case member-order='bysource'
options = {"members": 'foo, Bar, baz, qux, Quux, foobar',
'member-order': 'bysource',
"undoc-members": True}
actual = do_autodoc(app, 'module', 'target.sort_by_all', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:module:: target.sort_by_all',
'.. py:function:: baz()',
'.. py:function:: foo()',
'.. py:class:: Bar()',
'.. py:class:: Quux()',
'.. py:function:: foobar()',
'.. py:function:: qux()',
]
# case member-order='bysource' and ignore-module-all
options = {"members": 'foo, Bar, baz, qux, Quux, foobar',
'member-order': 'bysource',
"undoc-members": True,
"ignore-module-all": True}
actual = do_autodoc(app, 'module', 'target.sort_by_all', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:module:: target.sort_by_all',
'.. py:function:: foo()',
'.. py:class:: Bar()',
'.. py:function:: baz()',
'.. py:function:: qux()',
'.. py:class:: Quux()',
'.. py:function:: foobar()',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_module_scope(app): def test_autodoc_module_scope(app):
app.env.temp_data['autodoc:module'] = 'target' app.env.temp_data['autodoc:module'] = 'target'
@ -952,7 +1029,7 @@ def test_class_attributes(app):
actual = do_autodoc(app, 'class', 'target.AttCls', options) actual = do_autodoc(app, 'class', 'target.AttCls', options)
assert list(actual) == [ assert list(actual) == [
'', '',
'.. py:class:: AttCls', '.. py:class:: AttCls()',
' :module: target', ' :module: target',
'', '',
'', '',
@ -1072,7 +1149,7 @@ def test_slots(app):
' :module: target.slots', ' :module: target.slots',
'', '',
'', '',
'.. py:class:: Foo', '.. py:class:: Foo()',
' :module: target.slots', ' :module: target.slots',
'', '',
'', '',
@ -1088,7 +1165,7 @@ def test_enum_class(app):
actual = do_autodoc(app, 'class', 'target.enum.EnumCls', options) actual = do_autodoc(app, 'class', 'target.enum.EnumCls', options)
assert list(actual) == [ assert list(actual) == [
'', '',
'.. py:class:: EnumCls', '.. py:class:: EnumCls(value)',
' :module: target.enum', ' :module: target.enum',
'', '',
' this is enum class', ' this is enum class',
@ -1205,7 +1282,7 @@ def test_abstractmethods(app):
'.. py:module:: target.abstractmethods', '.. py:module:: target.abstractmethods',
'', '',
'', '',
'.. py:class:: Base', '.. py:class:: Base()',
' :module: target.abstractmethods', ' :module: target.abstractmethods',
'', '',
'', '',
@ -1322,7 +1399,7 @@ def test_coroutine(app):
actual = do_autodoc(app, 'class', 'target.coroutine.AsyncClass', options) actual = do_autodoc(app, 'class', 'target.coroutine.AsyncClass', options)
assert list(actual) == [ assert list(actual) == [
'', '',
'.. py:class:: AsyncClass', '.. py:class:: AsyncClass()',
' :module: target.coroutine', ' :module: target.coroutine',
'', '',
'', '',
@ -1364,7 +1441,7 @@ def test_coroutine(app):
def test_partialmethod(app): def test_partialmethod(app):
expected = [ expected = [
'', '',
'.. py:class:: Cell', '.. py:class:: Cell()',
' :module: target.partialmethod', ' :module: target.partialmethod',
'', '',
' An example for partialmethod.', ' An example for partialmethod.',
@ -1394,7 +1471,7 @@ def test_partialmethod(app):
def test_partialmethod_undoc_members(app): def test_partialmethod_undoc_members(app):
expected = [ expected = [
'', '',
'.. py:class:: Cell', '.. py:class:: Cell()',
' :module: target.partialmethod', ' :module: target.partialmethod',
'', '',
' An example for partialmethod.', ' An example for partialmethod.',
@ -1581,7 +1658,7 @@ def test_singledispatchmethod(app):
'.. py:module:: target.singledispatchmethod', '.. py:module:: target.singledispatchmethod',
'', '',
'', '',
'.. py:class:: Foo', '.. py:class:: Foo()',
' :module: target.singledispatchmethod', ' :module: target.singledispatchmethod',
'', '',
' docstring', ' docstring',
@ -1626,7 +1703,7 @@ def test_cython(app):
'.. py:module:: target.cython', '.. py:module:: target.cython',
'', '',
'', '',
'.. py:class:: Class', '.. py:class:: Class()',
' :module: target.cython', ' :module: target.cython',
'', '',
' Docstring.', ' Docstring.',
@ -1657,7 +1734,7 @@ def test_final(app):
'.. py:module:: target.final', '.. py:module:: target.final',
'', '',
'', '',
'.. py:class:: Class', '.. py:class:: Class()',
' :module: target.final', ' :module: target.final',
' :final:', ' :final:',
'', '',

View File

@ -9,6 +9,7 @@
""" """
import platform import platform
import sys
import pytest import pytest
@ -27,7 +28,7 @@ def test_autoclass_content_class(app):
'.. py:module:: target.autoclass_content', '.. py:module:: target.autoclass_content',
'', '',
'', '',
'.. py:class:: A', '.. py:class:: A()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, no __new__', ' A class having no __init__, no __new__',
@ -45,13 +46,13 @@ def test_autoclass_content_class(app):
' A class having __init__, no __new__', ' A class having __init__, no __new__',
'', '',
'', '',
'.. py:class:: D', '.. py:class:: D()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, __new__(no docstring)', ' A class having no __init__, __new__(no docstring)',
'', '',
'', '',
'.. py:class:: E', '.. py:class:: E()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, __new__', ' A class having no __init__, __new__',
@ -87,7 +88,7 @@ def test_autoclass_content_init(app):
'.. py:module:: target.autoclass_content', '.. py:module:: target.autoclass_content',
'', '',
'', '',
'.. py:class:: A', '.. py:class:: A()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, no __new__', ' A class having no __init__, no __new__',
@ -105,13 +106,13 @@ def test_autoclass_content_init(app):
' __init__ docstring', ' __init__ docstring',
'', '',
'', '',
'.. py:class:: D', '.. py:class:: D()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, __new__(no docstring)', ' A class having no __init__, __new__(no docstring)',
'', '',
'', '',
'.. py:class:: E', '.. py:class:: E()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' __new__ docstring', ' __new__ docstring',
@ -147,7 +148,7 @@ def test_autoclass_content_both(app):
'.. py:module:: target.autoclass_content', '.. py:module:: target.autoclass_content',
'', '',
'', '',
'.. py:class:: A', '.. py:class:: A()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, no __new__', ' A class having no __init__, no __new__',
@ -167,13 +168,13 @@ def test_autoclass_content_both(app):
' __init__ docstring', ' __init__ docstring',
'', '',
'', '',
'.. py:class:: D', '.. py:class:: D()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, __new__(no docstring)', ' A class having no __init__, __new__(no docstring)',
'', '',
'', '',
'.. py:class:: E', '.. py:class:: E()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, __new__', ' A class having no __init__, __new__',
@ -237,7 +238,7 @@ def test_autodoc_docstring_signature(app):
actual = do_autodoc(app, 'class', 'target.DocstringSig', options) actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
assert list(actual) == [ assert list(actual) == [
'', '',
'.. py:class:: DocstringSig', '.. py:class:: DocstringSig()',
' :module: target', ' :module: target',
'', '',
'', '',
@ -279,7 +280,7 @@ def test_autodoc_docstring_signature(app):
actual = do_autodoc(app, 'class', 'target.DocstringSig', options) actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
assert list(actual) == [ assert list(actual) == [
'', '',
'.. py:class:: DocstringSig', '.. py:class:: DocstringSig()',
' :module: target', ' :module: target',
'', '',
'', '',
@ -435,7 +436,7 @@ def test_mocked_module_imports(app, warning):
'.. py:module:: target.need_mocks', '.. py:module:: target.need_mocks',
'', '',
'', '',
'.. py:class:: TestAutodoc', '.. py:class:: TestAutodoc()',
' :module: target.need_mocks', ' :module: target.need_mocks',
'', '',
' TestAutodoc docstring.', ' TestAutodoc docstring.',
@ -493,6 +494,18 @@ def test_autodoc_typehints_signature(app):
' :module: target.typehints', ' :module: target.typehints',
'', '',
'', '',
'.. py:class:: NewAnnotation(i: int)',
' :module: target.typehints',
'',
'',
'.. py:class:: NewComment(i: int)',
' :module: target.typehints',
'',
'',
'.. py:class:: SignatureFromMetaclass(a: int)',
' :module: target.typehints',
'',
'',
'.. py:function:: complex_func(arg1: str, arg2: List[int], arg3: Tuple[int, ' '.. py:function:: complex_func(arg1: str, arg2: List[int], arg3: Tuple[int, '
'Union[str, Unknown]] = None, *args: str, **kwargs: str) -> None', 'Union[str, Unknown]] = None, *args: str, **kwargs: str) -> None',
' :module: target.typehints', ' :module: target.typehints',
@ -547,6 +560,18 @@ def test_autodoc_typehints_none(app):
' :module: target.typehints', ' :module: target.typehints',
'', '',
'', '',
'.. py:class:: NewAnnotation(i)',
' :module: target.typehints',
'',
'',
'.. py:class:: NewComment(i)',
' :module: target.typehints',
'',
'',
'.. py:class:: SignatureFromMetaclass(a)',
' :module: target.typehints',
'',
'',
'.. py:function:: complex_func(arg1, arg2, arg3=None, *args, **kwargs)', '.. py:function:: complex_func(arg1, arg2, arg3=None, *args, **kwargs)',
' :module: target.typehints', ' :module: target.typehints',
'', '',

View File

@ -292,7 +292,7 @@ def test_autosummary_generate(app, status, warning):
assert len(doctree[3][0][0][2]) == 5 assert len(doctree[3][0][0][2]) == 5
assert doctree[3][0][0][2][0].astext() == 'autosummary_dummy_module\n\n' assert doctree[3][0][0][2][0].astext() == 'autosummary_dummy_module\n\n'
assert doctree[3][0][0][2][1].astext() == 'autosummary_dummy_module.Foo()\n\n' assert doctree[3][0][0][2][1].astext() == 'autosummary_dummy_module.Foo()\n\n'
assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.Foo.Bar\n\n' assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.Foo.Bar()\n\n'
assert doctree[3][0][0][2][3].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n' assert doctree[3][0][0][2][3].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n'
assert doctree[3][0][0][2][4].astext() == 'autosummary_dummy_module.qux\n\na module-level attribute' assert doctree[3][0][0][2][4].astext() == 'autosummary_dummy_module.qux\n\na module-level attribute'

View File

@ -1394,6 +1394,26 @@ Summary
Attributes Attributes
---------- ----------
arg_ : type
some description
"""
expected = """
:ivar arg_: some description
:vartype arg_: type
"""
config = Config(napoleon_use_ivar=True)
app = mock.Mock()
actual = str(NumpyDocstring(docstring, config, app, "class"))
self.assertEqual(expected, actual)
def test_underscore_in_attribute_strip_signature_backslash(self):
docstring = """
Attributes
----------
arg_ : type arg_ : type
some description some description
""" """
@ -1404,6 +1424,7 @@ arg_ : type
""" """
config = Config(napoleon_use_ivar=True) config = Config(napoleon_use_ivar=True)
config.strip_signature_backslash = True
app = mock.Mock() app = mock.Mock()
actual = str(NumpyDocstring(docstring, config, app, "class")) actual = str(NumpyDocstring(docstring, config, app, "class"))

View File

@ -58,8 +58,8 @@ def test_project_path2doc(app):
assert project.path2doc('index.foo') is None # unknown extension assert project.path2doc('index.foo') is None # unknown extension
assert project.path2doc('index.foo.rst') == 'index.foo' assert project.path2doc('index.foo.rst') == 'index.foo'
assert project.path2doc('index') is None assert project.path2doc('index') is None
assert project.path2doc('/path/to/index.rst') == PathComparer('/path/to/index') assert project.path2doc('path/to/index.rst') == 'path/to/index'
assert project.path2doc(app.srcdir / '/to/index.rst') == PathComparer('/to/index') assert project.path2doc(app.srcdir / 'to/index.rst') == 'to/index'
@pytest.mark.sphinx(srcdir='project_doc2path', testroot='basic') @pytest.mark.sphinx(srcdir='project_doc2path', testroot='basic')

View File

@ -29,12 +29,14 @@ def test_signature():
with pytest.raises(TypeError): with pytest.raises(TypeError):
inspect.signature('') inspect.signature('')
# builitin classes # builtins are supported on a case-by-case basis, depending on whether
# they define __text_signature__
if getattr(list, '__text_signature__', None):
sig = inspect.stringify_signature(inspect.signature(list))
assert sig == '(iterable=(), /)'
else:
with pytest.raises(ValueError): with pytest.raises(ValueError):
inspect.signature(int) inspect.signature(list)
with pytest.raises(ValueError):
inspect.signature(str)
# normal function # normal function
def func(a, b, c=1, d=2, *e, **f): def func(a, b, c=1, d=2, *e, **f):