summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES7
-rw-r--r--doc/extdev/deprecated.rst5
-rw-r--r--doc/usage/restructuredtext/directives.rst6
-rw-r--r--setup.py1
-rw-r--r--sphinx/domains/python.py59
-rw-r--r--sphinx/ext/autodoc/__init__.py11
-rw-r--r--sphinx/ext/autosummary/__init__.py5
-rw-r--r--sphinx/ext/napoleon/docstring.py8
-rw-r--r--sphinx/project.py9
-rw-r--r--sphinx/testing/util.py2
-rw-r--r--sphinx/util/inspect.py11
-rw-r--r--sphinx/util/logging.py35
-rw-r--r--sphinx/writers/html.py19
-rw-r--r--sphinx/writers/html5.py19
-rw-r--r--tests/roots/test-ext-autodoc/target/cython.pyx12
-rw-r--r--tests/test_autodoc.py358
-rw-r--r--tests/test_domain_py.py57
-rw-r--r--tests/test_ext_autodoc_configs.py166
-rw-r--r--tests/test_ext_autodoc_events.py6
-rw-r--r--tests/test_ext_autodoc_private_members.py4
-rw-r--r--tests/test_ext_napoleon_docstring.py28
-rw-r--r--tests/test_util_logging.py11
22 files changed, 552 insertions, 287 deletions
diff --git a/CHANGES b/CHANGES
index ecba1c5e5..d3484f78c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -54,6 +54,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()``
@@ -83,6 +84,7 @@ Features added
* #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
@@ -114,6 +116,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
----------
@@ -128,9 +132,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``
@@ -141,6 +147,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
+* #6240: napoleon: Attributes and Methods sections ignore :noindex: option
Testing
--------
diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst
index dc8f9e7b2..9551f8613 100644
--- a/doc/extdev/deprecated.rst
+++ b/doc/extdev/deprecated.rst
@@ -51,6 +51,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
diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst
index b5b0a2980..7b9bd2f80 100644
--- a/doc/usage/restructuredtext/directives.rst
+++ b/doc/usage/restructuredtext/directives.rst
@@ -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::
diff --git a/setup.py b/setup.py
index 088d5b8e7..a427d5493 100644
--- a/setup.py
+++ b/setup.py
@@ -52,6 +52,7 @@ extras_require = {
'pytest-cov',
'html5lib',
'typed_ast', # for py35-37
+ 'cython',
],
}
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index 262347b55..fae1991c7 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -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
@@ -67,6 +68,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)
@@ -93,9 +146,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(' ')
@@ -354,7 +408,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:
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index 0832aeb3e..7b6b3e4aa 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -270,7 +270,10 @@ class Documenter:
def add_line(self, line: str, source: str, *lineno: int) -> None:
"""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
) -> Tuple[str, List[str]]:
@@ -1008,7 +1011,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:
@@ -1427,7 +1431,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):
diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py
index f79b7feb4..9c550c622 100644
--- a/sphinx/ext/autosummary/__init__.py
+++ b/sphinx/ext/autosummary/__init__.py
@@ -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
@@ -110,6 +110,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 = {}
@@ -766,7 +768,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)
diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py
index 7f6ebe478..820de6ee4 100644
--- a/sphinx/ext/napoleon/docstring.py
+++ b/sphinx/ext/napoleon/docstring.py
@@ -583,7 +583,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:
@@ -641,6 +645,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('')
diff --git a/sphinx/project.py b/sphinx/project.py
index d63af1fcb..f4afdadad 100644
--- a/sphinx/project.py
+++ b/sphinx/project.py
@@ -9,6 +9,7 @@
"""
import os
+from glob import glob
from sphinx.locale import __
from sphinx.util import get_matching_files
@@ -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)
diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py
index 75cd0f411..450241f55 100644
--- a/sphinx/testing/util.py
+++ b/sphinx/testing/util.py
@@ -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))
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index d22df7656..855b11d83 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -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
diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py
index a2dee807d..fbf161ec0 100644
--- a/sphinx/util/logging.py
+++ b/sphinx/util/logging.py
@@ -118,6 +118,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):
@@ -131,16 +132,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
@@ -446,6 +440,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
@@ -563,6 +577,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())
diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py
index e74c0334f..85eeb4376 100644
--- a/sphinx/writers/html.py
+++ b/sphinx/writers/html.py
@@ -11,6 +11,7 @@
import copy
import os
import posixpath
+import re
import warnings
from typing import Any, Iterable, Tuple
from typing import cast
@@ -38,6 +39,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.
@@ -597,11 +611,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']
diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py
index 0c00a1fa4..f94dd60a3 100644
--- a/sphinx/writers/html5.py
+++ b/sphinx/writers/html5.py
@@ -10,6 +10,7 @@
import os
import posixpath
+import re
import warnings
from typing import Any, Iterable, Tuple
from typing import 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 HTML5Translator(SphinxTranslator, BaseTranslator):
"""
Our custom HTML translator.
@@ -538,11 +552,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']
diff --git a/tests/roots/test-ext-autodoc/target/cython.pyx b/tests/roots/test-ext-autodoc/target/cython.pyx
new file mode 100644
index 000000000..1457db3c9
--- /dev/null
+++ b/tests/roots/test-ext-autodoc/target/cython.pyx
@@ -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
diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py
index b6323a46f..741c4bb60 100644
--- a/tests/test_autodoc.py
+++ b/tests/test_autodoc.py
@@ -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.',
+ '',
]
diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py
index 3a0ce1c68..e4bc17004 100644
--- a/tests/test_domain_py.py
+++ b/tests/test_domain_py.py
@@ -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])
@@ -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)
diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py
index e250b21b3..f351d0e4b 100644
--- a/tests/test_ext_autodoc_configs.py
+++ b/tests/test_ext_autodoc_configs.py
@@ -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',
diff --git a/tests/test_ext_autodoc_events.py b/tests/test_ext_autodoc_events.py
index 91fc19630..106c5793a 100644
--- a/tests/test_ext_autodoc_events.py
+++ b/tests/test_ext_autodoc_events.py
@@ -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',
- ' '
+ '',
]
diff --git a/tests/test_ext_autodoc_private_members.py b/tests/test_ext_autodoc_private_members.py
index e8f3e53ef..2d9208b41 100644
--- a/tests/test_ext_autodoc_private_members.py
+++ b/tests/test_ext_autodoc_private_members.py
@@ -40,7 +40,7 @@ def test_private_field_and_private_members(app):
' :module: target.private',
'',
' private_function is a docstring().',
- ' ',
+ '',
' :meta private:',
- ' '
+ '',
]
diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py
index 2ce754eff..160079a50 100644
--- a/tests/test_ext_napoleon_docstring.py
+++ b/tests/test_ext_napoleon_docstring.py
@@ -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 = [(
diff --git a/tests/test_util_logging.py b/tests/test_util_logging.py
index 85646112d..337782d87 100644
--- a/tests/test_util_logging.py
+++ b/tests/test_util_logging.py
@@ -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"]