Merge branch '3.x'

This commit is contained in:
Takeshi KOMIYA 2020-08-08 18:16:59 +09:00
commit a8927bcd3e
39 changed files with 789 additions and 261 deletions

88
CHANGES
View File

@ -42,7 +42,7 @@ Bugs fixed
Testing
--------
Release 3.2.0 (in development)
Release 3.3.0 (in development)
==============================
Dependencies
@ -54,6 +54,44 @@ Incompatible changes
Deprecated
----------
Features added
--------------
Bugs fixed
----------
Testing
--------
Release 3.2.1 (in development)
==============================
Dependencies
------------
Incompatible changes
--------------------
Deprecated
----------
Features added
--------------
Bugs fixed
----------
Testing
--------
Release 3.2.0 (released Aug 08, 2020)
=====================================
Deprecated
----------
* ``sphinx.ext.autodoc.members_set_option()``
* ``sphinx.ext.autodoc.merge_special_members_option()``
* ``sphinx.writers.texinfo.TexinfoWriter.desc``
* C, parsing of pre-v3 style type directives and roles, along with the options
:confval:`c_allow_pre_v3` and :confval:`c_warn_on_allowed_pre_v3`.
@ -62,14 +100,20 @@ Features added
--------------
* #2076: autodoc: Allow overriding of exclude-members in skip-member function
* #8034: autodoc: ``:private-member:`` can take an explicit list of member names
to be documented
* #2024: autosummary: Add :confval:`autosummary_filename_map` to avoid conflict
of filenames between two object with different case
* #8011: autosummary: Support instance attributes as a target of autosummary
directive
* #7849: html: Add :confval:`html_codeblock_linenos_style` to change the style
of line numbers for code-blocks
* #7853: C and C++, support parameterized GNU style attributes.
* #7888: napoleon: Add aliases Warn and Raise.
* #7690: napoleon: parse type strings and make them hyperlinks as possible. The
conversion rule can be updated via :confval:`napoleon_type_aliases`
* #8049: napoleon: Create a hyperlink for each the type of parameter when
:confval:`napoleon_use_params` is False
* C, added :rst:dir:`c:alias` directive for inserting copies
of existing declarations.
* #7745: html: inventory is broken if the docname contains a space
@ -77,8 +121,11 @@ Features added
* #7902: html theme: Add a new option :confval:`globaltoc_maxdepth` to control
the behavior of globaltoc in sidebar
* #7840: i18n: Optimize the dependencies check on bootstrap
* #7768: i18n: :confval:`figure_language_filename` supports ``docpath`` token
* #5208: linkcheck: Support checks for local links
* #5090: setuptools: Link verbosity to distutils' -v and -q option
* #6698: doctest: Add ``:trim-doctest-flags:`` and ``:no-trim-doctest-flags:``
options to doctest, testcode and testoutput directives
* #7052: add ``:noindexentry:`` to the Python, C, C++, and Javascript domains.
Update the documentation to better reflect the relationship between this option
and the ``:noindex:`` option.
@ -89,6 +136,7 @@ Features added
:confval:`c_warn_on_allowed_pre_v3`` to ``True``.
The functionality is immediately deprecated.
* #7999: C, add support for named variadic macro arguments.
* #8071: Allow to suppress "self referenced toctrees" warning
Bugs fixed
----------
@ -100,6 +148,12 @@ Bugs fixed
* #904: autodoc: An instance attribute cause a crash of autofunction directive
* #1362: autodoc: ``private-members`` option does not work for class attributes
* #7983: autodoc: Generator type annotation is wrongly rendered in py36
* #8030: autodoc: An uninitialized annotated instance variable is not documented
when ``:inherited-members:`` option given
* #8032: autodoc: A type hint for the instance variable defined at parent class
is not shown in the document of the derived class
* #8041: autodoc: An annotated instance variable on super class is not
documented when derived class has other annotated instance variables
* #7839: autosummary: cannot handle umlauts in function names
* #7865: autosummary: Failed to extract summary line when abbreviations found
* #7866: autosummary: Failed to extract correct summary line when docstring
@ -108,6 +162,9 @@ Bugs fixed
* #7940: apidoc: An extra newline is generated at the end of the rst file if a
module has submodules
* #4258: napoleon: decorated special methods are not shown
* #7799: napoleon: parameters are not escaped for combined params in numpydoc
* #7780: napoleon: multiple paramaters declaration in numpydoc was wrongly
recognized when napoleon_use_params=True
* #7715: LaTeX: ``numfig_secnum_depth > 1`` leads to wrong figure links
* #7846: html theme: XML-invalid files were generated
* #7894: gettext: Wrong source info is shown when using rst_epilog
@ -125,39 +182,18 @@ Bugs fixed
* #7993: texinfo: a warning not supporting desc_signature_line node is shown
* #7869: :rst:role:`abbr` role without an explanation will show the explanation
from the previous abbr role
* #8048: graphviz: graphviz.css was copied on building non-HTML document
* C and C++, removed ``noindex`` directive option as it did
nothing.
* #7619: Duplicated node IDs are generated if node has multiple IDs
* #2050: Symbols sections are appeared twice in the index page
* #8017: Fix circular import in sphinx.addnodes
Testing
--------
Release 3.1.3 (in development)
==============================
Dependencies
------------
Incompatible changes
--------------------
Deprecated
----------
Features added
--------------
Bugs fixed
----------
* #7986: CSS: make "highlight" selector more robust
* #7944: C++, parse non-type template parameters starting with
a dependent qualified name.
* C, don't deepcopy the entire symbol table and make a mess every time an
enumerator is handled.
Testing
--------
Release 3.1.2 (released Jul 05, 2020)
=====================================

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -12,5 +12,5 @@ SphinxProject -r-> .rst
.pot -r-> .po : Pootle
.po -d-> .mo : msgfmt
.mo -l-> TranslatedBuild
.rst -d-> TranslatedBuild : "sphinx-buid -Dlanguage="
.rst -d-> TranslatedBuild : "sphinx-build -Dlanguage="
@enduml

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -56,6 +56,16 @@ The following is a list of deprecated interfaces.
- 6.0
- ``docutils.utils.smartyquotes``
* - ``sphinx.ext.autodoc.members_set_option()``
- 3.2
- 5.0
- N/A
* - ``sphinx.ext.autodoc.merge_special_members_option()``
- 3.2
- 5.0
- ``sphinx.ext.autodoc.merge_members_option()``
* - ``sphinx.writers.texinfo.TexinfoWriter.desc``
- 3.2
- 5.0

View File

@ -20,7 +20,7 @@ Options
.. option:: -q, --quiet
Quiet mode that will skip interactive wizard to specify options.
Quiet mode that skips the interactive wizard for specifying options.
This option requires `-p`, `-a` and `-v` options.
.. option:: -h, --help, --version

View File

@ -7,7 +7,7 @@ Templating
==========
Sphinx uses the `Jinja <http://jinja.pocoo.org>`_ templating engine for its HTML
templates. Jinja is a text-based engine, and inspired by Django templates, so
templates. Jinja is a text-based engine, inspired by Django templates, so
anyone having used Django will already be familiar with it. It also has
excellent documentation for those who need to make themselves familiar with it.

View File

@ -6,8 +6,8 @@ Internationalization
.. versionadded:: 1.1
Complementary to translations provided for Sphinx-generated messages such as
navigation bars, Sphinx provides mechanisms facilitating *document* translations
in itself. See the :ref:`intl-options` for details on configuration.
navigation bars, Sphinx provides mechanisms facilitating the translation of
*documents*. See the :ref:`intl-options` for details on configuration.
.. figure:: /_static/translation.*
:width: 100%

View File

@ -823,6 +823,8 @@ documentation on :ref:`intl` for details.
extension, e.g. ``dirname/filename``
* ``{path}`` - the directory path component of the filename, with a trailing
slash if non-empty, e.g. ``dirname/``
* ``{docpath}`` - the directory path component for the current document, with
a trailing slash if non-empty.
* ``{basename}`` - the filename without the directory path or file extension
components, e.g. ``filename``
* ``{ext}`` - the file extension, e.g. ``.png``
@ -836,6 +838,9 @@ documentation on :ref:`intl` for details.
.. versionchanged:: 1.5
Added ``{path}`` and ``{basename}`` tokens.
.. versionchanged:: 3.2
Added ``{docpath}`` token.
.. _math-options:
@ -915,7 +920,7 @@ that use Sphinx's HTMLWriter class.
.. confval:: html_short_title
A shorter "title" for the HTML docs. This is used in for links in the
A shorter "title" for the HTML docs. This is used for links in the
header and in the HTML Help docs. If not given, it defaults to the value of
:confval:`html_title`.

View File

@ -136,9 +136,22 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
:undoc-members:
* "Private" members (that is, those named like ``_private`` or ``__private``)
will be included if the ``private-members`` flag option is given.
will be included if the ``private-members`` flag option is given::
.. automodule:: noodle
:members:
:private-members:
It can also take an explicit list of member names to be documented as
arguments::
.. automodule:: noodle
:members:
:private-members: _spicy, _garlickly
.. versionadded:: 1.1
.. versionchanged:: 3.2
The option can now take arguments.
* autodoc considers a member private if its docstring contains
``:meta private:`` in its :ref:`info-field-lists`.

View File

@ -67,7 +67,7 @@ a comma-separated list of group names.
default set of flags is specified by the :confval:`doctest_default_flags`
configuration variable.
This directive supports three options:
This directive supports five options:
* ``hide``, a flag option, hides the doctest block in other builders. By
default it is shown as a highlighted doctest block.
@ -102,6 +102,11 @@ a comma-separated list of group names.
Supported PEP-440 operands and notations
* ``trim-doctest-flags`` and ``no-trim-doctest-flags``, a flag option,
doctest flags (comments looking like ``# doctest: FLAG, ...``) at the
ends of lines and ``<BLANKLINE>`` markers are removed (or not removed)
individually. Default is ``trim-doctest-flags``.
Note that like with standard doctests, you have to use ``<BLANKLINE>`` to
signal a blank line in the expected output. The ``<BLANKLINE>`` is removed
when building presentation output (HTML, LaTeX etc.).
@ -119,11 +124,16 @@ a comma-separated list of group names.
A code block for a code-output-style test.
This directive supports one option:
This directive supports three options:
* ``hide``, a flag option, hides the code block in other builders. By
default it is shown as a highlighted code block.
* ``trim-doctest-flags`` and ``no-trim-doctest-flags``, a flag option,
doctest flags (comments looking like ``# doctest: FLAG, ...``) at the
ends of lines and ``<BLANKLINE>`` markers are removed (or not removed)
individually. Default is ``trim-doctest-flags``.
.. note::
Code in a ``testcode`` block is always executed all at once, no matter how
@ -149,7 +159,7 @@ a comma-separated list of group names.
The corresponding output, or the exception message, for the last
:rst:dir:`testcode` block.
This directive supports two options:
This directive supports four options:
* ``hide``, a flag option, hides the output block in other builders. By
default it is shown as a literal block without highlighting.
@ -157,6 +167,11 @@ a comma-separated list of group names.
* ``options``, a string option, can be used to give doctest flags
(comma-separated) just like in normal doctest blocks.
* ``trim-doctest-flags`` and ``no-trim-doctest-flags``, a flag option,
doctest flags (comments looking like ``# doctest: FLAG, ...``) at the
ends of lines and ``<BLANKLINE>`` markers are removed (or not removed)
individually. Default is ``trim-doctest-flags``.
Example::
.. testcode::

View File

@ -114,9 +114,9 @@ tables of contents. The ``toctree`` directive is the central element.
**Additional options**
You can use ``caption`` option to provide a toctree caption and you can use
``name`` option to provide implicit target name that can be referenced by
using :rst:role:`ref`::
You can use the ``caption`` option to provide a toctree caption and you can
use the ``name`` option to provide an implicit target name that can be
referenced by using :rst:role:`ref`::
.. toctree::
:caption: Table of Contents
@ -246,7 +246,7 @@ The special document names (and pages generated for them) are:
* every name beginning with ``_``
Though only few such names are currently used by Sphinx, you should not
Though few such names are currently used by Sphinx, you should not
create documents or document-containing directories with such names. (Using
``_`` as a prefix for a custom template directory is fine.)

View File

@ -348,7 +348,7 @@ Third Party Themes
There are many third-party themes available. Some of these are general use,
while others are specific to an individual project. A section of third-party
themes is listed below. Many more can be found on PyPI__, GitHub__ and
themes is listed below. Many more can be found on PyPI__, GitHub__, GitLab__ and
sphinx-themes.org__.
.. cssclass:: clear
@ -367,4 +367,5 @@ sphinx-themes.org__.
.. __: https://pypi.org/search/?q=&o=&c=Framework+%3A%3A+Sphinx+%3A%3A+Theme
.. __: https://github.com/search?utf8=%E2%9C%93&q=sphinx+theme&type=
.. __: https://gitlab.com/explore?name=sphinx+theme
.. __: https://sphinx-themes.org/

View File

@ -6250,23 +6250,18 @@ class DefinitionParser(BaseParser):
# ==========================================================================
def _parse_template_parameter_list(self) -> ASTTemplateParams:
# only: '<' parameter-list '>'
# we assume that 'template' has just been parsed
templateParams = [] # type: List[ASTTemplateParam]
self.skip_ws()
if not self.skip_string("<"):
self.fail("Expected '<' after 'template'")
prevErrors = []
while 1:
self.skip_ws()
if self.skip_word('template'):
# declare a tenplate template parameter
nestedParams = self._parse_template_parameter_list()
else:
nestedParams = None
self.skip_ws()
def _parse_template_paramter(self) -> ASTTemplateParam:
if self.skip_word('template'):
# declare a tenplate template parameter
nestedParams = self._parse_template_parameter_list()
else:
nestedParams = None
pos = self.pos
try:
# Unconstrained type parameter or template type parameter
key = None
self.skip_ws()
if self.skip_word_and_ws('typename'):
key = 'typename'
elif self.skip_word_and_ws('class'):
@ -6274,52 +6269,79 @@ class DefinitionParser(BaseParser):
elif nestedParams:
self.fail("Expected 'typename' or 'class' after "
"template template parameter list.")
if key:
# declare a type or template type parameter
self.skip_ws()
parameterPack = self.skip_string('...')
self.skip_ws()
if self.match(identifier_re):
identifier = ASTIdentifier(self.matched_text)
else:
identifier = None
self.skip_ws()
if not parameterPack and self.skip_string('='):
default = self._parse_type(named=False, outer=None)
else:
default = None
data = ASTTemplateKeyParamPackIdDefault(key, identifier,
parameterPack, default)
if nestedParams:
# template type
templateParams.append(
ASTTemplateParamTemplateType(nestedParams, data))
else:
# type
templateParams.append(ASTTemplateParamType(data))
else:
# declare a non-type parameter, or constrained type parameter
pos = self.pos
try:
param = self._parse_type_with_init('maybe', 'templateParam')
templateParams.append(ASTTemplateParamNonType(param))
except DefinitionError as e:
msg = "If non-type template parameter or constrained template parameter"
prevErrors.append((e, msg))
self.pos = pos
self.fail("Expected 'typename' or 'class' in tbe "
"beginning of template type parameter.")
self.skip_ws()
parameterPack = self.skip_string('...')
self.skip_ws()
if self.match(identifier_re):
identifier = ASTIdentifier(self.matched_text)
else:
identifier = None
self.skip_ws()
if not parameterPack and self.skip_string('='):
default = self._parse_type(named=False, outer=None)
else:
default = None
if self.current_char not in ',>':
self.fail('Expected "," or ">" after (template) type parameter.')
data = ASTTemplateKeyParamPackIdDefault(key, identifier,
parameterPack, default)
if nestedParams:
return ASTTemplateParamTemplateType(nestedParams, data)
else:
return ASTTemplateParamType(data)
except DefinitionError as eType:
if nestedParams:
raise
try:
# non-type parameter or constrained type parameter
self.pos = pos
param = self._parse_type_with_init('maybe', 'templateParam')
return ASTTemplateParamNonType(param)
except DefinitionError as eNonType:
self.pos = pos
header = "Error when parsing template parameter."
errs = []
errs.append(
(eType, "If unconstrained type parameter or template type parameter"))
errs.append(
(eNonType, "If constrained type parameter or non-type parameter"))
raise self._make_multi_error(errs, header)
def _parse_template_parameter_list(self) -> ASTTemplateParams:
# only: '<' parameter-list '>'
# we assume that 'template' has just been parsed
templateParams = [] # type: List[ASTTemplateParam]
self.skip_ws()
if not self.skip_string("<"):
self.fail("Expected '<' after 'template'")
while 1:
pos = self.pos
err = None
try:
param = self._parse_template_paramter()
templateParams.append(param)
except DefinitionError as eParam:
self.pos = pos
err = eParam
self.skip_ws()
if self.skip_string('>'):
return ASTTemplateParams(templateParams)
elif self.skip_string(','):
prevErrors = []
continue
else:
header = "Error in template parameter list."
errs = []
if err:
errs.append((err, "If parameter"))
try:
self.fail('Expected "=", ",", or ">".')
self.fail('Expected "," or ">".')
except DefinitionError as e:
prevErrors.append((e, ""))
raise self._make_multi_error(prevErrors, header)
errs.append((e, "If no parameter"))
print(errs)
raise self._make_multi_error(errs, header)
def _parse_template_introduction(self) -> ASTTemplateIntroduction:
pos = self.pos

View File

@ -584,7 +584,9 @@ class BuildEnvironment:
def traverse_toctree(parent: str, docname: str) -> Iterator[Tuple[str, str]]:
if parent == docname:
logger.warning(__('self referenced toctree found. Ignored.'), location=docname)
logger.warning(__('self referenced toctree found. Ignored.'),
location=docname, type='toc',
subtype='circular')
return
# traverse toctree by pre-order

View File

@ -18,7 +18,7 @@ from types import ModuleType
from typing import (
Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union
)
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, get_type_hints
from docutils.statemachine import StringList
@ -60,13 +60,29 @@ py_ext_sig_re = re.compile(
(?:\s* -> \s* (.*))? # return annotation
)? $ # and nothing more
''', re.VERBOSE)
special_member_re = re.compile(r'^__\S+__$')
def identity(x: Any) -> Any:
return x
ALL = object()
class _All:
"""A special value for :*-members: that matches to any member."""
def __contains__(self, item: Any) -> bool:
return True
class _Empty:
"""A special value for :exclude-members: that never matches to any member."""
def __contains__(self, item: Any) -> bool:
return False
ALL = _All()
EMPTY = _Empty()
UNINITIALIZED_ATTR = object()
INSTANCEATTR = object()
SLOTSATTR = object()
@ -81,11 +97,20 @@ def members_option(arg: Any) -> Union[object, List[str]]:
def members_set_option(arg: Any) -> Union[object, Set[str]]:
"""Used to convert the :members: option to auto directives."""
warnings.warn("members_set_option() is deprecated.",
RemovedInSphinx50Warning, stacklevel=2)
if arg is None:
return ALL
return {x.strip() for x in arg.split(',') if x.strip()}
def exclude_members_option(arg: Any) -> Union[object, Set[str]]:
"""Used to convert the :exclude-members: option."""
if arg is None:
return EMPTY
return {x.strip() for x in arg.split(',') if x.strip()}
def inherited_members_option(arg: Any) -> Union[object, Set[str]]:
"""Used to convert the :members: option to auto directives."""
if arg is None:
@ -124,6 +149,8 @@ def bool_option(arg: Any) -> bool:
def merge_special_members_option(options: Dict) -> None:
"""Merge :special-members: option to :members: option."""
warnings.warn("merge_special_members_option() is deprecated.",
RemovedInSphinx50Warning, stacklevel=2)
if 'special-members' in options and options['special-members'] is not ALL:
if options.get('members') is ALL:
pass
@ -135,6 +162,20 @@ def merge_special_members_option(options: Dict) -> None:
options['members'] = options['special-members']
def merge_members_option(options: Dict) -> None:
"""Merge :*-members: option to the :members: option."""
if options.get('members') is ALL:
# merging is not needed when members: ALL
return
members = options.setdefault('members', [])
for key in {'private-members', 'special-members'}:
if key in options and options[key] is not ALL:
for member in options[key]:
if member not in members:
members.append(member)
# Some useful event listener factories for autodoc-process-docstring.
def cut_lines(pre: int, post: int = 0, what: str = None) -> Callable:
@ -574,6 +615,8 @@ class Documenter:
return True
elif name in cls.__dict__:
return False
elif name in self.get_attr(cls, '__annotations__', {}):
return False
return False
@ -624,35 +667,40 @@ class Documenter:
if safe_getattr(member, '__sphinx_mock__', False):
# mocked module or object
pass
elif (self.options.exclude_members not in (None, ALL) and
membername in self.options.exclude_members):
elif self.options.exclude_members and membername in self.options.exclude_members:
# remove members given by exclude-members
keep = False
elif want_all and membername.startswith('__') and \
membername.endswith('__') and len(membername) > 4:
elif want_all and special_member_re.match(membername):
# special __methods__
if self.options.special_members is ALL:
if self.options.special_members and membername in self.options.special_members:
if membername == '__doc__':
keep = False
elif is_filtered_inherited_member(membername):
keep = False
else:
keep = has_doc or self.options.undoc_members
elif self.options.special_members:
if membername in self.options.special_members:
keep = has_doc or self.options.undoc_members
else:
keep = False
elif (namespace, membername) in attr_docs:
if want_all and isprivate:
# ignore members whose name starts with _ by default
keep = self.options.private_members
if self.options.private_members is None:
keep = False
else:
keep = membername in self.options.private_members
else:
# keep documented attributes
keep = True
isattr = True
elif want_all and isprivate:
# ignore members whose name starts with _ by default
keep = self.options.private_members and \
(has_doc or self.options.undoc_members)
if has_doc or self.options.undoc_members:
if self.options.private_members is None:
keep = False
elif is_filtered_inherited_member(membername):
keep = False
else:
keep = membername in self.options.private_members
else:
keep = False
else:
if self.options.members is ALL and is_filtered_inherited_member(membername):
keep = False
@ -853,14 +901,14 @@ class ModuleDocumenter(Documenter):
'noindex': bool_option, 'inherited-members': inherited_members_option,
'show-inheritance': bool_option, 'synopsis': identity,
'platform': identity, 'deprecated': bool_option,
'member-order': member_order_option, 'exclude-members': members_set_option,
'private-members': bool_option, 'special-members': members_option,
'member-order': member_order_option, 'exclude-members': exclude_members_option,
'private-members': members_option, 'special-members': members_option,
'imported-members': bool_option, 'ignore-module-all': bool_option
} # type: Dict[str, Callable]
def __init__(self, *args: Any) -> None:
super().__init__(*args)
merge_special_members_option(self.options)
merge_members_option(self.options)
self.__all__ = None
@classmethod
@ -1264,8 +1312,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
'members': members_option, 'undoc-members': bool_option,
'noindex': bool_option, 'inherited-members': inherited_members_option,
'show-inheritance': bool_option, 'member-order': member_order_option,
'exclude-members': members_set_option,
'private-members': bool_option, 'special-members': members_option,
'exclude-members': exclude_members_option,
'private-members': members_option, 'special-members': members_option,
} # type: Dict[str, Callable]
_signature_class = None # type: Any
@ -1273,7 +1321,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
def __init__(self, *args: Any) -> None:
super().__init__(*args)
merge_special_members_option(self.options)
merge_members_option(self.options)
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
@ -1539,8 +1587,12 @@ class DataDocumenter(ModuleLevelDocumenter):
sourcename = self.get_sourcename()
if not self.options.annotation:
# obtain annotation for this data
annotations = getattr(self.parent, '__annotations__', {})
if annotations and self.objpath[-1] in annotations:
try:
annotations = get_type_hints(self.parent)
except TypeError:
annotations = {}
if self.objpath[-1] in annotations:
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
self.add_line(' :type: ' + objrepr, sourcename)
else:
@ -1905,8 +1957,12 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
sourcename = self.get_sourcename()
if not self.options.annotation:
# obtain type annotation for this attribute
annotations = getattr(self.parent, '__annotations__', {})
if annotations and self.objpath[-1] in annotations:
try:
annotations = get_type_hints(self.parent)
except TypeError:
annotations = {}
if self.objpath[-1] in annotations:
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
self.add_line(' :type: ' + objrepr, sourcename)
else:
@ -2000,11 +2056,22 @@ class InstanceAttributeDocumenter(AttributeDocumenter):
isattr and
member is INSTANCEATTR)
def import_parent(self) -> Any:
try:
parent = importlib.import_module(self.modname)
for name in self.objpath[:-1]:
parent = self.get_attr(parent, name)
return parent
except (ImportError, AttributeError):
return None
def import_object(self, raiseerror: bool = False) -> bool:
"""Never import anything."""
# disguise as an attribute
self.objtype = 'attribute'
self.object = INSTANCEATTR
self.parent = self.import_parent()
self._datadescriptor = False
return True

View File

@ -17,6 +17,10 @@ from sphinx.pycode import ModuleAnalyzer
from sphinx.util import logging
from sphinx.util.inspect import isclass, isenumclass, safe_getattr
if False:
# For type annotation
from typing import Type # NOQA
logger = logging.getLogger(__name__)
@ -158,6 +162,24 @@ class Attribute(NamedTuple):
value: Any
def _getmro(obj: Any) -> Tuple["Type", ...]:
"""Get __mro__ from given *obj* safely."""
__mro__ = safe_getattr(obj, '__mro__', None)
if isinstance(__mro__, tuple):
return __mro__
else:
return tuple()
def _getannotations(obj: Any) -> Mapping[str, Any]:
"""Get __annotations__ from given *obj* safely."""
__annotations__ = safe_getattr(obj, '__annotations__', None)
if isinstance(__annotations__, Mapping):
return __annotations__
else:
return {}
def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
analyzer: ModuleAnalyzer = None) -> Dict[str, Attribute]:
"""Get members and attributes of target object."""
@ -199,11 +221,11 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
continue
# annotation only member (ex. attr: int)
if hasattr(subject, '__annotations__') and isinstance(subject.__annotations__, Mapping):
for name in subject.__annotations__:
name = unmangle(subject, name)
for i, cls in enumerate(_getmro(subject)):
for name in _getannotations(cls):
name = unmangle(cls, name)
if name and name not in members:
members[name] = Attribute(name, True, INSTANCEATTR)
members[name] = Attribute(name, i == 0, INSTANCEATTR)
if analyzer:
# append instance attributes (cf. self.attr1) if analyzer knows

View File

@ -75,7 +75,7 @@ from sphinx.application import Sphinx
from sphinx.deprecation import RemovedInSphinx50Warning
from sphinx.environment import BuildEnvironment
from sphinx.environment.adapters.toctree import TocTree
from sphinx.ext.autodoc import Documenter
from sphinx.ext.autodoc import Documenter, INSTANCEATTR
from sphinx.ext.autodoc.directive import DocumenterBridge, Options
from sphinx.ext.autodoc.importer import import_module
from sphinx.ext.autodoc.mock import mock
@ -281,6 +281,29 @@ class Autosummary(SphinxDirective):
return nodes
def import_by_name(self, name: str, prefixes: List[str]) -> Tuple[str, Any, Any, str]:
with mock(self.config.autosummary_mock_imports):
try:
return import_by_name(name, prefixes)
except ImportError as exc:
# check existence of instance attribute
try:
return import_ivar_by_name(name, prefixes)
except ImportError:
pass
raise exc # re-raise ImportError if instance attribute not found
def create_documenter(self, app: Sphinx, obj: Any,
parent: Any, full_name: str) -> "Documenter":
"""Get an autodoc.Documenter class suitable for documenting the given
object.
Wraps get_documenter and is meant as a hook for extensions.
"""
doccls = get_documenter(app, obj, parent)
return doccls(self.bridge, full_name)
def get_items(self, names: List[str]) -> List[Tuple[str, str, str, str]]:
"""Try to import the given names, and return a list of
``[(name, signature, summary_string, real_name), ...]``.
@ -298,8 +321,7 @@ class Autosummary(SphinxDirective):
display_name = name.split('.')[-1]
try:
with mock(self.config.autosummary_mock_imports):
real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes)
real_name, obj, parent, modname = self.import_by_name(name, prefixes=prefixes)
except ImportError:
logger.warning(__('autosummary: failed to import %s'), name,
location=self.get_source_info())
@ -313,8 +335,7 @@ class Autosummary(SphinxDirective):
full_name = modname + '::' + full_name[len(modname) + 1:]
# NB. using full_name here is important, since Documenters
# handle module prefixes slightly differently
doccls = get_documenter(self.env.app, obj, parent)
documenter = doccls(self.bridge, full_name)
documenter = self.create_documenter(self.env.app, obj, parent, full_name)
if not documenter.parse_name():
logger.warning(__('failed to parse name %s'), real_name,
location=self.get_source_info())
@ -632,6 +653,23 @@ def _import_by_name(name: str) -> Tuple[Any, Any, str]:
raise ImportError(*e.args) from e
def import_ivar_by_name(name: str, prefixes: List[str] = [None]) -> Tuple[str, Any, Any, str]:
"""Import an instance variable that has the given *name*, under one of the
*prefixes*. The first name that succeeds is used.
"""
try:
name, attr = name.rsplit(".", 1)
real_name, obj, parent, modname = import_by_name(name, prefixes)
qualname = real_name.replace(modname + ".", "")
analyzer = ModuleAnalyzer.for_module(modname)
if (qualname, attr) in analyzer.find_attr_docs():
return real_name + "." + attr, INSTANCEATTR, obj, modname
except (ImportError, ValueError):
pass
raise ImportError
# -- :autolink: (smart default role) -------------------------------------------
class AutoLink(SphinxRole):

View File

@ -41,7 +41,7 @@ from sphinx.builders import Builder
from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx50Warning
from sphinx.ext.autodoc import Documenter
from sphinx.ext.autosummary import import_by_name, get_documenter
from sphinx.ext.autosummary import import_by_name, import_ivar_by_name, get_documenter
from sphinx.locale import __
from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.registry import SphinxComponentRegistry
@ -395,8 +395,13 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
name, obj, parent, modname = import_by_name(entry.name)
qualname = name.replace(modname + ".", "")
except ImportError as e:
logger.warning(__('[autosummary] failed to import %r: %s') % (entry.name, e))
continue
try:
# try to importl as an instance attribute
name, obj, parent, modname = import_ivar_by_name(entry.name)
qualname = name.replace(modname + ".", "")
except ImportError:
logger.warning(__('[autosummary] failed to import %r: %s') % (entry.name, e))
continue
context = {}
if app:

View File

@ -82,7 +82,7 @@ class TestDirective(SphinxDirective):
# convert <BLANKLINE>s to ordinary blank lines for presentation
test = code
code = blankline_re.sub('', code)
if doctestopt_re.search(code):
if doctestopt_re.search(code) and 'no-trim-doctest-flags' not in self.options:
if not test:
test = code
code = doctestopt_re.sub('', code)
@ -142,6 +142,10 @@ class TestDirective(SphinxDirective):
line=self.lineno)
if 'skipif' in self.options:
node['skipif'] = self.options['skipif']
if 'trim-doctest-flags' in self.options:
node['trim_flags'] = True
elif 'no-trim-doctest-flags' in self.options:
node['trim_flags'] = False
return [node]
@ -156,26 +160,32 @@ class TestcleanupDirective(TestDirective):
class DoctestDirective(TestDirective):
option_spec = {
'hide': directives.flag,
'no-trim-doctest-flags': directives.flag,
'options': directives.unchanged,
'pyversion': directives.unchanged_required,
'skipif': directives.unchanged_required,
'trim-doctest-flags': directives.flag,
}
class TestcodeDirective(TestDirective):
option_spec = {
'hide': directives.flag,
'no-trim-doctest-flags': directives.flag,
'pyversion': directives.unchanged_required,
'skipif': directives.unchanged_required,
'trim-doctest-flags': directives.flag,
}
class TestoutputDirective(TestDirective):
option_spec = {
'hide': directives.flag,
'no-trim-doctest-flags': directives.flag,
'options': directives.unchanged,
'pyversion': directives.unchanged_required,
'skipif': directives.unchanged_required,
'trim-doctest-flags': directives.flag,
}

View File

@ -385,7 +385,7 @@ def man_visit_graphviz(self: ManualPageTranslator, node: graphviz) -> None:
def on_build_finished(app: Sphinx, exc: Exception) -> None:
if exc is None:
if exc is None and app.builder.format == 'html':
src = path.join(sphinx.package_dir, 'templates', 'graphviz', 'graphviz.css')
dst = path.join(app.outdir, '_static')
copy_asset(src, dst)

View File

@ -239,7 +239,7 @@ class Config:
napoleon_type_aliases : :obj:`dict` (Defaults to None)
Add a mapping of strings to string, translating types in numpy
style docstrings. Only works when ``napoleon_use_param = True``.
style docstrings.
napoleon_custom_sections : :obj:`list` (Defaults to None)
Add a list of custom sections to include, expanding the list of parsed sections.

View File

@ -22,6 +22,7 @@ from sphinx.ext.napoleon.iterators import modify_iter
from sphinx.locale import _, __
from sphinx.util import logging
logger = logging.getLogger(__name__)
_directive_regex = re.compile(r'\.\. \S+::')
@ -33,7 +34,7 @@ _xref_or_code_regex = re.compile(
r'((?::(?:[a-zA-Z0-9]+[\-_+:.])*[a-zA-Z0-9]+:`.+?`)|'
r'(?:``.+``))')
_xref_regex = re.compile(
r'(?::(?:[a-zA-Z0-9]+[\-_+:.])*[a-zA-Z0-9]+:`.+?`)'
r'(?:(?::(?:[a-zA-Z0-9]+[\-_+:.])*[a-zA-Z0-9]+:)?`.+?`)'
)
_bullet_list_regex = re.compile(r'^(\*|\+|\-)(\s+\S|\s*$)')
_enumerated_list_regex = re.compile(
@ -41,10 +42,15 @@ _enumerated_list_regex = re.compile(
r'(\d+|#|[ivxlcdm]+|[IVXLCDM]+|[a-zA-Z])'
r'(?(paren)\)|\.)(\s+\S|\s*$)')
_token_regex = re.compile(
r"(\sor\s|\sof\s|:\s|,\s|[{]|[}]"
r"(,\sor\s|\sor\s|\sof\s|:\s|\sto\s|,\sand\s|\sand\s|,\s"
r"|[{]|[}]"
r'|"(?:\\"|[^"])*"'
r"|'(?:\\'|[^'])*')"
)
_default_regex = re.compile(
r"^default[^_0-9A-Za-z].*$",
)
_SINGLETONS = ("None", "True", "False", "Ellipsis")
class GoogleDocstring:
@ -256,13 +262,16 @@ class GoogleDocstring:
_descs = self.__class__(_descs, self._config).lines()
return _name, _type, _descs
def _consume_fields(self, parse_type: bool = True, prefer_type: bool = False
) -> List[Tuple[str, str, List[str]]]:
def _consume_fields(self, parse_type: bool = True, prefer_type: bool = False,
multiple: bool = False) -> List[Tuple[str, str, List[str]]]:
self._consume_empty()
fields = []
while not self._is_section_break():
_name, _type, _desc = self._consume_field(parse_type, prefer_type)
if _name or _type or _desc:
if multiple and _name:
for name in _name.split(","):
fields.append((name.strip(), _type, _desc))
elif _name or _type or _desc:
fields.append((_name, _type, _desc,))
return fields
@ -671,10 +680,12 @@ class GoogleDocstring:
return self._format_fields(_('Other Parameters'), self._consume_fields())
def _parse_parameters_section(self, section: str) -> List[str]:
fields = self._consume_fields()
if self._config.napoleon_use_param:
# Allow to declare multiple parameters at once (ex: x, y: int)
fields = self._consume_fields(multiple=True)
return self._format_docutils_params(fields)
else:
fields = self._consume_fields()
return self._format_fields(_('Parameters'), fields)
def _parse_raises_section(self, section: str) -> List[str]:
@ -804,6 +815,9 @@ def _recombine_set_tokens(tokens: List[str]) -> List[str]:
previous_token = token
continue
if not token.strip():
continue
if token in keywords:
tokens.appendleft(token)
if previous_token is not None:
@ -842,8 +856,13 @@ def _recombine_set_tokens(tokens: List[str]) -> List[str]:
def _tokenize_type_spec(spec: str) -> List[str]:
def postprocess(item):
if item.startswith("default"):
return [item[:7], item[7:]]
if _default_regex.match(item):
default = item[:7]
# can't be separated by anything other than a single space
# for now
other = item[8:]
return [default, " ", other]
else:
return [item]
@ -857,10 +876,19 @@ def _tokenize_type_spec(spec: str) -> List[str]:
def _token_type(token: str, location: str = None) -> str:
def is_numeric(token):
try:
# use complex to make sure every numeric value is detected as literal
complex(token)
except ValueError:
return False
else:
return True
if token.startswith(" ") or token.endswith(" "):
type_ = "delimiter"
elif (
token.isnumeric() or
is_numeric(token) or
(token.startswith("{") and token.endswith("}")) or
(token.startswith('"') and token.endswith('"')) or
(token.startswith("'") and token.endswith("'"))
@ -910,9 +938,12 @@ def _convert_numpy_type_spec(_type: str, location: str = None, translations: dic
def convert_obj(obj, translations, default_translation):
translation = translations.get(obj, obj)
# use :class: (the default) only if obj is not a standard singleton (None, True, False)
if translation in ("None", "True", "False") and default_translation == ":class:`%s`":
# use :class: (the default) only if obj is not a standard singleton
if translation in _SINGLETONS and default_translation == ":class:`%s`":
default_translation = ":obj:`%s`"
elif translation == "..." and default_translation == ":class:`%s`":
# allow referencing the builtin ...
default_translation = ":obj:`%s <Ellipsis>`"
if _xref_regex.match(translation) is None:
translation = default_translation % translation
@ -926,16 +957,9 @@ def _convert_numpy_type_spec(_type: str, location: str = None, translations: dic
for token in combined_tokens
]
# don't use the object role if it's not necessary
default_translation = (
":class:`%s`"
if not all(type_ == "obj" for _, type_ in types)
else "%s"
)
converters = {
"literal": lambda x: "``%s``" % x,
"obj": lambda x: convert_obj(x, translations, default_translation),
"obj": lambda x: convert_obj(x, translations, ":class:`%s`"),
"control": lambda x: "*%s*" % x,
"delimiter": lambda x: x,
"reference": lambda x: x,
@ -1046,14 +1070,24 @@ class NumpyDocstring(GoogleDocstring):
super().__init__(docstring, config, app, what, name, obj, options)
def _get_location(self) -> str:
filepath = inspect.getfile(self._obj) if self._obj is not None else ""
filepath = inspect.getfile(self._obj) if self._obj is not None else None
name = self._name
if filepath is None and name is None:
return None
elif filepath is None:
filepath = ""
return ":".join([filepath, "docstring of %s" % name])
def _escape_args_and_kwargs(self, name: str) -> str:
func = super()._escape_args_and_kwargs
if ", " in name:
return ", ".join(func(param) for param in name.split(", "))
else:
return func(name)
def _consume_field(self, parse_type: bool = True, prefer_type: bool = False
) -> Tuple[str, str, List[str]]:
line = next(self._line_iter)
@ -1063,12 +1097,11 @@ class NumpyDocstring(GoogleDocstring):
_name, _type = line, ''
_name, _type = _name.strip(), _type.strip()
_name = self._escape_args_and_kwargs(_name)
if self._config.napoleon_use_param:
_type = _convert_numpy_type_spec(
_type,
location=self._get_location(),
translations=self._config.napoleon_type_aliases or {},
)
_type = _convert_numpy_type_spec(
_type,
location=self._get_location(),
translations=self._config.napoleon_type_aliases or {},
)
if prefer_type and not _type:
_type, _name = _name, _type

View File

@ -693,7 +693,7 @@ pre {
overflow-y: hidden; /* fixes display issues on Chrome browsers */
}
pre, div[class|="highlight"] {
pre, div[class*="highlight-"] {
clear: both;
}
@ -704,7 +704,7 @@ span.pre {
hyphens: none;
}
div[class^="highlight-"] {
div[class*="highlight-"] {
margin: 1em 0;
}

View File

@ -9,10 +9,10 @@
"""
import sys
from typing import Any, Dict, List, NamedTuple, Union
from typing import Any, Dict, List, NamedTuple
from docutils import nodes
from docutils.nodes import Node
from docutils.nodes import Node, TextElement
from pygments.lexers import PythonConsoleLexer, guess_lexer
from sphinx import addnodes
@ -94,9 +94,6 @@ class TrimDoctestFlagsTransform(SphinxTransform):
default_priority = HighlightLanguageTransform.default_priority + 1
def apply(self, **kwargs: Any) -> None:
if not self.config.trim_doctest_flags:
return
for lbnode in self.document.traverse(nodes.literal_block): # type: nodes.literal_block
if self.is_pyconsole(lbnode):
self.strip_doctest_flags(lbnode)
@ -104,8 +101,10 @@ class TrimDoctestFlagsTransform(SphinxTransform):
for dbnode in self.document.traverse(nodes.doctest_block): # type: nodes.doctest_block
self.strip_doctest_flags(dbnode)
@staticmethod
def strip_doctest_flags(node: Union[nodes.literal_block, nodes.doctest_block]) -> None:
def strip_doctest_flags(self, node: TextElement) -> None:
if not node.get('trim_flags', self.config.trim_doctest_flags):
return
source = node.rawsource
source = doctest.blankline_re.sub('', source)
source = doctest.doctestopt_re.sub('', source)

View File

@ -232,8 +232,12 @@ def get_image_filename_for_language(filename: str, env: "BuildEnvironment") -> s
dirname = path.dirname(d['root'])
if dirname and not dirname.endswith(path.sep):
dirname += path.sep
docpath = path.dirname(env.docname)
if docpath and not docpath.endswith(path.sep):
docpath += path.sep
d['path'] = dirname
d['basename'] = path.basename(d['root'])
d['docpath'] = docpath
d['language'] = env.config.language
try:
return filename_format.format(**d)

View File

@ -25,3 +25,7 @@ class Class:
self.attr5: int #: attr5
self.attr6 = 0 # type: int
"""attr6"""
class Derived(Class):
attr7: int

View File

@ -16,7 +16,8 @@ class Foo:
pass
def __init__(self):
pass
#: docstring
self.value = 1
def bar(self):
pass

View File

@ -10,6 +10,7 @@
autosummary_dummy_module
autosummary_dummy_module.Foo
autosummary_dummy_module.Foo.Bar
autosummary_dummy_module.Foo.value
autosummary_dummy_module.bar
autosummary_dummy_module.qux
autosummary_importfail

View File

@ -22,7 +22,19 @@ test-trim_doctest_flags
>>> datetime.date.now() # doctest: +QUX
datetime.date(2008, 1, 1)
.. doctest_block::
.. doctest::
>>> datetime.date.now() # doctest: +QUUX
datetime.date(2008, 1, 1)
.. doctest::
:trim-doctest-flags:
>>> datetime.date.now() # doctest: +CORGE
datetime.date(2008, 1, 1)
.. doctest::
:no-trim-doctest-flags:
>>> datetime.date.now() # doctest: +GRAULT
datetime.date(2008, 1, 1)

View File

@ -759,6 +759,7 @@ def test_templates():
check('class', "template<typename T = Test> {key}A", {2: "I0E1A"})
check('class', "template<template<typename> typename T> {key}A", {2: "II0E0E1A"})
check('class', "template<template<typename> class T> {key}A", {2: "II0E0E1A"})
check('class', "template<template<typename> typename> {key}A", {2: "II0E0E1A"})
check('class', "template<template<typename> typename ...T> {key}A", {2: "II0EDpE1A"})
check('class', "template<template<typename> typename...> {key}A", {2: "II0EDpE1A"})
@ -769,6 +770,16 @@ def test_templates():
check('class', "template<int T = 42> {key}A", {2: "I_iE1A"})
check('class', "template<int = 42> {key}A", {2: "I_iE1A"})
check('class', "template<typename A<B>::C> {key}A", {2: "I_N1AI1BE1CEE1A"})
check('class', "template<typename A<B>::C = 42> {key}A", {2: "I_N1AI1BE1CEE1A"})
# from #7944
check('function', "template<typename T, "
"typename std::enable_if<!has_overloaded_addressof<T>::value, bool>::type = false"
"> constexpr T *static_addressof(T &ref)",
{2: "I0_NSt9enable_ifIX!has_overloaded_addressof<T>::valueEbE4typeEE16static_addressofR1T",
3: "I0_NSt9enable_ifIXntN24has_overloaded_addressofI1TE5valueEEbE4typeEE16static_addressofR1T",
4: "I0_NSt9enable_ifIXntN24has_overloaded_addressofI1TE5valueEEbE4typeEE16static_addressofP1TR1T"})
check('class', "template<> {key}A<NS::B<>>", {2: "IE1AIN2NS1BIEEE"})
# from #2058

View File

@ -1176,44 +1176,44 @@ def test_slots(app):
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_class(app):
options = {"members": None}
actual = do_autodoc(app, 'class', 'target.enum.EnumCls', options)
actual = do_autodoc(app, 'class', 'target.enums.EnumCls', options)
assert list(actual) == [
'',
'.. py:class:: EnumCls(value)',
' :module: target.enum',
' :module: target.enums',
'',
' this is enum class',
'',
'',
' .. py:method:: EnumCls.say_goodbye()',
' :module: target.enum',
' :module: target.enums',
' :classmethod:',
'',
' a classmethod says good-bye to you.',
'',
'',
' .. py:method:: EnumCls.say_hello()',
' :module: target.enum',
' :module: target.enums',
'',
' a method says hello to you.',
'',
'',
' .. py:attribute:: EnumCls.val1',
' :module: target.enum',
' :module: target.enums',
' :value: 12',
'',
' doc for val1',
'',
'',
' .. py:attribute:: EnumCls.val2',
' :module: target.enum',
' :module: target.enums',
' :value: 23',
'',
' doc for val2',
'',
'',
' .. py:attribute:: EnumCls.val3',
' :module: target.enum',
' :module: target.enums',
' :value: 34',
'',
' doc for val3',
@ -1221,11 +1221,11 @@ def test_enum_class(app):
]
# checks for an attribute of EnumClass
actual = do_autodoc(app, 'attribute', 'target.enum.EnumCls.val1')
actual = do_autodoc(app, 'attribute', 'target.enums.EnumCls.val1')
assert list(actual) == [
'',
'.. py:attribute:: EnumCls.val1',
' :module: target.enum',
' :module: target.enums',
' :value: 12',
'',
' doc for val1',
@ -1576,6 +1576,15 @@ def test_autodoc_typed_instance_variables(app):
' This is descr4',
'',
'',
'.. py:class:: Derived()',
' :module: target.typed_vars',
'',
'',
' .. py:attribute:: Derived.attr7',
' :module: target.typed_vars',
' :type: int',
'',
'',
'.. py:data:: attr1',
' :module: target.typed_vars',
' :type: str',
@ -1601,6 +1610,47 @@ def test_autodoc_typed_instance_variables(app):
]
@pytest.mark.skipif(sys.version_info < (3, 6), reason='py36+ is available since python3.6.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_typed_inherited_instance_variables(app):
options = {"members": None,
"undoc-members": True,
"inherited-members": True}
actual = do_autodoc(app, 'class', 'target.typed_vars.Derived', options)
assert list(actual) == [
'',
'.. py:class:: Derived()',
' :module: target.typed_vars',
'',
'',
' .. py:attribute:: Derived.attr1',
' :module: target.typed_vars',
' :type: int',
' :value: 0',
'',
'',
' .. py:attribute:: Derived.attr2',
' :module: target.typed_vars',
' :type: int',
'',
'',
' .. py:attribute:: Derived.attr3',
' :module: target.typed_vars',
' :value: 0',
'',
'',
' .. py:attribute:: Derived.attr7',
' :module: target.typed_vars',
' :type: int',
'',
'',
' .. py:attribute:: Derived.descr4',
' :module: target.typed_vars',
' :type: int',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_GenericAlias(app):
options = {"members": None,

View File

@ -645,7 +645,7 @@ def test_autodoc_typehints_description_for_invalid_node(app):
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_default_options(app):
# no settings
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
actual = do_autodoc(app, 'class', 'target.enums.EnumCls')
assert ' .. py:attribute:: EnumCls.val1' not in actual
assert ' .. py:attribute:: EnumCls.val4' not in actual
actual = do_autodoc(app, 'class', 'target.CustomIter')
@ -655,13 +655,13 @@ def test_autodoc_default_options(app):
# with :members:
app.config.autodoc_default_options = {'members': None}
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
actual = do_autodoc(app, 'class', 'target.enums.EnumCls')
assert ' .. py:attribute:: EnumCls.val1' in actual
assert ' .. py:attribute:: EnumCls.val4' not in actual
# with :members: = True
app.config.autodoc_default_options = {'members': True}
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
actual = do_autodoc(app, 'class', 'target.enums.EnumCls')
assert ' .. py:attribute:: EnumCls.val1' in actual
assert ' .. py:attribute:: EnumCls.val4' not in actual
@ -670,7 +670,7 @@ def test_autodoc_default_options(app):
'members': None,
'undoc-members': None,
}
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
actual = do_autodoc(app, 'class', 'target.enums.EnumCls')
assert ' .. py:attribute:: EnumCls.val1' in actual
assert ' .. py:attribute:: EnumCls.val4' in actual
@ -696,7 +696,7 @@ def test_autodoc_default_options(app):
'members': None,
'exclude-members': None,
}
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
actual = do_autodoc(app, 'class', 'target.enums.EnumCls')
assert ' .. py:attribute:: EnumCls.val1' in actual
assert ' .. py:attribute:: EnumCls.val4' not in actual
app.config.autodoc_default_options = {
@ -720,7 +720,7 @@ def test_autodoc_default_options(app):
def test_autodoc_default_options_with_values(app):
# with :members:
app.config.autodoc_default_options = {'members': 'val1,val2'}
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
actual = do_autodoc(app, 'class', 'target.enums.EnumCls')
assert ' .. py:attribute:: EnumCls.val1' in actual
assert ' .. py:attribute:: EnumCls.val2' in actual
assert ' .. py:attribute:: EnumCls.val3' not in actual
@ -765,7 +765,7 @@ def test_autodoc_default_options_with_values(app):
'members': None,
'exclude-members': 'val1'
}
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
actual = do_autodoc(app, 'class', 'target.enums.EnumCls')
assert ' .. py:attribute:: EnumCls.val1' not in actual
assert ' .. py:attribute:: EnumCls.val2' in actual
assert ' .. py:attribute:: EnumCls.val3' in actual

View File

@ -60,3 +60,24 @@ def test_private_field_and_private_members(app):
' :meta private:',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_private_members(app):
app.config.autoclass_content = 'class'
options = {"members": None,
"private-members": "_public_function"}
actual = do_autodoc(app, 'module', 'target.private', options)
assert list(actual) == [
'',
'.. py:module:: target.private',
'',
'',
'.. py:function:: _public_function(name)',
' :module: target.private',
'',
' public_function is a docstring().',
'',
' :meta public:',
'',
]

View File

@ -293,15 +293,17 @@ def test_autosummary_generate(app, status, warning):
nodes.row,
nodes.row,
nodes.row,
nodes.row,
nodes.row)])])
assert_node(doctree[4][0], addnodes.toctree, caption="An autosummary")
assert len(doctree[3][0][0][2]) == 5
assert len(doctree[3][0][0][2]) == 6
assert doctree[3][0][0][2][0].astext() == 'autosummary_dummy_module\n\n'
assert doctree[3][0][0][2][1].astext() == 'autosummary_dummy_module.Foo()\n\n'
assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.Foo.Bar()\n\n'
assert doctree[3][0][0][2][3].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n'
assert doctree[3][0][0][2][4].astext() == 'autosummary_dummy_module.qux\n\na module-level attribute'
assert doctree[3][0][0][2][3].astext() == 'autosummary_dummy_module.Foo.value\n\ndocstring'
assert doctree[3][0][0][2][4].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n'
assert doctree[3][0][0][2][5].astext() == 'autosummary_dummy_module.qux\n\na module-level attribute'
module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text()
assert (' .. autosummary::\n'
@ -333,6 +335,11 @@ def test_autosummary_generate(app, status, warning):
'\n'
'.. autoclass:: Foo.Bar\n' in FooBar)
Foo_value = (app.srcdir / 'generated' / 'autosummary_dummy_module.Foo.value.rst').read_text()
assert ('.. currentmodule:: autosummary_dummy_module\n'
'\n'
'.. autoattribute:: Foo.value' in Foo_value)
qux = (app.srcdir / 'generated' / 'autosummary_dummy_module.qux.rst').read_text()
assert ('.. currentmodule:: autosummary_dummy_module\n'
'\n'

View File

@ -16,6 +16,8 @@ from inspect import cleandoc
from textwrap import dedent
from unittest import TestCase, mock
import pytest
from sphinx.ext.napoleon import Config
from sphinx.ext.napoleon.docstring import GoogleDocstring, NumpyDocstring
from sphinx.ext.napoleon.docstring import (
@ -64,19 +66,19 @@ Sample namedtuple subclass
Quick description of attr1
:type: Arbitrary type
:type: :class:`Arbitrary type`
.. attribute:: attr2
Quick description of attr2
:type: Another arbitrary type
:type: :class:`Another arbitrary type`
.. attribute:: attr3
Adds a newline after the type
:type: Type
:type: :class:`Type`
"""
self.assertEqual(expected, actual)
@ -1078,6 +1080,22 @@ Methods:
options={'noindex': True}))
self.assertEqual(expected, actual)
def test_keywords_with_types(self):
docstring = """\
Do as you please
Keyword Args:
gotham_is_yours (None): shall interfere.
"""
actual = str(GoogleDocstring(docstring))
expected = """\
Do as you please
:keyword gotham_is_yours: shall interfere.
:kwtype gotham_is_yours: None
"""
self.assertEqual(expected, actual)
class NumpyDocstringTest(BaseDocstringTest):
docstrings = [(
@ -1108,7 +1126,7 @@ class NumpyDocstringTest(BaseDocstringTest):
"""
Single line summary
:Parameters: **arg1** (*str*) -- Extended
:Parameters: **arg1** (:class:`str`) -- Extended
description of arg1
"""
), (
@ -1136,14 +1154,14 @@ class NumpyDocstringTest(BaseDocstringTest):
"""
Single line summary
:Parameters: * **arg1** (*str*) -- Extended
:Parameters: * **arg1** (:class:`str`) -- Extended
description of arg1
* **arg2** (*int*) -- Extended
* **arg2** (:class:`int`) -- Extended
description of arg2
:Keyword Arguments: * **kwarg1** (*str*) -- Extended
:Keyword Arguments: * **kwarg1** (:class:`str`) -- Extended
description of kwarg1
* **kwarg2** (*int*) -- Extended
* **kwarg2** (:class:`int`) -- Extended
description of kwarg2
"""
), (
@ -1194,7 +1212,7 @@ class NumpyDocstringTest(BaseDocstringTest):
"""
Single line summary
:Parameters: * **arg1** (*str*) -- Extended description of arg1
:Parameters: * **arg1** (:class:`str`) -- Extended description of arg1
* **\\*args** -- Variable length argument list.
* **\\*\\*kwargs** -- Arbitrary keyword arguments.
"""
@ -1202,6 +1220,23 @@ class NumpyDocstringTest(BaseDocstringTest):
"""
Single line summary
Parameters
----------
arg1:str
Extended description of arg1
*args, **kwargs:
Variable length argument list and arbitrary keyword arguments.
""",
"""
Single line summary
:Parameters: * **arg1** (:class:`str`) -- Extended description of arg1
* **\\*args, \\*\\*kwargs** -- Variable length argument list and arbitrary keyword arguments.
"""
), (
"""
Single line summary
Yield
-----
str
@ -1302,6 +1337,32 @@ param1 : :class:`MyClass <name.space.MyClass>` instance
expected = """\
:param param1:
:type param1: :class:`MyClass <name.space.MyClass>` instance
"""
self.assertEqual(expected, actual)
def test_multiple_parameters(self):
docstring = """\
Parameters
----------
x1, x2 : array_like
Input arrays, description of ``x1``, ``x2``.
"""
config = Config(napoleon_use_param=False)
actual = str(NumpyDocstring(docstring, config))
expected = """\
:Parameters: **x1, x2** (:class:`array_like`) -- Input arrays, description of ``x1``, ``x2``.
"""
self.assertEqual(expected, actual)
config = Config(napoleon_use_param=True)
actual = str(NumpyDocstring(dedent(docstring), config))
expected = """\
:param x1: Input arrays, description of ``x1``, ``x2``.
:type x1: :class:`array_like`
:param x2: Input arrays, description of ``x1``, ``x2``.
:type x2: :class:`array_like`
"""
self.assertEqual(expected, actual)
@ -1316,7 +1377,7 @@ param1 : MyClass instance
config = Config(napoleon_use_param=False)
actual = str(NumpyDocstring(docstring, config))
expected = """\
:Parameters: **param1** (*MyClass instance*)
:Parameters: **param1** (:class:`MyClass instance`)
"""
self.assertEqual(expected, actual)
@ -1324,7 +1385,7 @@ param1 : MyClass instance
actual = str(NumpyDocstring(dedent(docstring), config))
expected = """\
:param param1:
:type param1: MyClass instance
:type param1: :class:`MyClass instance`
"""
self.assertEqual(expected, actual)
@ -1413,7 +1474,7 @@ arg_ : type
expected = """
:ivar arg_: some description
:vartype arg_: type
:vartype arg_: :class:`type`
"""
config = Config(napoleon_use_ivar=True)
@ -1433,7 +1494,7 @@ arg_ : type
expected = """
:ivar arg\\_: some description
:vartype arg\\_: type
:vartype arg\\_: :class:`type`
"""
config = Config(napoleon_use_ivar=True)
@ -1801,59 +1862,59 @@ definition_after_normal_text : int
expected = """One line summary.
:param no_list:
:type no_list: int
:type no_list: :class:`int`
:param one_bullet_empty:
*
:type one_bullet_empty: int
:type one_bullet_empty: :class:`int`
:param one_bullet_single_line:
- first line
:type one_bullet_single_line: int
:type one_bullet_single_line: :class:`int`
:param one_bullet_two_lines:
+ first line
continued
:type one_bullet_two_lines: int
:type one_bullet_two_lines: :class:`int`
:param two_bullets_single_line:
- first line
- second line
:type two_bullets_single_line: int
:type two_bullets_single_line: :class:`int`
:param two_bullets_two_lines:
* first line
continued
* second line
continued
:type two_bullets_two_lines: int
:type two_bullets_two_lines: :class:`int`
:param one_enumeration_single_line:
1. first line
:type one_enumeration_single_line: int
:type one_enumeration_single_line: :class:`int`
:param one_enumeration_two_lines:
1) first line
continued
:type one_enumeration_two_lines: int
:type one_enumeration_two_lines: :class:`int`
:param two_enumerations_one_line:
(iii) first line
(iv) second line
:type two_enumerations_one_line: int
:type two_enumerations_one_line: :class:`int`
:param two_enumerations_two_lines:
a. first line
continued
b. second line
continued
:type two_enumerations_two_lines: int
:type two_enumerations_two_lines: :class:`int`
:param one_definition_one_line:
item 1
first line
:type one_definition_one_line: int
:type one_definition_one_line: :class:`int`
:param one_definition_two_lines:
item 1
first line
continued
:type one_definition_two_lines: int
:type one_definition_two_lines: :class:`int`
:param two_definitions_one_line:
item 1
first line
item 2
second line
:type two_definitions_one_line: int
:type two_definitions_one_line: :class:`int`
:param two_definitions_two_lines:
item 1
first line
@ -1861,14 +1922,14 @@ definition_after_normal_text : int
item 2
second line
continued
:type two_definitions_two_lines: int
:type two_definitions_two_lines: :class:`int`
:param one_definition_blank_line:
item 1
first line
extra first line
:type one_definition_blank_line: int
:type one_definition_blank_line: :class:`int`
:param two_definitions_blank_lines:
item 1
@ -1881,12 +1942,12 @@ definition_after_normal_text : int
second line
extra second line
:type two_definitions_blank_lines: int
:type two_definitions_blank_lines: :class:`int`
:param definition_after_normal_text: text line
item 1
first line
:type definition_after_normal_text: int
:type definition_after_normal_text: :class:`int`
"""
config = Config(napoleon_use_param=True)
actual = str(NumpyDocstring(docstring, config))
@ -1894,60 +1955,60 @@ definition_after_normal_text : int
expected = """One line summary.
:Parameters: * **no_list** (*int*)
* **one_bullet_empty** (*int*) --
:Parameters: * **no_list** (:class:`int`)
* **one_bullet_empty** (:class:`int`) --
*
* **one_bullet_single_line** (*int*) --
* **one_bullet_single_line** (:class:`int`) --
- first line
* **one_bullet_two_lines** (*int*) --
* **one_bullet_two_lines** (:class:`int`) --
+ first line
continued
* **two_bullets_single_line** (*int*) --
* **two_bullets_single_line** (:class:`int`) --
- first line
- second line
* **two_bullets_two_lines** (*int*) --
* **two_bullets_two_lines** (:class:`int`) --
* first line
continued
* second line
continued
* **one_enumeration_single_line** (*int*) --
* **one_enumeration_single_line** (:class:`int`) --
1. first line
* **one_enumeration_two_lines** (*int*) --
* **one_enumeration_two_lines** (:class:`int`) --
1) first line
continued
* **two_enumerations_one_line** (*int*) --
* **two_enumerations_one_line** (:class:`int`) --
(iii) first line
(iv) second line
* **two_enumerations_two_lines** (*int*) --
* **two_enumerations_two_lines** (:class:`int`) --
a. first line
continued
b. second line
continued
* **one_definition_one_line** (*int*) --
* **one_definition_one_line** (:class:`int`) --
item 1
first line
* **one_definition_two_lines** (*int*) --
* **one_definition_two_lines** (:class:`int`) --
item 1
first line
continued
* **two_definitions_one_line** (*int*) --
* **two_definitions_one_line** (:class:`int`) --
item 1
first line
item 2
second line
* **two_definitions_two_lines** (*int*) --
* **two_definitions_two_lines** (:class:`int`) --
item 1
first line
@ -1955,14 +2016,14 @@ definition_after_normal_text : int
item 2
second line
continued
* **one_definition_blank_line** (*int*) --
* **one_definition_blank_line** (:class:`int`) --
item 1
first line
extra first line
* **two_definitions_blank_lines** (*int*) --
* **two_definitions_blank_lines** (:class:`int`) --
item 1
@ -1975,7 +2036,7 @@ definition_after_normal_text : int
second line
extra second line
* **definition_after_normal_text** (*int*) -- text line
* **definition_after_normal_text** (:class:`int`) -- text line
item 1
first line
@ -1987,6 +2048,8 @@ definition_after_normal_text : int
def test_token_type(self):
tokens = (
("1", "literal"),
("-4.6", "literal"),
("2j", "literal"),
("'string'", "literal"),
('"another_string"', "literal"),
("{1, 2}", "literal"),
@ -2010,20 +2073,32 @@ definition_after_normal_text : int
def test_tokenize_type_spec(self):
specs = (
"str",
"defaultdict",
"int, float, or complex",
"int or float or None, optional",
'{"F", "C", "N"}',
"{'F', 'C', 'N'}, default: 'F'",
"{'F', 'C', 'N or C'}, default 'F'",
"str, default: 'F or C'",
"int, default: None",
"int, default None",
"int, default :obj:`None`",
'"ma{icious"',
r"'with \'quotes\''",
)
tokens = (
["str"],
["defaultdict"],
["int", ", ", "float", ", or ", "complex"],
["int", " or ", "float", " or ", "None", ", ", "optional"],
["{", '"F"', ", ", '"C"', ", ", '"N"', "}"],
["{", "'F'", ", ", "'C'", ", ", "'N'", "}", ", ", "default", ": ", "'F'"],
["{", "'F'", ", ", "'C'", ", ", "'N or C'", "}", ", ", "default", " ", "'F'"],
["str", ", ", "default", ": ", "'F or C'"],
["int", ", ", "default", ": ", "None"],
["int", ", " , "default", " ", "None"],
["int", ", ", "default", " ", ":obj:`None`"],
['"ma{icious"'],
[r"'with \'quotes\''"],
)
@ -2037,12 +2112,14 @@ definition_after_normal_text : int
["{", "1", ", ", "2", "}"],
["{", '"F"', ", ", '"C"', ", ", '"N"', "}", ", ", "optional"],
["{", "'F'", ", ", "'C'", ", ", "'N'", "}", ", ", "default", ": ", "None"],
["{", "'F'", ", ", "'C'", ", ", "'N'", "}", ", ", "default", " ", "None"],
)
combined_tokens = (
["{1, 2}"],
['{"F", "C", "N"}', ", ", "optional"],
["{'F', 'C', 'N'}", ", ", "default", ": ", "None"],
["{'F', 'C', 'N'}", ", ", "default", " ", "None"],
)
for tokens_, expected in zip(tokens, combined_tokens):
@ -2075,8 +2152,10 @@ definition_after_normal_text : int
"optional",
"str, optional",
"int or float or None, default: None",
"int, default None",
'{"F", "C", "N"}',
"{'F', 'C', 'N'}, default: 'N'",
"{'F', 'C', 'N'}, default 'N'",
"DataFrame, optional",
)
@ -2085,8 +2164,10 @@ definition_after_normal_text : int
"*optional*",
":class:`str`, *optional*",
":class:`int` or :class:`float` or :obj:`None`, *default*: :obj:`None`",
":class:`int`, *default* :obj:`None`",
'``{"F", "C", "N"}``',
"``{'F', 'C', 'N'}``, *default*: ``'N'``",
"``{'F', 'C', 'N'}``, *default* ``'N'``",
":class:`pandas.DataFrame`, *optional*",
)
@ -2100,7 +2181,7 @@ definition_after_normal_text : int
----------
param1 : DataFrame
the data to work on
param2 : int or float or None
param2 : int or float or None, optional
a parameter with different types
param3 : dict-like, optional
a optional mapping
@ -2108,21 +2189,35 @@ definition_after_normal_text : int
a optional parameter with different types
param5 : {"F", "C", "N"}, optional
a optional parameter with fixed values
param6 : int, default None
different default format
param7 : mapping of hashable to str, optional
a optional mapping
param8 : ... or Ellipsis
ellipsis
""")
expected = dedent("""\
:param param1: the data to work on
:type param1: DataFrame
:type param1: :class:`DataFrame`
:param param2: a parameter with different types
:type param2: :class:`int` or :class:`float` or :obj:`None`
:type param2: :class:`int` or :class:`float` or :obj:`None`, *optional*
:param param3: a optional mapping
:type param3: :term:`dict-like <mapping>`, *optional*
:param param4: a optional parameter with different types
:type param4: :class:`int` or :class:`float` or :obj:`None`, *optional*
:param param5: a optional parameter with fixed values
:type param5: ``{"F", "C", "N"}``, *optional*
:param param6: different default format
:type param6: :class:`int`, *default* :obj:`None`
:param param7: a optional mapping
:type param7: :term:`mapping` of :term:`hashable` to :class:`str`, *optional*
:param param8: ellipsis
:type param8: :obj:`... <Ellipsis>` or :obj:`Ellipsis`
""")
translations = {
"dict-like": ":term:`dict-like <mapping>`",
"mapping": ":term:`mapping`",
"hashable": ":term:`hashable`",
}
config = Config(
napoleon_use_param=True,
@ -2132,21 +2227,6 @@ definition_after_normal_text : int
actual = str(NumpyDocstring(docstring, config))
self.assertEqual(expected, actual)
def test_keywords_with_types(self):
docstring = """\
Do as you please
Keyword Args:
gotham_is_yours (None): shall interfere.
"""
actual = str(GoogleDocstring(docstring))
expected = """\
Do as you please
:keyword gotham_is_yours: shall interfere.
:kwtype gotham_is_yours: None
"""
self.assertEqual(expected, actual)
@contextmanager
def warns(warning, match):
@ -2182,3 +2262,17 @@ class TestNumpyDocstring:
for token, error in zip(tokens, errors):
with warns(warning, match=error):
_token_type(token)
@pytest.mark.parametrize(
("name", "expected"),
(
("x, y, z", "x, y, z"),
("*args, **kwargs", r"\*args, \*\*kwargs"),
("*x, **y", r"\*x, \*\*y"),
),
)
def test_escape_args_and_kwargs(self, name, expected):
numpy_docstring = NumpyDocstring("")
actual = numpy_docstring._escape_args_and_kwargs(name)
assert actual == expected

View File

@ -19,6 +19,23 @@ def test_trim_doctest_flags_html(app, status, warning):
assert 'BAZ' not in result
assert 'QUX' not in result
assert 'QUUX' not in result
assert 'CORGE' not in result
assert 'GRAULT' in result
@pytest.mark.sphinx('html', testroot='trim_doctest_flags',
confoverrides={'trim_doctest_flags': False})
def test_trim_doctest_flags_disabled(app, status, warning):
app.build()
result = (app.outdir / 'index.html').read_text()
assert 'FOO' in result
assert 'BAR' in result
assert 'BAZ' in result
assert 'QUX' in result
assert 'QUUX' not in result
assert 'CORGE' not in result
assert 'GRAULT' in result
@pytest.mark.sphinx('latex', testroot='trim_doctest_flags')
@ -31,3 +48,5 @@ def test_trim_doctest_flags_latex(app, status, warning):
assert 'BAZ' not in result
assert 'QUX' not in result
assert 'QUUX' not in result
assert 'CORGE' not in result
assert 'GRAULT' in result

View File

@ -90,6 +90,8 @@ def test_format_date():
@pytest.mark.xfail(os.name != 'posix', reason="Path separators don't match on windows")
def test_get_filename_for_language(app):
app.env.temp_data['docname'] = 'index'
# language is None
app.env.config.language = None
assert app.env.config.language is None
@ -145,6 +147,17 @@ def test_get_filename_for_language(app):
with pytest.raises(SphinxError):
i18n.get_image_filename_for_language('foo.png', app.env)
# docpath (for a document in the top of source directory)
app.env.config.language = 'en'
app.env.config.figure_language_filename = '/{docpath}{language}/{basename}{ext}'
assert (i18n.get_image_filename_for_language('foo.png', app.env) ==
'/en/foo.png')
# docpath (for a document in the sub directory)
app.env.temp_data['docname'] = 'subdir/index'
assert (i18n.get_image_filename_for_language('foo.png', app.env) ==
'/subdir/en/foo.png')
def test_CatalogRepository(tempdir):
(tempdir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs()