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
--------
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
------------
@ -85,6 +106,7 @@ Deprecated
* ``sphinx.domains.std.StandardDomain.add_object()``
* ``sphinx.domains.python.PyDecoratorMixin``
* ``sphinx.ext.autodoc.get_documenters()``
* ``sphinx.ext.autosummary.process_autosummary_toc()``
* ``sphinx.parsers.Parser.app``
* ``sphinx.testing.path.Path.text()``
* ``sphinx.testing.path.Path.bytes()``
@ -112,6 +134,9 @@ Features added
* #6895: py domain: Do not emit nitpicky warnings for built-in types
* py domain: Support lambda functions in function signature
* #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
:py:meth:`.Sphinx.connect()`
* #3077: Implement the scoping for :rst:dir:`productionlist` as indicated
@ -143,6 +168,8 @@ Features added
* Added ``SphinxDirective.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
----------
@ -157,9 +184,11 @@ Bugs fixed
* #7267: autodoc: error message for invalid directive options has wrong location
* #7329: autodoc: info-field-list is wrongly generated from type hints into the
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
* #7139: ``code-block:: guess`` does not work
* #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
``html_link_suffix`` in search results
* #7297: html theme: ``bizstyle`` does not support ``sidebarwidth``
@ -170,9 +199,7 @@ Bugs fixed
* #2377: C, parse function pointers even in complex types.
* #7345: sphinx-build: Sphinx crashes if output directory exists as a file
* #7290: sphinx-build: Ignore bdb.BdbQuit when handling exceptions
Testing
--------
* #6240: napoleon: Attributes and Methods sections ignore :noindex: option
Release 2.4.5 (in development)
==============================
@ -192,6 +219,8 @@ Features added
Bugs fixed
----------
* #7343: Sphinx builds has been slower since 2.4.0 on debug mode
Testing
--------

View File

@ -81,6 +81,11 @@ The following is a list of deprecated interfaces.
- 5.0
- ``sphinx.registry.documenters``
* - ``sphinx.ext.autosummary.process_autosummary_toc()``
- 3.0
- 5.0
- N/A
* - ``sphinx.parsers.Parser.app``
- 3.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
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)::
.. toctree::

View File

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

View File

@ -9,9 +9,11 @@
"""
import argparse
import bdb
import locale
import multiprocessing
import os
import pdb
import sys
import traceback
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
import bdb
if isinstance(exception, bdb.BdbQuit):
return
if args.pdb:
import pdb
print(red(__('Exception occurred while building, starting debugger:')),
file=stderr)
traceback.print_exc()

View File

@ -30,6 +30,7 @@ from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType, Index, IndexEntry
from sphinx.environment import BuildEnvironment
from sphinx.locale import _, __
from sphinx.pycode.ast import ast, parse as ast_parse
from sphinx.roles import XRefRole
from sphinx.util import logging
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:
"""Parse a list of arguments using AST parser"""
params = addnodes.desc_parameterlist(arglist)
@ -89,9 +142,10 @@ def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist:
node += addnodes.desc_sig_name('', param.name)
if param.annotation is not param.empty:
children = _parse_annotation(param.annotation)
node += addnodes.desc_sig_punctuation('', ':')
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.annotation is not param.empty:
node += nodes.Text(' ')
@ -350,7 +404,8 @@ class PyObject(ObjectDescription):
signode += addnodes.desc_parameterlist()
if retann:
signode += addnodes.desc_returns(retann, retann)
children = _parse_annotation(retann)
signode += addnodes.desc_returns(retann, '', *children)
anno = self.options.get('annotation')
if anno:
@ -366,7 +421,7 @@ class PyObject(ObjectDescription):
signode: desc_signature) -> None:
modname = self.options.get('module', self.env.ref_context.get('py:module'))
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)
# 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:
"""Append one line of generated reST to the output."""
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
) -> Tuple[str, List[str]]:
@ -1007,7 +1010,8 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
if self.env.config.autodoc_typehints in ('none', 'description'):
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
return None
try:
@ -1426,7 +1430,8 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
if self.env.config.autodoc_typehints == 'none':
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
return None
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
from sphinx import addnodes
from sphinx.application import Sphinx
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
from sphinx.environment import BuildEnvironment
from sphinx.environment.adapters.toctree import TocTree
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
not generate the toctree:: list.
"""
warnings.warn('process_autosummary_toc() is deprecated',
RemovedInSphinx50Warning, stacklevel=2)
env = app.builder.env
crawled = {}
@ -762,7 +764,6 @@ def setup(app: Sphinx) -> Dict[str, Any]:
texinfo=(autosummary_noop, autosummary_noop))
app.add_directive('autosummary', Autosummary)
app.add_role('autolink', AutoLink())
app.connect('doctree-read', process_autosummary_toc)
app.connect('builder-inited', process_generate_options)
app.add_config_value('autosummary_generate', [], True, [bool])
app.add_config_value('autosummary_generate_overwrite', True, False)

View File

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

View File

@ -9,6 +9,7 @@
"""
import os
from glob import glob
from typing import TYPE_CHECKING
from sphinx.locale import __
@ -55,7 +56,13 @@ class Project:
for filename in get_matching_files(self.srcdir, excludes): # type: ignore
docname = self.path2doc(filename)
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)
else:
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))
assert_node(node[0], cls[1:], xpath=xpath + "[0]", **kwargs)
elif isinstance(cls, tuple):
assert isinstance(node, nodes.Element), \
assert isinstance(node, (list, nodes.Element)), \
'The node%s does not have any items' % xpath
assert 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__:
method = getattr(self, 'visit_%s' % (node_class.__name__), None)
if method:
logger.debug('SphinxTranslator.dispatch_visit calling %s for %s',
method.__name__, node)
method(node)
break
else:
@ -478,8 +476,6 @@ class SphinxTranslator(nodes.NodeVisitor):
for node_class in node.__class__.__mro__:
method = getattr(self, 'depart_%s' % (node_class.__name__), None)
if method:
logger.debug('SphinxTranslator.dispatch_departure calling %s for %s',
method.__name__, node)
method(node)
break
else:

View File

@ -197,6 +197,14 @@ def isabstractmethod(obj: Any) -> bool:
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:
"""Check if the object is an attribute like descriptor."""
if inspect.isdatadescriptor(object):
@ -207,6 +215,9 @@ def isattributedescriptor(obj: Any) -> bool:
if isfunction(obj) or isbuiltin(obj) or inspect.ismethod(obj):
# attribute must not be either function, builtin and method
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):
# attribute must not be a class
return False

View File

@ -117,6 +117,7 @@ class SphinxWarningLogRecord(SphinxLogRecord):
class SphinxLoggerAdapter(logging.LoggerAdapter):
"""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:
if isinstance(level, int):
@ -130,16 +131,9 @@ class SphinxLoggerAdapter(logging.LoggerAdapter):
def process(self, msg: str, kwargs: Dict) -> Tuple[str, Dict]: # type: ignore
extra = kwargs.setdefault('extra', {})
if 'type' in kwargs:
extra['type'] = kwargs.pop('type')
if 'subtype' in kwargs:
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')
for keyword in self.KEYWORDS:
if keyword in kwargs:
extra[keyword] = kwargs.pop(keyword)
return msg, kwargs
@ -445,6 +439,26 @@ class MessagePrefixFilter(logging.Filter):
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):
"""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(WarningLogRecordTranslator(app))
warning_handler.addFilter(WarningIsErrorFilter(app))
warning_handler.addFilter(OnceFilter())
warning_handler.setLevel(logging.WARNING)
warning_handler.setFormatter(ColorizeFormatter())

View File

@ -9,6 +9,7 @@
"""
import re
import unicodedata
import warnings
from typing import Any, Callable, Iterable, List, Set, Tuple, Type
from typing import TYPE_CHECKING, cast
@ -434,6 +435,79 @@ def inline_all_toctrees(builder: "Builder", docnameset: Set[str], docname: str,
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,
prefix: str = '', term: str = None) -> str:
"""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*
if prefix and term:
node_id = nodes.make_id(idformat % term)
node_id = _make_id(idformat % term)
if node_id == prefix:
# *term* is not good to generate a node_id.
node_id = None
elif term:
node_id = nodes.make_id(term)
node_id = _make_id(term)
if node_id == '':
node_id = None # fallback to None

View File

@ -11,6 +11,7 @@
import copy
import os
import posixpath
import re
import warnings
from typing import Any, Iterable, Tuple
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
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):
# override embed-stylesheet default value to 0.
@ -596,11 +610,10 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
if 'height' in node:
atts['height'] = node['height']
if 'scale' in node:
scale = node['scale'] / 100.0
if 'width' in atts:
atts['width'] = int(atts['width']) * scale
atts['width'] = multiply_length(atts['width'], node['scale'])
if 'height' in atts:
atts['height'] = int(atts['height']) * scale
atts['height'] = multiply_length(atts['height'], node['scale'])
atts['alt'] = node.get('alt', uri)
if 'align' in node:
atts['class'] = 'align-%s' % node['align']

View File

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

View File

@ -175,9 +175,9 @@ def test_html4_output(app, status, warning):
r'-| |-'),
],
'autodoc.html': [
(".//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'kwds'),
(".//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'kwds'),
(".//dd/p", r'Return spam\.'),
],
'extapi.html': [
@ -222,7 +222,7 @@ def test_html4_output(app, status, warning):
"[@class='reference internal']/code/span[@class='pre']", 'HOME'),
(".//a[@href='#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$'),
(".//a[@href='#some-label'][@class='reference internal']/span", '^here$'),
(".//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'),
# a production list
(".//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
(".//p", 'A global substitution.'),
(".//p", 'In HTML.'),
@ -262,7 +262,7 @@ def test_html4_output(app, status, warning):
(".//p", 'Always present'),
# tests for ``any`` role
(".//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
(".//a[@href='#id1'][@class='reference internal']/span", 'Testing various markup'),
# tests for smartypants
@ -274,18 +274,18 @@ def test_html4_output(app, status, warning):
(".//p", 'Il dit : « Cest “super” ! »'),
],
'objects.html': [
(".//dt[@id='mod-cls-meth1']", ''),
(".//dt[@id='errmod-error']", ''),
(".//dt[@id='mod.cls.meth1']", ''),
(".//dt[@id='errmod.error']", ''),
(".//dt/code", r'long\(parameter,\s* list\)'),
(".//dt/code", 'another one'),
(".//a[@href='#mod-cls'][@class='reference internal']", ''),
(".//a[@href='#mod.cls'][@class='reference internal']", ''),
(".//dl[@class='std userdesc']", ''),
(".//dt[@id='userdesc-myobj']", ''),
(".//a[@href='#userdesc-myobj'][@class='reference internal']", ''),
# docfields
(".//a[@class='reference internal'][@href='#timeint']/em", 'TimeInt'),
(".//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
(".//span[@class='pre']", 'CFunction()'),
(".//a[@href='#c.Sphinx_DoSomething']", ''),
@ -324,7 +324,7 @@ def test_html4_output(app, status, warning):
'\\+p'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-objc']/code/span",
'--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'),
(".//a[@class='reference internal'][@href='#cmdoption-perl-arg-create-auth-token']"
"/code/span",

View File

@ -123,25 +123,25 @@ def test_domain_js_find_obj(app, status, warning):
('NestedParentA', ('roles', 'nestedparenta', 'class')))
assert (find_obj(None, None, 'NestedParentA.NestedChildA', 'class') ==
('NestedParentA.NestedChildA',
('roles', 'nestedparenta-nestedchilda', 'class')))
('roles', 'nestedparenta.nestedchilda', 'class')))
assert (find_obj(None, 'NestedParentA', 'NestedChildA', 'class') ==
('NestedParentA.NestedChildA',
('roles', 'nestedparenta-nestedchilda', 'class')))
('roles', 'nestedparenta.nestedchilda', 'class')))
assert (find_obj(None, None, 'NestedParentA.NestedChildA.subchild_1', 'func') ==
('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') ==
('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') ==
('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') ==
('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') ==
('module_a.submodule',
('module', 'module-module-a-submodule', 'module')))
('module', 'module-module_a.submodule', 'module')))
def test_get_full_qualified_name():

View File

@ -18,11 +18,11 @@ from sphinx import addnodes
from sphinx.addnodes import (
desc, desc_addname, desc_annotation, desc_content, desc_name, desc_optional,
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.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.util import assert_node
@ -78,7 +78,7 @@ def test_domain_py_xrefs(app, status, warning):
assert_node(node, **attributes)
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[1], None, None, 'top_level', '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
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,
'ModTopLevel', 'class')
assert_refnode(refnodes[1], 'module_a.submodule', 'ModTopLevel',
@ -125,7 +125,7 @@ def test_domain_py_xrefs(app, status, warning):
assert len(refnodes) == 16
doctree = app.env.get_doctree('module_option')
refnodes = list(doctree.traverse(addnodes.pending_xref))
refnodes = list(doctree.traverse(pending_xref))
print(refnodes)
print(refnodes[0])
print(refnodes[1])
@ -171,11 +171,11 @@ def test_resolve_xref_for_properties(app, status, warning):
app.builder.build_all()
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">'
'<code class="xref py py-attr docutils literal notranslate"><span class="pre">'
'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">'
'<code class="xref py py-meth docutils literal notranslate"><span class="pre">'
'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') ==
[('NestedParentA', ('roles', 'nestedparenta', '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') ==
[('NestedParentA.NestedChildA', ('roles', 'nestedparenta-nestedchilda', 'class'))])
[('NestedParentA.NestedChildA', ('roles', 'nestedparenta.nestedchilda', 'class'))])
assert (find_obj(None, None, 'NestedParentA.NestedChildA.subchild_1', 'meth') ==
[('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') ==
[('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') ==
[('NestedParentA.NestedChildA.subchild_1',
('roles', 'nestedparenta-nestedchilda-subchild-1', 'method'))])
('roles', 'nestedparenta.nestedchilda.subchild_1', 'method'))])
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'
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):
text = ".. py:function:: hello(name: str) -> str"
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_name, "hello"],
desc_parameterlist,
[desc_returns, "str"])],
[desc_returns, pending_xref, "str"])],
desc_content)]))
assert_node(doctree[1], addnodes.desc, desctype="function",
domain="py", objtype="function", noindex=False)
@ -250,7 +281,7 @@ def test_pyfunction_signature(app):
[desc_parameterlist, desc_parameter, ([desc_sig_name, "name"],
[desc_sig_punctuation, ":"],
" ",
[nodes.inline, "str"])])
[nodes.inline, pending_xref, "str"])])
def test_pyfunction_signature_full(app):
@ -260,7 +291,7 @@ def test_pyfunction_signature_full(app):
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_name, "hello"],
desc_parameterlist,
[desc_returns, "str"])],
[desc_returns, pending_xref, "str"])],
desc_content)]))
assert_node(doctree[1], addnodes.desc, desctype="function",
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_sig_punctuation, ":"],
" ",
[desc_sig_name, "str"])],
[desc_sig_name, pending_xref, "str"])],
[desc_parameter, ([desc_sig_name, "b"],
[desc_sig_operator, "="],
[nodes.inline, "1"])],
@ -276,11 +307,11 @@ def test_pyfunction_signature_full(app):
[desc_sig_name, "args"],
[desc_sig_punctuation, ":"],
" ",
[desc_sig_name, "str"])],
[desc_sig_name, pending_xref, "str"])],
[desc_parameter, ([desc_sig_name, "c"],
[desc_sig_punctuation, ":"],
" ",
[desc_sig_name, "bool"],
[desc_sig_name, pending_xref, "bool"],
" ",
[desc_sig_operator, "="],
" ",
@ -289,7 +320,7 @@ def test_pyfunction_signature_full(app):
[desc_sig_name, "kwargs"],
[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.')
@ -340,7 +371,7 @@ def test_optional_pyfunction_signature(app):
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_name, "compile"],
desc_parameterlist,
[desc_returns, "ast object"])],
[desc_returns, pending_xref, "ast object"])],
desc_content)]))
assert_node(doctree[1], addnodes.desc, desctype="function",
domain="py", objtype="function", noindex=False)
@ -483,61 +514,61 @@ def test_pymethod_options(app):
# method
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"],
[desc_parameterlist, ()])],
[desc_content, ()]))
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:
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 "],
[desc_name, "meth2"],
[desc_parameterlist, ()])],
[desc_content, ()]))
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:
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 "],
[desc_name, "meth3"],
[desc_parameterlist, ()])],
[desc_content, ()]))
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:
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 "],
[desc_name, "meth4"],
[desc_parameterlist, ()])],
[desc_content, ()]))
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:
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 "],
[desc_name, "meth5"])],
[desc_content, ()]))
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:
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 "],
[desc_name, "meth6"],
[desc_parameterlist, ()])],
[desc_content, ()]))
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):
@ -552,13 +583,13 @@ def test_pyclassmethod(app):
[desc_content, (addnodes.index,
desc)])]))
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 "],
[desc_name, "meth"],
[desc_parameterlist, ()])],
[desc_content, ()]))
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):
@ -573,13 +604,13 @@ def test_pystaticmethod(app):
[desc_content, (addnodes.index,
desc)])]))
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 "],
[desc_name, "meth"],
[desc_parameterlist, ()])],
[desc_content, ()]))
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):
@ -596,13 +627,13 @@ def test_pyattribute(app):
[desc_content, (addnodes.index,
desc)])]))
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"],
[desc_annotation, ": str"],
[desc_annotation, " = ''"])],
[desc_content, ()]))
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):
@ -648,10 +679,10 @@ def test_module_index(app):
assert index.generate() == (
[('d', [IndexEntry('docutils', 0, 'index', 'module-docutils', '', '', '')]),
('s', [IndexEntry('sphinx', 1, 'index', 'module-sphinx', '', '', ''),
IndexEntry('sphinx.builders', 2, 'index', 'module-sphinx-builders', '', '', ''), # NOQA
IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx-builders-html', '', '', ''), # NOQA
IndexEntry('sphinx.config', 2, 'index', 'module-sphinx-config', '', '', ''),
IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx-intl', '', '', '')])],
IndexEntry('sphinx.builders', 2, 'index', 'module-sphinx.builders', '', '', ''), # NOQA
IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx.builders.html', '', '', ''), # NOQA
IndexEntry('sphinx.config', 2, 'index', 'module-sphinx.config', '', '', ''),
IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx_intl', '', '', '')])],
False
)
@ -663,7 +694,7 @@ def test_module_index_submodule(app):
index = PythonModuleIndex(app.env.get_domain('py'))
assert index.generate() == (
[('s', [IndexEntry('sphinx', 1, '', '', '', '', ''),
IndexEntry('sphinx.config', 2, 'index', 'module-sphinx-config', '', '', '')])],
IndexEntry('sphinx.config', 2, 'index', 'module-sphinx.config', '', '', '')])],
False
)
@ -692,12 +723,12 @@ def test_modindex_common_prefix(app):
restructuredtext.parse(app, text)
index = PythonModuleIndex(app.env.get_domain('py'))
assert index.generate() == (
[('b', [IndexEntry('sphinx.builders', 1, 'index', 'module-sphinx-builders', '', '', ''), # NOQA
IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx-builders-html', '', '', '')]), # NOQA
('c', [IndexEntry('sphinx.config', 0, 'index', 'module-sphinx-config', '', '', '')]),
[('b', [IndexEntry('sphinx.builders', 1, 'index', 'module-sphinx.builders', '', '', ''), # NOQA
IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx.builders.html', '', '', '')]), # NOQA
('c', [IndexEntry('sphinx.config', 0, 'index', 'module-sphinx.config', '', '', '')]),
('d', [IndexEntry('docutils', 0, 'index', 'module-docutils', '', '', '')]),
('s', [IndexEntry('sphinx', 0, 'index', 'module-sphinx', '', '', ''),
IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx-intl', '', '', '')])],
IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx_intl', '', '', '')])],
True
)

View File

@ -84,7 +84,7 @@ def test_object_inventory(app):
refs = app.env.domaindata['py']['objects']
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 'mod.func_in_module' in refs
assert 'mod.Cls' in refs

View File

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

View File

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

View File

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

View File

@ -1020,6 +1020,34 @@ Sooper Warning:
actual = str(GoogleDocstring(docstring, testConfig))
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):
docstrings = [(

View File

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

View File

@ -103,6 +103,17 @@ def test_nonl_info_log(app, status, warning):
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():
suppress_warnings = ["ref", "files.*", "rest.duplicated_labels"]

View File

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