Merge branch '3.x' into 7668_wront_retann

This commit is contained in:
Takeshi KOMIYA 2020-05-22 23:15:47 +09:00 committed by GitHub
commit c5f7ded772
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1494 additions and 792 deletions

21
CHANGES
View File

@ -48,6 +48,7 @@ Features added
* #7466: autosummary: headings in generated documents are not translated
* #7490: autosummary: Add ``:caption:`` option to autosummary directive to set a
caption to the toctree
* #7469: autosummary: Support module attributes
* #248, #6040: autosummary: Add ``:recursive:`` option to autosummary directive
to generate stub files recursively
* #4030: autosummary: Add :confval:`autosummary_context` to add template
@ -65,6 +66,7 @@ Features added
* #7541: html theme: Add a "clearer" at the end of the "body"
* #7542: html theme: Make admonition/topic/sidebar scrollable
* #7543: html theme: Add top and bottom margins to tables
* #7695: html theme: Add viewport meta tag for basic theme
* C and C++: allow semicolon in the end of declarations.
* C++, parse parameterized noexcept specifiers.
* #7294: C++, parse expressions with user-defined literals.
@ -73,6 +75,8 @@ Features added
: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
* #7683: Add ``allowed_exceptions`` parameter to ``Sphinx.emit()`` to allow
handlers to raise specified exceptions
Bugs fixed
----------
@ -90,16 +94,32 @@ Bugs fixed
* #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
* #7676: autodoc: wrong value for :member-order: option is ignored silently
* #7676: autodoc: member-order="bysource" does not work for C module
* #7668: autodoc: wrong retann value is passed to a handler of
autodoc-proccess-signature
* #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
* #7685: autosummary: The template variable "members" contains imported members
even if :confval:`autossummary_imported_members` is False
* #7535: sphinx-autogen: crashes when custom template uses inheritance
* #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
* #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
--------
@ -124,6 +144,7 @@ Bugs fixed
* #7567: autodoc: parametrized types are shown twice for generic types
* #7637: autodoc: system defined TypeVars are shown in Python 3.9
* #7696: html: Updated jQuery version from 3.4.1 to 3.5.1 for security reasons
* #7611: md5 fails when OpenSSL FIPS is enabled
* #7626: release package does not contain ``CODE_OF_CONDUCT``

View File

@ -230,11 +230,15 @@ connect handlers to the events. Example:
.. event:: missing-reference (app, env, node, contnode)
Emitted when a cross-reference to a Python module or object cannot be
resolved. If the event handler can resolve the reference, it should return a
Emitted when a cross-reference to an object cannot be resolved.
If the event handler can resolve the reference, it should return a
new docutils node to be inserted in the document tree in place of the node
*node*. Usually this node is a :class:`reference` node containing *contnode*
as a child.
If the handler can not resolve the cross-reference,
it can either return ``None`` to let other handlers try,
or raise :class:`NoUri` to prevent other handlers in trying and suppress
a warning about this cross-reference being unresolved.
:param env: The build environment (``app.builder.env``).
:param node: The :class:`pending_xref` node to be resolved. Its attributes

View File

@ -285,8 +285,12 @@ The following variables available in the templates:
.. data:: attributes
List containing names of "public" attributes in the class. Only available
for classes.
List containing names of "public" attributes in the class/module. Only
available for classes and modules.
.. versionchanged:: 3.1
Attributes of modules are supported.
.. data:: modules

View File

@ -159,7 +159,7 @@ Doctest blocks
Doctest blocks (:duref:`ref <doctest-blocks>`) are interactive Python sessions
cut-and-pasted into docstrings. They do not require the
:ref:`literal blocks <rst-literal-blocks>` syntax. The doctest block must end
with a blank line and should *not* end with with an unused prompt::
with a blank line and should *not* end with an unused prompt::
>>> 1 + 1
2

View File

@ -112,6 +112,13 @@ class desc_signature(nodes.Part, nodes.Inline, nodes.TextElement):
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):
"""Node for a line in a multi-line object signatures.
@ -150,6 +157,9 @@ class desc_parameterlist(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for a general parameter list."""
child_text_separator = ', '
def astext(self):
return '({})'.format(super().astext())
class desc_parameter(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for a single parameter."""

View File

@ -436,22 +436,32 @@ class Sphinx:
logger.debug('[app] disconnecting event: [id=%s]', listener_id)
self.events.disconnect(listener_id)
def emit(self, event: str, *args: Any) -> List:
def emit(self, event: str, *args: Any,
allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> List:
"""Emit *event* and pass *arguments* to the callback functions.
Return the return values of all callbacks as a list. Do not emit core
Sphinx events in extensions!
"""
return self.events.emit(event, *args)
def emit_firstresult(self, event: str, *args: Any) -> Any:
.. versionchanged:: 3.1
Added *allowed_exceptions* to specify path-through exceptions
"""
return self.events.emit(event, *args, allowed_exceptions=allowed_exceptions)
def emit_firstresult(self, event: str, *args: Any,
allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> Any:
"""Emit *event* and pass *arguments* to the callback functions.
Return the result of the first callback that doesn't return ``None``.
.. versionadded:: 0.5
.. versionchanged:: 3.1
Added *allowed_exceptions* to specify path-through exceptions
"""
return self.events.emit_firstresult(event, *args)
return self.events.emit_firstresult(event, *args,
allowed_exceptions=allowed_exceptions)
# registering addon parts

View File

@ -357,6 +357,7 @@ def generate(d: Dict, overwrite: bool = True, silent: bool = False, templatedir:
d.setdefault('extensions', [])
d['copyright'] = time.strftime('%Y') + ', ' + d['author']
d["path"] = os.path.abspath(d['path'])
ensuredir(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,
# else append directly to signode.
# 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.
dest = signode
if mode == 'lastIsName':
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)):
ident = names[i]
if not first:

View File

@ -752,11 +752,17 @@ class ASTNestedName(ASTBase):
names = self.names[:-1] if mode == 'lastIsName' else self.names
# If lastIsName, then wrap all of the prefix in a desc_addname,
# 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.
dest = signode
if mode == 'lastIsName':
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)):
nne = names[i]
template = self.templates[i]
@ -3722,8 +3728,8 @@ class LookupKey:
class Symbol:
debug_indent = 0
debug_indent_string = " "
debug_lookup = False
debug_show_tree = False
debug_lookup = False # overridden by the corresponding config value
debug_show_tree = False # overridden by the corresponding config value
@staticmethod
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_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 {
'version': 'builtin',
'env_version': 2,
'env_version': 3,
'parallel_read_safe': True,
'parallel_write_safe': True,
}

View File

@ -116,7 +116,8 @@ class PycodeError(Exception):
class NoUri(Exception):
"""Raised by builder.get_relative_uri() if there is no URI available."""
"""Raised by builder.get_relative_uri() or from missing-reference handlers
if there is no URI available."""
pass

View File

@ -13,15 +13,16 @@
import warnings
from collections import defaultdict
from operator import attrgetter
from typing import Any, Callable, Dict, List, NamedTuple
from typing import Any, Callable, Dict, List, NamedTuple, Tuple
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.errors import ExtensionError
from sphinx.errors import ExtensionError, SphinxError
from sphinx.locale import __
from sphinx.util import logging
if False:
# For type annotation
from typing import Type # for python3.5.1
from sphinx.application import Sphinx
@ -88,7 +89,8 @@ class EventManager:
if listener.id == listener_id:
listeners.remove(listener)
def emit(self, name: str, *args: Any) -> List:
def emit(self, name: str, *args: Any,
allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> List:
"""Emit a Sphinx event."""
try:
logger.debug('[app] emitting event: %r%s', name, repr(args)[:100])
@ -100,19 +102,29 @@ class EventManager:
results = []
listeners = sorted(self.listeners[name], key=attrgetter("priority"))
for listener in listeners:
if self.app is None:
# for compatibility; RemovedInSphinx40Warning
results.append(listener.handler(*args))
else:
results.append(listener.handler(self.app, *args))
try:
if self.app is None:
# for compatibility; RemovedInSphinx40Warning
results.append(listener.handler(*args))
else:
results.append(listener.handler(self.app, *args))
except allowed_exceptions:
# pass through the errors specified as *allowed_exceptions*
raise
except SphinxError:
raise
except Exception as exc:
raise ExtensionError(__("Handler %r for event %r threw an exception") %
(listener.handler, name)) from exc
return results
def emit_firstresult(self, name: str, *args: Any) -> Any:
def emit_firstresult(self, name: str, *args: Any,
allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> Any:
"""Emit a Sphinx event and returns first result.
This returns the result of the first handler that doesn't return ``None``.
"""
for result in self.emit(name, *args):
for result in self.emit(name, *args, allowed_exceptions=allowed_exceptions):
if result is not None:
return result
return None

View File

@ -15,14 +15,15 @@ import re
import warnings
from inspect import Parameter
from types import ModuleType
from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union
from unittest.mock import patch
from typing import (
Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union
)
from docutils.statemachine import StringList
import sphinx
from sphinx.application import Sphinx
from sphinx.config import ENUM
from sphinx.config import Config, ENUM
from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
from sphinx.environment import BuildEnvironment
from sphinx.ext.autodoc.importer import import_object, get_module_members, get_object_members
@ -93,6 +94,16 @@ def inherited_members_option(arg: Any) -> Union[object, Set[str]]:
return arg
def member_order_option(arg: Any) -> Optional[str]:
"""Used to convert the :members: option to auto directives."""
if arg is None:
return None
elif arg in ('alphabetical', 'bysource', 'groupwise'):
return arg
else:
raise ValueError(__('invalid value for member-order option: %s') % arg)
SUPPRESS = object()
@ -427,8 +438,15 @@ class Documenter:
directive = getattr(self, 'directivetype', self.objtype)
name = self.format_name()
sourcename = self.get_sourcename()
self.add_line('.. %s:%s:: %s%s' % (domain, directive, name, sig),
sourcename)
# 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)
if i == 0:
prefix = " " * len(prefix)
if self.options.noindex:
self.add_line(' :noindex:', sourcename)
if self.objpath:
@ -521,12 +539,12 @@ class Documenter:
else:
logger.warning(__('missing attribute %s in object %s') %
(name, self.fullname), type='autodoc')
return False, sorted(selected)
return False, selected
elif self.options.inherited_members:
return False, sorted((m.name, m.value) for m in members.values())
return False, [(m.name, m.value) for m in members.values()]
else:
return False, sorted((m.name, m.value) for m in members.values()
if m.directly_defined)
return False, [(m.name, m.value) for m in members.values()
if m.directly_defined]
def filter_members(self, members: List[Tuple[str, Any]], want_all: bool
) -> List[Tuple[str, Any, bool]]:
@ -699,17 +717,26 @@ class Documenter:
member_order = self.options.member_order or \
self.env.config.autodoc_member_order
if member_order == 'groupwise':
# sort by group; relies on stable sort to keep items in the
# same group sorted alphabetically
memberdocumenters.sort(key=lambda e: e[0].member_order)
elif member_order == 'bysource' and self.analyzer:
# sort by source order, by virtue of the module analyzer
tagorder = self.analyzer.tagorder
# sort by group; alphabetically within groups
memberdocumenters.sort(key=lambda e: (e[0].member_order, e[0].name))
elif member_order == 'bysource':
if self.analyzer:
# sort by source order, by virtue of the module analyzer
tagorder = self.analyzer.tagorder
def keyfunc(entry: Tuple[Documenter, bool]) -> int:
fullname = entry[0].name.split('::')[1]
return tagorder.get(fullname, len(tagorder))
memberdocumenters.sort(key=keyfunc)
def keyfunc(entry: Tuple[Documenter, bool]) -> int:
fullname = entry[0].name.split('::')[1]
return tagorder.get(fullname, len(tagorder))
memberdocumenters.sort(key=keyfunc)
else:
# Assume that member discovery order matches source order.
# This is a reasonable assumption in Python 3.6 and up, where
# module.__dict__ is insertion-ordered.
pass
elif member_order == 'alphabetical':
memberdocumenters.sort(key=lambda e: e[0].name)
else:
raise ValueError("Illegal member order {}".format(member_order))
for documenter, isattr in memberdocumenters:
documenter.generate(
@ -817,7 +844,7 @@ class ModuleDocumenter(Documenter):
'noindex': bool_option, 'inherited-members': inherited_members_option,
'show-inheritance': bool_option, 'synopsis': identity,
'platform': identity, 'deprecated': bool_option,
'member-order': identity, 'exclude-members': members_set_option,
'member-order': member_order_option, 'exclude-members': members_set_option,
'private-members': bool_option, 'special-members': members_option,
'imported-members': bool_option, 'ignore-module-all': bool_option
} # type: Dict[str, Callable]
@ -948,7 +975,7 @@ class ClassLevelDocumenter(Documenter):
try:
modname, qualname = split_full_qualified_name(mod_cls)
parents = qualname.split(".")
parents = qualname.split(".") if qualname else []
except ImportError:
parents = mod_cls.split(".")
@ -1057,12 +1084,18 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
if self.env.config.autodoc_typehints in ('none', 'description'):
kwargs.setdefault('show_annotation', False)
unwrapped = inspect.unwrap(self.object)
try:
self.env.app.emit('autodoc-before-process-signature', unwrapped, False)
sig = inspect.signature(unwrapped)
self.env.app.emit('autodoc-before-process-signature', self.object, False)
if inspect.is_singledispatch_function(self.object):
sig = inspect.signature(self.object, follow_wrapped=True)
else:
sig = inspect.signature(self.object)
args = stringify_signature(sig, **kwargs)
except TypeError:
except TypeError as exc:
logger.warning(__("Failed to get a function signature for %s: %s"),
self.fullname, exc)
return None
except ValueError:
args = ''
if self.env.config.strip_signature_backslash:
@ -1075,41 +1108,28 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
def add_directive_header(self, sig: str) -> None:
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):
self.add_line(' :async:', sourcename)
def add_singledispatch_directive_header(self, sig: str) -> None:
sourcename = self.get_sourcename()
def format_signature(self, **kwargs: Any) -> str:
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)
if inspect.is_singledispatch_function(self.object):
# append signature of singledispatch'ed functions
for typ, func in self.object.registry.items():
if typ is object:
pass # default implementation. skipped.
else:
self.annotate_to_first_argument(func, typ)
# output first line of header
self.add_line(*add_line.call_args_list[0][0])
documenter = FunctionDocumenter(self.directive, '')
documenter.object = func
sigs.append(documenter.format_signature())
# inserts signature of singledispatch'ed functions
for typ, func in self.object.registry.items():
if typ is object:
pass # default implementation. skipped.
else:
self.annotate_to_first_argument(func, typ)
documenter = FunctionDocumenter(self.directive, '')
documenter.object = func
self.add_line(' %s%s' % (self.format_name(),
documenter.format_signature()),
sourcename)
# output remains of directive header
for call in add_line.call_args_list[1:]:
self.add_line(*call[0])
return "\n".join(sigs)
def annotate_to_first_argument(self, func: Callable, typ: Type) -> None:
"""Annotate type hint to the first argument of function if needed."""
@ -1157,7 +1177,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
option_spec = {
'members': members_option, 'undoc-members': bool_option,
'noindex': bool_option, 'inherited-members': inherited_members_option,
'show-inheritance': bool_option, 'member-order': identity,
'show-inheritance': bool_option, 'member-order': member_order_option,
'exclude-members': members_set_option,
'private-members': bool_option, 'special-members': members_option,
} # type: Dict[str, Callable]
@ -1453,7 +1473,6 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
if self.env.config.autodoc_typehints in ('none', 'description'):
kwargs.setdefault('show_annotation', False)
unwrapped = inspect.unwrap(self.object)
try:
if self.object == object.__init__ and self.parent != object:
# Classes not having own __init__() method are shown as no arguments.
@ -1462,13 +1481,23 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
# But it makes users confused.
args = '()'
else:
if inspect.isstaticmethod(unwrapped, cls=self.parent, name=self.object_name):
self.env.app.emit('autodoc-before-process-signature', unwrapped, False)
sig = inspect.signature(unwrapped, bound_method=False)
if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name):
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', unwrapped, True)
sig = inspect.signature(unwrapped, bound_method=True)
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)
except TypeError as exc:
logger.warning(__("Failed to get a method signature for %s: %s"),
self.fullname, exc)
return None
except ValueError:
args = ''
@ -1478,11 +1507,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
return args
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()
obj = self.parent.__dict__.get(self.object_name, self.object)
@ -1500,34 +1525,26 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
def document_members(self, all_members: bool = False) -> None:
pass
def add_singledispatch_directive_header(self, sig: str) -> None:
sourcename = self.get_sourcename()
def format_signature(self, **kwargs: Any) -> str:
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])
for typ, func in meth.dispatcher.registry.items():
if typ is object:
pass # default implementation. skipped.
else:
self.annotate_to_first_argument(func, typ)
if inspect.is_singledispatch_method(meth):
# append signature of singledispatch'ed functions
for typ, func in meth.dispatcher.registry.items():
if typ is object:
pass # default implementation. skipped.
else:
self.annotate_to_first_argument(func, typ)
documenter = MethodDocumenter(self.directive, '')
documenter.object = func
self.add_line(' %s%s' % (self.format_name(),
documenter.format_signature()),
sourcename)
documenter = MethodDocumenter(self.directive, '')
documenter.parent = self.parent
documenter.object = func
documenter.objpath = [None]
sigs.append(documenter.format_signature())
# output remains of directive header
for call in add_line.call_args_list[1:]:
self.add_line(*call[0])
return "\n".join(sigs)
def annotate_to_first_argument(self, func: Callable, typ: Type) -> None:
"""Annotate type hint to the first argument of function if needed."""
@ -1764,6 +1781,14 @@ def autodoc_attrgetter(app: Sphinx, obj: Any, name: str, *defargs: Any) -> Any:
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]:
app.add_autodocumenter(ModuleDocumenter)
app.add_autodocumenter(ClassDocumenter)
@ -1779,7 +1804,8 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_autodocumenter(SlotsAttributeDocumenter)
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_docstring_signature', True, True)
app.add_config_value('autodoc_mock_imports', [], True)
@ -1792,6 +1818,8 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_event('autodoc-process-signature')
app.add_event('autodoc-skip-member')
app.connect('config-inited', migrate_autodoc_member_order, priority=800)
app.setup_extension('sphinx.ext.autodoc.type_comment')
app.setup_extension('sphinx.ext.autodoc.typehints')

View File

@ -15,6 +15,7 @@ from docutils import nodes
from docutils.nodes import Node
from sphinx.application import Sphinx
from sphinx.domains.std import StandardDomain
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.nodes import clean_astext
@ -33,8 +34,7 @@ def get_node_depth(node: Node) -> int:
def register_sections_as_label(app: Sphinx, document: Node) -> None:
labels = app.env.domaindata['std']['labels']
anonlabels = app.env.domaindata['std']['anonlabels']
domain = cast(StandardDomain, app.env.get_domain('std'))
for node in document.traverse(nodes.section):
if (app.config.autosectionlabel_maxdepth and
get_node_depth(node) >= app.config.autosectionlabel_maxdepth):
@ -49,13 +49,13 @@ def register_sections_as_label(app: Sphinx, document: Node) -> None:
name = nodes.fully_normalize_name(ref_name)
sectname = clean_astext(title)
if name in labels:
if name in domain.labels:
logger.warning(__('duplicate label %s, other instance in %s'),
name, app.env.doc2path(labels[name][0]),
name, app.env.doc2path(domain.labels[name][0]),
location=node, type='autosectionlabel', subtype=docname)
anonlabels[name] = docname, labelid
labels[name] = docname, labelid, sectname
domain.anonlabels[name] = docname, labelid
domain.labels[name] = docname, labelid, sectname
def setup(app: Sphinx) -> Dict[str, Any]:

View File

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

View File

@ -18,6 +18,7 @@
"""
import argparse
import inspect
import locale
import os
import pkgutil
@ -42,6 +43,7 @@ from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warnin
from sphinx.ext.autodoc import Documenter
from sphinx.ext.autosummary import import_by_name, get_documenter
from sphinx.locale import __
from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.registry import SphinxComponentRegistry
from sphinx.util import logging
from sphinx.util import rst
@ -175,6 +177,56 @@ class AutosummaryRenderer:
# -- Generating output ---------------------------------------------------------
class ModuleScanner:
def __init__(self, app: Any, obj: Any) -> None:
self.app = app
self.object = obj
def get_object_type(self, name: str, value: Any) -> str:
return get_documenter(self.app, value, self.object).objtype
def is_skipped(self, name: str, value: Any, objtype: str) -> bool:
try:
return self.app.emit_firstresult('autodoc-skip-member', objtype,
name, value, False, {})
except Exception as exc:
logger.warning(__('autosummary: failed to determine %r to be documented, '
'the following exception was raised:\n%s'),
name, exc, type='autosummary')
return False
def scan(self, imported_members: bool) -> List[str]:
members = []
for name in dir(self.object):
try:
value = safe_getattr(self.object, name)
except AttributeError:
value = None
objtype = self.get_object_type(name, value)
if self.is_skipped(name, value, objtype):
continue
try:
if inspect.ismodule(value):
imported = True
elif safe_getattr(value, '__module__') != self.object.__name__:
imported = True
else:
imported = False
except AttributeError:
imported = False
if imported_members:
# list all members up
members.append(name)
elif imported is False:
# list not-imported members up
members.append(name)
return members
def generate_autosummary_content(name: str, obj: Any, parent: Any,
template: AutosummaryRenderer, template_name: str,
imported_members: bool, app: Any,
@ -218,6 +270,21 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
public.append(name)
return public, items
def get_module_attrs(members: Any) -> Tuple[List[str], List[str]]:
"""Find module attributes with docstrings."""
attrs, public = [], []
try:
analyzer = ModuleAnalyzer.for_module(name)
attr_docs = analyzer.find_attr_docs()
for namespace, attr_name in attr_docs:
if namespace == '' and attr_name in members:
attrs.append(attr_name)
if not attr_name.startswith('_'):
public.append(attr_name)
except PycodeError:
pass # give up if ModuleAnalyzer fails to parse code
return public, attrs
def get_modules(obj: Any) -> Tuple[List[str], List[str]]:
items = [] # type: List[str]
for _, modname, ispkg in pkgutil.iter_modules(obj.__path__):
@ -230,13 +297,16 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
ns.update(context)
if doc.objtype == 'module':
ns['members'] = dir(obj)
scanner = ModuleScanner(app, obj)
ns['members'] = scanner.scan(imported_members)
ns['functions'], ns['all_functions'] = \
get_members(obj, {'function'}, imported=imported_members)
ns['classes'], ns['all_classes'] = \
get_members(obj, {'class'}, imported=imported_members)
ns['exceptions'], ns['all_exceptions'] = \
get_members(obj, {'exception'}, imported=imported_members)
ns['attributes'], ns['all_attributes'] = \
get_module_attrs(ns['members'])
ispackage = hasattr(obj, '__path__')
if ispackage and recursive:
ns['modules'], ns['all_modules'] = get_modules(obj)

View File

@ -2,6 +2,17 @@
.. automodule:: {{ fullname }}
{% block attributes %}
{% if attributes %}
.. rubric:: Module Attributes
.. autosummary::
{% for item in attributes %}
{{ item }}
{%- endfor %}
{% endif %}
{% endblock %}
{% block functions %}
{% if functions %}
.. rubric:: {{ _('Functions') }}

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
) -> Node:
# resolve our "viewcode" reference nodes -- they need special treatment
if node['reftype'] == 'viewcode':
if app.builder.format != 'html':
return None
elif node['reftype'] == 'viewcode':
# resolve our "viewcode" reference nodes -- they need special treatment
return make_refnode(app.builder, node['refdoc'], node['reftarget'],
node['refid'], contnode)

View File

@ -27,9 +27,9 @@ if "%1" == "help" (
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and an HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. qthelp to make HTML files and a Qt help project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. epub to make an EPUB
echo. latex to make LaTeX files (you can set PAPER=a4 or PAPER=letter)
echo. latexpdf to make LaTeX files and then PDFs out of them
echo. text to make text files
@ -69,7 +69,7 @@ if errorlevel 9009 (
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
echo.https://sphinx-doc.org/
exit /b 1
)

View File

@ -120,6 +120,7 @@
{%- else %}
<meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" />
{%- endif %}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{- metatags }}
{%- block htmltitle %}
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>

File diff suppressed because one or more lines are too long

View File

@ -92,7 +92,8 @@ class ReferencesResolver(SphinxPostTransform):
# no new node found? try the missing-reference event
if newnode is None:
newnode = self.app.emit_firstresult('missing-reference', self.env,
node, contnode)
node, contnode,
allowed_exceptions=(NoUri,))
# still not found? warn if node wishes to be warned about or
# we are in nit-picky mode
if newnode is None:

View File

@ -408,13 +408,20 @@ def is_builtin_class_method(obj: Any, attr_name: str) -> bool:
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*.
: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*).
"""
try:
signature = inspect.signature(subject)
try:
signature = inspect.signature(subject, follow_wrapped=follow_wrapped)
except ValueError:
# follow built-in wrappers up (ex. functools.lru_cache)
signature = inspect.signature(subject)
parameters = list(signature.parameters.values())
return_annotation = signature.return_annotation
except IndexError:

View File

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

View File

@ -1,4 +1,4 @@
from os import * # NOQA
from os import path # NOQA
from typing import Union
@ -17,5 +17,25 @@ class Foo:
pass
def bar(x: Union[int, str], y: int = 1):
class _Baz:
pass
def bar(x: Union[int, str], y: int = 1) -> None:
pass
def _quux():
pass
class Exc(Exception):
pass
class _Exc(Exception):
pass
#: a module-level attribute
qux = 2

View File

@ -11,4 +11,5 @@
autosummary_dummy_module.Foo
autosummary_dummy_module.Foo.Bar
autosummary_dummy_module.bar
autosummary_dummy_module.qux
autosummary_importfail

View File

@ -27,15 +27,29 @@ def parse(name, string):
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
ast = parse(name, input)
ast = parse(name, inputActual)
res = str(ast)
if res != output:
if res != outputAst:
print("")
print("Input: ", input)
print("Result: ", res)
print("Expected: ", output)
print("Expected: ", outputAst)
raise DefinitionError("")
rootSymbol = Symbol(None, None, None, None)
symbol = rootSymbol.add_declaration(ast, docname="TestDoc")
@ -43,6 +57,13 @@ def _check(name, input, idDict, output):
signode = addnodes.desc_signature(input, '')
parentNode += signode
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]
for i in range(1, _max_id + 1):
@ -75,14 +96,15 @@ def _check(name, input, idDict, output):
raise DefinitionError("")
def check(name, input, idDict, output=None):
def check(name, input, idDict, output=None, key=None, asTextOutput=None):
if output is None:
output = input
# First, check without semicolon
_check(name, input, idDict, output)
_check(name, input, idDict, output, key, asTextOutput)
if name != 'macro':
# 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():
@ -234,24 +256,24 @@ def test_expressions():
def test_type_definitions():
check('type', "T", {1: "T"})
check('type', "{key}T", {1: "T"})
check('type', "bool *b", {1: 'b'})
check('type', "bool *const b", {1: 'b'})
check('type', "bool *const *b", {1: 'b'})
check('type', "bool *volatile *b", {1: 'b'})
check('type', "bool *restrict *b", {1: 'b'})
check('type', "bool *volatile const b", {1: 'b'})
check('type', "bool *volatile const b", {1: 'b'})
check('type', "bool *volatile const *b", {1: 'b'})
check('type', "bool b[]", {1: 'b'})
check('type', "long long int foo", {1: 'foo'})
check('type', "{key}bool *b", {1: 'b'}, key='typedef')
check('type', "{key}bool *const b", {1: 'b'}, key='typedef')
check('type', "{key}bool *const *b", {1: 'b'}, key='typedef')
check('type', "{key}bool *volatile *b", {1: 'b'}, key='typedef')
check('type', "{key}bool *restrict *b", {1: 'b'}, key='typedef')
check('type', "{key}bool *volatile const b", {1: 'b'}, key='typedef')
check('type', "{key}bool *volatile const b", {1: 'b'}, key='typedef')
check('type', "{key}bool *volatile const *b", {1: 'b'}, key='typedef')
check('type', "{key}bool b[]", {1: 'b'}, key='typedef')
check('type', "{key}long long int foo", {1: 'foo'}, key='typedef')
# 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
check('type', 'void (*gpio_callback_t)(struct device *port, uint32_t pin)',
{1: 'gpio_callback_t'})
check('type', '{key}void (*gpio_callback_t)(struct device *port, uint32_t pin)',
{1: 'gpio_callback_t'}, key='typedef')
def test_macro_definitions():
@ -378,28 +400,34 @@ def test_function_definitions():
output='void f(int arr[static volatile const 42])')
def test_union_definitions():
check('struct', 'A', {1: 'A'})
class test_nested_name():
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():
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():
check('enum', 'A', {1: 'A'})
check('enum', '{key}A', {1: 'A'})
check('enumerator', 'A', {1: 'A'})
check('enumerator', 'A = 42', {1: 'A'})
check('enumerator', '{key}A', {1: 'A'})
check('enumerator', '{key}A = 42', {1: 'A'})
def test_anon_definitions():
return # TODO
check('class', '@a', {3: "Ut1_a"})
check('union', '@a', {3: "Ut1_a"})
check('enum', '@a', {3: "Ut1_a"})
check('class', '@1', {3: "Ut1_1"})
check('class', '@a::A', {3: "NUt1_a1AE"})
check('struct', '@a', {1: "@a"}, asTextOutput='struct [anonymous]')
check('union', '@a', {1: "@a"}, asTextOutput='union [anonymous]')
check('enum', '@a', {1: "@a"}, asTextOutput='enum [anonymous]')
check('struct', '@1', {1: "@1"}, asTextOutput='struct [anonymous]')
check('struct', '@a.A', {1: "@a.A"}, asTextOutput='struct [anonymous].A')
def test_initializers():

View File

@ -33,15 +33,29 @@ def parse(name, string):
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
ast = parse(name, input)
ast = parse(name, inputActual)
res = str(ast)
if res != output:
if res != outputAst:
print("")
print("Input: ", input)
print("Result: ", res)
print("Expected: ", output)
print("Expected: ", outputAst)
raise DefinitionError("")
rootSymbol = Symbol(None, None, None, None, None, None)
symbol = rootSymbol.add_declaration(ast, docname="TestDoc")
@ -49,6 +63,13 @@ def _check(name, input, idDict, output):
signode = addnodes.desc_signature(input, '')
parentNode += signode
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]
for i in range(1, _max_id + 1):
@ -81,13 +102,14 @@ def _check(name, input, idDict, output):
raise DefinitionError("")
def check(name, input, idDict, output=None):
def check(name, input, idDict, output=None, key=None, asTextOutput=None):
if output is None:
output = input
# First, check without semicolon
_check(name, input, idDict, output)
_check(name, input, idDict, output, key, asTextOutput)
# 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():
@ -113,10 +135,11 @@ def test_fundamental_types():
def test_expressions():
def exprCheck(expr, id, id4=None):
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:
idDict[4] = ids % id4
check('class', 'template<> C<a[%s]>' % expr, idDict)
check('class', 'template<> {key}C<a[%s]>' % expr, idDict)
class Config:
cpp_id_attributes = ["id_attr"]
@ -229,8 +252,8 @@ def test_expressions():
exprCheck('new int()', 'nw_ipiE')
exprCheck('new int(5, 42)', 'nw_ipiL5EL42EE')
exprCheck('::new int', 'nw_iE')
exprCheck('new int{}', 'nw_iilE')
exprCheck('new int{5, 42}', 'nw_iilL5EL42EE')
exprCheck('new int{{}}', 'nw_iilE')
exprCheck('new int{{5, 42}}', 'nw_iilL5EL42EE')
# delete-expression
exprCheck('delete p', 'dl1p')
exprCheck('delete [] p', 'da1p')
@ -291,7 +314,7 @@ def test_expressions():
exprCheck('a xor_eq 5', 'eO1aL5E')
exprCheck('a |= 5', 'oR1aL5E')
exprCheck('a or_eq 5', 'oR1aL5E')
exprCheck('a = {1, 2, 3}', 'aS1ailL1EL2EL3EE')
exprCheck('a = {{1, 2, 3}}', 'aS1ailL1EL2EL3EE')
# comma operator
exprCheck('a, 5', 'cm1aL5E')
@ -301,8 +324,8 @@ def test_expressions():
check('function', 'template<> void f(A<B, 2> &v)',
{2: "IE1fR1AI1BX2EE", 3: "IE1fR1AI1BXL2EEE", 4: "IE1fvR1AI1BXL2EEE"})
exprCheck('A<1>::value', 'N1AIXL1EEE5valueE')
check('class', "template<int T = 42> A", {2: "I_iE1A"})
check('enumerator', 'A = std::numeric_limits<unsigned long>::max()', {2: "1A"})
check('class', "template<int T = 42> {key}A", {2: "I_iE1A"})
check('enumerator', '{key}A = std::numeric_limits<unsigned long>::max()', {2: "1A"})
exprCheck('operator()()', 'clclE')
exprCheck('operator()<int>()', 'clclIiEE')
@ -312,58 +335,59 @@ def test_expressions():
def test_type_definitions():
check("type", "public bool b", {1: "b", 2: "1b"}, "bool b")
check("type", "bool A::b", {1: "A::b", 2: "N1A1bE"})
check("type", "bool *b", {1: "b", 2: "1b"})
check("type", "bool *const b", {1: "b", 2: "1b"})
check("type", "bool *volatile const b", {1: "b", 2: "1b"})
check("type", "bool *volatile const b", {1: "b", 2: "1b"})
check("type", "bool *volatile const *b", {1: "b", 2: "1b"})
check("type", "bool &b", {1: "b", 2: "1b"})
check("type", "bool b[]", {1: "b", 2: "1b"})
check("type", "std::pair<int, int> coord", {1: "coord", 2: "5coord"})
check("type", "long long int foo", {1: "foo", 2: "3foo"})
check("type", 'std::vector<std::pair<std::string, long long>> module::blah',
{1: "module::blah", 2: "N6module4blahE"})
check("type", "std::function<void()> F", {1: "F", 2: "1F"})
check("type", "std::function<R(A1, A2)> F", {1: "F", 2: "1F"})
check("type", "std::function<R(A1, A2, A3)> F", {1: "F", 2: "1F"})
check("type", "std::function<R(A1, A2, A3, As...)> F", {1: "F", 2: "1F"})
check("type", "MyContainer::const_iterator",
check("type", "public bool b", {1: "b", 2: "1b"}, "{key}bool b", key='typedef')
check("type", "{key}bool A::b", {1: "A::b", 2: "N1A1bE"}, key='typedef')
check("type", "{key}bool *b", {1: "b", 2: "1b"}, key='typedef')
check("type", "{key}bool *const b", {1: "b", 2: "1b"}, key='typedef')
check("type", "{key}bool *volatile const b", {1: "b", 2: "1b"}, key='typedef')
check("type", "{key}bool *volatile const b", {1: "b", 2: "1b"}, key='typedef')
check("type", "{key}bool *volatile const *b", {1: "b", 2: "1b"}, key='typedef')
check("type", "{key}bool &b", {1: "b", 2: "1b"}, key='typedef')
check("type", "{key}bool b[]", {1: "b", 2: "1b"}, key='typedef')
check("type", "{key}std::pair<int, int> coord", {1: "coord", 2: "5coord"}, key='typedef')
check("type", "{key}long long int foo", {1: "foo", 2: "3foo"}, key='typedef')
check("type", '{key}std::vector<std::pair<std::string, long long>> module::blah',
{1: "module::blah", 2: "N6module4blahE"}, key='typedef')
check("type", "{key}std::function<void()> F", {1: "F", 2: "1F"}, key='typedef')
check("type", "{key}std::function<R(A1, A2)> F", {1: "F", 2: "1F"}, key='typedef')
check("type", "{key}std::function<R(A1, A2, A3)> F", {1: "F", 2: "1F"}, key='typedef')
check("type", "{key}std::function<R(A1, A2, A3, As...)> F", {1: "F", 2: "1F"}, key='typedef')
check("type", "{key}MyContainer::const_iterator",
{1: "MyContainer::const_iterator", 2: "N11MyContainer14const_iteratorE"})
check("type",
"public MyContainer::const_iterator",
{1: "MyContainer::const_iterator", 2: "N11MyContainer14const_iteratorE"},
output="MyContainer::const_iterator")
output="{key}MyContainer::const_iterator")
# 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
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', 'A = decltype(b)', {2: '1A'})
check('type', '{key}A = B', {2: '1A'}, key='using')
check('type', '{key}A = decltype(b)', {2: '1A'}, key='using')
# from breathe#267 (named function parameters for function pointers
check('type', 'void (*gpio_callback_t)(struct device *port, uint32_t pin)',
{1: 'gpio_callback_t', 2: '15gpio_callback_t'})
check('type', 'void (*f)(std::function<void(int i)> g)', {1: 'f', 2: '1f'})
check('type', '{key}void (*gpio_callback_t)(struct device *port, uint32_t pin)',
{1: 'gpio_callback_t', 2: '15gpio_callback_t'}, key='typedef')
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', 'T = Q<A::operator()<int>>', {2: '1T'})
check('type', 'T = Q<A::operator bool>', {2: '1T'})
check('type', '{key}T = Q<A::operator()>', {2: '1T'}, key='using')
check('type', '{key}T = Q<A::operator()<int>>', {2: '1T'}, key='using')
check('type', '{key}T = Q<A::operator bool>', {2: '1T'}, key='using')
def test_concept_definitions():
check('concept', 'template<typename Param> A::B::Concept',
check('concept', 'template<typename Param> {key}A::B::Concept',
{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'})
with pytest.raises(DefinitionError):
parse('concept', 'Foo')
parse('concept', '{key}Foo')
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():
@ -639,95 +663,102 @@ def test_operators():
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():
check('class', 'public A', {1: "A", 2: "1A"}, output='A')
check('class', 'private A', {1: "A", 2: "1A"})
check('class', 'A final', {1: 'A', 2: '1A'})
check('class', 'public A', {1: "A", 2: "1A"}, output='{key}A')
check('class', 'private {key}A', {1: "A", 2: "1A"})
check('class', '{key}A final', {1: 'A', 2: '1A'})
# test bases
check('class', 'A', {1: "A", 2: "1A"})
check('class', 'A::B::C', {1: "A::B::C", 2: "N1A1B1CE"})
check('class', 'A : B', {1: "A", 2: "1A"})
check('class', 'A : private B', {1: "A", 2: "1A"})
check('class', 'A : public B', {1: "A", 2: "1A"})
check('class', 'A : B, C', {1: "A", 2: "1A"})
check('class', '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 : private virtual B', {1: 'A', 2: '1A'})
check('class', 'A : B, virtual C', {1: 'A', 2: '1A'})
check('class', 'A : public virtual B', {1: 'A', 2: '1A'})
check('class', 'A : B, C...', {1: 'A', 2: '1A'})
check('class', 'A : B..., C', {1: 'A', 2: '1A'})
check('class', '{key}A', {1: "A", 2: "1A"})
check('class', '{key}A::B::C', {1: "A::B::C", 2: "N1A1B1CE"})
check('class', '{key}A : B', {1: "A", 2: "1A"})
check('class', '{key}A : private B', {1: "A", 2: "1A"})
check('class', '{key}A : public B', {1: "A", 2: "1A"})
check('class', '{key}A : B, C', {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='{key}A : private virtual B')
check('class', '{key}A : private virtual B', {1: 'A', 2: '1A'})
check('class', '{key}A : B, virtual C', {1: 'A', 2: '1A'})
check('class', '{key}A : public virtual B', {1: 'A', 2: '1A'})
check('class', '{key}A : B, C...', {1: 'A', 2: '1A'})
check('class', '{key}A : B..., C', {1: 'A', 2: '1A'})
# from #4094
check('class', 'template<class, class = std::void_t<>> has_var', {2: 'I00E7has_var'})
check('class', 'template<class T> has_var<T, std::void_t<decltype(&T::var)>>',
check('class', 'template<class, class = std::void_t<>> {key}has_var', {2: 'I00E7has_var'})
check('class', 'template<class T> {key}has_var<T, std::void_t<decltype(&T::var)>>',
{2: 'I0E7has_varI1TNSt6void_tIDTadN1T3varEEEEE'})
check('class', 'template<typename ...Ts> T<int (*)(Ts)...>',
check('class', 'template<typename ...Ts> {key}T<int (*)(Ts)...>',
{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'})
def test_union_definitions():
check('union', 'A', {2: "1A"})
check('union', '{key}A', {2: "1A"})
def test_enum_definitions():
check('enum', 'A', {2: "1A"})
check('enum', 'A : std::underlying_type<B>::type', {2: "1A"})
check('enum', 'A : unsigned int', {2: "1A"})
check('enum', 'public A', {2: "1A"}, output='A')
check('enum', 'private A', {2: "1A"})
check('enum', '{key}A', {2: "1A"})
check('enum', '{key}A : std::underlying_type<B>::type', {2: "1A"})
check('enum', '{key}A : unsigned int', {2: "1A"})
check('enum', 'public A', {2: "1A"}, output='{key}A')
check('enum', 'private {key}A', {2: "1A"})
check('enumerator', 'A', {2: "1A"})
check('enumerator', 'A = std::numeric_limits<unsigned long>::max()', {2: "1A"})
check('enumerator', '{key}A', {2: "1A"})
check('enumerator', '{key}A = std::numeric_limits<unsigned long>::max()', {2: "1A"})
def test_anon_definitions():
check('class', '@a', {3: "Ut1_a"})
check('union', '@a', {3: "Ut1_a"})
check('enum', '@a', {3: "Ut1_a"})
check('class', '@1', {3: "Ut1_1"})
check('class', '@a::A', {3: "NUt1_a1AE"})
check('class', '@a', {3: "Ut1_a"}, asTextOutput='class [anonymous]')
check('union', '@a', {3: "Ut1_a"}, asTextOutput='union [anonymous]')
check('enum', '@a', {3: "Ut1_a"}, asTextOutput='enum [anonymous]')
check('class', '@1', {3: "Ut1_1"}, asTextOutput='class [anonymous]')
check('class', '@a::A', {3: "NUt1_a1AE"}, asTextOutput='class [anonymous]::A')
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
check('class', "template<> A", {2: "IE1A"})
check('class', "template<> {key}A", {2: "IE1A"})
check('function', "template<> void A()", {2: "IE1Av", 4: "IE1Avv"})
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):
parse('enum', "template<> A")
with pytest.raises(DefinitionError):
parse('enumerator', "template<> A")
# then all the real tests
check('class', "template<typename T1, typename T2> A", {2: "I00E1A"})
check('type', "template<> a", {2: "IE1a"})
check('class', "template<typename T1, typename T2> {key}A", {2: "I00E1A"})
check('type', "template<> {key}a", {2: "IE1a"}, key='using')
check('class', "template<typename T> A", {2: "I0E1A"})
check('class', "template<class T> A", {2: "I0E1A"})
check('class', "template<typename ...T> A", {2: "IDpE1A"})
check('class', "template<typename...> A", {2: "IDpE1A"})
check('class', "template<typename = Test> A", {2: "I0E1A"})
check('class', "template<typename T = Test> A", {2: "I0E1A"})
check('class', "template<typename T> {key}A", {2: "I0E1A"})
check('class', "template<class T> {key}A", {2: "I0E1A"})
check('class', "template<typename ...T> {key}A", {2: "IDpE1A"})
check('class', "template<typename...> {key}A", {2: "IDpE1A"})
check('class', "template<typename = Test> {key}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> A", {2: "II0E0E1A"})
check('class', "template<template<typename> typename ...T> A", {2: "II0EDpE1A"})
check('class', "template<template<typename> typename...> A", {2: "II0EDpE1A"})
check('class', "template<template<typename> typename T> {key}A", {2: "II0E0E1A"})
check('class', "template<template<typename> typename> {key}A", {2: "II0E0E1A"})
check('class', "template<template<typename> typename ...T> {key}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 T> A", {2: "I_iE1A"})
check('class', "template<int... T> A", {2: "I_DpiE1A"})
check('class', "template<int T = 42> A", {2: "I_iE1A"})
check('class', "template<int = 42> A", {2: "I_iE1A"})
check('class', "template<int> {key}A", {2: "I_iE1A"})
check('class', "template<int T> {key}A", {2: "I_iE1A"})
check('class', "template<int... T> {key}A", {2: "I_DpiE1A"})
check('class', "template<int T = 42> {key}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
check('function',
@ -747,21 +778,21 @@ def test_templates():
parse('enum', 'abc::ns::foo{id_0, id_1, id_2} A')
with pytest.raises(DefinitionError):
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'})
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'})
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'})
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'})
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',
{2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE'})
check('type', 'abc::ns::foo{id_0, id_1, ...id_2} xyz::bar = ghi::qux',
{2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barE'})
check('type', 'abc::ns::foo{{id_0, id_1, id_2}} {key}xyz::bar = ghi::qux',
{2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE'}, key='using')
check('type', 'abc::ns::foo{{id_0, id_1, ...id_2}} {key}xyz::bar = ghi::qux',
{2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barE'}, key='using')
check('function', 'abc::ns::foo{id_0, id_1, id_2} void xyz::bar()',
{2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barEv',
4: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barEvv'})
@ -772,8 +803,8 @@ def test_templates():
{2: 'I000EXN3abc2ns3fooEI4id_04id_14id_2EEN3xyz3barE'})
check('member', 'abc::ns::foo{id_0, id_1, ...id_2} ghi::qux xyz::bar',
{2: 'I00DpEXN3abc2ns3fooEI4id_04id_1sp4id_2EEN3xyz3barE'})
check('concept', 'Iterator{T, U} Another', {2: 'I00EX8IteratorI1T1UEE7Another'})
check('concept', 'template<typename ...Pack> Numerics = (... && Numeric<Pack>)',
check('concept', 'Iterator{{T, U}} {key}Another', {2: 'I00EX8IteratorI1T1UEE7Another'})
check('concept', 'template<typename ...Pack> {key}Numerics = (... && Numeric<Pack>)',
{2: 'IDpE8Numerics'})
# explicit specializations of members
@ -785,7 +816,7 @@ def test_templates():
output='template<> template<> int A<int>::B<int>::b') # same as above
# 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():
@ -797,9 +828,10 @@ def test_template_args():
3: "I0E5allowP1FN4funcI1F1BXne1GL1EEE4typeE",
4: "I0E5allowvP1FN4funcI1F1BXne1GL1EEE4typeE"})
# 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>",
{2: "I0E21enable_if_not_array_t"})
{2: "I0E21enable_if_not_array_t"},
key='using')
def test_initializers():

View File

@ -40,22 +40,22 @@ def parse(sig):
def test_function_signatures():
rv = parse('func(a=1) -> int object')
assert rv == 'a=1'
assert rv == '(a=1)'
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])')
assert rv == 'a=1, [b=None]'
assert rv == '(a=1, [b=None])'
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])')
assert rv == 'a=[], [b=None]'
assert rv == '(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')
@ -453,8 +453,8 @@ def test_pyobject_prefix(app):
desc,
addnodes.index,
desc)])]))
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][1].astext().strip() == 'say()' # prefix is stripped
assert doctree[1][1][3].astext().strip() == 'FooBar.say()' # not stripped
def test_pydata(app):

View File

@ -8,6 +8,9 @@
:license: BSD, see LICENSE for details.
"""
import pytest
from sphinx.errors import ExtensionError
from sphinx.events import EventManager
@ -22,3 +25,19 @@ def test_event_priority():
events.emit('builder-inited')
assert result == [3, 1, 2, 5, 4]
def test_event_allowed_exceptions():
def raise_error(app):
raise RuntimeError
events = EventManager(object()) # pass an dummy object as an app
events.connect('builder-inited', raise_error, priority=500)
# all errors are conveted to ExtensionError
with pytest.raises(ExtensionError):
events.emit('builder-inited')
# Allow RuntimeError (pass-through)
with pytest.raises(RuntimeError):
events.emit('builder-inited', allowed_exceptions=(RuntimeError,))

View File

@ -59,7 +59,7 @@ def make_directive_bridge(env):
platform = '',
deprecated = False,
members = [],
member_order = 'alphabetic',
member_order = 'alphabetical',
exclude_members = set(),
ignore_module_all = False,
)
@ -142,6 +142,7 @@ def test_format_signature(app):
inst = app.registry.documenters[objtype](directive, name)
inst.fullname = name
inst.doc_as_attr = False # for class objtype
inst.parent = object # dummy
inst.object = obj
inst.objpath = [name]
inst.args = args
@ -271,6 +272,7 @@ def test_get_doc(app):
def getdocl(objtype, obj):
inst = app.registry.documenters[objtype](directive, 'tmp')
inst.parent = object # dummy
inst.object = obj
inst.objpath = [obj.__name__]
inst.doc_as_attr = False
@ -1263,6 +1265,17 @@ def test_autofunction_for_methoddescriptor(app):
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autofunction_for_decorated(app):
actual = do_autodoc(app, 'function', 'target.decorator.foo')
assert list(actual) == [
'',
'.. py:function:: foo()',
' :module: target.decorator',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_automethod_for_builtin(app):
actual = do_autodoc(app, 'method', 'builtins.int.__add__')
@ -1276,6 +1289,17 @@ def test_automethod_for_builtin(app):
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_automethod_for_decorated(app):
actual = do_autodoc(app, 'method', 'target.decorator.Bar.meth')
assert list(actual) == [
'',
'.. py:method:: Bar.meth()',
' :module: target.decorator',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_abstractmethods(app):
options = {"members": None,
@ -1435,7 +1459,7 @@ def test_coroutine(app):
actual = do_autodoc(app, 'function', 'target.coroutine.sync_func')
assert list(actual) == [
'',
'.. py:function:: sync_func()',
'.. py:function:: sync_func(*args, **kwargs)',
' :module: target.coroutine',
'',
]
@ -1655,8 +1679,8 @@ def test_singledispatch(app):
'',
'',
'.. py:function:: func(arg, kwarg=None)',
' func(arg: int, kwarg=None)',
' func(arg: str, kwarg=None)',
' func(arg: int, kwarg=None)',
' func(arg: str, kwarg=None)',
' :module: target.singledispatch',
'',
' A function for general use.',
@ -1671,8 +1695,8 @@ def test_singledispatch_autofunction(app):
assert list(actual) == [
'',
'.. py:function:: func(arg, kwarg=None)',
' func(arg: int, kwarg=None)',
' func(arg: str, kwarg=None)',
' func(arg: int, kwarg=None)',
' func(arg: str, kwarg=None)',
' :module: target.singledispatch',
'',
' A function for general use.',
@ -1698,8 +1722,8 @@ def test_singledispatchmethod(app):
'',
'',
' .. py:method:: Foo.meth(arg, kwarg=None)',
' Foo.meth(arg: int, kwarg=None)',
' Foo.meth(arg: str, kwarg=None)',
' Foo.meth(arg: int, kwarg=None)',
' Foo.meth(arg: str, kwarg=None)',
' :module: target.singledispatchmethod',
'',
' A method for general use.',
@ -1716,8 +1740,8 @@ def test_singledispatchmethod_automethod(app):
assert list(actual) == [
'',
'.. py:method:: Foo.meth(arg, kwarg=None)',
' Foo.meth(arg: int, kwarg=None)',
' Foo.meth(arg: str, kwarg=None)',
' Foo.meth(arg: int, kwarg=None)',
' Foo.meth(arg: str, kwarg=None)',
' :module: target.singledispatchmethod',
'',
' A method for general use.',
@ -1795,7 +1819,7 @@ def test_autodoc(app, status, warning):
content = app.env.get_doctree('index')
assert isinstance(content[3], addnodes.desc)
assert content[3][0].astext() == 'autodoc_dummy_module.test'
assert content[3][0].astext() == 'autodoc_dummy_module.test()'
assert content[3][1].astext() == 'Dummy function using dummy.*'
# issue sphinx-doc/sphinx#2437

View File

@ -19,7 +19,10 @@ from sphinx import addnodes
from sphinx.ext.autosummary import (
autosummary_table, autosummary_toc, mangle_signature, import_by_name, extract_summary
)
from sphinx.ext.autosummary.generate import AutosummaryEntry, generate_autosummary_docs, main as autogen_main
from sphinx.ext.autosummary.generate import (
AutosummaryEntry, generate_autosummary_content, generate_autosummary_docs,
main as autogen_main
)
from sphinx.testing.util import assert_node, etree_parse
from sphinx.util.docutils import new_document
from sphinx.util.osutil import cd
@ -189,6 +192,83 @@ def test_escaping(app, status, warning):
assert str_content(title) == 'underscore_module_'
@pytest.mark.sphinx(testroot='ext-autosummary')
def test_autosummary_generate_content_for_module(app):
import autosummary_dummy_module
template = Mock()
generate_autosummary_content('autosummary_dummy_module', autosummary_dummy_module, None,
template, None, False, app, False, {})
assert template.render.call_args[0][0] == 'module'
context = template.render.call_args[0][1]
assert context['members'] == ['Exc', 'Foo', '_Baz', '_Exc', '__builtins__',
'__cached__', '__doc__', '__file__', '__name__',
'__package__', '_quux', 'bar', 'qux']
assert context['functions'] == ['bar']
assert context['all_functions'] == ['_quux', 'bar']
assert context['classes'] == ['Foo']
assert context['all_classes'] == ['Foo', '_Baz']
assert context['exceptions'] == ['Exc']
assert context['all_exceptions'] == ['Exc', '_Exc']
assert context['attributes'] == ['qux']
assert context['all_attributes'] == ['qux']
assert context['fullname'] == 'autosummary_dummy_module'
assert context['module'] == 'autosummary_dummy_module'
assert context['objname'] == ''
assert context['name'] == ''
assert context['objtype'] == 'module'
@pytest.mark.sphinx(testroot='ext-autosummary')
def test_autosummary_generate_content_for_module_skipped(app):
import autosummary_dummy_module
template = Mock()
def skip_member(app, what, name, obj, skip, options):
if name in ('Foo', 'bar', 'Exc'):
return True
app.connect('autodoc-skip-member', skip_member)
generate_autosummary_content('autosummary_dummy_module', autosummary_dummy_module, None,
template, None, False, app, False, {})
context = template.render.call_args[0][1]
assert context['members'] == ['_Baz', '_Exc', '__builtins__', '__cached__', '__doc__',
'__file__', '__name__', '__package__', '_quux', 'qux']
assert context['functions'] == []
assert context['classes'] == []
assert context['exceptions'] == []
@pytest.mark.sphinx(testroot='ext-autosummary')
def test_autosummary_generate_content_for_module_imported_members(app):
import autosummary_dummy_module
template = Mock()
generate_autosummary_content('autosummary_dummy_module', autosummary_dummy_module, None,
template, None, True, app, False, {})
assert template.render.call_args[0][0] == 'module'
context = template.render.call_args[0][1]
assert context['members'] == ['Exc', 'Foo', 'Union', '_Baz', '_Exc', '__builtins__',
'__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', '__spec__', '_quux',
'bar', 'path', 'qux']
assert context['functions'] == ['bar']
assert context['all_functions'] == ['_quux', 'bar']
assert context['classes'] == ['Foo']
assert context['all_classes'] == ['Foo', '_Baz']
assert context['exceptions'] == ['Exc']
assert context['all_exceptions'] == ['Exc', '_Exc']
assert context['attributes'] == ['qux']
assert context['all_attributes'] == ['qux']
assert context['fullname'] == 'autosummary_dummy_module'
assert context['module'] == 'autosummary_dummy_module'
assert context['objname'] == ''
assert context['name'] == ''
assert context['objtype'] == 'module'
@pytest.mark.sphinx('dummy', testroot='ext-autosummary')
def test_autosummary_generate(app, status, warning):
app.builder.build_all()
@ -209,11 +289,12 @@ def test_autosummary_generate(app, status, warning):
nodes.row)])])
assert_node(doctree[4][0], addnodes.toctree, caption="An autosummary")
assert len(doctree[3][0][0][2]) == 5
assert doctree[3][0][0][2][0].astext() == 'autosummary_dummy_module\n\n'
assert doctree[3][0][0][2][1].astext() == 'autosummary_dummy_module.Foo()\n\n'
assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.Foo.Bar\n\n'
assert doctree[3][0][0][2][3].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n'
assert doctree[3][0][0][2][4].astext() == 'autosummary_importfail\n\n'
assert doctree[3][0][0][2][4].astext() == 'autosummary_dummy_module.qux\n\na module-level attribute'
module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text()
assert (' .. autosummary::\n'
@ -238,6 +319,11 @@ def test_autosummary_generate(app, status, warning):
'\n'
'.. autoclass:: Foo.Bar\n' in FooBar)
qux = (app.srcdir / 'generated' / 'autosummary_dummy_module.qux.rst').read_text()
assert ('.. currentmodule:: autosummary_dummy_module\n'
'\n'
'.. autodata:: qux' in qux)
@pytest.mark.sphinx('dummy', testroot='ext-autosummary',
confoverrides={'autosummary_generate_overwrite': False})
@ -397,7 +483,7 @@ def test_autosummary_template(app):
confoverrides={'autosummary_generate': []})
def test_empty_autosummary_generate(app, status, warning):
app.build()
assert ("WARNING: autosummary: stub file not found 'autosummary_importfail'"
assert ("WARNING: autosummary: failed to import autosummary_importfail"
in warning.getvalue())

View File

@ -74,7 +74,7 @@ def test_js_source(app, status, warning):
app.builder.build(['contents'])
v = '3.4.1'
v = '3.5.1'
msg = 'jquery.js version does not match to {v}'.format(v=v)
jquery_min = (app.outdir / '_static' / 'jquery.js').read_text()
assert 'jQuery v{v}'.format(v=v) in jquery_min, msg

View File

@ -97,7 +97,7 @@ def test_signature_methods():
# 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():