Merge branch '3.x'

This commit is contained in:
Takeshi KOMIYA 2020-03-23 00:07:51 +09:00
commit 90fb44ee43
31 changed files with 718 additions and 362 deletions

39
CHANGES
View File

@ -29,8 +29,29 @@ Bugs fixed
Testing Testing
-------- --------
Release 3.0.0 (in development) Release 3.0.0 beta2 (in development)
============================== ====================================
Dependencies
------------
Incompatible changes
--------------------
Deprecated
----------
Features added
--------------
Bugs fixed
----------
Testing
--------
Release 3.0.0 beta1 (released Mar 23, 2020)
===========================================
Dependencies Dependencies
------------ ------------
@ -85,6 +106,7 @@ Deprecated
* ``sphinx.domains.std.StandardDomain.add_object()`` * ``sphinx.domains.std.StandardDomain.add_object()``
* ``sphinx.domains.python.PyDecoratorMixin`` * ``sphinx.domains.python.PyDecoratorMixin``
* ``sphinx.ext.autodoc.get_documenters()`` * ``sphinx.ext.autodoc.get_documenters()``
* ``sphinx.ext.autosummary.process_autosummary_toc()``
* ``sphinx.parsers.Parser.app`` * ``sphinx.parsers.Parser.app``
* ``sphinx.testing.path.Path.text()`` * ``sphinx.testing.path.Path.text()``
* ``sphinx.testing.path.Path.bytes()`` * ``sphinx.testing.path.Path.bytes()``
@ -112,6 +134,9 @@ Features added
* #6895: py domain: Do not emit nitpicky warnings for built-in types * #6895: py domain: Do not emit nitpicky warnings for built-in types
* py domain: Support lambda functions in function signature * py domain: Support lambda functions in function signature
* #6417: py domain: Allow to make a style for arguments of functions and methods * #6417: py domain: Allow to make a style for arguments of functions and methods
* #7238, #7239: py domain: Emit a warning on describing a python object if the
entry is already added as the same name
* #7341: py domain: type annotations in singature are converted to cross refs
* Support priority of event handlers. For more detail, see * Support priority of event handlers. For more detail, see
:py:meth:`.Sphinx.connect()` :py:meth:`.Sphinx.connect()`
* #3077: Implement the scoping for :rst:dir:`productionlist` as indicated * #3077: Implement the scoping for :rst:dir:`productionlist` as indicated
@ -143,6 +168,8 @@ Features added
* Added ``SphinxDirective.get_source_info()`` * Added ``SphinxDirective.get_source_info()``
and ``SphinxRole.get_source_info()``. and ``SphinxRole.get_source_info()``.
* #7324: sphinx-build: Emit a warning if multiple files having different file
extensions for same document found
Bugs fixed Bugs fixed
---------- ----------
@ -157,9 +184,11 @@ Bugs fixed
* #7267: autodoc: error message for invalid directive options has wrong location * #7267: autodoc: error message for invalid directive options has wrong location
* #7329: autodoc: info-field-list is wrongly generated from type hints into the * #7329: autodoc: info-field-list is wrongly generated from type hints into the
class description even if ``autoclass_content='class'`` set class description even if ``autoclass_content='class'`` set
* #7331: autodoc: a cython-function is not recognized as a function
* #5637: inheritance_diagram: Incorrect handling of nested class names * #5637: inheritance_diagram: Incorrect handling of nested class names
* #7139: ``code-block:: guess`` does not work * #7139: ``code-block:: guess`` does not work
* #7325: html: source_suffix containing dot leads to wrong source link * #7325: html: source_suffix containing dot leads to wrong source link
* #7357: html: Resizing SVG image fails with ValueError
* #7278: html search: Fix use of ``html_file_suffix`` instead of * #7278: html search: Fix use of ``html_file_suffix`` instead of
``html_link_suffix`` in search results ``html_link_suffix`` in search results
* #7297: html theme: ``bizstyle`` does not support ``sidebarwidth`` * #7297: html theme: ``bizstyle`` does not support ``sidebarwidth``
@ -170,9 +199,7 @@ Bugs fixed
* #2377: C, parse function pointers even in complex types. * #2377: C, parse function pointers even in complex types.
* #7345: sphinx-build: Sphinx crashes if output directory exists as a file * #7345: sphinx-build: Sphinx crashes if output directory exists as a file
* #7290: sphinx-build: Ignore bdb.BdbQuit when handling exceptions * #7290: sphinx-build: Ignore bdb.BdbQuit when handling exceptions
* #6240: napoleon: Attributes and Methods sections ignore :noindex: option
Testing
--------
Release 2.4.5 (in development) Release 2.4.5 (in development)
============================== ==============================
@ -192,6 +219,8 @@ Features added
Bugs fixed Bugs fixed
---------- ----------
* #7343: Sphinx builds has been slower since 2.4.0 on debug mode
Testing Testing
-------- --------

View File

@ -81,6 +81,11 @@ The following is a list of deprecated interfaces.
- 5.0 - 5.0
- ``sphinx.registry.documenters`` - ``sphinx.registry.documenters``
* - ``sphinx.ext.autosummary.process_autosummary_toc()``
- 3.0
- 5.0
- N/A
* - ``sphinx.parsers.Parser.app`` * - ``sphinx.parsers.Parser.app``
- 3.0 - 3.0
- 5.0 - 5.0

View File

@ -48,6 +48,12 @@ tables of contents. The ``toctree`` directive is the central element.
to the source directory. A numeric ``maxdepth`` option may be given to to the source directory. A numeric ``maxdepth`` option may be given to
indicate the depth of the tree; by default, all levels are included. [#]_ indicate the depth of the tree; by default, all levels are included. [#]_
The representation of "TOC tree" is changed in each output format. The
builders that output multiple files (ex. HTML) treat it as a collection of
hyperlinks. On the other hand, the builders that output a single file (ex.
LaTeX, man page, etc.) replace it with the content of the documents on the
TOC tree.
Consider this example (taken from the Python docs' library reference index):: Consider this example (taken from the Python docs' library reference index)::
.. toctree:: .. toctree::

View File

@ -52,6 +52,7 @@ extras_require = {
'pytest-cov', 'pytest-cov',
'html5lib', 'html5lib',
'typed_ast', # for py35-37 'typed_ast', # for py35-37
'cython',
], ],
} }

View File

@ -9,9 +9,11 @@
""" """
import argparse import argparse
import bdb
import locale import locale
import multiprocessing import multiprocessing
import os import os
import pdb
import sys import sys
import traceback import traceback
from typing import Any, IO, List from typing import Any, IO, List
@ -29,13 +31,10 @@ from sphinx.util.docutils import docutils_namespace, patch_docutils
def handle_exception(app: Sphinx, args: Any, exception: BaseException, stderr: IO = sys.stderr) -> None: # NOQA def handle_exception(app: Sphinx, args: Any, exception: BaseException, stderr: IO = sys.stderr) -> None: # NOQA
import bdb
if isinstance(exception, bdb.BdbQuit): if isinstance(exception, bdb.BdbQuit):
return return
if args.pdb: if args.pdb:
import pdb
print(red(__('Exception occurred while building, starting debugger:')), print(red(__('Exception occurred while building, starting debugger:')),
file=stderr) file=stderr)
traceback.print_exc() traceback.print_exc()

View File

@ -30,6 +30,7 @@ from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType, Index, IndexEntry from sphinx.domains import Domain, ObjType, Index, IndexEntry
from sphinx.environment import BuildEnvironment from sphinx.environment import BuildEnvironment
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.pycode.ast import ast, parse as ast_parse
from sphinx.roles import XRefRole from sphinx.roles import XRefRole
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.docfields import Field, GroupedField, TypedField
@ -63,6 +64,58 @@ pairindextypes = {
} }
def _parse_annotation(annotation: str) -> List[Node]:
"""Parse type annotation."""
def make_xref(text: str) -> addnodes.pending_xref:
return pending_xref('', nodes.Text(text),
refdomain='py', reftype='class', reftarget=text)
def unparse(node: ast.AST) -> List[Node]:
if isinstance(node, ast.Attribute):
return [nodes.Text("%s.%s" % (unparse(node.value)[0], node.attr))]
elif isinstance(node, ast.Expr):
return unparse(node.value)
elif isinstance(node, ast.Index):
return unparse(node.value)
elif isinstance(node, ast.List):
result = [addnodes.desc_sig_punctuation('', '[')] # type: List[Node]
for elem in node.elts:
result.extend(unparse(elem))
result.append(addnodes.desc_sig_punctuation('', ', '))
result.pop()
result.append(addnodes.desc_sig_punctuation('', ']'))
return result
elif isinstance(node, ast.Module):
return sum((unparse(e) for e in node.body), [])
elif isinstance(node, ast.Name):
return [nodes.Text(node.id)]
elif isinstance(node, ast.Subscript):
result = unparse(node.value)
result.append(addnodes.desc_sig_punctuation('', '['))
result.extend(unparse(node.slice))
result.append(addnodes.desc_sig_punctuation('', ']'))
return result
elif isinstance(node, ast.Tuple):
result = []
for elem in node.elts:
result.extend(unparse(elem))
result.append(addnodes.desc_sig_punctuation('', ', '))
result.pop()
return result
else:
raise SyntaxError # unsupported syntax
try:
tree = ast_parse(annotation)
result = unparse(tree)
for i, node in enumerate(result):
if isinstance(node, nodes.Text):
result[i] = make_xref(str(node))
return result
except SyntaxError:
return [make_xref(annotation)]
def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist: def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist:
"""Parse a list of arguments using AST parser""" """Parse a list of arguments using AST parser"""
params = addnodes.desc_parameterlist(arglist) params = addnodes.desc_parameterlist(arglist)
@ -89,9 +142,10 @@ def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist:
node += addnodes.desc_sig_name('', param.name) node += addnodes.desc_sig_name('', param.name)
if param.annotation is not param.empty: if param.annotation is not param.empty:
children = _parse_annotation(param.annotation)
node += addnodes.desc_sig_punctuation('', ':') node += addnodes.desc_sig_punctuation('', ':')
node += nodes.Text(' ') node += nodes.Text(' ')
node += addnodes.desc_sig_name('', param.annotation) node += addnodes.desc_sig_name('', '', *children) # type: ignore
if param.default is not param.empty: if param.default is not param.empty:
if param.annotation is not param.empty: if param.annotation is not param.empty:
node += nodes.Text(' ') node += nodes.Text(' ')
@ -350,7 +404,8 @@ class PyObject(ObjectDescription):
signode += addnodes.desc_parameterlist() signode += addnodes.desc_parameterlist()
if retann: if retann:
signode += addnodes.desc_returns(retann, retann) children = _parse_annotation(retann)
signode += addnodes.desc_returns(retann, '', *children)
anno = self.options.get('annotation') anno = self.options.get('annotation')
if anno: if anno:
@ -366,7 +421,7 @@ class PyObject(ObjectDescription):
signode: desc_signature) -> None: signode: desc_signature) -> None:
modname = self.options.get('module', self.env.ref_context.get('py:module')) modname = self.options.get('module', self.env.ref_context.get('py:module'))
fullname = (modname + '.' if modname else '') + name_cls[0] fullname = (modname + '.' if modname else '') + name_cls[0]
node_id = make_id(self.env, self.state.document, modname or '', name_cls[0]) node_id = make_id(self.env, self.state.document, '', fullname)
signode['ids'].append(node_id) signode['ids'].append(node_id)
# Assign old styled node_id(fullname) not to break old hyperlinks (if possible) # Assign old styled node_id(fullname) not to break old hyperlinks (if possible)

View File

@ -269,7 +269,10 @@ class Documenter:
def add_line(self, line: str, source: str, *lineno: int) -> None: def add_line(self, line: str, source: str, *lineno: int) -> None:
"""Append one line of generated reST to the output.""" """Append one line of generated reST to the output."""
self.directive.result.append(self.indent + line, source, *lineno) if line.strip(): # not a blank line
self.directive.result.append(self.indent + line, source, *lineno)
else:
self.directive.result.append('', source, *lineno)
def resolve_name(self, modname: str, parents: Any, path: str, base: Any def resolve_name(self, modname: str, parents: Any, path: str, base: Any
) -> Tuple[str, List[str]]: ) -> Tuple[str, List[str]]:
@ -1007,7 +1010,8 @@ 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)
if inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object): if ((inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object)) and
not inspect.is_cython_function_or_method(self.object)):
# cannot introspect arguments of a C function or method # cannot introspect arguments of a C function or method
return None return None
try: try:
@ -1426,7 +1430,8 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
if self.env.config.autodoc_typehints == 'none': if self.env.config.autodoc_typehints == 'none':
kwargs.setdefault('show_annotation', False) kwargs.setdefault('show_annotation', False)
if inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object): if ((inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object)) and
not inspect.is_cython_function_or_method(self.object)):
# can never get arguments of a C function or method # can never get arguments of a C function or method
return None return None
if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name): if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name):

View File

@ -72,7 +72,7 @@ from docutils.statemachine import StringList
import sphinx import sphinx
from sphinx import addnodes from sphinx import addnodes
from sphinx.application import Sphinx from sphinx.application import Sphinx
from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
from sphinx.environment import BuildEnvironment from sphinx.environment import BuildEnvironment
from sphinx.environment.adapters.toctree import TocTree from sphinx.environment.adapters.toctree import TocTree
from sphinx.ext.autodoc import Documenter from sphinx.ext.autodoc import Documenter
@ -106,6 +106,8 @@ def process_autosummary_toc(app: Sphinx, doctree: nodes.document) -> None:
"""Insert items described in autosummary:: to the TOC tree, but do """Insert items described in autosummary:: to the TOC tree, but do
not generate the toctree:: list. not generate the toctree:: list.
""" """
warnings.warn('process_autosummary_toc() is deprecated',
RemovedInSphinx50Warning, stacklevel=2)
env = app.builder.env env = app.builder.env
crawled = {} crawled = {}
@ -762,7 +764,6 @@ def setup(app: Sphinx) -> Dict[str, Any]:
texinfo=(autosummary_noop, autosummary_noop)) texinfo=(autosummary_noop, autosummary_noop))
app.add_directive('autosummary', Autosummary) app.add_directive('autosummary', Autosummary)
app.add_role('autolink', AutoLink()) app.add_role('autolink', AutoLink())
app.connect('doctree-read', process_autosummary_toc)
app.connect('builder-inited', process_generate_options) app.connect('builder-inited', process_generate_options)
app.add_config_value('autosummary_generate', [], True, [bool]) app.add_config_value('autosummary_generate', [], True, [bool])
app.add_config_value('autosummary_generate_overwrite', True, False) app.add_config_value('autosummary_generate_overwrite', True, False)

View File

@ -579,7 +579,11 @@ class GoogleDocstring:
if _type: if _type:
lines.append(':vartype %s: %s' % (_name, _type)) lines.append(':vartype %s: %s' % (_name, _type))
else: else:
lines.extend(['.. attribute:: ' + _name, '']) lines.append('.. attribute:: ' + _name)
if self._opt and 'noindex' in self._opt:
lines.append(' :noindex:')
lines.append('')
fields = self._format_field('', '', _desc) fields = self._format_field('', '', _desc)
lines.extend(self._indent(fields, 3)) lines.extend(self._indent(fields, 3))
if _type: if _type:
@ -637,6 +641,8 @@ class GoogleDocstring:
lines = [] # type: List[str] lines = [] # type: List[str]
for _name, _type, _desc in self._consume_fields(parse_type=False): for _name, _type, _desc in self._consume_fields(parse_type=False):
lines.append('.. method:: %s' % _name) lines.append('.. method:: %s' % _name)
if self._opt and 'noindex' in self._opt:
lines.append(' :noindex:')
if _desc: if _desc:
lines.extend([''] + self._indent(_desc, 3)) lines.extend([''] + self._indent(_desc, 3))
lines.append('') lines.append('')

View File

@ -9,6 +9,7 @@
""" """
import os import os
from glob import glob
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sphinx.locale import __ from sphinx.locale import __
@ -55,7 +56,13 @@ class Project:
for filename in get_matching_files(self.srcdir, excludes): # type: ignore for filename in get_matching_files(self.srcdir, excludes): # type: ignore
docname = self.path2doc(filename) docname = self.path2doc(filename)
if docname: if docname:
if os.access(os.path.join(self.srcdir, filename), os.R_OK): if docname in self.docnames:
pattern = os.path.join(self.srcdir, docname) + '.*'
files = [relpath(f, self.srcdir) for f in glob(pattern)]
logger.warning(__('multiple files found for the document "%s": %r\n'
'Use %r for the build.'),
docname, files, self.doc2path(docname), once=True)
elif os.access(os.path.join(self.srcdir, filename), os.R_OK):
self.docnames.add(docname) self.docnames.add(docname)
else: else:
logger.warning(__("document not readable. Ignored."), location=docname) logger.warning(__("document not readable. Ignored."), location=docname)

View File

@ -63,7 +63,7 @@ def assert_node(node: Node, cls: Any = None, xpath: str = "", **kwargs: Any) ->
'The node%s has %d child nodes, not one' % (xpath, len(node)) 'The node%s has %d child nodes, not one' % (xpath, len(node))
assert_node(node[0], cls[1:], xpath=xpath + "[0]", **kwargs) assert_node(node[0], cls[1:], xpath=xpath + "[0]", **kwargs)
elif isinstance(cls, tuple): elif isinstance(cls, tuple):
assert isinstance(node, nodes.Element), \ assert isinstance(node, (list, nodes.Element)), \
'The node%s does not have any items' % xpath 'The node%s does not have any items' % xpath
assert len(node) == len(cls), \ assert len(node) == len(cls), \
'The node%s has %d child nodes, not %r' % (xpath, len(node), len(cls)) 'The node%s has %d child nodes, not %r' % (xpath, len(node), len(cls))

View File

@ -459,8 +459,6 @@ class SphinxTranslator(nodes.NodeVisitor):
for node_class in node.__class__.__mro__: for node_class in node.__class__.__mro__:
method = getattr(self, 'visit_%s' % (node_class.__name__), None) method = getattr(self, 'visit_%s' % (node_class.__name__), None)
if method: if method:
logger.debug('SphinxTranslator.dispatch_visit calling %s for %s',
method.__name__, node)
method(node) method(node)
break break
else: else:
@ -478,8 +476,6 @@ class SphinxTranslator(nodes.NodeVisitor):
for node_class in node.__class__.__mro__: for node_class in node.__class__.__mro__:
method = getattr(self, 'depart_%s' % (node_class.__name__), None) method = getattr(self, 'depart_%s' % (node_class.__name__), None)
if method: if method:
logger.debug('SphinxTranslator.dispatch_departure calling %s for %s',
method.__name__, node)
method(node) method(node)
break break
else: else:

View File

@ -197,6 +197,14 @@ def isabstractmethod(obj: Any) -> bool:
return safe_getattr(obj, '__isabstractmethod__', False) is True return safe_getattr(obj, '__isabstractmethod__', False) is True
def is_cython_function_or_method(obj: Any) -> bool:
"""Check if the object is a function or method in cython."""
try:
return obj.__class__.__name__ == 'cython_function_or_method'
except AttributeError:
return False
def isattributedescriptor(obj: Any) -> bool: def isattributedescriptor(obj: Any) -> bool:
"""Check if the object is an attribute like descriptor.""" """Check if the object is an attribute like descriptor."""
if inspect.isdatadescriptor(object): if inspect.isdatadescriptor(object):
@ -207,6 +215,9 @@ def isattributedescriptor(obj: Any) -> bool:
if isfunction(obj) or isbuiltin(obj) or inspect.ismethod(obj): if isfunction(obj) or isbuiltin(obj) or inspect.ismethod(obj):
# attribute must not be either function, builtin and method # attribute must not be either function, builtin and method
return False return False
elif is_cython_function_or_method(obj):
# attribute must not be either function and method (for cython)
return False
elif inspect.isclass(obj): elif inspect.isclass(obj):
# attribute must not be a class # attribute must not be a class
return False return False

View File

@ -117,6 +117,7 @@ class SphinxWarningLogRecord(SphinxLogRecord):
class SphinxLoggerAdapter(logging.LoggerAdapter): class SphinxLoggerAdapter(logging.LoggerAdapter):
"""LoggerAdapter allowing ``type`` and ``subtype`` keywords.""" """LoggerAdapter allowing ``type`` and ``subtype`` keywords."""
KEYWORDS = ['type', 'subtype', 'location', 'nonl', 'color', 'once']
def log(self, level: Union[int, str], msg: str, *args: Any, **kwargs: Any) -> None: def log(self, level: Union[int, str], msg: str, *args: Any, **kwargs: Any) -> None:
if isinstance(level, int): if isinstance(level, int):
@ -130,16 +131,9 @@ class SphinxLoggerAdapter(logging.LoggerAdapter):
def process(self, msg: str, kwargs: Dict) -> Tuple[str, Dict]: # type: ignore def process(self, msg: str, kwargs: Dict) -> Tuple[str, Dict]: # type: ignore
extra = kwargs.setdefault('extra', {}) extra = kwargs.setdefault('extra', {})
if 'type' in kwargs: for keyword in self.KEYWORDS:
extra['type'] = kwargs.pop('type') if keyword in kwargs:
if 'subtype' in kwargs: extra[keyword] = kwargs.pop(keyword)
extra['subtype'] = kwargs.pop('subtype')
if 'location' in kwargs:
extra['location'] = kwargs.pop('location')
if 'nonl' in kwargs:
extra['nonl'] = kwargs.pop('nonl')
if 'color' in kwargs:
extra['color'] = kwargs.pop('color')
return msg, kwargs return msg, kwargs
@ -445,6 +439,26 @@ class MessagePrefixFilter(logging.Filter):
return True return True
class OnceFilter(logging.Filter):
"""Show the message only once."""
def __init__(self, name: str = '') -> None:
super().__init__(name)
self.messages = {} # type: Dict[str, List]
def filter(self, record: logging.LogRecord) -> bool:
once = getattr(record, 'once', '')
if not once:
return True
else:
params = self.messages.setdefault(record.msg, [])
if record.args in params:
return False
params.append(record.args)
return True
class SphinxLogRecordTranslator(logging.Filter): class SphinxLogRecordTranslator(logging.Filter):
"""Converts a log record to one Sphinx expects """Converts a log record to one Sphinx expects
@ -562,6 +576,7 @@ def setup(app: "Sphinx", status: IO, warning: IO) -> None:
warning_handler.addFilter(WarningSuppressor(app)) warning_handler.addFilter(WarningSuppressor(app))
warning_handler.addFilter(WarningLogRecordTranslator(app)) warning_handler.addFilter(WarningLogRecordTranslator(app))
warning_handler.addFilter(WarningIsErrorFilter(app)) warning_handler.addFilter(WarningIsErrorFilter(app))
warning_handler.addFilter(OnceFilter())
warning_handler.setLevel(logging.WARNING) warning_handler.setLevel(logging.WARNING)
warning_handler.setFormatter(ColorizeFormatter()) warning_handler.setFormatter(ColorizeFormatter())

View File

@ -9,6 +9,7 @@
""" """
import re import re
import unicodedata
import warnings import warnings
from typing import Any, Callable, Iterable, List, Set, Tuple, Type from typing import Any, Callable, Iterable, List, Set, Tuple, Type
from typing import TYPE_CHECKING, cast from typing import TYPE_CHECKING, cast
@ -434,6 +435,79 @@ def inline_all_toctrees(builder: "Builder", docnameset: Set[str], docname: str,
return tree return tree
def _make_id(string: str) -> str:
"""Convert `string` into an identifier and return it.
This function is a modified version of ``docutils.nodes.make_id()`` of
docutils-0.16.
Changes:
* Allow to use dots (".") and underscores ("_") for an identifier
without a leading character.
# Author: David Goodger <goodger@python.org>
# Maintainer: docutils-develop@lists.sourceforge.net
# Copyright: This module has been placed in the public domain.
"""
id = string.lower()
id = id.translate(_non_id_translate_digraphs)
id = id.translate(_non_id_translate)
# get rid of non-ascii characters.
# 'ascii' lowercase to prevent problems with turkish locale.
id = unicodedata.normalize('NFKD', id).encode('ascii', 'ignore').decode('ascii')
# shrink runs of whitespace and replace by hyphen
id = _non_id_chars.sub('-', ' '.join(id.split()))
id = _non_id_at_ends.sub('', id)
return str(id)
_non_id_chars = re.compile('[^a-z0-9._]+')
_non_id_at_ends = re.compile('^[-0-9._]+|-+$')
_non_id_translate = {
0x00f8: u'o', # o with stroke
0x0111: u'd', # d with stroke
0x0127: u'h', # h with stroke
0x0131: u'i', # dotless i
0x0142: u'l', # l with stroke
0x0167: u't', # t with stroke
0x0180: u'b', # b with stroke
0x0183: u'b', # b with topbar
0x0188: u'c', # c with hook
0x018c: u'd', # d with topbar
0x0192: u'f', # f with hook
0x0199: u'k', # k with hook
0x019a: u'l', # l with bar
0x019e: u'n', # n with long right leg
0x01a5: u'p', # p with hook
0x01ab: u't', # t with palatal hook
0x01ad: u't', # t with hook
0x01b4: u'y', # y with hook
0x01b6: u'z', # z with stroke
0x01e5: u'g', # g with stroke
0x0225: u'z', # z with hook
0x0234: u'l', # l with curl
0x0235: u'n', # n with curl
0x0236: u't', # t with curl
0x0237: u'j', # dotless j
0x023c: u'c', # c with stroke
0x023f: u's', # s with swash tail
0x0240: u'z', # z with swash tail
0x0247: u'e', # e with stroke
0x0249: u'j', # j with stroke
0x024b: u'q', # q with hook tail
0x024d: u'r', # r with stroke
0x024f: u'y', # y with stroke
}
_non_id_translate_digraphs = {
0x00df: u'sz', # ligature sz
0x00e6: u'ae', # ae
0x0153: u'oe', # ligature oe
0x0238: u'db', # db digraph
0x0239: u'qp', # qp digraph
}
def make_id(env: "BuildEnvironment", document: nodes.document, def make_id(env: "BuildEnvironment", document: nodes.document,
prefix: str = '', term: str = None) -> str: prefix: str = '', term: str = None) -> str:
"""Generate an appropriate node_id for given *prefix* and *term*.""" """Generate an appropriate node_id for given *prefix* and *term*."""
@ -445,12 +519,12 @@ def make_id(env: "BuildEnvironment", document: nodes.document,
# try to generate node_id by *term* # try to generate node_id by *term*
if prefix and term: if prefix and term:
node_id = nodes.make_id(idformat % term) node_id = _make_id(idformat % term)
if node_id == prefix: if node_id == prefix:
# *term* is not good to generate a node_id. # *term* is not good to generate a node_id.
node_id = None node_id = None
elif term: elif term:
node_id = nodes.make_id(term) node_id = _make_id(term)
if node_id == '': if node_id == '':
node_id = None # fallback to None node_id = None # fallback to None

View File

@ -11,6 +11,7 @@
import copy import copy
import os import os
import posixpath import posixpath
import re
import warnings import warnings
from typing import Any, Iterable, Tuple from typing import Any, Iterable, Tuple
from typing import TYPE_CHECKING, cast from typing import TYPE_CHECKING, cast
@ -37,6 +38,19 @@ logger = logging.getLogger(__name__)
# http://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html # http://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html
def multiply_length(length: str, scale: int) -> str:
"""Multiply *length* (width or height) by *scale*."""
matched = re.match(r'^(\d*\.?\d*)\s*(\S*)$', length)
if not matched:
return length
elif scale == 100:
return length
else:
amount, unit = matched.groups()
result = float(amount) * scale / 100
return "%s%s" % (int(result), unit)
class HTMLWriter(Writer): class HTMLWriter(Writer):
# override embed-stylesheet default value to 0. # override embed-stylesheet default value to 0.
@ -596,11 +610,10 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
if 'height' in node: if 'height' in node:
atts['height'] = node['height'] atts['height'] = node['height']
if 'scale' in node: if 'scale' in node:
scale = node['scale'] / 100.0
if 'width' in atts: if 'width' in atts:
atts['width'] = int(atts['width']) * scale atts['width'] = multiply_length(atts['width'], node['scale'])
if 'height' in atts: if 'height' in atts:
atts['height'] = int(atts['height']) * scale atts['height'] = multiply_length(atts['height'], node['scale'])
atts['alt'] = node.get('alt', uri) atts['alt'] = node.get('alt', uri)
if 'align' in node: if 'align' in node:
atts['class'] = 'align-%s' % node['align'] atts['class'] = 'align-%s' % node['align']

View File

@ -10,6 +10,7 @@
import os import os
import posixpath import posixpath
import re
import warnings import warnings
from typing import Any, Iterable, Tuple from typing import Any, Iterable, Tuple
from typing import TYPE_CHECKING, cast from typing import TYPE_CHECKING, cast
@ -36,6 +37,19 @@ logger = logging.getLogger(__name__)
# http://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html # http://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html
def multiply_length(length: str, scale: int) -> str:
"""Multiply *length* (width or height) by *scale*."""
matched = re.match(r'^(\d*\.?\d*)\s*(\S*)$', length)
if not matched:
return length
elif scale == 100:
return length
else:
amount, unit = matched.groups()
result = float(amount) * scale / 100
return "%s%s" % (int(result), unit)
class HTML5Translator(SphinxTranslator, BaseTranslator): class HTML5Translator(SphinxTranslator, BaseTranslator):
""" """
Our custom HTML translator. Our custom HTML translator.
@ -537,11 +551,10 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
if 'height' in node: if 'height' in node:
atts['height'] = node['height'] atts['height'] = node['height']
if 'scale' in node: if 'scale' in node:
scale = node['scale'] / 100.0
if 'width' in atts: if 'width' in atts:
atts['width'] = int(atts['width']) * scale atts['width'] = multiply_length(atts['width'], node['scale'])
if 'height' in atts: if 'height' in atts:
atts['height'] = int(atts['height']) * scale atts['height'] = multiply_length(atts['height'], node['scale'])
atts['alt'] = node.get('alt', uri) atts['alt'] = node.get('alt', uri)
if 'align' in node: if 'align' in node:
atts['class'] = 'align-%s' % node['align'] atts['class'] = 'align-%s' % node['align']

View File

@ -0,0 +1,12 @@
# cython: binding=True
def foo(*args, **kwargs):
"""Docstring."""
class Class:
"""Docstring."""
def meth(self, name: str, age: int = 0) -> None:
"""Docstring."""
pass

View File

@ -22,6 +22,13 @@ from sphinx.testing.util import SphinxTestApp, Struct # NOQA
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.docutils import LoggingReporter from sphinx.util.docutils import LoggingReporter
try:
# Enable pyximport to test cython module
import pyximport
pyximport.install()
except ImportError:
pyximport = None
app = None app = None
@ -361,7 +368,7 @@ def test_new_documenter(app):
' :module: target', ' :module: target',
'', '',
' documentation for the integer', ' documentation for the integer',
' ' '',
] ]
@ -420,7 +427,7 @@ def test_py_module(app, warning):
' :module: target', ' :module: target',
'', '',
' Function.', ' Function.',
' ' '',
] ]
assert ("don't know which module to import for autodocumenting 'Class.meth'" assert ("don't know which module to import for autodocumenting 'Class.meth'"
not in warning.getvalue()) not in warning.getvalue())
@ -435,7 +442,7 @@ def test_autodoc_decorator(app):
' :module: target.decorator', ' :module: target.decorator',
'', '',
' docstring for deco1', ' docstring for deco1',
' ' '',
] ]
actual = do_autodoc(app, 'decorator', 'target.decorator.deco2') actual = do_autodoc(app, 'decorator', 'target.decorator.deco2')
@ -445,7 +452,7 @@ def test_autodoc_decorator(app):
' :module: target.decorator', ' :module: target.decorator',
'', '',
' docstring for deco2', ' docstring for deco2',
' ' '',
] ]
@ -458,7 +465,7 @@ def test_autodoc_exception(app):
' :module: target', ' :module: target',
'', '',
' My custom exception.', ' My custom exception.',
' ' '',
] ]
@ -726,7 +733,7 @@ def test_autodoc_subclass_of_builtin_class(app):
' :module: target', ' :module: target',
'', '',
' Docstring.', ' Docstring.',
' ' '',
] ]
@ -740,23 +747,23 @@ def test_autodoc_inner_class(app):
' :module: target', ' :module: target',
'', '',
' Foo', ' Foo',
' ', '',
' ', '',
' .. py:class:: Outer.Inner', ' .. py:class:: Outer.Inner',
' :module: target', ' :module: target',
' ', '',
' Foo', ' Foo',
' ', '',
' ', '',
' .. py:method:: Outer.Inner.meth()', ' .. py:method:: Outer.Inner.meth()',
' :module: target', ' :module: target',
' ', '',
' Foo', ' Foo',
' ', '',
' ', '',
' .. py:attribute:: Outer.factory', ' .. py:attribute:: Outer.factory',
' :module: target', ' :module: target',
' ', '',
' alias of :class:`builtins.dict`' ' alias of :class:`builtins.dict`'
] ]
@ -767,13 +774,13 @@ def test_autodoc_inner_class(app):
' :module: target.Outer', ' :module: target.Outer',
'', '',
' Foo', ' Foo',
' ', '',
' ', '',
' .. py:method:: Inner.meth()', ' .. py:method:: Inner.meth()',
' :module: target.Outer', ' :module: target.Outer',
' ', '',
' Foo', ' Foo',
' ', '',
] ]
options['show-inheritance'] = True options['show-inheritance'] = True
@ -785,7 +792,7 @@ def test_autodoc_inner_class(app):
' Bases: :class:`target.Outer.Inner`', ' Bases: :class:`target.Outer.Inner`',
'', '',
' InnerChild docstring', ' InnerChild docstring',
' ' '',
] ]
@ -799,7 +806,7 @@ def test_autodoc_classmethod(app):
' :classmethod:', ' :classmethod:',
'', '',
' Inherited class method.', ' Inherited class method.',
' ' '',
] ]
@ -813,7 +820,7 @@ def test_autodoc_staticmethod(app):
' :staticmethod:', ' :staticmethod:',
'', '',
' Inherited static method.', ' Inherited static method.',
' ' '',
] ]
@ -827,19 +834,19 @@ def test_autodoc_descriptor(app):
'.. py:class:: Class', '.. py:class:: Class',
' :module: target.descriptor', ' :module: target.descriptor',
'', '',
' ', '',
' .. py:attribute:: Class.descr', ' .. py:attribute:: Class.descr',
' :module: target.descriptor', ' :module: target.descriptor',
' ', '',
' Descriptor instance docstring.', ' Descriptor instance docstring.',
' ', '',
' ', '',
' .. py:method:: Class.prop', ' .. py:method:: Class.prop',
' :module: target.descriptor', ' :module: target.descriptor',
' :property:', ' :property:',
' ', '',
' Property.', ' Property.',
' ' ''
] ]
@ -854,7 +861,7 @@ def test_autodoc_c_module(app):
" Convert a time tuple to a string, e.g. 'Sat Jun 06 16:26:11 1998'.", " Convert a time tuple to a string, e.g. 'Sat Jun 06 16:26:11 1998'.",
' When the time tuple is not present, current time as returned by localtime()', ' When the time tuple is not present, current time as returned by localtime()',
' is used.', ' is used.',
' ' '',
] ]
@ -946,7 +953,7 @@ def test_autodoc_module_scope(app):
' :value: <_io.StringIO object>', ' :value: <_io.StringIO object>',
'', '',
' should be documented as well - süß', ' should be documented as well - süß',
' ' '',
] ]
@ -962,7 +969,7 @@ def test_autodoc_class_scope(app):
' :value: <_io.StringIO object>', ' :value: <_io.StringIO object>',
'', '',
' should be documented as well - süß', ' should be documented as well - süß',
' ' '',
] ]
@ -976,16 +983,16 @@ def test_class_attributes(app):
'.. py:class:: AttCls', '.. py:class:: AttCls',
' :module: target', ' :module: target',
'', '',
' ', '',
' .. py:attribute:: AttCls.a1', ' .. py:attribute:: AttCls.a1',
' :module: target', ' :module: target',
' :value: hello world', ' :value: hello world',
' ', '',
' ', '',
' .. py:attribute:: AttCls.a2', ' .. py:attribute:: AttCls.a2',
' :module: target', ' :module: target',
' :value: None', ' :value: None',
' ' ''
] ]
@ -999,43 +1006,43 @@ def test_instance_attributes(app):
' :module: target', ' :module: target',
'', '',
' Class with documented class and instance attributes.', ' Class with documented class and instance attributes.',
' ', '',
' ', '',
' .. py:attribute:: InstAttCls.ca1', ' .. py:attribute:: InstAttCls.ca1',
' :module: target', ' :module: target',
" :value: 'a'", " :value: 'a'",
' ', '',
' Doc comment for class attribute InstAttCls.ca1.', ' Doc comment for class attribute InstAttCls.ca1.',
' It can have multiple lines.', ' It can have multiple lines.',
' ', '',
' ', '',
' .. py:attribute:: InstAttCls.ca2', ' .. py:attribute:: InstAttCls.ca2',
' :module: target', ' :module: target',
" :value: 'b'", " :value: 'b'",
' ', '',
' Doc comment for InstAttCls.ca2. One line only.', ' Doc comment for InstAttCls.ca2. One line only.',
' ', '',
' ', '',
' .. py:attribute:: InstAttCls.ca3', ' .. py:attribute:: InstAttCls.ca3',
' :module: target', ' :module: target',
" :value: 'c'", " :value: 'c'",
' ', '',
' Docstring for class attribute InstAttCls.ca3.', ' Docstring for class attribute InstAttCls.ca3.',
' ', '',
' ', '',
' .. py:attribute:: InstAttCls.ia1', ' .. py:attribute:: InstAttCls.ia1',
' :module: target', ' :module: target',
' :value: None', ' :value: None',
' ', '',
' Doc comment for instance attribute InstAttCls.ia1', ' Doc comment for instance attribute InstAttCls.ia1',
' ', '',
' ', '',
' .. py:attribute:: InstAttCls.ia2', ' .. py:attribute:: InstAttCls.ia2',
' :module: target', ' :module: target',
' :value: None', ' :value: None',
' ', '',
' Docstring for instance attribute InstAttCls.ia2.', ' Docstring for instance attribute InstAttCls.ia2.',
' ' ''
] ]
# pick up arbitrary attributes # pick up arbitrary attributes
@ -1047,22 +1054,22 @@ def test_instance_attributes(app):
' :module: target', ' :module: target',
'', '',
' Class with documented class and instance attributes.', ' Class with documented class and instance attributes.',
' ', '',
' ', '',
' .. py:attribute:: InstAttCls.ca1', ' .. py:attribute:: InstAttCls.ca1',
' :module: target', ' :module: target',
" :value: 'a'", " :value: 'a'",
' ', '',
' Doc comment for class attribute InstAttCls.ca1.', ' Doc comment for class attribute InstAttCls.ca1.',
' It can have multiple lines.', ' It can have multiple lines.',
' ', '',
' ', '',
' .. py:attribute:: InstAttCls.ia1', ' .. py:attribute:: InstAttCls.ia1',
' :module: target', ' :module: target',
' :value: None', ' :value: None',
' ', '',
' Doc comment for instance attribute InstAttCls.ia1', ' Doc comment for instance attribute InstAttCls.ia1',
' ' ''
] ]
@ -1079,30 +1086,30 @@ def test_slots(app):
'.. py:class:: Bar()', '.. py:class:: Bar()',
' :module: target.slots', ' :module: target.slots',
'', '',
' ', '',
' .. py:attribute:: Bar.attr1', ' .. py:attribute:: Bar.attr1',
' :module: target.slots', ' :module: target.slots',
' ', '',
' docstring of attr1', ' docstring of attr1',
' ', '',
' ', '',
' .. py:attribute:: Bar.attr2', ' .. py:attribute:: Bar.attr2',
' :module: target.slots', ' :module: target.slots',
' ', '',
' docstring of instance attr2', ' docstring of instance attr2',
' ', '',
' ', '',
' .. py:attribute:: Bar.attr3', ' .. py:attribute:: Bar.attr3',
' :module: target.slots', ' :module: target.slots',
' ', '',
'', '',
'.. py:class:: Foo', '.. py:class:: Foo',
' :module: target.slots', ' :module: target.slots',
'', '',
' ', '',
' .. py:attribute:: Foo.attr', ' .. py:attribute:: Foo.attr',
' :module: target.slots', ' :module: target.slots',
' ', '',
] ]
@ -1117,39 +1124,39 @@ def test_enum_class(app):
' :module: target.enum', ' :module: target.enum',
'', '',
' this is enum class', ' this is enum class',
' ', '',
' ', '',
' .. py:method:: EnumCls.say_hello()', ' .. py:method:: EnumCls.say_hello()',
' :module: target.enum', ' :module: target.enum',
' ', '',
' a method says hello to you.', ' a method says hello to you.',
' ', '',
' ', '',
' .. py:attribute:: EnumCls.val1', ' .. py:attribute:: EnumCls.val1',
' :module: target.enum', ' :module: target.enum',
' :value: 12', ' :value: 12',
' ', '',
' doc for val1', ' doc for val1',
' ', '',
' ', '',
' .. py:attribute:: EnumCls.val2', ' .. py:attribute:: EnumCls.val2',
' :module: target.enum', ' :module: target.enum',
' :value: 23', ' :value: 23',
' ', '',
' doc for val2', ' doc for val2',
' ', '',
' ', '',
' .. py:attribute:: EnumCls.val3', ' .. py:attribute:: EnumCls.val3',
' :module: target.enum', ' :module: target.enum',
' :value: 34', ' :value: 34',
' ', '',
' doc for val3', ' doc for val3',
' ', '',
' ', '',
' .. py:attribute:: EnumCls.val4', ' .. py:attribute:: EnumCls.val4',
' :module: target.enum', ' :module: target.enum',
' :value: 34', ' :value: 34',
' ' ''
] ]
# checks for an attribute of EnumClass # checks for an attribute of EnumClass
@ -1161,7 +1168,7 @@ def test_enum_class(app):
' :value: 12', ' :value: 12',
'', '',
' doc for val1', ' doc for val1',
' ' ''
] ]
@ -1178,19 +1185,19 @@ def test_descriptor_class(app):
' :module: target.descriptor', ' :module: target.descriptor',
'', '',
' Descriptor class docstring.', ' Descriptor class docstring.',
' ', '',
' ', '',
' .. py:method:: CustomDataDescriptor.meth()', ' .. py:method:: CustomDataDescriptor.meth()',
' :module: target.descriptor', ' :module: target.descriptor',
' ', '',
' Function.', ' Function.',
' ', '',
'', '',
'.. py:class:: CustomDataDescriptor2(doc)', '.. py:class:: CustomDataDescriptor2(doc)',
' :module: target.descriptor', ' :module: target.descriptor',
'', '',
' Descriptor class with custom metaclass docstring.', ' Descriptor class with custom metaclass docstring.',
' ' '',
] ]
@ -1203,7 +1210,7 @@ def test_autofunction_for_callable(app):
' :module: target.callable', ' :module: target.callable',
'', '',
' A callable object that behaves like a function.', ' A callable object that behaves like a function.',
' ' '',
] ]
@ -1216,7 +1223,7 @@ def test_autofunction_for_method(app):
' :module: target.callable', ' :module: target.callable',
'', '',
' docstring of Callable.method().', ' docstring of Callable.method().',
' ' '',
] ]
@ -1233,39 +1240,39 @@ def test_abstractmethods():
'.. py:class:: Base', '.. py:class:: Base',
' :module: target.abstractmethods', ' :module: target.abstractmethods',
'', '',
' ', '',
' .. py:method:: Base.abstractmeth()', ' .. py:method:: Base.abstractmeth()',
' :module: target.abstractmethods', ' :module: target.abstractmethods',
' :abstractmethod:', ' :abstractmethod:',
' ', '',
' ', '',
' .. py:method:: Base.classmeth()', ' .. py:method:: Base.classmeth()',
' :module: target.abstractmethods', ' :module: target.abstractmethods',
' :abstractmethod:', ' :abstractmethod:',
' :classmethod:', ' :classmethod:',
' ', '',
' ', '',
' .. py:method:: Base.coroutinemeth()', ' .. py:method:: Base.coroutinemeth()',
' :module: target.abstractmethods', ' :module: target.abstractmethods',
' :abstractmethod:', ' :abstractmethod:',
' :async:', ' :async:',
' ', '',
' ', '',
' .. py:method:: Base.meth()', ' .. py:method:: Base.meth()',
' :module: target.abstractmethods', ' :module: target.abstractmethods',
' ', '',
' ', '',
' .. py:method:: Base.prop', ' .. py:method:: Base.prop',
' :module: target.abstractmethods', ' :module: target.abstractmethods',
' :abstractmethod:', ' :abstractmethod:',
' :property:', ' :property:',
' ', '',
' ', '',
' .. py:method:: Base.staticmeth()', ' .. py:method:: Base.staticmeth()',
' :module: target.abstractmethods', ' :module: target.abstractmethods',
' :abstractmethod:', ' :abstractmethod:',
' :staticmethod:', ' :staticmethod:',
' ' '',
] ]
@ -1282,25 +1289,25 @@ def test_partialfunction():
' :module: target.partialfunction', ' :module: target.partialfunction',
'', '',
' docstring of func1', ' docstring of func1',
' ', '',
'', '',
'.. py:function:: func2(b, c)', '.. py:function:: func2(b, c)',
' :module: target.partialfunction', ' :module: target.partialfunction',
'', '',
' docstring of func1', ' docstring of func1',
' ', '',
'', '',
'.. py:function:: func3(c)', '.. py:function:: func3(c)',
' :module: target.partialfunction', ' :module: target.partialfunction',
'', '',
' docstring of func3', ' docstring of func3',
' ', '',
'', '',
'.. py:function:: func4()', '.. py:function:: func4()',
' :module: target.partialfunction', ' :module: target.partialfunction',
'', '',
' docstring of func3', ' docstring of func3',
' ' '',
] ]
@ -1328,7 +1335,7 @@ def test_bound_method():
' :module: target.bound_method', ' :module: target.bound_method',
'', '',
' Method docstring', ' Method docstring',
' ', '',
] ]
@ -1350,29 +1357,29 @@ def test_coroutine():
'.. py:class:: AsyncClass', '.. py:class:: AsyncClass',
' :module: target.coroutine', ' :module: target.coroutine',
'', '',
' ', '',
' .. py:method:: AsyncClass.do_coroutine()', ' .. py:method:: AsyncClass.do_coroutine()',
' :module: target.coroutine', ' :module: target.coroutine',
' :async:', ' :async:',
' ', '',
' A documented coroutine function', ' A documented coroutine function',
' ', '',
' ', '',
' .. py:method:: AsyncClass.do_coroutine2()', ' .. py:method:: AsyncClass.do_coroutine2()',
' :module: target.coroutine', ' :module: target.coroutine',
' :async:', ' :async:',
' :classmethod:', ' :classmethod:',
' ', '',
' A documented coroutine classmethod', ' A documented coroutine classmethod',
' ', '',
' ', '',
' .. py:method:: AsyncClass.do_coroutine3()', ' .. py:method:: AsyncClass.do_coroutine3()',
' :module: target.coroutine', ' :module: target.coroutine',
' :async:', ' :async:',
' :staticmethod:', ' :staticmethod:',
' ', '',
' A documented coroutine staticmethod', ' A documented coroutine staticmethod',
' ', '',
] ]
@ -1384,21 +1391,21 @@ def test_partialmethod(app):
' :module: target.partialmethod', ' :module: target.partialmethod',
'', '',
' An example for partialmethod.', ' An example for partialmethod.',
' ', '',
' refs: https://docs.python.jp/3/library/functools.html#functools.partialmethod', ' refs: https://docs.python.jp/3/library/functools.html#functools.partialmethod',
' ', '',
' ', '',
' .. py:method:: Cell.set_alive()', ' .. py:method:: Cell.set_alive()',
' :module: target.partialmethod', ' :module: target.partialmethod',
' ', '',
' Make a cell alive.', ' Make a cell alive.',
' ', '',
' ', '',
' .. py:method:: Cell.set_state(state)', ' .. py:method:: Cell.set_state(state)',
' :module: target.partialmethod', ' :module: target.partialmethod',
' ', '',
' Update state of cell to *state*.', ' Update state of cell to *state*.',
' ', '',
] ]
options = {"members": None} options = {"members": None}
@ -1414,25 +1421,25 @@ def test_partialmethod_undoc_members(app):
' :module: target.partialmethod', ' :module: target.partialmethod',
'', '',
' An example for partialmethod.', ' An example for partialmethod.',
' ', '',
' refs: https://docs.python.jp/3/library/functools.html#functools.partialmethod', ' refs: https://docs.python.jp/3/library/functools.html#functools.partialmethod',
' ', '',
' ', '',
' .. py:method:: Cell.set_alive()', ' .. py:method:: Cell.set_alive()',
' :module: target.partialmethod', ' :module: target.partialmethod',
' ', '',
' Make a cell alive.', ' Make a cell alive.',
' ', '',
' ', '',
' .. py:method:: Cell.set_dead()', ' .. py:method:: Cell.set_dead()',
' :module: target.partialmethod', ' :module: target.partialmethod',
' ', '',
' ', '',
' .. py:method:: Cell.set_state(state)', ' .. py:method:: Cell.set_state(state)',
' :module: target.partialmethod', ' :module: target.partialmethod',
' ', '',
' Update state of cell to *state*.', ' Update state of cell to *state*.',
' ', '',
] ]
options = {"members": None, options = {"members": None,
@ -1455,48 +1462,48 @@ def test_autodoc_typed_instance_variables(app):
'.. py:class:: Class()', '.. py:class:: Class()',
' :module: target.typed_vars', ' :module: target.typed_vars',
'', '',
' ', '',
' .. py:attribute:: Class.attr1', ' .. py:attribute:: Class.attr1',
' :module: target.typed_vars', ' :module: target.typed_vars',
' :type: int', ' :type: int',
' :value: 0', ' :value: 0',
' ', '',
' ', '',
' .. py:attribute:: Class.attr2', ' .. py:attribute:: Class.attr2',
' :module: target.typed_vars', ' :module: target.typed_vars',
' :type: int', ' :type: int',
' :value: None', ' :value: None',
' ', '',
' ', '',
' .. py:attribute:: Class.attr3', ' .. py:attribute:: Class.attr3',
' :module: target.typed_vars', ' :module: target.typed_vars',
' :type: int', ' :type: int',
' :value: 0', ' :value: 0',
' ', '',
' ', '',
' .. py:attribute:: Class.attr4', ' .. py:attribute:: Class.attr4',
' :module: target.typed_vars', ' :module: target.typed_vars',
' :type: int', ' :type: int',
' :value: None', ' :value: None',
' ', '',
' attr4', ' attr4',
' ', '',
' ', '',
' .. py:attribute:: Class.attr5', ' .. py:attribute:: Class.attr5',
' :module: target.typed_vars', ' :module: target.typed_vars',
' :type: int', ' :type: int',
' :value: None', ' :value: None',
' ', '',
' attr5', ' attr5',
' ', '',
' ', '',
' .. py:attribute:: Class.attr6', ' .. py:attribute:: Class.attr6',
' :module: target.typed_vars', ' :module: target.typed_vars',
' :type: int', ' :type: int',
' :value: None', ' :value: None',
' ', '',
' attr6', ' attr6',
' ', '',
'', '',
'.. py:data:: attr1', '.. py:data:: attr1',
' :module: target.typed_vars', ' :module: target.typed_vars',
@ -1504,7 +1511,7 @@ def test_autodoc_typed_instance_variables(app):
" :value: ''", " :value: ''",
'', '',
' attr1', ' attr1',
' ', '',
'', '',
'.. py:data:: attr2', '.. py:data:: attr2',
' :module: target.typed_vars', ' :module: target.typed_vars',
@ -1512,7 +1519,7 @@ def test_autodoc_typed_instance_variables(app):
' :value: None', ' :value: None',
'', '',
' attr2', ' attr2',
' ', '',
'', '',
'.. py:data:: attr3', '.. py:data:: attr3',
' :module: target.typed_vars', ' :module: target.typed_vars',
@ -1520,7 +1527,7 @@ def test_autodoc_typed_instance_variables(app):
" :value: ''", " :value: ''",
'', '',
' attr3', ' attr3',
' ' '',
] ]
@ -1538,7 +1545,7 @@ def test_autodoc_Annotated(app):
' :module: target.annotated', ' :module: target.annotated',
'', '',
' docstring', ' docstring',
' ' '',
] ]
@ -1557,7 +1564,7 @@ def test_autodoc_for_egged_code(app):
' :value: 1', ' :value: 1',
'', '',
' constant on sample.py', ' constant on sample.py',
' ', '',
'', '',
'.. py:function:: hello(s)', '.. py:function:: hello(s)',
' :module: sample', ' :module: sample',
@ -1580,7 +1587,7 @@ def test_singledispatch():
' :module: target.singledispatch', ' :module: target.singledispatch',
'', '',
' A function for general use.', ' A function for general use.',
' ' '',
] ]
@ -1599,13 +1606,44 @@ def test_singledispatchmethod():
' :module: target.singledispatchmethod', ' :module: target.singledispatchmethod',
'', '',
' docstring', ' docstring',
' ', '',
' ', '',
' .. py:method:: Foo.meth(arg, kwarg=None)', ' .. py:method:: Foo.meth(arg, kwarg=None)',
' Foo.meth(arg: int, kwarg=None)', ' Foo.meth(arg: int, kwarg=None)',
' Foo.meth(arg: str, kwarg=None)', ' Foo.meth(arg: str, kwarg=None)',
' :module: target.singledispatchmethod', ' :module: target.singledispatchmethod',
' ', '',
' A method for general use.', ' A method for general use.',
' ' '',
]
@pytest.mark.usefixtures('setup_test')
@pytest.mark.skipif(pyximport is None, reason='cython is not installed')
def test_cython():
options = {"members": None,
"undoc-members": None}
actual = do_autodoc(app, 'module', 'target.cython', options)
assert list(actual) == [
'',
'.. py:module:: target.cython',
'',
'',
'.. py:class:: Class',
' :module: target.cython',
'',
' Docstring.',
'',
'',
' .. py:method:: Class.meth(name: str, age: int = 0) -> None',
' :module: target.cython',
'',
' Docstring.',
'',
'',
'.. py:function:: foo(*args, **kwargs)',
' :module: target.cython',
'',
' Docstring.',
'',
] ]

View File

@ -318,13 +318,13 @@ def test_epub_anchor_id(app):
app.build() app.build()
html = (app.outdir / 'index.xhtml').read_text() html = (app.outdir / 'index.xhtml').read_text()
assert ('<p id="std-setting-staticfiles-finders">' assert ('<p id="std-setting-staticfiles_finders">'
'<span id="std-setting-STATICFILES_FINDERS"></span>' '<span id="std-setting-STATICFILES_FINDERS"></span>'
'blah blah blah</p>' in html) 'blah blah blah</p>' in html)
assert ('<span id="std-setting-staticfiles-section"></span>' assert ('<span id="std-setting-staticfiles_section"></span>'
'<span id="std-setting-STATICFILES_SECTION"></span>' '<span id="std-setting-STATICFILES_SECTION"></span>'
'<h1>blah blah blah</h1>' in html) '<h1>blah blah blah</h1>' in html)
assert 'see <a class="reference internal" href="#std-setting-staticfiles-finders">' in html assert 'see <a class="reference internal" href="#std-setting-staticfiles_finders">' in html
@pytest.mark.sphinx('epub', testroot='html_assets') @pytest.mark.sphinx('epub', testroot='html_assets')

View File

@ -175,9 +175,9 @@ def test_html4_output(app, status, warning):
r'-| |-'), r'-| |-'),
], ],
'autodoc.html': [ 'autodoc.html': [
(".//dl[@class='py class']/dt[@id='autodoc-target-class']", ''), (".//dl[@class='py class']/dt[@id='autodoc_target.class']", ''),
(".//dl[@class='py function']/dt[@id='autodoc-target-function']/em/span", r'\*\*'), (".//dl[@class='py function']/dt[@id='autodoc_target.function']/em/span", r'\*\*'),
(".//dl[@class='py function']/dt[@id='autodoc-target-function']/em/span", r'kwds'), (".//dl[@class='py function']/dt[@id='autodoc_target.function']/em/span", r'kwds'),
(".//dd/p", r'Return spam\.'), (".//dd/p", r'Return spam\.'),
], ],
'extapi.html': [ 'extapi.html': [
@ -222,7 +222,7 @@ def test_html4_output(app, status, warning):
"[@class='reference internal']/code/span[@class='pre']", 'HOME'), "[@class='reference internal']/code/span[@class='pre']", 'HOME'),
(".//a[@href='#with']" (".//a[@href='#with']"
"[@class='reference internal']/code/span[@class='pre']", '^with$'), "[@class='reference internal']/code/span[@class='pre']", '^with$'),
(".//a[@href='#grammar-token-try-stmt']" (".//a[@href='#grammar-token-try_stmt']"
"[@class='reference internal']/code/span", '^statement$'), "[@class='reference internal']/code/span", '^statement$'),
(".//a[@href='#some-label'][@class='reference internal']/span", '^here$'), (".//a[@href='#some-label'][@class='reference internal']/span", '^here$'),
(".//a[@href='#some-label'][@class='reference internal']/span", '^there$'), (".//a[@href='#some-label'][@class='reference internal']/span", '^there$'),
@ -254,7 +254,7 @@ def test_html4_output(app, status, warning):
(".//dl/dt[@id='term-boson']", 'boson'), (".//dl/dt[@id='term-boson']", 'boson'),
# a production list # a production list
(".//pre/strong", 'try_stmt'), (".//pre/strong", 'try_stmt'),
(".//pre/a[@href='#grammar-token-try1-stmt']/code/span", 'try1_stmt'), (".//pre/a[@href='#grammar-token-try1_stmt']/code/span", 'try1_stmt'),
# tests for ``only`` directive # tests for ``only`` directive
(".//p", 'A global substitution.'), (".//p", 'A global substitution.'),
(".//p", 'In HTML.'), (".//p", 'In HTML.'),
@ -262,7 +262,7 @@ def test_html4_output(app, status, warning):
(".//p", 'Always present'), (".//p", 'Always present'),
# tests for ``any`` role # tests for ``any`` role
(".//a[@href='#with']/span", 'headings'), (".//a[@href='#with']/span", 'headings'),
(".//a[@href='objects.html#func-without-body']/code/span", 'objects'), (".//a[@href='objects.html#func_without_body']/code/span", 'objects'),
# tests for numeric labels # tests for numeric labels
(".//a[@href='#id1'][@class='reference internal']/span", 'Testing various markup'), (".//a[@href='#id1'][@class='reference internal']/span", 'Testing various markup'),
# tests for smartypants # tests for smartypants
@ -274,18 +274,18 @@ def test_html4_output(app, status, warning):
(".//p", 'Il dit : « Cest “super” ! »'), (".//p", 'Il dit : « Cest “super” ! »'),
], ],
'objects.html': [ 'objects.html': [
(".//dt[@id='mod-cls-meth1']", ''), (".//dt[@id='mod.cls.meth1']", ''),
(".//dt[@id='errmod-error']", ''), (".//dt[@id='errmod.error']", ''),
(".//dt/code", r'long\(parameter,\s* list\)'), (".//dt/code", r'long\(parameter,\s* list\)'),
(".//dt/code", 'another one'), (".//dt/code", 'another one'),
(".//a[@href='#mod-cls'][@class='reference internal']", ''), (".//a[@href='#mod.cls'][@class='reference internal']", ''),
(".//dl[@class='std userdesc']", ''), (".//dl[@class='std userdesc']", ''),
(".//dt[@id='userdesc-myobj']", ''), (".//dt[@id='userdesc-myobj']", ''),
(".//a[@href='#userdesc-myobj'][@class='reference internal']", ''), (".//a[@href='#userdesc-myobj'][@class='reference internal']", ''),
# docfields # docfields
(".//a[@class='reference internal'][@href='#timeint']/em", 'TimeInt'), (".//a[@class='reference internal'][@href='#timeint']/em", 'TimeInt'),
(".//a[@class='reference internal'][@href='#time']", 'Time'), (".//a[@class='reference internal'][@href='#time']", 'Time'),
(".//a[@class='reference internal'][@href='#errmod-error']/strong", 'Error'), (".//a[@class='reference internal'][@href='#errmod.error']/strong", 'Error'),
# C references # C references
(".//span[@class='pre']", 'CFunction()'), (".//span[@class='pre']", 'CFunction()'),
(".//a[@href='#c.Sphinx_DoSomething']", ''), (".//a[@href='#c.Sphinx_DoSomething']", ''),
@ -324,7 +324,7 @@ def test_html4_output(app, status, warning):
'\\+p'), '\\+p'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-objc']/code/span", (".//a[@class='reference internal'][@href='#cmdoption-perl-objc']/code/span",
'--ObjC\\+\\+'), '--ObjC\\+\\+'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-plugin-option']/code/span", (".//a[@class='reference internal'][@href='#cmdoption-perl-plugin.option']/code/span",
'--plugin.option'), '--plugin.option'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-create-auth-token']" (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-create-auth-token']"
"/code/span", "/code/span",

View File

@ -123,25 +123,25 @@ def test_domain_js_find_obj(app, status, warning):
('NestedParentA', ('roles', 'nestedparenta', 'class'))) ('NestedParentA', ('roles', 'nestedparenta', 'class')))
assert (find_obj(None, None, 'NestedParentA.NestedChildA', 'class') == assert (find_obj(None, None, 'NestedParentA.NestedChildA', 'class') ==
('NestedParentA.NestedChildA', ('NestedParentA.NestedChildA',
('roles', 'nestedparenta-nestedchilda', 'class'))) ('roles', 'nestedparenta.nestedchilda', 'class')))
assert (find_obj(None, 'NestedParentA', 'NestedChildA', 'class') == assert (find_obj(None, 'NestedParentA', 'NestedChildA', 'class') ==
('NestedParentA.NestedChildA', ('NestedParentA.NestedChildA',
('roles', 'nestedparenta-nestedchilda', 'class'))) ('roles', 'nestedparenta.nestedchilda', 'class')))
assert (find_obj(None, None, 'NestedParentA.NestedChildA.subchild_1', 'func') == assert (find_obj(None, None, 'NestedParentA.NestedChildA.subchild_1', 'func') ==
('NestedParentA.NestedChildA.subchild_1', ('NestedParentA.NestedChildA.subchild_1',
('roles', 'nestedparenta-nestedchilda-subchild-1', 'function'))) ('roles', 'nestedparenta.nestedchilda.subchild_1', 'function')))
assert (find_obj(None, 'NestedParentA', 'NestedChildA.subchild_1', 'func') == assert (find_obj(None, 'NestedParentA', 'NestedChildA.subchild_1', 'func') ==
('NestedParentA.NestedChildA.subchild_1', ('NestedParentA.NestedChildA.subchild_1',
('roles', 'nestedparenta-nestedchilda-subchild-1', 'function'))) ('roles', 'nestedparenta.nestedchilda.subchild_1', 'function')))
assert (find_obj(None, 'NestedParentA.NestedChildA', 'subchild_1', 'func') == assert (find_obj(None, 'NestedParentA.NestedChildA', 'subchild_1', 'func') ==
('NestedParentA.NestedChildA.subchild_1', ('NestedParentA.NestedChildA.subchild_1',
('roles', 'nestedparenta-nestedchilda-subchild-1', 'function'))) ('roles', 'nestedparenta.nestedchilda.subchild_1', 'function')))
assert (find_obj('module_a.submodule', 'ModTopLevel', 'mod_child_2', 'meth') == assert (find_obj('module_a.submodule', 'ModTopLevel', 'mod_child_2', 'meth') ==
('module_a.submodule.ModTopLevel.mod_child_2', ('module_a.submodule.ModTopLevel.mod_child_2',
('module', 'module-a-submodule-modtoplevel-mod-child-2', 'method'))) ('module', 'module_a.submodule.modtoplevel.mod_child_2', 'method')))
assert (find_obj('module_b.submodule', 'ModTopLevel', 'module_a.submodule', 'mod') == assert (find_obj('module_b.submodule', 'ModTopLevel', 'module_a.submodule', 'mod') ==
('module_a.submodule', ('module_a.submodule',
('module', 'module-module-a-submodule', 'module'))) ('module', 'module-module_a.submodule', 'module')))
def test_get_full_qualified_name(): def test_get_full_qualified_name():

View File

@ -18,11 +18,11 @@ from sphinx import addnodes
from sphinx.addnodes import ( from sphinx.addnodes import (
desc, desc_addname, desc_annotation, desc_content, desc_name, desc_optional, desc, desc_addname, desc_annotation, desc_content, desc_name, desc_optional,
desc_parameter, desc_parameterlist, desc_returns, desc_signature, desc_parameter, desc_parameterlist, desc_returns, desc_signature,
desc_sig_name, desc_sig_operator, desc_sig_punctuation, desc_sig_name, desc_sig_operator, desc_sig_punctuation, pending_xref,
) )
from sphinx.domains import IndexEntry from sphinx.domains import IndexEntry
from sphinx.domains.python import ( from sphinx.domains.python import (
py_sig_re, _pseudo_parse_arglist, PythonDomain, PythonModuleIndex py_sig_re, _parse_annotation, _pseudo_parse_arglist, PythonDomain, PythonModuleIndex
) )
from sphinx.testing import restructuredtext from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node from sphinx.testing.util import assert_node
@ -78,7 +78,7 @@ def test_domain_py_xrefs(app, status, warning):
assert_node(node, **attributes) assert_node(node, **attributes)
doctree = app.env.get_doctree('roles') doctree = app.env.get_doctree('roles')
refnodes = list(doctree.traverse(addnodes.pending_xref)) refnodes = list(doctree.traverse(pending_xref))
assert_refnode(refnodes[0], None, None, 'TopLevel', 'class') assert_refnode(refnodes[0], None, None, 'TopLevel', 'class')
assert_refnode(refnodes[1], None, None, 'top_level', 'meth') assert_refnode(refnodes[1], None, None, 'top_level', 'meth')
assert_refnode(refnodes[2], None, 'NestedParentA', 'child_1', 'meth') assert_refnode(refnodes[2], None, 'NestedParentA', 'child_1', 'meth')
@ -96,7 +96,7 @@ def test_domain_py_xrefs(app, status, warning):
assert len(refnodes) == 13 assert len(refnodes) == 13
doctree = app.env.get_doctree('module') doctree = app.env.get_doctree('module')
refnodes = list(doctree.traverse(addnodes.pending_xref)) refnodes = list(doctree.traverse(pending_xref))
assert_refnode(refnodes[0], 'module_a.submodule', None, assert_refnode(refnodes[0], 'module_a.submodule', None,
'ModTopLevel', 'class') 'ModTopLevel', 'class')
assert_refnode(refnodes[1], 'module_a.submodule', 'ModTopLevel', assert_refnode(refnodes[1], 'module_a.submodule', 'ModTopLevel',
@ -125,7 +125,7 @@ def test_domain_py_xrefs(app, status, warning):
assert len(refnodes) == 16 assert len(refnodes) == 16
doctree = app.env.get_doctree('module_option') doctree = app.env.get_doctree('module_option')
refnodes = list(doctree.traverse(addnodes.pending_xref)) refnodes = list(doctree.traverse(pending_xref))
print(refnodes) print(refnodes)
print(refnodes[0]) print(refnodes[0])
print(refnodes[1]) print(refnodes[1])
@ -171,11 +171,11 @@ def test_resolve_xref_for_properties(app, status, warning):
app.builder.build_all() app.builder.build_all()
content = (app.outdir / 'module.html').read_text() content = (app.outdir / 'module.html').read_text()
assert ('Link to <a class="reference internal" href="#module-a-submodule-modtoplevel-prop"' assert ('Link to <a class="reference internal" href="#module_a.submodule.modtoplevel.prop"'
' title="module_a.submodule.ModTopLevel.prop">' ' title="module_a.submodule.ModTopLevel.prop">'
'<code class="xref py py-attr docutils literal notranslate"><span class="pre">' '<code class="xref py py-attr docutils literal notranslate"><span class="pre">'
'prop</span> <span class="pre">attribute</span></code></a>' in content) 'prop</span> <span class="pre">attribute</span></code></a>' in content)
assert ('Link to <a class="reference internal" href="#module-a-submodule-modtoplevel-prop"' assert ('Link to <a class="reference internal" href="#module_a.submodule.modtoplevel.prop"'
' title="module_a.submodule.ModTopLevel.prop">' ' title="module_a.submodule.ModTopLevel.prop">'
'<code class="xref py py-meth docutils literal notranslate"><span class="pre">' '<code class="xref py py-meth docutils literal notranslate"><span class="pre">'
'prop</span> <span class="pre">method</span></code></a>' in content) 'prop</span> <span class="pre">method</span></code></a>' in content)
@ -194,18 +194,18 @@ def test_domain_py_find_obj(app, status, warning):
assert (find_obj(None, None, 'NestedParentA', 'class') == assert (find_obj(None, None, 'NestedParentA', 'class') ==
[('NestedParentA', ('roles', 'nestedparenta', 'class'))]) [('NestedParentA', ('roles', 'nestedparenta', 'class'))])
assert (find_obj(None, None, 'NestedParentA.NestedChildA', 'class') == assert (find_obj(None, None, 'NestedParentA.NestedChildA', 'class') ==
[('NestedParentA.NestedChildA', ('roles', 'nestedparenta-nestedchilda', 'class'))]) [('NestedParentA.NestedChildA', ('roles', 'nestedparenta.nestedchilda', 'class'))])
assert (find_obj(None, 'NestedParentA', 'NestedChildA', 'class') == assert (find_obj(None, 'NestedParentA', 'NestedChildA', 'class') ==
[('NestedParentA.NestedChildA', ('roles', 'nestedparenta-nestedchilda', 'class'))]) [('NestedParentA.NestedChildA', ('roles', 'nestedparenta.nestedchilda', 'class'))])
assert (find_obj(None, None, 'NestedParentA.NestedChildA.subchild_1', 'meth') == assert (find_obj(None, None, 'NestedParentA.NestedChildA.subchild_1', 'meth') ==
[('NestedParentA.NestedChildA.subchild_1', [('NestedParentA.NestedChildA.subchild_1',
('roles', 'nestedparenta-nestedchilda-subchild-1', 'method'))]) ('roles', 'nestedparenta.nestedchilda.subchild_1', 'method'))])
assert (find_obj(None, 'NestedParentA', 'NestedChildA.subchild_1', 'meth') == assert (find_obj(None, 'NestedParentA', 'NestedChildA.subchild_1', 'meth') ==
[('NestedParentA.NestedChildA.subchild_1', [('NestedParentA.NestedChildA.subchild_1',
('roles', 'nestedparenta-nestedchilda-subchild-1', 'method'))]) ('roles', 'nestedparenta.nestedchilda.subchild_1', 'method'))])
assert (find_obj(None, 'NestedParentA.NestedChildA', 'subchild_1', 'meth') == assert (find_obj(None, 'NestedParentA.NestedChildA', 'subchild_1', 'meth') ==
[('NestedParentA.NestedChildA.subchild_1', [('NestedParentA.NestedChildA.subchild_1',
('roles', 'nestedparenta-nestedchilda-subchild-1', 'method'))]) ('roles', 'nestedparenta.nestedchilda.subchild_1', 'method'))])
def test_get_full_qualified_name(): def test_get_full_qualified_name():
@ -236,13 +236,44 @@ def test_get_full_qualified_name():
assert domain.get_full_qualified_name(node) == 'module1.Class.func' assert domain.get_full_qualified_name(node) == 'module1.Class.func'
def test_parse_annotation():
doctree = _parse_annotation("int")
assert_node(doctree, ([pending_xref, "int"],))
doctree = _parse_annotation("List[int]")
assert_node(doctree, ([pending_xref, "List"],
[desc_sig_punctuation, "["],
[pending_xref, "int"],
[desc_sig_punctuation, "]"]))
doctree = _parse_annotation("Tuple[int, int]")
assert_node(doctree, ([pending_xref, "Tuple"],
[desc_sig_punctuation, "["],
[pending_xref, "int"],
[desc_sig_punctuation, ", "],
[pending_xref, "int"],
[desc_sig_punctuation, "]"]))
doctree = _parse_annotation("Callable[[int, int], int]")
assert_node(doctree, ([pending_xref, "Callable"],
[desc_sig_punctuation, "["],
[desc_sig_punctuation, "["],
[pending_xref, "int"],
[desc_sig_punctuation, ", "],
[pending_xref, "int"],
[desc_sig_punctuation, "]"],
[desc_sig_punctuation, ", "],
[pending_xref, "int"],
[desc_sig_punctuation, "]"]))
def test_pyfunction_signature(app): def test_pyfunction_signature(app):
text = ".. py:function:: hello(name: str) -> str" text = ".. py:function:: hello(name: str) -> str"
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, "hello"], [desc, ([desc_signature, ([desc_name, "hello"],
desc_parameterlist, desc_parameterlist,
[desc_returns, "str"])], [desc_returns, pending_xref, "str"])],
desc_content)])) desc_content)]))
assert_node(doctree[1], addnodes.desc, desctype="function", assert_node(doctree[1], addnodes.desc, desctype="function",
domain="py", objtype="function", noindex=False) domain="py", objtype="function", noindex=False)
@ -250,7 +281,7 @@ def test_pyfunction_signature(app):
[desc_parameterlist, desc_parameter, ([desc_sig_name, "name"], [desc_parameterlist, desc_parameter, ([desc_sig_name, "name"],
[desc_sig_punctuation, ":"], [desc_sig_punctuation, ":"],
" ", " ",
[nodes.inline, "str"])]) [nodes.inline, pending_xref, "str"])])
def test_pyfunction_signature_full(app): def test_pyfunction_signature_full(app):
@ -260,7 +291,7 @@ def test_pyfunction_signature_full(app):
assert_node(doctree, (addnodes.index, assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_name, "hello"], [desc, ([desc_signature, ([desc_name, "hello"],
desc_parameterlist, desc_parameterlist,
[desc_returns, "str"])], [desc_returns, pending_xref, "str"])],
desc_content)])) desc_content)]))
assert_node(doctree[1], addnodes.desc, desctype="function", assert_node(doctree[1], addnodes.desc, desctype="function",
domain="py", objtype="function", noindex=False) domain="py", objtype="function", noindex=False)
@ -268,7 +299,7 @@ def test_pyfunction_signature_full(app):
[desc_parameterlist, ([desc_parameter, ([desc_sig_name, "a"], [desc_parameterlist, ([desc_parameter, ([desc_sig_name, "a"],
[desc_sig_punctuation, ":"], [desc_sig_punctuation, ":"],
" ", " ",
[desc_sig_name, "str"])], [desc_sig_name, pending_xref, "str"])],
[desc_parameter, ([desc_sig_name, "b"], [desc_parameter, ([desc_sig_name, "b"],
[desc_sig_operator, "="], [desc_sig_operator, "="],
[nodes.inline, "1"])], [nodes.inline, "1"])],
@ -276,11 +307,11 @@ def test_pyfunction_signature_full(app):
[desc_sig_name, "args"], [desc_sig_name, "args"],
[desc_sig_punctuation, ":"], [desc_sig_punctuation, ":"],
" ", " ",
[desc_sig_name, "str"])], [desc_sig_name, pending_xref, "str"])],
[desc_parameter, ([desc_sig_name, "c"], [desc_parameter, ([desc_sig_name, "c"],
[desc_sig_punctuation, ":"], [desc_sig_punctuation, ":"],
" ", " ",
[desc_sig_name, "bool"], [desc_sig_name, pending_xref, "bool"],
" ", " ",
[desc_sig_operator, "="], [desc_sig_operator, "="],
" ", " ",
@ -289,7 +320,7 @@ def test_pyfunction_signature_full(app):
[desc_sig_name, "kwargs"], [desc_sig_name, "kwargs"],
[desc_sig_punctuation, ":"], [desc_sig_punctuation, ":"],
" ", " ",
[desc_sig_name, "str"])])]) [desc_sig_name, pending_xref, "str"])])])
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') @pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
@ -340,7 +371,7 @@ def test_optional_pyfunction_signature(app):
assert_node(doctree, (addnodes.index, assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_name, "compile"], [desc, ([desc_signature, ([desc_name, "compile"],
desc_parameterlist, desc_parameterlist,
[desc_returns, "ast object"])], [desc_returns, pending_xref, "ast object"])],
desc_content)])) desc_content)]))
assert_node(doctree[1], addnodes.desc, desctype="function", assert_node(doctree[1], addnodes.desc, desctype="function",
domain="py", objtype="function", noindex=False) domain="py", objtype="function", noindex=False)
@ -483,61 +514,61 @@ def test_pymethod_options(app):
# method # method
assert_node(doctree[1][1][0], addnodes.index, assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'meth1() (Class method)', 'class-meth1', '', None)]) entries=[('single', 'meth1() (Class method)', 'class.meth1', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "meth1"], assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "meth1"],
[desc_parameterlist, ()])], [desc_parameterlist, ()])],
[desc_content, ()])) [desc_content, ()]))
assert 'Class.meth1' in domain.objects assert 'Class.meth1' in domain.objects
assert domain.objects['Class.meth1'] == ('index', 'class-meth1', 'method') assert domain.objects['Class.meth1'] == ('index', 'class.meth1', 'method')
# :classmethod: # :classmethod:
assert_node(doctree[1][1][2], addnodes.index, assert_node(doctree[1][1][2], addnodes.index,
entries=[('single', 'meth2() (Class class method)', 'class-meth2', '', None)]) entries=[('single', 'meth2() (Class class method)', 'class.meth2', '', None)])
assert_node(doctree[1][1][3], ([desc_signature, ([desc_annotation, "classmethod "], assert_node(doctree[1][1][3], ([desc_signature, ([desc_annotation, "classmethod "],
[desc_name, "meth2"], [desc_name, "meth2"],
[desc_parameterlist, ()])], [desc_parameterlist, ()])],
[desc_content, ()])) [desc_content, ()]))
assert 'Class.meth2' in domain.objects assert 'Class.meth2' in domain.objects
assert domain.objects['Class.meth2'] == ('index', 'class-meth2', 'method') assert domain.objects['Class.meth2'] == ('index', 'class.meth2', 'method')
# :staticmethod: # :staticmethod:
assert_node(doctree[1][1][4], addnodes.index, assert_node(doctree[1][1][4], addnodes.index,
entries=[('single', 'meth3() (Class static method)', 'class-meth3', '', None)]) entries=[('single', 'meth3() (Class static method)', 'class.meth3', '', None)])
assert_node(doctree[1][1][5], ([desc_signature, ([desc_annotation, "static "], assert_node(doctree[1][1][5], ([desc_signature, ([desc_annotation, "static "],
[desc_name, "meth3"], [desc_name, "meth3"],
[desc_parameterlist, ()])], [desc_parameterlist, ()])],
[desc_content, ()])) [desc_content, ()]))
assert 'Class.meth3' in domain.objects assert 'Class.meth3' in domain.objects
assert domain.objects['Class.meth3'] == ('index', 'class-meth3', 'method') assert domain.objects['Class.meth3'] == ('index', 'class.meth3', 'method')
# :async: # :async:
assert_node(doctree[1][1][6], addnodes.index, assert_node(doctree[1][1][6], addnodes.index,
entries=[('single', 'meth4() (Class method)', 'class-meth4', '', None)]) entries=[('single', 'meth4() (Class method)', 'class.meth4', '', None)])
assert_node(doctree[1][1][7], ([desc_signature, ([desc_annotation, "async "], assert_node(doctree[1][1][7], ([desc_signature, ([desc_annotation, "async "],
[desc_name, "meth4"], [desc_name, "meth4"],
[desc_parameterlist, ()])], [desc_parameterlist, ()])],
[desc_content, ()])) [desc_content, ()]))
assert 'Class.meth4' in domain.objects assert 'Class.meth4' in domain.objects
assert domain.objects['Class.meth4'] == ('index', 'class-meth4', 'method') assert domain.objects['Class.meth4'] == ('index', 'class.meth4', 'method')
# :property: # :property:
assert_node(doctree[1][1][8], addnodes.index, assert_node(doctree[1][1][8], addnodes.index,
entries=[('single', 'meth5() (Class property)', 'class-meth5', '', None)]) entries=[('single', 'meth5() (Class property)', 'class.meth5', '', None)])
assert_node(doctree[1][1][9], ([desc_signature, ([desc_annotation, "property "], assert_node(doctree[1][1][9], ([desc_signature, ([desc_annotation, "property "],
[desc_name, "meth5"])], [desc_name, "meth5"])],
[desc_content, ()])) [desc_content, ()]))
assert 'Class.meth5' in domain.objects assert 'Class.meth5' in domain.objects
assert domain.objects['Class.meth5'] == ('index', 'class-meth5', 'method') assert domain.objects['Class.meth5'] == ('index', 'class.meth5', 'method')
# :abstractmethod: # :abstractmethod:
assert_node(doctree[1][1][10], addnodes.index, assert_node(doctree[1][1][10], addnodes.index,
entries=[('single', 'meth6() (Class method)', 'class-meth6', '', None)]) entries=[('single', 'meth6() (Class method)', 'class.meth6', '', None)])
assert_node(doctree[1][1][11], ([desc_signature, ([desc_annotation, "abstract "], assert_node(doctree[1][1][11], ([desc_signature, ([desc_annotation, "abstract "],
[desc_name, "meth6"], [desc_name, "meth6"],
[desc_parameterlist, ()])], [desc_parameterlist, ()])],
[desc_content, ()])) [desc_content, ()]))
assert 'Class.meth6' in domain.objects assert 'Class.meth6' in domain.objects
assert domain.objects['Class.meth6'] == ('index', 'class-meth6', 'method') assert domain.objects['Class.meth6'] == ('index', 'class.meth6', 'method')
def test_pyclassmethod(app): def test_pyclassmethod(app):
@ -552,13 +583,13 @@ def test_pyclassmethod(app):
[desc_content, (addnodes.index, [desc_content, (addnodes.index,
desc)])])) desc)])]))
assert_node(doctree[1][1][0], addnodes.index, assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'meth() (Class class method)', 'class-meth', '', None)]) entries=[('single', 'meth() (Class class method)', 'class.meth', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, "classmethod "], assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, "classmethod "],
[desc_name, "meth"], [desc_name, "meth"],
[desc_parameterlist, ()])], [desc_parameterlist, ()])],
[desc_content, ()])) [desc_content, ()]))
assert 'Class.meth' in domain.objects assert 'Class.meth' in domain.objects
assert domain.objects['Class.meth'] == ('index', 'class-meth', 'method') assert domain.objects['Class.meth'] == ('index', 'class.meth', 'method')
def test_pystaticmethod(app): def test_pystaticmethod(app):
@ -573,13 +604,13 @@ def test_pystaticmethod(app):
[desc_content, (addnodes.index, [desc_content, (addnodes.index,
desc)])])) desc)])]))
assert_node(doctree[1][1][0], addnodes.index, assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'meth() (Class static method)', 'class-meth', '', None)]) entries=[('single', 'meth() (Class static method)', 'class.meth', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, "static "], assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, "static "],
[desc_name, "meth"], [desc_name, "meth"],
[desc_parameterlist, ()])], [desc_parameterlist, ()])],
[desc_content, ()])) [desc_content, ()]))
assert 'Class.meth' in domain.objects assert 'Class.meth' in domain.objects
assert domain.objects['Class.meth'] == ('index', 'class-meth', 'method') assert domain.objects['Class.meth'] == ('index', 'class.meth', 'method')
def test_pyattribute(app): def test_pyattribute(app):
@ -596,13 +627,13 @@ def test_pyattribute(app):
[desc_content, (addnodes.index, [desc_content, (addnodes.index,
desc)])])) desc)])]))
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, ": str"],
[desc_annotation, " = ''"])], [desc_annotation, " = ''"])],
[desc_content, ()])) [desc_content, ()]))
assert 'Class.attr' in domain.objects assert 'Class.attr' in domain.objects
assert domain.objects['Class.attr'] == ('index', 'class-attr', 'attribute') assert domain.objects['Class.attr'] == ('index', 'class.attr', 'attribute')
def test_pydecorator_signature(app): def test_pydecorator_signature(app):
@ -648,10 +679,10 @@ def test_module_index(app):
assert index.generate() == ( assert index.generate() == (
[('d', [IndexEntry('docutils', 0, 'index', 'module-docutils', '', '', '')]), [('d', [IndexEntry('docutils', 0, 'index', 'module-docutils', '', '', '')]),
('s', [IndexEntry('sphinx', 1, 'index', 'module-sphinx', '', '', ''), ('s', [IndexEntry('sphinx', 1, 'index', 'module-sphinx', '', '', ''),
IndexEntry('sphinx.builders', 2, 'index', 'module-sphinx-builders', '', '', ''), # NOQA IndexEntry('sphinx.builders', 2, 'index', 'module-sphinx.builders', '', '', ''), # NOQA
IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx-builders-html', '', '', ''), # NOQA IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx.builders.html', '', '', ''), # NOQA
IndexEntry('sphinx.config', 2, 'index', 'module-sphinx-config', '', '', ''), IndexEntry('sphinx.config', 2, 'index', 'module-sphinx.config', '', '', ''),
IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx-intl', '', '', '')])], IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx_intl', '', '', '')])],
False False
) )
@ -663,7 +694,7 @@ def test_module_index_submodule(app):
index = PythonModuleIndex(app.env.get_domain('py')) index = PythonModuleIndex(app.env.get_domain('py'))
assert index.generate() == ( assert index.generate() == (
[('s', [IndexEntry('sphinx', 1, '', '', '', '', ''), [('s', [IndexEntry('sphinx', 1, '', '', '', '', ''),
IndexEntry('sphinx.config', 2, 'index', 'module-sphinx-config', '', '', '')])], IndexEntry('sphinx.config', 2, 'index', 'module-sphinx.config', '', '', '')])],
False False
) )
@ -692,12 +723,12 @@ def test_modindex_common_prefix(app):
restructuredtext.parse(app, text) restructuredtext.parse(app, text)
index = PythonModuleIndex(app.env.get_domain('py')) index = PythonModuleIndex(app.env.get_domain('py'))
assert index.generate() == ( assert index.generate() == (
[('b', [IndexEntry('sphinx.builders', 1, 'index', 'module-sphinx-builders', '', '', ''), # NOQA [('b', [IndexEntry('sphinx.builders', 1, 'index', 'module-sphinx.builders', '', '', ''), # NOQA
IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx-builders-html', '', '', '')]), # NOQA IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx.builders.html', '', '', '')]), # NOQA
('c', [IndexEntry('sphinx.config', 0, 'index', 'module-sphinx-config', '', '', '')]), ('c', [IndexEntry('sphinx.config', 0, 'index', 'module-sphinx.config', '', '', '')]),
('d', [IndexEntry('docutils', 0, 'index', 'module-docutils', '', '', '')]), ('d', [IndexEntry('docutils', 0, 'index', 'module-docutils', '', '', '')]),
('s', [IndexEntry('sphinx', 0, 'index', 'module-sphinx', '', '', ''), ('s', [IndexEntry('sphinx', 0, 'index', 'module-sphinx', '', '', ''),
IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx-intl', '', '', '')])], IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx_intl', '', '', '')])],
True True
) )

View File

@ -84,7 +84,7 @@ def test_object_inventory(app):
refs = app.env.domaindata['py']['objects'] refs = app.env.domaindata['py']['objects']
assert 'func_without_module' in refs assert 'func_without_module' in refs
assert refs['func_without_module'] == ('objects', 'func-without-module', 'function') assert refs['func_without_module'] == ('objects', 'func_without_module', 'function')
assert 'func_without_module2' in refs assert 'func_without_module2' in refs
assert 'mod.func_in_module' in refs assert 'mod.func_in_module' in refs
assert 'mod.Cls' in refs assert 'mod.Cls' in refs

View File

@ -31,49 +31,49 @@ def test_autoclass_content_class(app):
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, no __new__', ' A class having no __init__, no __new__',
' ', '',
'', '',
'.. py:class:: B()', '.. py:class:: B()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having __init__(no docstring), no __new__', ' A class having __init__(no docstring), no __new__',
' ', '',
'', '',
'.. py:class:: C()', '.. py:class:: C()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having __init__, no __new__', ' A class having __init__, no __new__',
' ', '',
'', '',
'.. py:class:: D', '.. py:class:: D',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, __new__(no docstring)', ' A class having no __init__, __new__(no docstring)',
' ', '',
'', '',
'.. py:class:: E', '.. py:class:: E',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, __new__', ' A class having no __init__, __new__',
' ', '',
'', '',
'.. py:class:: F()', '.. py:class:: F()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having both __init__ and __new__', ' A class having both __init__ and __new__',
' ', '',
'', '',
'.. py:class:: G()', '.. py:class:: G()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class inherits __init__ without docstring.', ' A class inherits __init__ without docstring.',
' ', '',
'', '',
'.. py:class:: H()', '.. py:class:: H()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class inherits __new__ without docstring.', ' A class inherits __new__ without docstring.',
' ' '',
] ]
@ -91,49 +91,49 @@ def test_autoclass_content_init(app):
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, no __new__', ' A class having no __init__, no __new__',
' ', '',
'', '',
'.. py:class:: B()', '.. py:class:: B()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having __init__(no docstring), no __new__', ' A class having __init__(no docstring), no __new__',
' ', '',
'', '',
'.. py:class:: C()', '.. py:class:: C()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' __init__ docstring', ' __init__ docstring',
' ', '',
'', '',
'.. py:class:: D', '.. py:class:: D',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, __new__(no docstring)', ' A class having no __init__, __new__(no docstring)',
' ', '',
'', '',
'.. py:class:: E', '.. py:class:: E',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' __new__ docstring', ' __new__ docstring',
' ', '',
'', '',
'.. py:class:: F()', '.. py:class:: F()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' __init__ docstring', ' __init__ docstring',
' ', '',
'', '',
'.. py:class:: G()', '.. py:class:: G()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' __init__ docstring', ' __init__ docstring',
' ', '',
'', '',
'.. py:class:: H()', '.. py:class:: H()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' __new__ docstring', ' __new__ docstring',
' ' '',
] ]
@ -151,59 +151,59 @@ def test_autoclass_content_both(app):
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, no __new__', ' A class having no __init__, no __new__',
' ', '',
'', '',
'.. py:class:: B()', '.. py:class:: B()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having __init__(no docstring), no __new__', ' A class having __init__(no docstring), no __new__',
' ', '',
'', '',
'.. py:class:: C()', '.. py:class:: C()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having __init__, no __new__', ' A class having __init__, no __new__',
' ', '',
' __init__ docstring', ' __init__ docstring',
' ', '',
'', '',
'.. py:class:: D', '.. py:class:: D',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, __new__(no docstring)', ' A class having no __init__, __new__(no docstring)',
' ', '',
'', '',
'.. py:class:: E', '.. py:class:: E',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having no __init__, __new__', ' A class having no __init__, __new__',
' ', '',
' __new__ docstring', ' __new__ docstring',
' ', '',
'', '',
'.. py:class:: F()', '.. py:class:: F()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class having both __init__ and __new__', ' A class having both __init__ and __new__',
' ', '',
' __init__ docstring', ' __init__ docstring',
' ', '',
'', '',
'.. py:class:: G()', '.. py:class:: G()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class inherits __init__ without docstring.', ' A class inherits __init__ without docstring.',
' ', '',
' __init__ docstring', ' __init__ docstring',
' ', '',
'', '',
'.. py:class:: H()', '.. py:class:: H()',
' :module: target.autoclass_content', ' :module: target.autoclass_content',
'', '',
' A class inherits __new__ without docstring.', ' A class inherits __new__ without docstring.',
' ', '',
' __new__ docstring', ' __new__ docstring',
' ' '',
] ]
@ -217,7 +217,7 @@ def test_autodoc_inherit_docstrings(app):
' :module: target.inheritance', ' :module: target.inheritance',
'', '',
' Inherited function.', ' Inherited function.',
' ' '',
] ]
# disable autodoc_inherit_docstrings # disable autodoc_inherit_docstrings
@ -240,38 +240,38 @@ def test_autodoc_docstring_signature(app):
'.. py:class:: DocstringSig', '.. py:class:: DocstringSig',
' :module: target', ' :module: target',
'', '',
' ', '',
' .. py:method:: DocstringSig.meth(FOO, BAR=1) -> BAZ', ' .. py:method:: DocstringSig.meth(FOO, BAR=1) -> BAZ',
' :module: target', ' :module: target',
' ', '',
' First line of docstring', ' First line of docstring',
' ', '',
' rest of docstring', ' rest of docstring',
' ', '',
' ', '',
' .. py:method:: DocstringSig.meth2()', ' .. py:method:: DocstringSig.meth2()',
' :module: target', ' :module: target',
' ', '',
' First line, no signature', ' First line, no signature',
' Second line followed by indentation::', ' Second line followed by indentation::',
' ', '',
' indented line', ' indented line',
' ', '',
' ', '',
' .. py:method:: DocstringSig.prop1', ' .. py:method:: DocstringSig.prop1',
' :module: target', ' :module: target',
' :property:', ' :property:',
' ', '',
' First line of docstring', ' First line of docstring',
' ', '',
' ', '',
' .. py:method:: DocstringSig.prop2', ' .. py:method:: DocstringSig.prop2',
' :module: target', ' :module: target',
' :property:', ' :property:',
' ', '',
' First line of docstring', ' First line of docstring',
' Second line of docstring', ' Second line of docstring',
' ' '',
] ]
# disable autodoc_docstring_signature # disable autodoc_docstring_signature
@ -282,41 +282,41 @@ def test_autodoc_docstring_signature(app):
'.. py:class:: DocstringSig', '.. py:class:: DocstringSig',
' :module: target', ' :module: target',
'', '',
' ', '',
' .. py:method:: DocstringSig.meth()', ' .. py:method:: DocstringSig.meth()',
' :module: target', ' :module: target',
' ', '',
' meth(FOO, BAR=1) -> BAZ', ' meth(FOO, BAR=1) -> BAZ',
' First line of docstring', ' First line of docstring',
' ', '',
' rest of docstring', ' rest of docstring',
' ', '',
' ', '',
' ', '',
' .. py:method:: DocstringSig.meth2()', ' .. py:method:: DocstringSig.meth2()',
' :module: target', ' :module: target',
' ', '',
' First line, no signature', ' First line, no signature',
' Second line followed by indentation::', ' Second line followed by indentation::',
' ', '',
' indented line', ' indented line',
' ', '',
' ', '',
' .. py:method:: DocstringSig.prop1', ' .. py:method:: DocstringSig.prop1',
' :module: target', ' :module: target',
' :property:', ' :property:',
' ', '',
' DocstringSig.prop1(self)', ' DocstringSig.prop1(self)',
' First line of docstring', ' First line of docstring',
' ', '',
' ', '',
' .. py:method:: DocstringSig.prop2', ' .. py:method:: DocstringSig.prop2',
' :module: target', ' :module: target',
' :property:', ' :property:',
' ', '',
' First line of docstring', ' First line of docstring',
' Second line of docstring', ' Second line of docstring',
' ' '',
] ]
@ -397,13 +397,13 @@ def test_autoclass_content_and_docstring_signature_both(app):
' :module: target.docstring_signature', ' :module: target.docstring_signature',
'', '',
' B(foo, bar, baz)', ' B(foo, bar, baz)',
' ', '',
'', '',
'.. py:class:: C(foo, bar)', '.. py:class:: C(foo, bar)',
' :module: target.docstring_signature', ' :module: target.docstring_signature',
'', '',
' C(foo, bar, baz)', ' C(foo, bar, baz)',
' ', '',
'', '',
'.. py:class:: D(foo, bar, baz)', '.. py:class:: D(foo, bar, baz)',
' :module: target.docstring_signature', ' :module: target.docstring_signature',
@ -439,25 +439,25 @@ def test_mocked_module_imports(app, warning):
' :module: target.need_mocks', ' :module: target.need_mocks',
'', '',
' TestAutodoc docstring.', ' TestAutodoc docstring.',
' ', '',
' ', '',
' .. py:method:: TestAutodoc.decoratedMethod()', ' .. py:method:: TestAutodoc.decoratedMethod()',
' :module: target.need_mocks', ' :module: target.need_mocks',
' ', '',
' TestAutodoc::decoratedMethod docstring', ' TestAutodoc::decoratedMethod docstring',
' ', '',
'', '',
'.. py:function:: decoratedFunction()', '.. py:function:: decoratedFunction()',
' :module: target.need_mocks', ' :module: target.need_mocks',
'', '',
' decoratedFunction docstring', ' decoratedFunction docstring',
' ', '',
'', '',
'.. py:function:: func(arg: missing_module.Class)', '.. py:function:: func(arg: missing_module.Class)',
' :module: target.need_mocks', ' :module: target.need_mocks',
'', '',
' a function takes mocked object as an argument', ' a function takes mocked object as an argument',
' ' '',
] ]
assert warning.getvalue() == '' assert warning.getvalue() == ''
@ -476,22 +476,22 @@ def test_autodoc_typehints_signature(app):
'.. py:class:: Math(s: str, o: object = None)', '.. py:class:: Math(s: str, o: object = None)',
' :module: target.typehints', ' :module: target.typehints',
'', '',
' ', '',
' .. py:method:: Math.decr(a: int, b: int = 1) -> int', ' .. py:method:: Math.decr(a: int, b: int = 1) -> int',
' :module: target.typehints', ' :module: target.typehints',
' ', '',
' ', '',
' .. py:method:: Math.horse(a: str, b: int) -> None', ' .. py:method:: Math.horse(a: str, b: int) -> None',
' :module: target.typehints', ' :module: target.typehints',
' ', '',
' ', '',
' .. py:method:: Math.incr(a: int, b: int = 1) -> int', ' .. py:method:: Math.incr(a: int, b: int = 1) -> int',
' :module: target.typehints', ' :module: target.typehints',
' ', '',
' ', '',
' .. py:method:: Math.nothing() -> None', ' .. py:method:: Math.nothing() -> None',
' :module: target.typehints', ' :module: target.typehints',
' ', '',
'', '',
'.. py:function:: complex_func(arg1: str, arg2: List[int], arg3: Tuple[int, ' '.. py:function:: complex_func(arg1: str, arg2: List[int], arg3: Tuple[int, '
'Union[str, Unknown]] = None, *args: str, **kwargs: str) -> None', 'Union[str, Unknown]] = None, *args: str, **kwargs: str) -> None',
@ -526,22 +526,22 @@ def test_autodoc_typehints_none(app):
'.. py:class:: Math(s, o=None)', '.. py:class:: Math(s, o=None)',
' :module: target.typehints', ' :module: target.typehints',
'', '',
' ', '',
' .. py:method:: Math.decr(a, b=1)', ' .. py:method:: Math.decr(a, b=1)',
' :module: target.typehints', ' :module: target.typehints',
' ', '',
' ', '',
' .. py:method:: Math.horse(a, b)', ' .. py:method:: Math.horse(a, b)',
' :module: target.typehints', ' :module: target.typehints',
' ', '',
' ', '',
' .. py:method:: Math.incr(a, b=1)', ' .. py:method:: Math.incr(a, b=1)',
' :module: target.typehints', ' :module: target.typehints',
' ', '',
' ', '',
' .. py:method:: Math.nothing()', ' .. py:method:: Math.nothing()',
' :module: target.typehints', ' :module: target.typehints',
' ', '',
'', '',
'.. py:function:: complex_func(arg1, arg2, arg3=None, *args, **kwargs)', '.. py:function:: complex_func(arg1, arg2, arg3=None, *args, **kwargs)',
' :module: target.typehints', ' :module: target.typehints',

View File

@ -44,7 +44,7 @@ def test_cut_lines(app):
' :module: target.process_docstring', ' :module: target.process_docstring',
'', '',
' second line', ' second line',
' ' '',
] ]
@ -60,7 +60,7 @@ def test_between(app):
' :module: target.process_docstring', ' :module: target.process_docstring',
'', '',
' second line', ' second line',
' ' '',
] ]
@ -77,5 +77,5 @@ def test_between_exclude(app):
'', '',
' first line', ' first line',
' third line', ' third line',
' ' '',
] ]

View File

@ -40,7 +40,7 @@ def test_private_field_and_private_members(app):
' :module: target.private', ' :module: target.private',
'', '',
' private_function is a docstring().', ' private_function is a docstring().',
' ', '',
' :meta private:', ' :meta private:',
' ' '',
] ]

View File

@ -1020,6 +1020,34 @@ Sooper Warning:
actual = str(GoogleDocstring(docstring, testConfig)) actual = str(GoogleDocstring(docstring, testConfig))
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
def test_noindex(self):
docstring = """
Attributes:
arg
description
Methods:
func(i, j)
description
"""
expected = """
.. attribute:: arg
:noindex:
description
.. method:: func(i, j)
:noindex:
description
"""
config = Config()
actual = str(GoogleDocstring(docstring, config=config, app=None, what='module',
options={'noindex': True}))
self.assertEqual(expected, actual)
class NumpyDocstringTest(BaseDocstringTest): class NumpyDocstringTest(BaseDocstringTest):
docstrings = [( docstrings = [(

View File

@ -870,7 +870,7 @@ def test_xml_refs_in_python_domain(app):
assert_elem( assert_elem(
para0[0], para0[0],
['SEE THIS DECORATOR:', 'sensitive_variables()', '.'], ['SEE THIS DECORATOR:', 'sensitive_variables()', '.'],
['sensitive-sensitive-variables']) ['sensitive.sensitive_variables'])
@sphinx_intl @sphinx_intl

View File

@ -103,6 +103,17 @@ def test_nonl_info_log(app, status, warning):
assert 'message1message2\nmessage3' in status.getvalue() assert 'message1message2\nmessage3' in status.getvalue()
def test_once_warning_log(app, status, warning):
logging.setup(app, status, warning)
logger = logging.getLogger(__name__)
logger.warning('message: %d', 1, once=True)
logger.warning('message: %d', 1, once=True)
logger.warning('message: %d', 2, once=True)
assert 'WARNING: message: 1\nWARNING: message: 2\n' in strip_escseq(warning.getvalue())
def test_is_suppressed_warning(): def test_is_suppressed_warning():
suppress_warnings = ["ref", "files.*", "rest.duplicated_labels"] suppress_warnings = ["ref", "files.*", "rest.duplicated_labels"]

View File

@ -189,9 +189,9 @@ def test_clean_astext():
('', '', 'id0'), ('', '', 'id0'),
('term', '', 'term-0'), ('term', '', 'term-0'),
('term', 'Sphinx', 'term-sphinx'), ('term', 'Sphinx', 'term-sphinx'),
('', 'io.StringIO', 'io-stringio'), # contains a dot ('', 'io.StringIO', 'io.stringio'), # contains a dot
('', 'sphinx.setup_command', 'sphinx-setup-command'), # contains a dot ('', 'sphinx.setup_command', 'sphinx.setup_command'), # contains a dot & underscore
('', '_io.StringIO', 'io-stringio'), # starts with underscore ('', '_io.StringIO', 'io.stringio'), # starts with underscore
('', '', 'sphinx'), # alphabets in unicode fullwidth characters ('', '', 'sphinx'), # alphabets in unicode fullwidth characters
('', '悠好', 'id0'), # multibytes text (in Chinese) ('', '悠好', 'id0'), # multibytes text (in Chinese)
('', 'Hello=悠好=こんにちは', 'hello'), # alphabets and multibytes text ('', 'Hello=悠好=こんにちは', 'hello'), # alphabets and multibytes text