Merge branch '3.x'

This commit is contained in:
Takeshi KOMIYA 2020-05-17 19:12:56 +09:00
commit 3c7d35d2a2
114 changed files with 2863 additions and 2490 deletions

View File

@ -85,6 +85,7 @@ Other contributors, listed alphabetically, are:
* Daniel Pizetta -- inheritance diagram improvements * Daniel Pizetta -- inheritance diagram improvements
* KINEBUCHI Tomohiko -- typing Sphinx as well as docutils * KINEBUCHI Tomohiko -- typing Sphinx as well as docutils
* Adrián Chaves (Gallaecio) -- coverage builder improvements * Adrián Chaves (Gallaecio) -- coverage builder improvements
* Lars Hupfeldt Nielsen - OpenSSL FIPS mode md5 bug fix
Many thanks for all contributions! Many thanks for all contributions!

25
CHANGES
View File

@ -64,6 +64,7 @@ Deprecated
generate_autosummary_docs()`` generate_autosummary_docs()``
* The ``ignore`` argument of ``sphinx.util.docstring.prepare_docstring()`` * The ``ignore`` argument of ``sphinx.util.docstring.prepare_docstring()``
* ``sphinx.ext.autosummary.generate.AutosummaryRenderer.exists()`` * ``sphinx.ext.autosummary.generate.AutosummaryRenderer.exists()``
* ``sphinx.util.rpartition()``
Features added Features added
-------------- --------------
@ -107,6 +108,7 @@ Features added
* C++, parse trailing return types. * C++, parse trailing return types.
* #7143: py domain: Add ``:final:`` option to :rst:dir:`py:class:`, * #7143: py domain: Add ``:final:`` option to :rst:dir:`py:class:`,
: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
* #7582: napoleon: a type for attribute are represented like type annotation * #7582: napoleon: a type for attribute are represented like type annotation
Bugs fixed Bugs fixed
@ -121,10 +123,30 @@ Bugs fixed
* #6857: autodoc: failed to detect a classmethod on Enum class * #6857: autodoc: failed to detect a classmethod on Enum class
* #7562: autodoc: a typehint contains spaces is wrongly rendered under * #7562: autodoc: a typehint contains spaces is wrongly rendered under
autodoc_typehints='description' mode autodoc_typehints='description' mode
* #7551: autodoc: failed to import nested class
* #7362: autodoc: does not render correct signatures for built-in functions
* #7654: autodoc: ``Optional[Union[foo, bar]]`` is presented as
``Union[foo, bar, None]``
* #7629: autodoc: autofunction emits an unfriendly warning if an invalid object
specified
* #7650: autodoc: undecorated signature is shown for decorated functions
* #7676: autodoc: typo in the default value of autodoc_member_order
* #7551: autosummary: a nested class is indexed as non-nested class
* #7661: autosummary: autosummary directive emits warnings twices if failed to
import the target module
* #7535: sphinx-autogen: crashes when custom template uses inheritance * #7535: sphinx-autogen: crashes when custom template uses inheritance
* #7536: sphinx-autogen: crashes when template uses i18n feature * #7536: sphinx-autogen: crashes when template uses i18n feature
* #7653: sphinx-quickstart: Fix multiple directory creation for nested relpath
* #2785: html: Bad alignment of equation links * #2785: html: Bad alignment of equation links
* #7581: napoleon: bad parsing of inline code in attribute docstrings * #7581: napoleon: bad parsing of inline code in attribute docstrings
* #7628: imgconverter: runs imagemagick once unnecessary for builders not
supporting images
* #7610: incorrectly renders consecutive backslashes for docutils-0.16
* #7646: handle errors on event handlers
* C++, fix rendering and xrefs in nested names explicitly starting
in global scope, e.g., ``::A::B``.
* C, fix rendering and xrefs in nested names explicitly starting
in global scope, e.g., ``.A.B``.
Testing Testing
-------- --------
@ -148,6 +170,9 @@ 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
* #7611: md5 fails when OpenSSL FIPS is enabled
* #7626: release package does not contain ``CODE_OF_CONDUCT``
Testing Testing
-------- --------

View File

@ -3,6 +3,7 @@ include LICENSE
include AUTHORS include AUTHORS
include CHANGES include CHANGES
include CHANGES.old include CHANGES.old
include CODE_OF_CONDUCT
include CONTRIBUTING.rst include CONTRIBUTING.rst
include EXAMPLES include EXAMPLES

View File

@ -108,6 +108,11 @@ The following is a list of deprecated interfaces.
- 5.0 - 5.0
- N/A - N/A
* - ``sphinx.util.rpartition()``
- 3.1
- 5.0
- ``str.rpartition()``
* - ``desc_signature['first']`` * - ``desc_signature['first']``
- -
- 3.0 - 3.0

View File

@ -1384,7 +1384,7 @@ that use Sphinx's HTMLWriter class.
.. versionadded:: 1.3 .. versionadded:: 1.3
.. versionchanged:: 2.4 .. versionchanged:: 3.0
It is disabled for images having ``no-scaled-link`` class It is disabled for images having ``no-scaled-link`` class

View File

@ -109,6 +109,13 @@ class desc_signature(nodes.Part, nodes.Inline, nodes.TextElement):
In that case all child nodes must be ``desc_signature_line`` nodes. In that case all child nodes must be ``desc_signature_line`` nodes.
""" """
@property
def child_text_separator(self):
if self.get('is_multiline'):
return ' '
else:
return super().child_text_separator
class desc_signature_line(nodes.Part, nodes.Inline, nodes.FixedTextElement): class desc_signature_line(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for a line in a multi-line object signatures. """Node for a line in a multi-line object signatures.
@ -147,6 +154,9 @@ class desc_parameterlist(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for a general parameter list.""" """Node for a general parameter list."""
child_text_separator = ', ' child_text_separator = ', '
def astext(self):
return '({})'.format(super().astext())
class desc_parameter(nodes.Part, nodes.Inline, nodes.FixedTextElement): class desc_parameter(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for a single parameter.""" """Node for a single parameter."""

View File

@ -12,7 +12,6 @@ import html
import posixpath import posixpath
import re import re
import sys import sys
from hashlib import md5
from os import path from os import path
from typing import Any, Dict, IO, Iterable, Iterator, List, Set, Tuple, Type from typing import Any, Dict, IO, Iterable, Iterator, List, Set, Tuple, Type
@ -36,7 +35,7 @@ from sphinx.highlighting import PygmentsBridge
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.search import js_index from sphinx.search import js_index
from sphinx.theming import HTMLThemeFactory from sphinx.theming import HTMLThemeFactory
from sphinx.util import logging, progress_message, status_iterator from sphinx.util import logging, progress_message, status_iterator, md5
from sphinx.util.docutils import is_html5_writer_available, new_document from sphinx.util.docutils import is_html5_writer_available, new_document
from sphinx.util.fileutil import copy_asset from sphinx.util.fileutil import copy_asset
from sphinx.util.i18n import format_date from sphinx.util.i18n import format_date

View File

@ -319,6 +319,7 @@ def generate(d: Dict, overwrite: bool = True, silent: bool = False, templatedir:
d.setdefault('extensions', []) d.setdefault('extensions', [])
d['copyright'] = time.strftime('%Y') + ', ' + d['author'] d['copyright'] = time.strftime('%Y') + ', ' + d['author']
d["path"] = os.path.abspath(d['path'])
ensuredir(d['path']) ensuredir(d['path'])
srcdir = path.join(d['path'], 'source') if d['sep'] else d['path'] srcdir = path.join(d['path'], 'source') if d['sep'] else d['path']

View File

@ -186,11 +186,17 @@ class ASTNestedName(ASTBase):
# If lastIsName, then wrap all of the prefix in a desc_addname, # If lastIsName, then wrap all of the prefix in a desc_addname,
# else append directly to signode. # else append directly to signode.
# TODO: also for C? # TODO: also for C?
# NOTE: Breathe relies on the prefix being in the desc_addname node, # NOTE: Breathe previously relied on the prefix being in the desc_addname node,
# so it can remove it in inner declarations. # so it can remove it in inner declarations.
dest = signode dest = signode
if mode == 'lastIsName': if mode == 'lastIsName':
dest = addnodes.desc_addname() dest = addnodes.desc_addname()
if self.rooted:
prefix += '.'
if mode == 'lastIsName' and len(names) == 0:
signode += nodes.Text('.')
else:
dest += nodes.Text('.')
for i in range(len(names)): for i in range(len(names)):
ident = names[i] ident = names[i]
if not first: if not first:

View File

@ -752,11 +752,17 @@ class ASTNestedName(ASTBase):
names = self.names[:-1] if mode == 'lastIsName' else self.names names = self.names[:-1] if mode == 'lastIsName' else self.names
# If lastIsName, then wrap all of the prefix in a desc_addname, # If lastIsName, then wrap all of the prefix in a desc_addname,
# else append directly to signode. # else append directly to signode.
# NOTE: Breathe relies on the prefix being in the desc_addname node, # NOTE: Breathe previously relied on the prefix being in the desc_addname node,
# so it can remove it in inner declarations. # so it can remove it in inner declarations.
dest = signode dest = signode
if mode == 'lastIsName': if mode == 'lastIsName':
dest = addnodes.desc_addname() dest = addnodes.desc_addname()
if self.rooted:
prefix += '::'
if mode == 'lastIsName' and len(names) == 0:
signode += nodes.Text('::')
else:
dest += nodes.Text('::')
for i in range(len(names)): for i in range(len(names)):
nne = names[i] nne = names[i]
template = self.templates[i] template = self.templates[i]
@ -3722,8 +3728,8 @@ class LookupKey:
class Symbol: class Symbol:
debug_indent = 0 debug_indent = 0
debug_indent_string = " " debug_indent_string = " "
debug_lookup = False debug_lookup = False # overridden by the corresponding config value
debug_show_tree = False debug_show_tree = False # overridden by the corresponding config value
@staticmethod @staticmethod
def debug_print(*args: Any) -> None: def debug_print(*args: Any) -> None:
@ -7383,9 +7389,18 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value("cpp_paren_attributes", [], 'env') app.add_config_value("cpp_paren_attributes", [], 'env')
app.add_post_transform(AliasTransform) app.add_post_transform(AliasTransform)
# debug stuff
app.add_config_value("cpp_debug_lookup", False, '')
app.add_config_value("cpp_debug_show_tree", False, '')
def setDebugFlags(app):
Symbol.debug_lookup = app.config.cpp_debug_lookup
Symbol.debug_show_tree = app.config.cpp_debug_show_tree
app.connect("builder-inited", setDebugFlags)
return { return {
'version': 'builtin', 'version': 'builtin',
'env_version': 2, 'env_version': 3,
'parallel_read_safe': True, 'parallel_read_safe': True,
'parallel_write_safe': True, 'parallel_write_safe': True,
} }

View File

@ -74,9 +74,8 @@ ModuleEntry = NamedTuple('ModuleEntry', [('docname', str),
('deprecated', bool)]) ('deprecated', bool)])
def _parse_annotation(annotation: str) -> List[Node]: def type_to_xref(text: str) -> addnodes.pending_xref:
"""Parse type annotation.""" """Convert a type string to a cross reference node."""
def make_xref(text: str) -> addnodes.pending_xref:
if text == 'None': if text == 'None':
reftype = 'obj' reftype = 'obj'
else: else:
@ -85,6 +84,9 @@ def _parse_annotation(annotation: str) -> List[Node]:
return pending_xref('', nodes.Text(text), return pending_xref('', nodes.Text(text),
refdomain='py', reftype=reftype, reftarget=text) refdomain='py', reftype=reftype, reftarget=text)
def _parse_annotation(annotation: str) -> List[Node]:
"""Parse type annotation."""
def unparse(node: ast.AST) -> List[Node]: def unparse(node: ast.AST) -> List[Node]:
if isinstance(node, ast.Attribute): if isinstance(node, ast.Attribute):
return [nodes.Text("%s.%s" % (unparse(node.value)[0], node.attr))] return [nodes.Text("%s.%s" % (unparse(node.value)[0], node.attr))]
@ -130,10 +132,10 @@ def _parse_annotation(annotation: str) -> List[Node]:
result = unparse(tree) result = unparse(tree)
for i, node in enumerate(result): for i, node in enumerate(result):
if isinstance(node, nodes.Text): if isinstance(node, nodes.Text):
result[i] = make_xref(str(node)) result[i] = type_to_xref(str(node))
return result return result
except SyntaxError: except SyntaxError:
return [make_xref(annotation)] return [type_to_xref(annotation)]
def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist: def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist:
@ -590,7 +592,7 @@ class PyVariable(PyObject):
typ = self.options.get('type') typ = self.options.get('type')
if typ: if typ:
signode += addnodes.desc_annotation(typ, ': ' + typ) signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), type_to_xref(typ))
value = self.options.get('value') value = self.options.get('value')
if value: if value:
@ -750,7 +752,7 @@ class PyAttribute(PyObject):
typ = self.options.get('type') typ = self.options.get('type')
if typ: if typ:
signode += addnodes.desc_annotation(typ, ': ' + typ) signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), type_to_xref(typ))
value = self.options.get('value') value = self.options.get('value')
if value: if value:

View File

@ -15,7 +15,7 @@ from operator import attrgetter
from typing import Any, Callable, Dict, List, NamedTuple from typing import Any, Callable, Dict, List, NamedTuple
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sphinx.errors import ExtensionError from sphinx.errors import ExtensionError, SphinxError
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util import logging from sphinx.util import logging
@ -95,7 +95,13 @@ class EventManager:
results = [] results = []
listeners = sorted(self.listeners[name], key=attrgetter("priority")) listeners = sorted(self.listeners[name], key=attrgetter("priority"))
for listener in listeners: for listener in listeners:
try:
results.append(listener.handler(self.app, *args)) results.append(listener.handler(self.app, *args))
except SphinxError:
raise
except Exception as exc:
raise ExtensionError(__("Handler %r for event %r threw an exception") %
(listener.handler, name)) from exc
return results return results
def emit_firstresult(self, name: str, *args: Any) -> Any: def emit_firstresult(self, name: str, *args: Any) -> Any:

View File

@ -17,13 +17,12 @@ from inspect import Parameter
from types import ModuleType from types import ModuleType
from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from unittest.mock import patch
from docutils.statemachine import StringList from docutils.statemachine import StringList
import sphinx import sphinx
from sphinx.application import Sphinx from sphinx.application import Sphinx
from sphinx.config import ENUM from sphinx.config import Config, ENUM
from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.deprecation import RemovedInSphinx50Warning
from sphinx.environment import BuildEnvironment from sphinx.environment import BuildEnvironment
from sphinx.ext.autodoc.importer import import_object, get_module_members, get_object_members from sphinx.ext.autodoc.importer import import_object, get_module_members, get_object_members
@ -32,7 +31,7 @@ from sphinx.locale import _, __
from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.util import inspect from sphinx.util import inspect
from sphinx.util import logging from sphinx.util import logging
from sphinx.util import rpartition from sphinx.util import split_full_qualified_name
from sphinx.util.docstrings import extract_metadata, prepare_docstring from sphinx.util.docstrings import extract_metadata, prepare_docstring
from sphinx.util.inspect import getdoc, object_description, safe_getattr, stringify_signature from sphinx.util.inspect import getdoc, object_description, safe_getattr, stringify_signature
from sphinx.util.typing import stringify as stringify_typehint from sphinx.util.typing import stringify as stringify_typehint
@ -311,6 +310,7 @@ class Documenter:
modname = None modname = None
parents = [] parents = []
with mock(self.env.config.autodoc_mock_imports):
self.modname, self.objpath = self.resolve_name(modname, parents, path, base) self.modname, self.objpath = self.resolve_name(modname, parents, path, base)
if not self.modname: if not self.modname:
@ -419,8 +419,15 @@ class Documenter:
directive = getattr(self, 'directivetype', self.objtype) directive = getattr(self, 'directivetype', self.objtype)
name = self.format_name() name = self.format_name()
sourcename = self.get_sourcename() sourcename = self.get_sourcename()
self.add_line('.. %s:%s:: %s%s' % (domain, directive, name, sig),
# one signature per line, indented by column
prefix = '.. %s:%s:: ' % (domain, directive)
for i, sig_line in enumerate(sig.split("\n")):
self.add_line('%s%s%s' % (prefix, name, sig_line),
sourcename) sourcename)
if i == 0:
prefix = " " * len(prefix)
if self.options.noindex: if self.options.noindex:
self.add_line(' :noindex:', sourcename) self.add_line(' :noindex:', sourcename)
if self.objpath: if self.objpath:
@ -893,8 +900,14 @@ class ModuleLevelDocumenter(Documenter):
) -> Tuple[str, List[str]]: ) -> Tuple[str, List[str]]:
if modname is None: if modname is None:
if path: if path:
modname = path.rstrip('.') stripped = path.rstrip('.')
modname, qualname = split_full_qualified_name(stripped)
if qualname:
parents = qualname.split(".")
else: else:
parents = []
if modname is None:
# if documenting a toplevel object without explicit module, # if documenting a toplevel object without explicit module,
# it can be contained in another auto directive ... # it can be contained in another auto directive ...
modname = self.env.temp_data.get('autodoc:module') modname = self.env.temp_data.get('autodoc:module')
@ -927,8 +940,13 @@ class ClassLevelDocumenter(Documenter):
# ... if still None, there's no way to know # ... if still None, there's no way to know
if mod_cls is None: if mod_cls is None:
return None, [] return None, []
modname, cls = rpartition(mod_cls, '.')
parents = [cls] try:
modname, qualname = split_full_qualified_name(mod_cls)
parents = qualname.split(".") if qualname else []
except ImportError:
parents = mod_cls.split(".")
# if the module name is still missing, get it like above # if the module name is still missing, get it like above
if not modname: if not modname:
modname = self.env.temp_data.get('autodoc:module') modname = self.env.temp_data.get('autodoc:module')
@ -1026,43 +1044,19 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
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)
unwrapped = inspect.unwrap(self.object)
if ((inspect.isbuiltin(unwrapped) or inspect.ismethoddescriptor(unwrapped)) and
not inspect.is_cython_function_or_method(unwrapped)):
# cannot introspect arguments of a C function or method
return None
try: try:
if (not inspect.isfunction(unwrapped) and self.env.app.emit('autodoc-before-process-signature', self.object, False)
not inspect.ismethod(unwrapped) and if inspect.is_singledispatch_function(self.object):
not inspect.isbuiltin(unwrapped) and sig = inspect.signature(self.object, follow_wrapped=True)
not inspect.is_cython_function_or_method(unwrapped) and
not inspect.isclass(unwrapped) and
hasattr(unwrapped, '__call__')):
self.env.app.emit('autodoc-before-process-signature',
unwrapped.__call__, False)
sig = inspect.signature(unwrapped.__call__)
else: else:
self.env.app.emit('autodoc-before-process-signature', unwrapped, False) sig = inspect.signature(self.object)
sig = inspect.signature(unwrapped)
args = stringify_signature(sig, **kwargs) args = stringify_signature(sig, **kwargs)
except TypeError: except TypeError as exc:
if (inspect.is_builtin_class_method(unwrapped, '__new__') and logger.warning(__("Failed to get a function signature for %s: %s"),
inspect.is_builtin_class_method(unwrapped, '__init__')): self.fullname, exc)
raise TypeError('%r is a builtin class' % unwrapped) return None
except ValueError:
# if a class should be documented as function (yay duck args = ''
# typing) we try to use the constructor signature as function
# signature without the first argument.
try:
self.env.app.emit('autodoc-before-process-signature',
unwrapped.__new__, True)
sig = inspect.signature(unwrapped.__new__, bound_method=True)
args = stringify_signature(sig, show_return_annotation=False, **kwargs)
except TypeError:
self.env.app.emit('autodoc-before-process-signature',
unwrapped.__init__, True)
sig = inspect.signature(unwrapped.__init__, bound_method=True)
args = stringify_signature(sig, show_return_annotation=False, **kwargs)
if self.env.config.strip_signature_backslash: if self.env.config.strip_signature_backslash:
# escape backslashes for reST # escape backslashes for reST
@ -1074,26 +1068,17 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
def add_directive_header(self, sig: str) -> None: def add_directive_header(self, sig: str) -> None:
sourcename = self.get_sourcename() sourcename = self.get_sourcename()
if inspect.is_singledispatch_function(self.object):
self.add_singledispatch_directive_header(sig)
else:
super().add_directive_header(sig) super().add_directive_header(sig)
if inspect.iscoroutinefunction(self.object): if inspect.iscoroutinefunction(self.object):
self.add_line(' :async:', sourcename) self.add_line(' :async:', sourcename)
def add_singledispatch_directive_header(self, sig: str) -> None: def format_signature(self, **kwargs: Any) -> str:
sourcename = self.get_sourcename() sig = super().format_signature(**kwargs)
sigs = [sig]
# intercept generated directive headers if inspect.is_singledispatch_function(self.object):
# TODO: It is very hacky to use mock to intercept header generation # append signature of singledispatch'ed functions
with patch.object(self, 'add_line') as add_line:
super().add_directive_header(sig)
# output first line of header
self.add_line(*add_line.call_args_list[0][0])
# inserts signature of singledispatch'ed functions
for typ, func in self.object.registry.items(): for typ, func in self.object.registry.items():
if typ is object: if typ is object:
pass # default implementation. skipped. pass # default implementation. skipped.
@ -1102,13 +1087,9 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
documenter = FunctionDocumenter(self.directive, '') documenter = FunctionDocumenter(self.directive, '')
documenter.object = func documenter.object = func
self.add_line(' %s%s' % (self.format_name(), sigs.append(documenter.format_signature())
documenter.format_signature()),
sourcename)
# output remains of directive header return "\n".join(sigs)
for call in add_line.call_args_list[1:]:
self.add_line(*call[0])
def annotate_to_first_argument(self, func: Callable, typ: Type) -> None: def annotate_to_first_argument(self, func: Callable, typ: Type) -> None:
"""Annotate type hint to the first argument of function if needed.""" """Annotate type hint to the first argument of function if needed."""
@ -1448,18 +1429,33 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
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)
unwrapped = inspect.unwrap(self.object) try:
if ((inspect.isbuiltin(unwrapped) or inspect.ismethoddescriptor(unwrapped)) and if self.object == object.__init__ and self.parent != object:
not inspect.is_cython_function_or_method(unwrapped)): # Classes not having own __init__() method are shown as no arguments.
# can never get arguments of a C function or method #
return None # Note: The signature of object.__init__() is (self, /, *args, **kwargs).
if inspect.isstaticmethod(unwrapped, cls=self.parent, name=self.object_name): # But it makes users confused.
self.env.app.emit('autodoc-before-process-signature', unwrapped, False) args = '()'
sig = inspect.signature(unwrapped, bound_method=False)
else: else:
self.env.app.emit('autodoc-before-process-signature', unwrapped, True) if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name):
sig = inspect.signature(unwrapped, bound_method=True) self.env.app.emit('autodoc-before-process-signature', self.object, False)
sig = inspect.signature(self.object, bound_method=False)
else:
self.env.app.emit('autodoc-before-process-signature', self.object, True)
meth = self.parent.__dict__.get(self.objpath[-1], None)
if meth and inspect.is_singledispatch_method(meth):
sig = inspect.signature(self.object, bound_method=True,
follow_wrapped=True)
else:
sig = inspect.signature(self.object, bound_method=True)
args = stringify_signature(sig, **kwargs) args = stringify_signature(sig, **kwargs)
except TypeError as exc:
logger.warning(__("Failed to get a method signature for %s: %s"),
self.fullname, exc)
return None
except ValueError:
args = ''
if self.env.config.strip_signature_backslash: if self.env.config.strip_signature_backslash:
# escape backslashes for reST # escape backslashes for reST
@ -1467,10 +1463,6 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
return args return args
def add_directive_header(self, sig: str) -> None: def add_directive_header(self, sig: str) -> None:
meth = self.parent.__dict__.get(self.objpath[-1])
if inspect.is_singledispatch_method(meth):
self.add_singledispatch_directive_header(sig)
else:
super().add_directive_header(sig) super().add_directive_header(sig)
sourcename = self.get_sourcename() sourcename = self.get_sourcename()
@ -1489,19 +1481,13 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
def document_members(self, all_members: bool = False) -> None: def document_members(self, all_members: bool = False) -> None:
pass pass
def add_singledispatch_directive_header(self, sig: str) -> None: def format_signature(self, **kwargs: Any) -> str:
sourcename = self.get_sourcename() sig = super().format_signature(**kwargs)
sigs = [sig]
# intercept generated directive headers
# TODO: It is very hacky to use mock to intercept header generation
with patch.object(self, 'add_line') as add_line:
super().add_directive_header(sig)
# output first line of header
self.add_line(*add_line.call_args_list[0][0])
# inserts signature of singledispatch'ed functions
meth = self.parent.__dict__.get(self.objpath[-1]) meth = self.parent.__dict__.get(self.objpath[-1])
if inspect.is_singledispatch_method(meth):
# append signature of singledispatch'ed functions
for typ, func in meth.dispatcher.registry.items(): for typ, func in meth.dispatcher.registry.items():
if typ is object: if typ is object:
pass # default implementation. skipped. pass # default implementation. skipped.
@ -1509,14 +1495,12 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
self.annotate_to_first_argument(func, typ) self.annotate_to_first_argument(func, typ)
documenter = MethodDocumenter(self.directive, '') documenter = MethodDocumenter(self.directive, '')
documenter.parent = self.parent
documenter.object = func documenter.object = func
self.add_line(' %s%s' % (self.format_name(), documenter.objpath = [None]
documenter.format_signature()), sigs.append(documenter.format_signature())
sourcename)
# output remains of directive header return "\n".join(sigs)
for call in add_line.call_args_list[1:]:
self.add_line(*call[0])
def annotate_to_first_argument(self, func: Callable, typ: Type) -> None: def annotate_to_first_argument(self, func: Callable, typ: Type) -> None:
"""Annotate type hint to the first argument of function if needed.""" """Annotate type hint to the first argument of function if needed."""
@ -1753,6 +1737,14 @@ def autodoc_attrgetter(app: Sphinx, obj: Any, name: str, *defargs: Any) -> Any:
return safe_getattr(obj, name, *defargs) return safe_getattr(obj, name, *defargs)
def migrate_autodoc_member_order(app: Sphinx, config: Config) -> None:
if config.autodoc_member_order == 'alphabetic':
# RemovedInSphinx50Warning
logger.warning(__('autodoc_member_order now accepts "alphabetical" '
'instead of "alphabetic". Please update your setting.'))
config.autodoc_member_order = 'alphabetical' # type: ignore
def setup(app: Sphinx) -> Dict[str, Any]: def setup(app: Sphinx) -> Dict[str, Any]:
app.add_autodocumenter(ModuleDocumenter) app.add_autodocumenter(ModuleDocumenter)
app.add_autodocumenter(ClassDocumenter) app.add_autodocumenter(ClassDocumenter)
@ -1768,7 +1760,8 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_autodocumenter(SlotsAttributeDocumenter) app.add_autodocumenter(SlotsAttributeDocumenter)
app.add_config_value('autoclass_content', 'class', True, ENUM('both', 'class', 'init')) app.add_config_value('autoclass_content', 'class', True, ENUM('both', 'class', 'init'))
app.add_config_value('autodoc_member_order', 'alphabetic', True) app.add_config_value('autodoc_member_order', 'alphabetical', True,
ENUM('alphabetic', 'alphabetical', 'bysource', 'groupwise'))
app.add_config_value('autodoc_default_options', {}, True) app.add_config_value('autodoc_default_options', {}, True)
app.add_config_value('autodoc_docstring_signature', True, True) app.add_config_value('autodoc_docstring_signature', True, True)
app.add_config_value('autodoc_mock_imports', [], True) app.add_config_value('autodoc_mock_imports', [], True)
@ -1781,6 +1774,8 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_event('autodoc-process-signature') app.add_event('autodoc-process-signature')
app.add_event('autodoc-skip-member') app.add_event('autodoc-skip-member')
app.connect('config-inited', migrate_autodoc_member_order)
app.setup_extension('sphinx.ext.autodoc.type_comment') app.setup_extension('sphinx.ext.autodoc.type_comment')
app.setup_extension('sphinx.ext.autodoc.typehints') app.setup_extension('sphinx.ext.autodoc.typehints')

View File

@ -298,8 +298,7 @@ class Autosummary(SphinxDirective):
with mock(self.config.autosummary_mock_imports): with mock(self.config.autosummary_mock_imports):
real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes) real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes)
except ImportError: except ImportError:
logger.warning(__('failed to import %s'), name) logger.warning(__('autosummary: failed to import %s'), name)
items.append((name, '', '', name))
continue continue
self.bridge.result = StringList() # initialize for each documenter self.bridge.result = StringList() # initialize for each documenter

View File

@ -45,6 +45,7 @@ from sphinx.locale import __
from sphinx.registry import SphinxComponentRegistry from sphinx.registry import SphinxComponentRegistry
from sphinx.util import logging from sphinx.util import logging
from sphinx.util import rst from sphinx.util import rst
from sphinx.util import split_full_qualified_name
from sphinx.util.inspect import safe_getattr from sphinx.util.inspect import safe_getattr
from sphinx.util.osutil import ensuredir from sphinx.util.osutil import ensuredir
from sphinx.util.template import SphinxTemplateLoader from sphinx.util.template import SphinxTemplateLoader
@ -244,19 +245,19 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
ns['attributes'], ns['all_attributes'] = \ ns['attributes'], ns['all_attributes'] = \
get_members(obj, {'attribute', 'property'}) get_members(obj, {'attribute', 'property'})
parts = name.split('.') modname, qualname = split_full_qualified_name(name)
if doc.objtype in ('method', 'attribute', 'property'): if doc.objtype in ('method', 'attribute', 'property'):
mod_name = '.'.join(parts[:-2]) ns['class'] = qualname.rsplit(".", 1)[0]
cls_name = parts[-2]
obj_name = '.'.join(parts[-2:]) if doc.objtype in ('class',):
ns['class'] = cls_name shortname = qualname
else: else:
mod_name, obj_name = '.'.join(parts[:-1]), parts[-1] shortname = qualname.rsplit(".", 1)[-1]
ns['fullname'] = name ns['fullname'] = name
ns['module'] = mod_name ns['module'] = modname
ns['objname'] = obj_name ns['objname'] = qualname
ns['name'] = parts[-1] ns['name'] = shortname
ns['objtype'] = doc.objtype ns['objtype'] = doc.objtype
ns['underline'] = len(name) * '=' ns['underline'] = len(name) * '='

View File

@ -12,7 +12,6 @@
import posixpath import posixpath
import re import re
import subprocess import subprocess
from hashlib import sha1
from os import path from os import path
from subprocess import CalledProcessError, PIPE from subprocess import CalledProcessError, PIPE
from typing import Any, Dict, List, Tuple from typing import Any, Dict, List, Tuple
@ -25,7 +24,7 @@ import sphinx
from sphinx.application import Sphinx from sphinx.application import Sphinx
from sphinx.errors import SphinxError from sphinx.errors import SphinxError
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.util import logging from sphinx.util import logging, sha1
from sphinx.util.docutils import SphinxDirective, SphinxTranslator from sphinx.util.docutils import SphinxDirective, SphinxTranslator
from sphinx.util.fileutil import copy_asset from sphinx.util.fileutil import copy_asset
from sphinx.util.i18n import search_image_for_language from sphinx.util.i18n import search_image_for_language

View File

@ -14,7 +14,6 @@ import shutil
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
from hashlib import sha1
from os import path from os import path
from subprocess import CalledProcessError, PIPE from subprocess import CalledProcessError, PIPE
from typing import Any, Dict, List, Tuple from typing import Any, Dict, List, Tuple
@ -29,7 +28,7 @@ from sphinx.builders import Builder
from sphinx.config import Config from sphinx.config import Config
from sphinx.errors import SphinxError from sphinx.errors import SphinxError
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.util import logging from sphinx.util import logging, sha1
from sphinx.util.math import get_node_equation_number, wrap_displaymath from sphinx.util.math import get_node_equation_number, wrap_displaymath
from sphinx.util.osutil import ensuredir from sphinx.util.osutil import ensuredir
from sphinx.util.png import read_png_depth, write_png_depth from sphinx.util.png import read_png_depth, write_png_depth

View File

@ -38,7 +38,6 @@ r"""
import builtins import builtins
import inspect import inspect
import re import re
from hashlib import md5
from importlib import import_module from importlib import import_module
from typing import Any, Dict, Iterable, List, Tuple from typing import Any, Dict, Iterable, List, Tuple
from typing import cast from typing import cast
@ -55,6 +54,7 @@ from sphinx.ext.graphviz import (
graphviz, figure_wrapper, graphviz, figure_wrapper,
render_dot_html, render_dot_latex, render_dot_texinfo render_dot_html, render_dot_latex, render_dot_texinfo
) )
from sphinx.util import md5
from sphinx.util.docutils import SphinxDirective from sphinx.util.docutils import SphinxDirective
from sphinx.writers.html import HTMLTranslator from sphinx.writers.html import HTMLTranslator
from sphinx.writers.latex import LaTeXTranslator from sphinx.writers.latex import LaTeXTranslator

View File

@ -131,8 +131,10 @@ def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str],
def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node
) -> Node: ) -> Node:
if app.builder.format != 'html':
return None
elif node['reftype'] == 'viewcode':
# resolve our "viewcode" reference nodes -- they need special treatment # resolve our "viewcode" reference nodes -- they need special treatment
if node['reftype'] == 'viewcode':
return make_refnode(app.builder, node['refdoc'], node['reftarget'], return make_refnode(app.builder, node['refdoc'], node['reftarget'],
node['refid'], contnode) node['refid'], contnode)

View File

@ -75,81 +75,35 @@ def unparse(node: Optional[ast.AST]) -> Optional[str]:
return None return None
elif isinstance(node, str): elif isinstance(node, str):
return node return node
elif node.__class__ in OPERATORS: return _UnparseVisitor().visit(node)
# a greatly cut-down version of `ast._Unparser`
class _UnparseVisitor(ast.NodeVisitor):
def _visit_op(self, node: ast.AST) -> str:
return OPERATORS[node.__class__] return OPERATORS[node.__class__]
elif isinstance(node, ast.arg): for _op in OPERATORS:
locals()['visit_{}'.format(_op.__name__)] = _visit_op
def visit_arg(self, node: ast.arg) -> str:
if node.annotation: if node.annotation:
return "%s: %s" % (node.arg, unparse(node.annotation)) return "%s: %s" % (node.arg, self.visit(node.annotation))
else: else:
return node.arg return node.arg
elif isinstance(node, ast.arguments):
return unparse_arguments(node)
elif isinstance(node, ast.Attribute):
return "%s.%s" % (unparse(node.value), node.attr)
elif isinstance(node, ast.BinOp):
return " ".join(unparse(e) for e in [node.left, node.op, node.right])
elif isinstance(node, ast.BoolOp):
op = " %s " % unparse(node.op)
return op.join(unparse(e) for e in node.values)
elif isinstance(node, ast.Bytes):
return repr(node.s)
elif isinstance(node, ast.Call):
args = ([unparse(e) for e in node.args] +
["%s=%s" % (k.arg, unparse(k.value)) for k in node.keywords])
return "%s(%s)" % (unparse(node.func), ", ".join(args))
elif isinstance(node, ast.Dict):
keys = (unparse(k) for k in node.keys)
values = (unparse(v) for v in node.values)
items = (k + ": " + v for k, v in zip(keys, values))
return "{" + ", ".join(items) + "}"
elif isinstance(node, ast.Ellipsis):
return "..."
elif isinstance(node, ast.Index):
return unparse(node.value)
elif isinstance(node, ast.Lambda):
return "lambda %s: ..." % unparse(node.args)
elif isinstance(node, ast.List):
return "[" + ", ".join(unparse(e) for e in node.elts) + "]"
elif isinstance(node, ast.Name):
return node.id
elif isinstance(node, ast.NameConstant):
return repr(node.value)
elif isinstance(node, ast.Num):
return repr(node.n)
elif isinstance(node, ast.Set):
return "{" + ", ".join(unparse(e) for e in node.elts) + "}"
elif isinstance(node, ast.Str):
return repr(node.s)
elif isinstance(node, ast.Subscript):
return "%s[%s]" % (unparse(node.value), unparse(node.slice))
elif isinstance(node, ast.UnaryOp):
return "%s %s" % (unparse(node.op), unparse(node.operand))
elif isinstance(node, ast.Tuple):
if node.elts:
return ", ".join(unparse(e) for e in node.elts)
else:
return "()"
elif sys.version_info > (3, 6) and isinstance(node, ast.Constant):
# this branch should be placed at last
return repr(node.value)
else:
raise NotImplementedError('Unable to parse %s object' % type(node).__name__)
def _visit_arg_with_default(self, arg: ast.arg, default: Optional[ast.AST]) -> str:
def _unparse_arg(arg: ast.arg, default: Optional[ast.AST]) -> str:
"""Unparse a single argument to a string.""" """Unparse a single argument to a string."""
name = unparse(arg) name = self.visit(arg)
if default: if default:
if arg.annotation: if arg.annotation:
name += " = %s" % unparse(default) name += " = %s" % self.visit(default)
else: else:
name += "=%s" % unparse(default) name += "=%s" % self.visit(default)
return name return name
def visit_arguments(self, node: ast.arguments) -> str:
def unparse_arguments(node: ast.arguments) -> str: defaults = list(node.defaults)
"""Unparse an arguments to string."""
defaults = list(node.defaults) # type: List[Optional[ast.AST]]
positionals = len(node.args) positionals = len(node.args)
posonlyargs = 0 posonlyargs = 0
if hasattr(node, "posonlyargs"): # for py38+ if hasattr(node, "posonlyargs"): # for py38+
@ -158,30 +112,105 @@ def unparse_arguments(node: ast.arguments) -> str:
for _ in range(len(defaults), positionals): for _ in range(len(defaults), positionals):
defaults.insert(0, None) defaults.insert(0, None)
kw_defaults = list(node.kw_defaults) # type: List[Optional[ast.AST]] kw_defaults = list(node.kw_defaults)
for _ in range(len(kw_defaults), len(node.kwonlyargs)): for _ in range(len(kw_defaults), len(node.kwonlyargs)):
kw_defaults.insert(0, None) kw_defaults.insert(0, None)
args = [] # type: List[str] args = [] # type: List[str]
if hasattr(node, "posonlyargs"): # for py38+ if hasattr(node, "posonlyargs"): # for py38+
for i, arg in enumerate(node.posonlyargs): # type: ignore for i, arg in enumerate(node.posonlyargs): # type: ignore
args.append(_unparse_arg(arg, defaults[i])) args.append(self._visit_arg_with_default(arg, defaults[i]))
if node.posonlyargs: # type: ignore if node.posonlyargs: # type: ignore
args.append('/') args.append('/')
for i, arg in enumerate(node.args): for i, arg in enumerate(node.args):
args.append(_unparse_arg(arg, defaults[i + posonlyargs])) args.append(self._visit_arg_with_default(arg, defaults[i + posonlyargs]))
if node.vararg: if node.vararg:
args.append("*" + unparse(node.vararg)) args.append("*" + self.visit(node.vararg))
if node.kwonlyargs and not node.vararg: if node.kwonlyargs and not node.vararg:
args.append('*') args.append('*')
for i, arg in enumerate(node.kwonlyargs): for i, arg in enumerate(node.kwonlyargs):
args.append(_unparse_arg(arg, kw_defaults[i])) args.append(self._visit_arg_with_default(arg, kw_defaults[i]))
if node.kwarg: if node.kwarg:
args.append("**" + unparse(node.kwarg)) args.append("**" + self.visit(node.kwarg))
return ", ".join(args) return ", ".join(args)
def visit_Attribute(self, node: ast.Attribute) -> str:
return "%s.%s" % (self.visit(node.value), node.attr)
def visit_BinOp(self, node: ast.BinOp) -> str:
return " ".join(self.visit(e) for e in [node.left, node.op, node.right])
def visit_BoolOp(self, node: ast.BoolOp) -> str:
op = " %s " % self.visit(node.op)
return op.join(self.visit(e) for e in node.values)
def visit_Call(self, node: ast.Call) -> str:
args = ([self.visit(e) for e in node.args] +
["%s=%s" % (k.arg, self.visit(k.value)) for k in node.keywords])
return "%s(%s)" % (self.visit(node.func), ", ".join(args))
def visit_Dict(self, node: ast.Dict) -> str:
keys = (self.visit(k) for k in node.keys)
values = (self.visit(v) for v in node.values)
items = (k + ": " + v for k, v in zip(keys, values))
return "{" + ", ".join(items) + "}"
def visit_Index(self, node: ast.Index) -> str:
return self.visit(node.value)
def visit_Lambda(self, node: ast.Lambda) -> str:
return "lambda %s: ..." % self.visit(node.args)
def visit_List(self, node: ast.List) -> str:
return "[" + ", ".join(self.visit(e) for e in node.elts) + "]"
def visit_Name(self, node: ast.Name) -> str:
return node.id
def visit_Set(self, node: ast.Set) -> str:
return "{" + ", ".join(self.visit(e) for e in node.elts) + "}"
def visit_Subscript(self, node: ast.Subscript) -> str:
return "%s[%s]" % (self.visit(node.value), self.visit(node.slice))
def visit_UnaryOp(self, node: ast.UnaryOp) -> str:
return "%s %s" % (self.visit(node.op), self.visit(node.operand))
def visit_Tuple(self, node: ast.Tuple) -> str:
if node.elts:
return ", ".join(self.visit(e) for e in node.elts)
else:
return "()"
if sys.version_info >= (3, 6):
def visit_Constant(self, node: ast.Constant) -> str:
if node.value is Ellipsis:
return "..."
else:
return repr(node.value)
if sys.version_info < (3, 8):
# these ast nodes were deprecated in python 3.8
def visit_Bytes(self, node: ast.Bytes) -> str:
return repr(node.s)
def visit_Ellipsis(self, node: ast.Ellipsis) -> str:
return "..."
def visit_NameConstant(self, node: ast.NameConstant) -> str:
return repr(node.value)
def visit_Num(self, node: ast.Num) -> str:
return repr(node.n)
def visit_Str(self, node: ast.Str) -> str:
return repr(node.s)
def generic_visit(self, node):
raise NotImplementedError('Unable to parse %s object' % type(node).__name__)

View File

@ -22,7 +22,7 @@ from sphinx import addnodes
from sphinx import package_dir from sphinx import package_dir
from sphinx.environment import BuildEnvironment from sphinx.environment import BuildEnvironment
from sphinx.search.jssplitter import splitter_code from sphinx.search.jssplitter import splitter_code
from sphinx.util import jsdump, rpartition from sphinx.util import jsdump
class SearchLanguage: class SearchLanguage:
@ -324,7 +324,7 @@ class IndexBuilder:
continue continue
fullname = html.escape(fullname) fullname = html.escape(fullname)
dispname = html.escape(dispname) dispname = html.escape(dispname)
prefix, name = rpartition(dispname, '.') prefix, _, name = dispname.rpartition('.')
pdict = rv.setdefault(prefix, {}) pdict = rv.setdefault(prefix, {})
try: try:
typeindex = otypes[domainname, type] typeindex = otypes[domainname, type]

View File

@ -23,6 +23,7 @@ from docutils.utils.smartquotes import smartchars
from sphinx import addnodes from sphinx import addnodes
from sphinx.config import Config from sphinx.config import Config
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.util import docutils
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.docutils import new_document from sphinx.util.docutils import new_document
from sphinx.util.i18n import format_date from sphinx.util.i18n import format_date
@ -359,12 +360,18 @@ class SphinxSmartQuotes(SmartQuotes, SphinxTransform):
def get_tokens(self, txtnodes: List[Text]) -> Generator[Tuple[str, str], None, None]: def get_tokens(self, txtnodes: List[Text]) -> Generator[Tuple[str, str], None, None]:
# A generator that yields ``(texttype, nodetext)`` tuples for a list # A generator that yields ``(texttype, nodetext)`` tuples for a list
# of "Text" nodes (interface to ``smartquotes.educate_tokens()``). # of "Text" nodes (interface to ``smartquotes.educate_tokens()``).
texttype = {True: 'literal', # "literal" text is not changed:
False: 'plain'}
for txtnode in txtnodes: for txtnode in txtnodes:
notsmartquotable = not is_smartquotable(txtnode) if is_smartquotable(txtnode):
yield (texttype[notsmartquotable], txtnode.astext()) if docutils.__version_info__ >= (0, 16):
# SmartQuotes uses backslash escapes instead of null-escapes
text = re.sub(r'(?<=\x00)([-\\\'".`])', r'\\\1', str(txtnode))
else:
text = txtnode.astext()
yield ('plain', text)
else:
# skip smart quotes
yield ('literal', txtnode.astext())
class DoctreeReadEvent(SphinxTransform): class DoctreeReadEvent(SphinxTransform):

View File

@ -10,7 +10,6 @@
import os import os
import re import re
from hashlib import sha1
from math import ceil from math import ceil
from typing import Any, Dict, List, Tuple from typing import Any, Dict, List, Tuple
@ -19,7 +18,7 @@ from docutils import nodes
from sphinx.application import Sphinx from sphinx.application import Sphinx
from sphinx.locale import __ from sphinx.locale import __
from sphinx.transforms import SphinxTransform from sphinx.transforms import SphinxTransform
from sphinx.util import epoch_to_rfc1123, rfc1123_to_epoch from sphinx.util import epoch_to_rfc1123, rfc1123_to_epoch, sha1
from sphinx.util import logging, requests from sphinx.util import logging, requests
from sphinx.util.images import guess_mimetype, get_image_extension, parse_data_uri from sphinx.util.images import guess_mimetype, get_image_extension, parse_data_uri
from sphinx.util.osutil import ensuredir, movefile from sphinx.util.osutil import ensuredir, movefile
@ -194,7 +193,9 @@ class ImageConverter(BaseImageConverter):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def match(self, node: nodes.image) -> bool: def match(self, node: nodes.image) -> bool:
if self.available is None: if not self.app.builder.supported_image_types:
return False
elif self.available is None:
self.available = self.is_available() self.available = self.is_available()
if not self.available: if not self.available:

View File

@ -9,15 +9,16 @@
""" """
import functools import functools
import hashlib
import os import os
import posixpath import posixpath
import re import re
import sys import sys
import tempfile import tempfile
import traceback import traceback
import warnings
import unicodedata import unicodedata
from datetime import datetime from datetime import datetime
from hashlib import md5
from importlib import import_module from importlib import import_module
from os import path from os import path
from time import mktime, strptime from time import mktime, strptime
@ -25,6 +26,7 @@ from typing import Any, Callable, Dict, IO, Iterable, Iterator, List, Pattern, S
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from urllib.parse import urlsplit, urlunsplit, quote_plus, parse_qsl, urlencode from urllib.parse import urlsplit, urlunsplit, quote_plus, parse_qsl, urlencode
from sphinx.deprecation import RemovedInSphinx50Warning
from sphinx.errors import SphinxParallelError, ExtensionError, FiletypeNotFoundError from sphinx.errors import SphinxParallelError, ExtensionError, FiletypeNotFoundError
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util import logging from sphinx.util import logging
@ -145,6 +147,36 @@ class FilenameUniqDict(dict):
self._existing = state self._existing = state
def md5(data=b'', **kwargs):
"""Wrapper around hashlib.md5
Attempt call with 'usedforsecurity=False' if we get a ValueError, which happens when
OpenSSL FIPS mode is enabled:
ValueError: error:060800A3:digital envelope routines:EVP_DigestInit_ex:disabled for fips
See: https://github.com/sphinx-doc/sphinx/issues/7611
"""
try:
return hashlib.md5(data, **kwargs) # type: ignore
except ValueError:
return hashlib.md5(data, **kwargs, usedforsecurity=False) # type: ignore
def sha1(data=b'', **kwargs):
"""Wrapper around hashlib.sha1
Attempt call with 'usedforsecurity=False' if we get a ValueError
See: https://github.com/sphinx-doc/sphinx/issues/7611
"""
try:
return hashlib.sha1(data, **kwargs) # type: ignore
except ValueError:
return hashlib.sha1(data, **kwargs, usedforsecurity=False) # type: ignore
class DownloadFiles(dict): class DownloadFiles(dict):
"""A special dictionary for download files. """A special dictionary for download files.
@ -310,6 +342,7 @@ def parselinenos(spec: str, total: int) -> List[int]:
def rpartition(s: str, t: str) -> Tuple[str, str]: def rpartition(s: str, t: str) -> Tuple[str, str]:
"""Similar to str.rpartition from 2.5, but doesn't return the separator.""" """Similar to str.rpartition from 2.5, but doesn't return the separator."""
warnings.warn('rpartition() is now deprecated.', RemovedInSphinx50Warning, stacklevel=2)
i = s.rfind(t) i = s.rfind(t)
if i != -1: if i != -1:
return s[:i], s[i + len(t):] return s[:i], s[i + len(t):]
@ -378,6 +411,31 @@ def import_object(objname: str, source: str = None) -> Any:
raise ExtensionError('Could not import %s' % objname, exc) raise ExtensionError('Could not import %s' % objname, exc)
def split_full_qualified_name(name: str) -> Tuple[str, str]:
"""Split full qualified name to a pair of modname and qualname.
A qualname is an abbreviation for "Qualified name" introduced at PEP-3155
(https://www.python.org/dev/peps/pep-3155/). It is a dotted path name
from the module top-level.
A "full" qualified name means a string containing both module name and
qualified name.
.. note:: This function imports module actually to check the exisitence.
Therefore you need to mock 3rd party modules if needed before
calling this function.
"""
parts = name.split('.')
for i, part in enumerate(parts, 1):
try:
modname = ".".join(parts[:i])
import_module(modname)
except ImportError:
return ".".join(parts[:i - 1]), ".".join(parts[i - 1:])
return name, ""
def encode_uri(uri: str) -> str: def encode_uri(uri: str) -> str:
split = list(urlsplit(uri)) split = list(urlsplit(uri))
split[1] = split[1].encode('idna').decode('ascii') split[1] = split[1].encode('idna').decode('ascii')

View File

@ -372,26 +372,38 @@ def is_builtin_class_method(obj: Any, attr_name: str) -> bool:
Why this function needed? CPython implements int.__init__ by Descriptor Why this function needed? CPython implements int.__init__ by Descriptor
but PyPy implements it by pure Python code. but PyPy implements it by pure Python code.
""" """
classes = [c for c in inspect.getmro(obj) if attr_name in c.__dict__] try:
cls = classes[0] if classes else object mro = inspect.getmro(obj)
except AttributeError:
if not hasattr(builtins, safe_getattr(cls, '__name__', '')): # no __mro__, assume the object has no methods as we know them
return False return False
return getattr(builtins, safe_getattr(cls, '__name__', '')) is cls
try:
cls = next(c for c in mro if attr_name in safe_getattr(c, '__dict__', {}))
except StopIteration:
return False
try:
name = safe_getattr(cls, '__name__')
except AttributeError:
return False
return getattr(builtins, name, None) is cls
def signature(subject: Callable, bound_method: bool = False) -> inspect.Signature: def signature(subject: Callable, bound_method: bool = False, follow_wrapped: bool = False
) -> inspect.Signature:
"""Return a Signature object for the given *subject*. """Return a Signature object for the given *subject*.
:param bound_method: Specify *subject* is a bound method or not :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*).
""" """
# check subject is not a built-in class (ex. int, str)
if (isinstance(subject, type) and
is_builtin_class_method(subject, "__new__") and
is_builtin_class_method(subject, "__init__")):
raise TypeError("can't compute signature for built-in type {}".format(subject))
try: try:
try:
signature = inspect.signature(subject, follow_wrapped=follow_wrapped)
except ValueError:
# follow built-in wrappers up (ex. functools.lru_cache)
signature = inspect.signature(subject) signature = inspect.signature(subject)
parameters = list(signature.parameters.values()) parameters = list(signature.parameters.values())
return_annotation = signature.return_annotation return_annotation = signature.return_annotation

View File

@ -39,6 +39,12 @@ TitleGetter = Callable[[nodes.Node], str]
Inventory = Dict[str, Dict[str, Tuple[str, str, str, str]]] Inventory = Dict[str, Dict[str, Tuple[str, str, str, str]]]
def is_system_TypeVar(typ: Any) -> bool:
"""Check *typ* is system defined TypeVar."""
modname = getattr(typ, '__module__', '')
return modname == 'typing' and isinstance(typ, TypeVar) # type: ignore
def stringify(annotation: Any) -> str: def stringify(annotation: Any) -> str:
"""Stringify type annotation object.""" """Stringify type annotation object."""
if isinstance(annotation, str): if isinstance(annotation, str):
@ -85,18 +91,23 @@ def _stringify_py37(annotation: Any) -> str:
if getattr(annotation, '__args__', None): if getattr(annotation, '__args__', None):
if qualname == 'Union': if qualname == 'Union':
if len(annotation.__args__) == 2 and annotation.__args__[1] is NoneType: # type: ignore # NOQA if len(annotation.__args__) > 1 and annotation.__args__[-1] is NoneType: # type: ignore # NOQA
if len(annotation.__args__) > 2:
args = ', '.join(stringify(a) for a in annotation.__args__[:-1])
return 'Optional[Union[%s]]' % args
else:
return 'Optional[%s]' % stringify(annotation.__args__[0]) return 'Optional[%s]' % stringify(annotation.__args__[0])
else: else:
args = ', '.join(stringify(a) for a in annotation.__args__) args = ', '.join(stringify(a) for a in annotation.__args__)
return '%s[%s]' % (qualname, args) return 'Union[%s]' % args
elif qualname == 'Callable': elif qualname == 'Callable':
args = ', '.join(stringify(a) for a in annotation.__args__[:-1]) args = ', '.join(stringify(a) for a in annotation.__args__[:-1])
returns = stringify(annotation.__args__[-1]) returns = stringify(annotation.__args__[-1])
return '%s[[%s], %s]' % (qualname, args, returns) return '%s[[%s], %s]' % (qualname, args, returns)
elif str(annotation).startswith('typing.Annotated'): # for py39+ elif str(annotation).startswith('typing.Annotated'): # for py39+
return stringify(annotation.__args__[0]) return stringify(annotation.__args__[0])
elif getattr(annotation, '_special', False): elif all(is_system_TypeVar(a) for a in annotation.__args__):
# Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT])
return qualname return qualname
else: else:
args = ', '.join(stringify(a) for a in annotation.__args__) args = ', '.join(stringify(a) for a in annotation.__args__)
@ -148,7 +159,11 @@ def _stringify_py36(annotation: Any) -> str:
annotation.__origin__ is typing.Union): annotation.__origin__ is typing.Union):
params = annotation.__args__ params = annotation.__args__
if params is not None: if params is not None:
if len(params) == 2 and params[1] is NoneType: # type: ignore if len(params) > 1 and params[-1] is NoneType: # type: ignore
if len(params) > 2:
param_str = ", ".join(stringify(p) for p in params[:-1])
return 'Optional[Union[%s]]' % param_str
else:
return 'Optional[%s]' % stringify(params[0]) return 'Optional[%s]' % stringify(params[0])
else: else:
param_str = ', '.join(stringify(p) for p in params) param_str = ', '.join(stringify(p) for p in params)

View File

@ -0,0 +1,12 @@
class Foo:
pass
class Bar:
def __init__(self, x, y):
pass
class Baz:
def __new__(cls, x, y):
pass

View File

@ -1,5 +1,9 @@
from functools import wraps
def deco1(func): def deco1(func):
"""docstring for deco1""" """docstring for deco1"""
@wraps(func)
def wrapper(): def wrapper():
return func() return func()
@ -14,3 +18,14 @@ def deco2(condition, message):
return wrapper return wrapper
return decorator return decorator
@deco1
def foo(name=None, age=None):
pass
class Bar:
@deco1
def meth(self, name=None, age=None):
pass

View File

@ -3,6 +3,9 @@ from typing import Union
class Foo: class Foo:
class Bar:
pass
def __init__(self): def __init__(self):
pass pass

View File

@ -9,5 +9,6 @@
autosummary_dummy_module autosummary_dummy_module
autosummary_dummy_module.Foo autosummary_dummy_module.Foo
autosummary_dummy_module.Foo.Bar
autosummary_dummy_module.bar autosummary_dummy_module.bar
autosummary_importfail autosummary_importfail

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,6 @@
import os import os
import re import re
from hashlib import md5
from itertools import cycle, chain from itertools import cycle, chain
import pytest import pytest
@ -19,6 +18,7 @@ from html5lib import HTMLParser
from sphinx.builders.html import validate_html_extra_path, validate_html_static_path from sphinx.builders.html import validate_html_extra_path, validate_html_static_path
from sphinx.errors import ConfigError from sphinx.errors import ConfigError
from sphinx.testing.util import strip_escseq from sphinx.testing.util import strip_escseq
from sphinx.util import md5
from sphinx.util.inventory import InventoryFile from sphinx.util.inventory import InventoryFile

View File

@ -27,15 +27,29 @@ def parse(name, string):
return ast return ast
def _check(name, input, idDict, output): def _check(name, input, idDict, output, key, asTextOutput):
if key is None:
key = name
key += ' '
if name in ('function', 'member'):
inputActual = input
outputAst = output
outputAsText = output
else:
inputActual = input.format(key='')
outputAst = output.format(key='')
outputAsText = output.format(key=key)
if asTextOutput is not None:
outputAsText = asTextOutput
# first a simple check of the AST # first a simple check of the AST
ast = parse(name, input) ast = parse(name, inputActual)
res = str(ast) res = str(ast)
if res != output: if res != outputAst:
print("") print("")
print("Input: ", input) print("Input: ", input)
print("Result: ", res) print("Result: ", res)
print("Expected: ", output) print("Expected: ", outputAst)
raise DefinitionError("") raise DefinitionError("")
rootSymbol = Symbol(None, None, None, None) rootSymbol = Symbol(None, None, None, None)
symbol = rootSymbol.add_declaration(ast, docname="TestDoc") symbol = rootSymbol.add_declaration(ast, docname="TestDoc")
@ -43,6 +57,13 @@ def _check(name, input, idDict, output):
signode = addnodes.desc_signature(input, '') signode = addnodes.desc_signature(input, '')
parentNode += signode parentNode += signode
ast.describe_signature(signode, 'lastIsName', symbol, options={}) ast.describe_signature(signode, 'lastIsName', symbol, options={})
resAsText = parentNode.astext()
if resAsText != outputAsText:
print("")
print("Input: ", input)
print("astext(): ", resAsText)
print("Expected: ", outputAsText)
raise DefinitionError("")
idExpected = [None] idExpected = [None]
for i in range(1, _max_id + 1): for i in range(1, _max_id + 1):
@ -75,14 +96,15 @@ def _check(name, input, idDict, output):
raise DefinitionError("") raise DefinitionError("")
def check(name, input, idDict, output=None): def check(name, input, idDict, output=None, key=None, asTextOutput=None):
if output is None: if output is None:
output = input output = input
# First, check without semicolon # First, check without semicolon
_check(name, input, idDict, output) _check(name, input, idDict, output, key, asTextOutput)
if name != 'macro': if name != 'macro':
# Second, check with semicolon # Second, check with semicolon
_check(name, input + ' ;', idDict, output + ';') _check(name, input + ' ;', idDict, output + ';', key,
asTextOutput + ';' if asTextOutput is not None else None)
def test_expressions(): def test_expressions():
@ -234,24 +256,24 @@ def test_expressions():
def test_type_definitions(): def test_type_definitions():
check('type', "T", {1: "T"}) check('type', "{key}T", {1: "T"})
check('type', "bool *b", {1: 'b'}) check('type', "{key}bool *b", {1: 'b'}, key='typedef')
check('type', "bool *const b", {1: 'b'}) check('type', "{key}bool *const b", {1: 'b'}, key='typedef')
check('type', "bool *const *b", {1: 'b'}) check('type', "{key}bool *const *b", {1: 'b'}, key='typedef')
check('type', "bool *volatile *b", {1: 'b'}) check('type', "{key}bool *volatile *b", {1: 'b'}, key='typedef')
check('type', "bool *restrict *b", {1: 'b'}) check('type', "{key}bool *restrict *b", {1: 'b'}, key='typedef')
check('type', "bool *volatile const b", {1: 'b'}) check('type', "{key}bool *volatile const b", {1: 'b'}, key='typedef')
check('type', "bool *volatile const b", {1: 'b'}) check('type', "{key}bool *volatile const b", {1: 'b'}, key='typedef')
check('type', "bool *volatile const *b", {1: 'b'}) check('type', "{key}bool *volatile const *b", {1: 'b'}, key='typedef')
check('type', "bool b[]", {1: 'b'}) check('type', "{key}bool b[]", {1: 'b'}, key='typedef')
check('type', "long long int foo", {1: 'foo'}) check('type', "{key}long long int foo", {1: 'foo'}, key='typedef')
# test decl specs on right # test decl specs on right
check('type', "bool const b", {1: 'b'}) check('type', "{key}bool const b", {1: 'b'}, key='typedef')
# from breathe#267 (named function parameters for function pointers # from breathe#267 (named function parameters for function pointers
check('type', 'void (*gpio_callback_t)(struct device *port, uint32_t pin)', check('type', '{key}void (*gpio_callback_t)(struct device *port, uint32_t pin)',
{1: 'gpio_callback_t'}) {1: 'gpio_callback_t'}, key='typedef')
def test_macro_definitions(): def test_macro_definitions():
@ -378,28 +400,34 @@ def test_function_definitions():
output='void f(int arr[static volatile const 42])') output='void f(int arr[static volatile const 42])')
def test_union_definitions(): class test_nested_name():
check('struct', 'A', {1: 'A'}) check('struct', '{key}.A', {1: "A"})
check('struct', '{key}.A.B', {1: "A.B"})
check('function', 'void f(.A a)', {1: "f"})
check('function', 'void f(.A.B a)', {1: "f"})
def test_union_definitions(): def test_union_definitions():
check('union', 'A', {1: 'A'}) check('struct', '{key}A', {1: 'A'})
def test_union_definitions():
check('union', '{key}A', {1: 'A'})
def test_enum_definitions(): def test_enum_definitions():
check('enum', 'A', {1: 'A'}) check('enum', '{key}A', {1: 'A'})
check('enumerator', 'A', {1: 'A'}) check('enumerator', '{key}A', {1: 'A'})
check('enumerator', 'A = 42', {1: 'A'}) check('enumerator', '{key}A = 42', {1: 'A'})
def test_anon_definitions(): def test_anon_definitions():
return # TODO check('struct', '@a', {1: "@a"}, asTextOutput='struct [anonymous]')
check('class', '@a', {3: "Ut1_a"}) check('union', '@a', {1: "@a"}, asTextOutput='union [anonymous]')
check('union', '@a', {3: "Ut1_a"}) check('enum', '@a', {1: "@a"}, asTextOutput='enum [anonymous]')
check('enum', '@a', {3: "Ut1_a"}) check('struct', '@1', {1: "@1"}, asTextOutput='struct [anonymous]')
check('class', '@1', {3: "Ut1_1"}) check('struct', '@a.A', {1: "@a.A"}, asTextOutput='struct [anonymous].A')
check('class', '@a::A', {3: "NUt1_a1AE"})
def test_initializers(): def test_initializers():

View File

@ -32,15 +32,29 @@ def parse(name, string):
return ast return ast
def _check(name, input, idDict, output): def _check(name, input, idDict, output, key, asTextOutput):
if key is None:
key = name
key += ' '
if name in ('function', 'member'):
inputActual = input
outputAst = output
outputAsText = output
else:
inputActual = input.format(key='')
outputAst = output.format(key='')
outputAsText = output.format(key=key)
if asTextOutput is not None:
outputAsText = asTextOutput
# first a simple check of the AST # first a simple check of the AST
ast = parse(name, input) ast = parse(name, inputActual)
res = str(ast) res = str(ast)
if res != output: if res != outputAst:
print("") print("")
print("Input: ", input) print("Input: ", input)
print("Result: ", res) print("Result: ", res)
print("Expected: ", output) print("Expected: ", outputAst)
raise DefinitionError("") raise DefinitionError("")
rootSymbol = Symbol(None, None, None, None, None, None) rootSymbol = Symbol(None, None, None, None, None, None)
symbol = rootSymbol.add_declaration(ast, docname="TestDoc") symbol = rootSymbol.add_declaration(ast, docname="TestDoc")
@ -48,6 +62,13 @@ def _check(name, input, idDict, output):
signode = addnodes.desc_signature(input, '') signode = addnodes.desc_signature(input, '')
parentNode += signode parentNode += signode
ast.describe_signature(signode, 'lastIsName', symbol, options={}) ast.describe_signature(signode, 'lastIsName', symbol, options={})
resAsText = parentNode.astext()
if resAsText != outputAsText:
print("")
print("Input: ", input)
print("astext(): ", resAsText)
print("Expected: ", outputAsText)
raise DefinitionError("")
idExpected = [None] idExpected = [None]
for i in range(1, _max_id + 1): for i in range(1, _max_id + 1):
@ -80,13 +101,14 @@ def _check(name, input, idDict, output):
raise DefinitionError("") raise DefinitionError("")
def check(name, input, idDict, output=None): def check(name, input, idDict, output=None, key=None, asTextOutput=None):
if output is None: if output is None:
output = input output = input
# First, check without semicolon # First, check without semicolon
_check(name, input, idDict, output) _check(name, input, idDict, output, key, asTextOutput)
# Second, check with semicolon # Second, check with semicolon
_check(name, input + ' ;', idDict, output + ';') _check(name, input + ' ;', idDict, output + ';', key,
asTextOutput + ';' if asTextOutput is not None else None)
def test_fundamental_types(): def test_fundamental_types():
@ -112,10 +134,11 @@ def test_fundamental_types():
def test_expressions(): def test_expressions():
def exprCheck(expr, id, id4=None): def exprCheck(expr, id, id4=None):
ids = 'IE1CIA%s_1aE' ids = 'IE1CIA%s_1aE'
idDict = {2: ids % expr, 3: ids % id} # call .format() on the expr to unescape double curly braces
idDict = {2: ids % expr.format(), 3: ids % id}
if id4 is not None: if id4 is not None:
idDict[4] = ids % id4 idDict[4] = ids % id4
check('class', 'template<> C<a[%s]>' % expr, idDict) check('class', 'template<> {key}C<a[%s]>' % expr, idDict)
class Config: class Config:
cpp_id_attributes = ["id_attr"] cpp_id_attributes = ["id_attr"]
@ -228,8 +251,8 @@ def test_expressions():
exprCheck('new int()', 'nw_ipiE') exprCheck('new int()', 'nw_ipiE')
exprCheck('new int(5, 42)', 'nw_ipiL5EL42EE') exprCheck('new int(5, 42)', 'nw_ipiL5EL42EE')
exprCheck('::new int', 'nw_iE') exprCheck('::new int', 'nw_iE')
exprCheck('new int{}', 'nw_iilE') exprCheck('new int{{}}', 'nw_iilE')
exprCheck('new int{5, 42}', 'nw_iilL5EL42EE') exprCheck('new int{{5, 42}}', 'nw_iilL5EL42EE')
# delete-expression # delete-expression
exprCheck('delete p', 'dl1p') exprCheck('delete p', 'dl1p')
exprCheck('delete [] p', 'da1p') exprCheck('delete [] p', 'da1p')
@ -290,7 +313,7 @@ def test_expressions():
exprCheck('a xor_eq 5', 'eO1aL5E') exprCheck('a xor_eq 5', 'eO1aL5E')
exprCheck('a |= 5', 'oR1aL5E') exprCheck('a |= 5', 'oR1aL5E')
exprCheck('a or_eq 5', 'oR1aL5E') exprCheck('a or_eq 5', 'oR1aL5E')
exprCheck('a = {1, 2, 3}', 'aS1ailL1EL2EL3EE') exprCheck('a = {{1, 2, 3}}', 'aS1ailL1EL2EL3EE')
# comma operator # comma operator
exprCheck('a, 5', 'cm1aL5E') exprCheck('a, 5', 'cm1aL5E')
@ -300,8 +323,8 @@ def test_expressions():
check('function', 'template<> void f(A<B, 2> &v)', check('function', 'template<> void f(A<B, 2> &v)',
{2: "IE1fR1AI1BX2EE", 3: "IE1fR1AI1BXL2EEE", 4: "IE1fvR1AI1BXL2EEE"}) {2: "IE1fR1AI1BX2EE", 3: "IE1fR1AI1BXL2EEE", 4: "IE1fvR1AI1BXL2EEE"})
exprCheck('A<1>::value', 'N1AIXL1EEE5valueE') exprCheck('A<1>::value', 'N1AIXL1EEE5valueE')
check('class', "template<int T = 42> A", {2: "I_iE1A"}) check('class', "template<int T = 42> {key}A", {2: "I_iE1A"})
check('enumerator', 'A = std::numeric_limits<unsigned long>::max()', {2: "1A"}) check('enumerator', '{key}A = std::numeric_limits<unsigned long>::max()', {2: "1A"})
exprCheck('operator()()', 'clclE') exprCheck('operator()()', 'clclE')
exprCheck('operator()<int>()', 'clclIiEE') exprCheck('operator()<int>()', 'clclIiEE')
@ -311,58 +334,59 @@ def test_expressions():
def test_type_definitions(): def test_type_definitions():
check("type", "public bool b", {1: "b", 2: "1b"}, "bool b") check("type", "public bool b", {1: "b", 2: "1b"}, "{key}bool b", key='typedef')
check("type", "bool A::b", {1: "A::b", 2: "N1A1bE"}) check("type", "{key}bool A::b", {1: "A::b", 2: "N1A1bE"}, key='typedef')
check("type", "bool *b", {1: "b", 2: "1b"}) check("type", "{key}bool *b", {1: "b", 2: "1b"}, key='typedef')
check("type", "bool *const b", {1: "b", 2: "1b"}) check("type", "{key}bool *const b", {1: "b", 2: "1b"}, key='typedef')
check("type", "bool *volatile const b", {1: "b", 2: "1b"}) check("type", "{key}bool *volatile const b", {1: "b", 2: "1b"}, key='typedef')
check("type", "bool *volatile const b", {1: "b", 2: "1b"}) check("type", "{key}bool *volatile const b", {1: "b", 2: "1b"}, key='typedef')
check("type", "bool *volatile const *b", {1: "b", 2: "1b"}) check("type", "{key}bool *volatile const *b", {1: "b", 2: "1b"}, key='typedef')
check("type", "bool &b", {1: "b", 2: "1b"}) check("type", "{key}bool &b", {1: "b", 2: "1b"}, key='typedef')
check("type", "bool b[]", {1: "b", 2: "1b"}) check("type", "{key}bool b[]", {1: "b", 2: "1b"}, key='typedef')
check("type", "std::pair<int, int> coord", {1: "coord", 2: "5coord"}) check("type", "{key}std::pair<int, int> coord", {1: "coord", 2: "5coord"}, key='typedef')
check("type", "long long int foo", {1: "foo", 2: "3foo"}) check("type", "{key}long long int foo", {1: "foo", 2: "3foo"}, key='typedef')
check("type", 'std::vector<std::pair<std::string, long long>> module::blah', check("type", '{key}std::vector<std::pair<std::string, long long>> module::blah',
{1: "module::blah", 2: "N6module4blahE"}) {1: "module::blah", 2: "N6module4blahE"}, key='typedef')
check("type", "std::function<void()> F", {1: "F", 2: "1F"}) check("type", "{key}std::function<void()> F", {1: "F", 2: "1F"}, key='typedef')
check("type", "std::function<R(A1, A2)> F", {1: "F", 2: "1F"}) check("type", "{key}std::function<R(A1, A2)> F", {1: "F", 2: "1F"}, key='typedef')
check("type", "std::function<R(A1, A2, A3)> F", {1: "F", 2: "1F"}) check("type", "{key}std::function<R(A1, A2, A3)> F", {1: "F", 2: "1F"}, key='typedef')
check("type", "std::function<R(A1, A2, A3, As...)> F", {1: "F", 2: "1F"}) check("type", "{key}std::function<R(A1, A2, A3, As...)> F", {1: "F", 2: "1F"}, key='typedef')
check("type", "MyContainer::const_iterator", check("type", "{key}MyContainer::const_iterator",
{1: "MyContainer::const_iterator", 2: "N11MyContainer14const_iteratorE"}) {1: "MyContainer::const_iterator", 2: "N11MyContainer14const_iteratorE"})
check("type", check("type",
"public MyContainer::const_iterator", "public MyContainer::const_iterator",
{1: "MyContainer::const_iterator", 2: "N11MyContainer14const_iteratorE"}, {1: "MyContainer::const_iterator", 2: "N11MyContainer14const_iteratorE"},
output="MyContainer::const_iterator") output="{key}MyContainer::const_iterator")
# test decl specs on right # test decl specs on right
check("type", "bool const b", {1: "b", 2: "1b"}) check("type", "{key}bool const b", {1: "b", 2: "1b"}, key='typedef')
# test name in global scope # test name in global scope
check("type", "bool ::B::b", {1: "B::b", 2: "N1B1bE"}) check("type", "{key}bool ::B::b", {1: "B::b", 2: "N1B1bE"}, key='typedef')
check('type', 'A = B', {2: '1A'}) check('type', '{key}A = B', {2: '1A'}, key='using')
check('type', 'A = decltype(b)', {2: '1A'}) check('type', '{key}A = decltype(b)', {2: '1A'}, key='using')
# from breathe#267 (named function parameters for function pointers # from breathe#267 (named function parameters for function pointers
check('type', 'void (*gpio_callback_t)(struct device *port, uint32_t pin)', check('type', '{key}void (*gpio_callback_t)(struct device *port, uint32_t pin)',
{1: 'gpio_callback_t', 2: '15gpio_callback_t'}) {1: 'gpio_callback_t', 2: '15gpio_callback_t'}, key='typedef')
check('type', 'void (*f)(std::function<void(int i)> g)', {1: 'f', 2: '1f'}) check('type', '{key}void (*f)(std::function<void(int i)> g)', {1: 'f', 2: '1f'},
key='typedef')
check('type', 'T = A::template B<int>::template C<double>', {2: '1T'}) check('type', '{key}T = A::template B<int>::template C<double>', {2: '1T'}, key='using')
check('type', 'T = Q<A::operator()>', {2: '1T'}) check('type', '{key}T = Q<A::operator()>', {2: '1T'}, key='using')
check('type', 'T = Q<A::operator()<int>>', {2: '1T'}) check('type', '{key}T = Q<A::operator()<int>>', {2: '1T'}, key='using')
check('type', 'T = Q<A::operator bool>', {2: '1T'}) check('type', '{key}T = Q<A::operator bool>', {2: '1T'}, key='using')
def test_concept_definitions(): def test_concept_definitions():
check('concept', 'template<typename Param> A::B::Concept', check('concept', 'template<typename Param> {key}A::B::Concept',
{2: 'I0EN1A1B7ConceptE'}) {2: 'I0EN1A1B7ConceptE'})
check('concept', 'template<typename A, typename B, typename ...C> Foo', check('concept', 'template<typename A, typename B, typename ...C> {key}Foo',
{2: 'I00DpE3Foo'}) {2: 'I00DpE3Foo'})
with pytest.raises(DefinitionError): with pytest.raises(DefinitionError):
parse('concept', 'Foo') parse('concept', '{key}Foo')
with pytest.raises(DefinitionError): with pytest.raises(DefinitionError):
parse('concept', 'template<typename T> template<typename U> Foo') parse('concept', 'template<typename T> template<typename U> {key}Foo')
def test_member_definitions(): def test_member_definitions():
@ -638,95 +662,102 @@ def test_operators():
check('function', 'void operator[]()', {1: "subscript-operator", 2: "ixv"}) check('function', 'void operator[]()', {1: "subscript-operator", 2: "ixv"})
class test_nested_name():
check('class', '{key}::A', {1: "A", 2: "1A"})
check('class', '{key}::A::B', {1: "A::B", 2: "N1A1BE"})
check('function', 'void f(::A a)', {1: "f__A", 2: "1f1A"})
check('function', 'void f(::A::B a)', {1: "f__A::B", 2: "1fN1A1BE"})
def test_class_definitions(): def test_class_definitions():
check('class', 'public A', {1: "A", 2: "1A"}, output='A') check('class', 'public A', {1: "A", 2: "1A"}, output='{key}A')
check('class', 'private A', {1: "A", 2: "1A"}) check('class', 'private {key}A', {1: "A", 2: "1A"})
check('class', 'A final', {1: 'A', 2: '1A'}) check('class', '{key}A final', {1: 'A', 2: '1A'})
# test bases # test bases
check('class', 'A', {1: "A", 2: "1A"}) check('class', '{key}A', {1: "A", 2: "1A"})
check('class', 'A::B::C', {1: "A::B::C", 2: "N1A1B1CE"}) check('class', '{key}A::B::C', {1: "A::B::C", 2: "N1A1B1CE"})
check('class', 'A : B', {1: "A", 2: "1A"}) check('class', '{key}A : B', {1: "A", 2: "1A"})
check('class', 'A : private B', {1: "A", 2: "1A"}) check('class', '{key}A : private B', {1: "A", 2: "1A"})
check('class', 'A : public B', {1: "A", 2: "1A"}) check('class', '{key}A : public B', {1: "A", 2: "1A"})
check('class', 'A : B, C', {1: "A", 2: "1A"}) check('class', '{key}A : B, C', {1: "A", 2: "1A"})
check('class', 'A : B, protected C, D', {1: "A", 2: "1A"}) check('class', '{key}A : B, protected C, D', {1: "A", 2: "1A"})
check('class', 'A : virtual private B', {1: 'A', 2: '1A'}, output='A : private virtual B') check('class', 'A : virtual private B', {1: 'A', 2: '1A'}, output='{key}A : private virtual B')
check('class', 'A : private virtual B', {1: 'A', 2: '1A'}) check('class', '{key}A : private virtual B', {1: 'A', 2: '1A'})
check('class', 'A : B, virtual C', {1: 'A', 2: '1A'}) check('class', '{key}A : B, virtual C', {1: 'A', 2: '1A'})
check('class', 'A : public virtual B', {1: 'A', 2: '1A'}) check('class', '{key}A : public virtual B', {1: 'A', 2: '1A'})
check('class', 'A : B, C...', {1: 'A', 2: '1A'}) check('class', '{key}A : B, C...', {1: 'A', 2: '1A'})
check('class', 'A : B..., C', {1: 'A', 2: '1A'}) check('class', '{key}A : B..., C', {1: 'A', 2: '1A'})
# from #4094 # from #4094
check('class', 'template<class, class = std::void_t<>> has_var', {2: 'I00E7has_var'}) check('class', 'template<class, class = std::void_t<>> {key}has_var', {2: 'I00E7has_var'})
check('class', 'template<class T> has_var<T, std::void_t<decltype(&T::var)>>', check('class', 'template<class T> {key}has_var<T, std::void_t<decltype(&T::var)>>',
{2: 'I0E7has_varI1TNSt6void_tIDTadN1T3varEEEEE'}) {2: 'I0E7has_varI1TNSt6void_tIDTadN1T3varEEEEE'})
check('class', 'template<typename ...Ts> T<int (*)(Ts)...>', check('class', 'template<typename ...Ts> {key}T<int (*)(Ts)...>',
{2: 'IDpE1TIJPFi2TsEEE'}) {2: 'IDpE1TIJPFi2TsEEE'})
check('class', 'template<int... Is> T<(Is)...>', check('class', 'template<int... Is> {key}T<(Is)...>',
{2: 'I_DpiE1TIJX(Is)EEE', 3: 'I_DpiE1TIJX2IsEEE'}) {2: 'I_DpiE1TIJX(Is)EEE', 3: 'I_DpiE1TIJX2IsEEE'})
def test_union_definitions(): def test_union_definitions():
check('union', 'A', {2: "1A"}) check('union', '{key}A', {2: "1A"})
def test_enum_definitions(): def test_enum_definitions():
check('enum', 'A', {2: "1A"}) check('enum', '{key}A', {2: "1A"})
check('enum', 'A : std::underlying_type<B>::type', {2: "1A"}) check('enum', '{key}A : std::underlying_type<B>::type', {2: "1A"})
check('enum', 'A : unsigned int', {2: "1A"}) check('enum', '{key}A : unsigned int', {2: "1A"})
check('enum', 'public A', {2: "1A"}, output='A') check('enum', 'public A', {2: "1A"}, output='{key}A')
check('enum', 'private A', {2: "1A"}) check('enum', 'private {key}A', {2: "1A"})
check('enumerator', 'A', {2: "1A"}) check('enumerator', '{key}A', {2: "1A"})
check('enumerator', 'A = std::numeric_limits<unsigned long>::max()', {2: "1A"}) check('enumerator', '{key}A = std::numeric_limits<unsigned long>::max()', {2: "1A"})
def test_anon_definitions(): def test_anon_definitions():
check('class', '@a', {3: "Ut1_a"}) check('class', '@a', {3: "Ut1_a"}, asTextOutput='class [anonymous]')
check('union', '@a', {3: "Ut1_a"}) check('union', '@a', {3: "Ut1_a"}, asTextOutput='union [anonymous]')
check('enum', '@a', {3: "Ut1_a"}) check('enum', '@a', {3: "Ut1_a"}, asTextOutput='enum [anonymous]')
check('class', '@1', {3: "Ut1_1"}) check('class', '@1', {3: "Ut1_1"}, asTextOutput='class [anonymous]')
check('class', '@a::A', {3: "NUt1_a1AE"}) check('class', '@a::A', {3: "NUt1_a1AE"}, asTextOutput='class [anonymous]::A')
def test_templates(): def test_templates():
check('class', "A<T>", {2: "IE1AI1TE"}, output="template<> A<T>") check('class', "A<T>", {2: "IE1AI1TE"}, output="template<> {key}A<T>")
# first just check which objects support templating # first just check which objects support templating
check('class', "template<> A", {2: "IE1A"}) check('class', "template<> {key}A", {2: "IE1A"})
check('function', "template<> void A()", {2: "IE1Av", 4: "IE1Avv"}) check('function', "template<> void A()", {2: "IE1Av", 4: "IE1Avv"})
check('member', "template<> A a", {2: "IE1a"}) check('member', "template<> A a", {2: "IE1a"})
check('type', "template<> a = A", {2: "IE1a"}) check('type', "template<> {key}a = A", {2: "IE1a"}, key='using')
with pytest.raises(DefinitionError): with pytest.raises(DefinitionError):
parse('enum', "template<> A") parse('enum', "template<> A")
with pytest.raises(DefinitionError): with pytest.raises(DefinitionError):
parse('enumerator', "template<> A") parse('enumerator', "template<> A")
# then all the real tests # then all the real tests
check('class', "template<typename T1, typename T2> A", {2: "I00E1A"}) check('class', "template<typename T1, typename T2> {key}A", {2: "I00E1A"})
check('type', "template<> a", {2: "IE1a"}) check('type', "template<> {key}a", {2: "IE1a"}, key='using')
check('class', "template<typename T> A", {2: "I0E1A"}) check('class', "template<typename T> {key}A", {2: "I0E1A"})
check('class', "template<class T> A", {2: "I0E1A"}) check('class', "template<class T> {key}A", {2: "I0E1A"})
check('class', "template<typename ...T> A", {2: "IDpE1A"}) check('class', "template<typename ...T> {key}A", {2: "IDpE1A"})
check('class', "template<typename...> A", {2: "IDpE1A"}) check('class', "template<typename...> {key}A", {2: "IDpE1A"})
check('class', "template<typename = Test> A", {2: "I0E1A"}) check('class', "template<typename = Test> {key}A", {2: "I0E1A"})
check('class', "template<typename T = Test> A", {2: "I0E1A"}) check('class', "template<typename T = Test> {key}A", {2: "I0E1A"})
check('class', "template<template<typename> typename T> A", {2: "II0E0E1A"}) check('class', "template<template<typename> typename T> {key}A", {2: "II0E0E1A"})
check('class', "template<template<typename> typename> A", {2: "II0E0E1A"}) check('class', "template<template<typename> typename> {key}A", {2: "II0E0E1A"})
check('class', "template<template<typename> typename ...T> A", {2: "II0EDpE1A"}) check('class', "template<template<typename> typename ...T> {key}A", {2: "II0EDpE1A"})
check('class', "template<template<typename> typename...> A", {2: "II0EDpE1A"}) check('class', "template<template<typename> typename...> {key}A", {2: "II0EDpE1A"})
check('class', "template<int> A", {2: "I_iE1A"}) check('class', "template<int> {key}A", {2: "I_iE1A"})
check('class', "template<int T> A", {2: "I_iE1A"}) check('class', "template<int T> {key}A", {2: "I_iE1A"})
check('class', "template<int... T> A", {2: "I_DpiE1A"}) check('class', "template<int... T> {key}A", {2: "I_DpiE1A"})
check('class', "template<int T = 42> A", {2: "I_iE1A"}) check('class', "template<int T = 42> {key}A", {2: "I_iE1A"})
check('class', "template<int = 42> A", {2: "I_iE1A"}) check('class', "template<int = 42> {key}A", {2: "I_iE1A"})
check('class', "template<> A<NS::B<>>", {2: "IE1AIN2NS1BIEEE"}) check('class', "template<> {key}A<NS::B<>>", {2: "IE1AIN2NS1BIEEE"})
# from #2058 # from #2058
check('function', check('function',
@ -746,21 +777,21 @@ def test_templates():
parse('enum', 'abc::ns::foo{id_0, id_1, id_2} A') parse('enum', 'abc::ns::foo{id_0, id_1, id_2} A')
with pytest.raises(DefinitionError): with pytest.raises(DefinitionError):
parse('enumerator', 'abc::ns::foo{id_0, id_1, id_2} A') parse('enumerator', 'abc::ns::foo{id_0, id_1, id_2} A')
check('class', 'abc::ns::foo{id_0, id_1, id_2} xyz::bar', check('class', 'abc::ns::foo{{id_0, id_1, id_2}} {key}xyz::bar',
{2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE'}) {2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE'})
check('class', 'abc::ns::foo{id_0, id_1, ...id_2} xyz::bar', check('class', 'abc::ns::foo{{id_0, id_1, ...id_2}} {key}xyz::bar',
{2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barE'}) {2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barE'})
check('class', 'abc::ns::foo{id_0, id_1, id_2} xyz::bar<id_0, id_1, id_2>', check('class', 'abc::ns::foo{{id_0, id_1, id_2}} {key}xyz::bar<id_0, id_1, id_2>',
{2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barI4id_04id_14id_2EE'}) {2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barI4id_04id_14id_2EE'})
check('class', 'abc::ns::foo{id_0, id_1, ...id_2} xyz::bar<id_0, id_1, id_2...>', check('class', 'abc::ns::foo{{id_0, id_1, ...id_2}} {key}xyz::bar<id_0, id_1, id_2...>',
{2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barI4id_04id_1Dp4id_2EE'}) {2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barI4id_04id_1Dp4id_2EE'})
check('class', 'template<> Concept{U} A<int>::B', {2: 'IEI0EX7ConceptI1UEEN1AIiE1BE'}) check('class', 'template<> Concept{{U}} {key}A<int>::B', {2: 'IEI0EX7ConceptI1UEEN1AIiE1BE'})
check('type', 'abc::ns::foo{id_0, id_1, id_2} xyz::bar = ghi::qux', check('type', 'abc::ns::foo{{id_0, id_1, id_2}} {key}xyz::bar = ghi::qux',
{2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE'}) {2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE'}, key='using')
check('type', 'abc::ns::foo{id_0, id_1, ...id_2} xyz::bar = ghi::qux', check('type', 'abc::ns::foo{{id_0, id_1, ...id_2}} {key}xyz::bar = ghi::qux',
{2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barE'}) {2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barE'}, key='using')
check('function', 'abc::ns::foo{id_0, id_1, id_2} void xyz::bar()', check('function', 'abc::ns::foo{id_0, id_1, id_2} void xyz::bar()',
{2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barEv', {2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barEv',
4: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barEvv'}) 4: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barEvv'})
@ -771,8 +802,8 @@ def test_templates():
{2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE'}) {2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE'})
check('member', 'abc::ns::foo{id_0, id_1, ...id_2} ghi::qux xyz::bar', check('member', 'abc::ns::foo{id_0, id_1, ...id_2} ghi::qux xyz::bar',
{2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barE'}) {2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barE'})
check('concept', 'Iterator{T, U} Another', {2: 'I00EX8IteratorI1T1UEE7Another'}) check('concept', 'Iterator{{T, U}} {key}Another', {2: 'I00EX8IteratorI1T1UEE7Another'})
check('concept', 'template<typename ...Pack> Numerics = (... && Numeric<Pack>)', check('concept', 'template<typename ...Pack> {key}Numerics = (... && Numeric<Pack>)',
{2: 'IDpE8Numerics'}) {2: 'IDpE8Numerics'})
# explicit specializations of members # explicit specializations of members
@ -784,7 +815,7 @@ def test_templates():
output='template<> template<> int A<int>::B<int>::b') # same as above output='template<> template<> int A<int>::B<int>::b') # same as above
# defaulted constrained type parameters # defaulted constrained type parameters
check('type', 'template<C T = int&> A', {2: 'I_1CE1A'}) check('type', 'template<C T = int&> {key}A', {2: 'I_1CE1A'}, key='using')
def test_template_args(): def test_template_args():
@ -796,9 +827,10 @@ def test_template_args():
3: "I0E5allowP1FN4funcI1F1BXne1GL1EEE4typeE", 3: "I0E5allowP1FN4funcI1F1BXne1GL1EEE4typeE",
4: "I0E5allowvP1FN4funcI1F1BXne1GL1EEE4typeE"}) 4: "I0E5allowvP1FN4funcI1F1BXne1GL1EEE4typeE"})
# from #3542 # from #3542
check('type', "template<typename T> " check('type', "template<typename T> {key}"
"enable_if_not_array_t = std::enable_if_t<!is_array<T>::value, int>", "enable_if_not_array_t = std::enable_if_t<!is_array<T>::value, int>",
{2: "I0E21enable_if_not_array_t"}) {2: "I0E21enable_if_not_array_t"},
key='using')
def test_initializers(): def test_initializers():

View File

@ -40,22 +40,22 @@ def parse(sig):
def test_function_signatures(): def test_function_signatures():
rv = parse('func(a=1) -> int object') rv = parse('func(a=1) -> int object')
assert rv == 'a=1' assert rv == '(a=1)'
rv = parse('func(a=1, [b=None])') rv = parse('func(a=1, [b=None])')
assert rv == 'a=1, [b=None]' assert rv == '(a=1, [b=None])'
rv = parse('func(a=1[, b=None])') rv = parse('func(a=1[, b=None])')
assert rv == 'a=1, [b=None]' assert rv == '(a=1, [b=None])'
rv = parse("compile(source : string, filename, symbol='file')") rv = parse("compile(source : string, filename, symbol='file')")
assert rv == "source : string, filename, symbol='file'" assert rv == "(source : string, filename, symbol='file')"
rv = parse('func(a=[], [b=None])') rv = parse('func(a=[], [b=None])')
assert rv == 'a=[], [b=None]' assert rv == '(a=[], [b=None])'
rv = parse('func(a=[][, b=None])') rv = parse('func(a=[][, b=None])')
assert rv == 'a=[], [b=None]' assert rv == '(a=[], [b=None])'
@pytest.mark.sphinx('dummy', testroot='domain-py') @pytest.mark.sphinx('dummy', testroot='domain-py')
@ -422,7 +422,8 @@ def test_pydata_signature(app):
doctree = restructuredtext.parse(app, text) doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index, assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_name, "version"], [desc, ([desc_signature, ([desc_name, "version"],
[desc_annotation, ": int"], [desc_annotation, (": ",
[pending_xref, "int"])],
[desc_annotation, " = 1"])], [desc_annotation, " = 1"])],
desc_content)])) desc_content)]))
assert_node(doctree[1], addnodes.desc, desctype="data", assert_node(doctree[1], addnodes.desc, desctype="data",
@ -454,8 +455,8 @@ def test_pyobject_prefix(app):
desc, desc,
addnodes.index, addnodes.index,
desc)])])) desc)])]))
assert doctree[1][1][1].astext().strip() == 'say' # prefix is stripped assert doctree[1][1][1].astext().strip() == 'say()' # prefix is stripped
assert doctree[1][1][3].astext().strip() == 'FooBar.say' # not stripped assert doctree[1][1][3].astext().strip() == 'FooBar.say()' # not stripped
def test_pydata(app): def test_pydata(app):
@ -692,7 +693,8 @@ def test_pyattribute(app):
assert_node(doctree[1][1][0], addnodes.index, assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'attr (Class attribute)', 'Class.attr', '', None)]) entries=[('single', 'attr (Class attribute)', 'Class.attr', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "attr"], assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "attr"],
[desc_annotation, ": str"], [desc_annotation, (": ",
[pending_xref, "str"])],
[desc_annotation, " = ''"])], [desc_annotation, " = ''"])],
[desc_content, ()])) [desc_content, ()]))
assert 'Class.attr' in domain.objects assert 'Class.attr' in domain.objects

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@ import platform
import pytest import pytest
from test_autodoc import do_autodoc from test_ext_autodoc import do_autodoc
IS_PYPY = platform.python_implementation() == 'PyPy' IS_PYPY = platform.python_implementation() == 'PyPy'

View File

@ -11,7 +11,7 @@
import pytest import pytest
from sphinx.ext.autodoc import between, cut_lines from sphinx.ext.autodoc import between, cut_lines
from test_autodoc import do_autodoc from test_ext_autodoc import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')

View File

@ -10,7 +10,7 @@
import pytest import pytest
from test_autodoc import do_autodoc from test_ext_autodoc import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')

View File

@ -208,10 +208,11 @@ def test_autosummary_generate(app, status, warning):
nodes.row)])]) nodes.row)])])
assert_node(doctree[4][0], addnodes.toctree, caption="An autosummary") assert_node(doctree[4][0], addnodes.toctree, caption="An autosummary")
assert len(doctree[3][0][0][2]) == 4
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.bar(x[, y])\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_importfail\n\n' assert doctree[3][0][0][2][3].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n'
module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text() module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text()
assert (' .. autosummary::\n' assert (' .. autosummary::\n'
@ -231,6 +232,11 @@ def test_autosummary_generate(app, status, warning):
' ~Foo.baz\n' ' ~Foo.baz\n'
' \n' in Foo) ' \n' in Foo)
FooBar = (app.srcdir / 'generated' / 'autosummary_dummy_module.Foo.Bar.rst').read_text()
assert ('.. currentmodule:: autosummary_dummy_module\n'
'\n'
'.. autoclass:: Foo.Bar\n' in FooBar)
@pytest.mark.sphinx('dummy', testroot='ext-autosummary', @pytest.mark.sphinx('dummy', testroot='ext-autosummary',
confoverrides={'autosummary_generate_overwrite': False}) confoverrides={'autosummary_generate_overwrite': False})
@ -390,7 +396,7 @@ def test_autosummary_template(app):
confoverrides={'autosummary_generate': []}) confoverrides={'autosummary_generate': []})
def test_empty_autosummary_generate(app, status, warning): def test_empty_autosummary_generate(app, status, warning):
app.build() app.build()
assert ("WARNING: autosummary: stub file not found 'autosummary_importfail'" assert ("WARNING: autosummary: failed to import autosummary_importfail"
in warning.getvalue()) in warning.getvalue())

View File

@ -13,7 +13,6 @@ import re
import pytest import pytest
from docutils import frontend, utils, nodes from docutils import frontend, utils, nodes
from docutils.parsers.rst import Parser as RstParser from docutils.parsers.rst import Parser as RstParser
from docutils.transforms.universal import SmartQuotes
from sphinx import addnodes from sphinx import addnodes
from sphinx.builders.html.transforms import KeyboardTransform from sphinx.builders.html.transforms import KeyboardTransform
@ -21,6 +20,8 @@ from sphinx.builders.latex import LaTeXBuilder
from sphinx.builders.latex.theming import ThemeFactory from sphinx.builders.latex.theming import ThemeFactory
from sphinx.roles import XRefRole from sphinx.roles import XRefRole
from sphinx.testing.util import Struct, assert_node from sphinx.testing.util import Struct, assert_node
from sphinx.transforms import SphinxSmartQuotes
from sphinx.util import docutils
from sphinx.util import texescape from sphinx.util import texescape
from sphinx.util.docutils import sphinx_domains from sphinx.util.docutils import sphinx_domains
from sphinx.writers.html import HTMLWriter, HTMLTranslator from sphinx.writers.html import HTMLWriter, HTMLTranslator
@ -67,7 +68,7 @@ def parse(new_document):
document = new_document() document = new_document()
parser = RstParser() parser = RstParser()
parser.parse(rst, document) parser.parse(rst, document)
SmartQuotes(document, startnode=None).apply() SphinxSmartQuotes(document, startnode=None).apply()
for msg in document.traverse(nodes.system_message): for msg in document.traverse(nodes.system_message):
if msg['level'] == 1: if msg['level'] == 1:
msg.replace_self([]) msg.replace_self([])
@ -349,6 +350,21 @@ def test_inline(get_verifier, type, rst, html_expected, latex_expected):
verifier(rst, html_expected, latex_expected) verifier(rst, html_expected, latex_expected)
@pytest.mark.parametrize('type,rst,html_expected,latex_expected', [
(
'verify',
r'4 backslashes \\\\',
r'<p>4 backslashes \\</p>',
None,
),
])
@pytest.mark.skipif(docutils.__version_info__ < (0, 16),
reason='docutils-0.16 or above is required')
def test_inline_docutils16(get_verifier, type, rst, html_expected, latex_expected):
verifier = get_verifier(type)
verifier(rst, html_expected, latex_expected)
@pytest.mark.sphinx(confoverrides={'latex_engine': 'xelatex'}) @pytest.mark.sphinx(confoverrides={'latex_engine': 'xelatex'})
@pytest.mark.parametrize('type,rst,html_expected,latex_expected', [ @pytest.mark.parametrize('type,rst,html_expected,latex_expected', [
( (

View File

@ -18,7 +18,7 @@ from inspect import Parameter
import pytest import pytest
from sphinx.util import inspect from sphinx.util import inspect
from sphinx.util.inspect import stringify_signature from sphinx.util.inspect import stringify_signature, is_builtin_class_method
def test_signature(): def test_signature():
@ -30,10 +30,10 @@ def test_signature():
inspect.signature('') inspect.signature('')
# builitin classes # builitin classes
with pytest.raises(TypeError): with pytest.raises(ValueError):
inspect.signature(int) inspect.signature(int)
with pytest.raises(TypeError): with pytest.raises(ValueError):
inspect.signature(str) inspect.signature(str)
# normal function # normal function
@ -97,7 +97,7 @@ def test_signature_methods():
# wrapped bound method # wrapped bound method
sig = inspect.signature(wrapped_bound_method) sig = inspect.signature(wrapped_bound_method)
assert stringify_signature(sig) == '(arg1, **kwargs)' assert stringify_signature(sig) == '(*args, **kwargs)'
def test_signature_partialmethod(): def test_signature_partialmethod():
@ -127,7 +127,7 @@ def test_signature_partialmethod():
def test_signature_annotations(): def test_signature_annotations():
from typing_test_data import (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, from typing_test_data import (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10,
f11, f12, f13, f14, f15, f16, f17, f18, f19, Node) f11, f12, f13, f14, f15, f16, f17, f18, f19, f20, Node)
# Class annotations # Class annotations
sig = inspect.signature(f0) sig = inspect.signature(f0)
@ -184,6 +184,10 @@ def test_signature_annotations():
sig = inspect.signature(f13) sig = inspect.signature(f13)
assert stringify_signature(sig) == '() -> Optional[str]' assert stringify_signature(sig) == '() -> Optional[str]'
# optional union
sig = inspect.signature(f20)
assert stringify_signature(sig) == '() -> Optional[Union[int, str]]'
# Any # Any
sig = inspect.signature(f14) sig = inspect.signature(f14)
assert stringify_signature(sig) == '() -> Any' assert stringify_signature(sig) == '() -> Any'
@ -579,3 +583,21 @@ def test_getdoc_inherited_decorated_method():
assert inspect.getdoc(Bar.meth, getattr, False, Bar, "meth") is None assert inspect.getdoc(Bar.meth, getattr, False, Bar, "meth") is None
assert inspect.getdoc(Bar.meth, getattr, True, Bar, "meth") == "docstring." assert inspect.getdoc(Bar.meth, getattr, True, Bar, "meth") == "docstring."
def test_is_builtin_class_method():
class MyInt(int):
def my_method(self):
pass
assert inspect.is_builtin_class_method(MyInt, 'to_bytes')
assert inspect.is_builtin_class_method(MyInt, '__init__')
assert not inspect.is_builtin_class_method(MyInt, 'my_method')
assert not inspect.is_builtin_class_method(MyInt, 'does_not_exist')
assert not inspect.is_builtin_class_method(4, 'still does not crash')
class ObjectWithMroAttr:
def __init__(self, mro_attr):
self.__mro__ = mro_attr
assert not inspect.is_builtin_class_method(ObjectWithMroAttr([1, 2, 3]), 'still does not crash')

View File

@ -96,6 +96,10 @@ def f19(*args: int, **kwargs: str):
pass pass
def f20() -> Optional[Union[int, str]]:
pass
class Node: class Node:
def __init__(self, parent: Optional['Node']) -> None: def __init__(self, parent: Optional['Node']) -> None: