Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/sphinx-doc/sphinx.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES82
-rw-r--r--CONTRIBUTING.rst3
-rw-r--r--doc/extdev/deprecated.rst10
-rw-r--r--sphinx/builders/gettext.py2
-rw-r--r--sphinx/builders/html/__init__.py5
-rw-r--r--sphinx/config.py8
-rw-r--r--sphinx/domains/python.py2
-rw-r--r--sphinx/ext/autodoc/__init__.py225
-rw-r--r--sphinx/ext/autodoc/importer.py101
-rw-r--r--sphinx/ext/napoleon/docstring.py11
-rw-r--r--sphinx/themes/basic/layout.html4
-rw-r--r--sphinx/transforms/post_transforms/images.py4
-rw-r--r--sphinx/util/docfields.py1
-rw-r--r--sphinx/util/inspect.py18
-rw-r--r--sphinx/util/osutil.py5
-rw-r--r--tests/roots/test-ext-autodoc/target/annotations.py8
-rw-r--r--tests/roots/test-ext-autodoc/target/instance_variable.py10
-rw-r--r--tests/roots/test-ext-autodoc/target/slots.py6
-rw-r--r--tests/roots/test-ext-autodoc/target/typed_vars.py3
-rw-r--r--tests/test_build.py4
-rw-r--r--tests/test_domain_py.py47
-rw-r--r--tests/test_ext_autodoc.py33
-rw-r--r--tests/test_ext_autodoc_autoattribute.py28
-rw-r--r--tests/test_ext_autodoc_autoclass.py81
-rw-r--r--tests/test_ext_autodoc_configs.py34
-rw-r--r--tests/test_ext_napoleon_docstring.py70
26 files changed, 711 insertions, 94 deletions
diff --git a/CHANGES b/CHANGES
index 0f392893a..67760a75c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -54,7 +54,7 @@ Bugs fixed
Testing
--------
-Release 3.4.0 (in development)
+Release 3.5.0 (in development)
==============================
Dependencies
@@ -63,6 +63,45 @@ Dependencies
Incompatible changes
--------------------
+Deprecated
+----------
+
+Features added
+--------------
+
+Bugs fixed
+----------
+
+Testing
+--------
+
+Release 3.4.1 (in development)
+==============================
+
+Dependencies
+------------
+
+Incompatible changes
+--------------------
+
+Deprecated
+----------
+
+Features added
+--------------
+
+Bugs fixed
+----------
+
+Testing
+--------
+
+Release 3.4.0 (released Dec 20, 2020)
+=====================================
+
+Incompatible changes
+--------------------
+
* #8105: autodoc: the signature of class constructor will be shown for decorated
classes, not a signature of decorator
@@ -79,7 +118,9 @@ Deprecated
* ``sphinx.ext.autodoc.SlotsAttributeDocumenter``
* ``sphinx.ext.autodoc.TypeVarDocumenter``
* ``sphinx.ext.autodoc.importer._getannotations()``
+* ``sphinx.ext.autodoc.importer._getmro()``
* ``sphinx.pycode.ModuleAnalyzer.parse()``
+* ``sphinx.util.osutil.movefile()``
* ``sphinx.util.requests.is_ssl_error()``
Features added
@@ -98,6 +139,7 @@ Features added
* #8460: autodoc: Support custom types defined by typing.NewType
* #8285: napoleon: Add :confval:`napoleon_attr_annotations` to merge type hints
on source code automatically if any type is specified in docstring
+* #8236: napoleon: Support numpydoc's "Receives" section
* #6914: Add a new event :event:`warn-missing-reference` to custom warning
messages when failed to resolve a cross-reference
* #6914: Emit a detailed warning when failed to resolve a ``:ref:`` reference
@@ -121,49 +163,37 @@ Bugs fixed
attributes
* #8503: autodoc: autoattribute could not create document for a GenericAlias as
class attributes correctly
+* #8534: autodoc: autoattribute could not create document for a commented
+ attribute in alias class
* #8452: autodoc: autodoc_type_aliases doesn't work when autodoc_typehints is
set to "description"
+* #8541: autodoc: autodoc_type_aliases doesn't work for the type annotation to
+ instance attributes
* #8460: autodoc: autodata and autoattribute directives do not display type
information of TypeVars
* #8493: autodoc: references to builtins not working in class aliases
* #8522: autodoc: ``__bool__`` method could be called
+* #8067: autodoc: A typehint for the instance variable having type_comment on
+ super class is not displayed
+* #8545: autodoc: a __slots__ attribute is not documented even having docstring
+* #741: autodoc: inherited-members doesn't work for instance attributes on super
+ class
* #8477: autosummary: non utf-8 reST files are generated when template contains
multibyte characters
* #8501: autosummary: summary extraction splits text after "el at." unexpectedly
+* #8524: html: Wrong url_root has been generated on a document named "index"
* #8419: html search: Do not load ``language_data.js`` in non-search pages
+* #8549: i18n: ``-D gettext_compact=0`` is no longer working
* #8454: graphviz: The layout option for graph and digraph directives don't work
* #8131: linkcheck: Use GET when HEAD requests cause Too Many Redirects, to
accommodate infinite redirect loops on HEAD
* #8437: Makefile: ``make clean`` with empty BUILDDIR is dangerous
+* #8365: py domain: ``:type:`` and ``:rtype:`` gives false ambiguous class
+ lookup warnings
* #8352: std domain: Failed to parse an option that starts with bracket
* #8519: LaTeX: Prevent page brake in the middle of a seealso
-
-Testing
---------
-
-Release 3.3.2 (in development)
-==============================
-
-Dependencies
-------------
-
-Incompatible changes
---------------------
-
-Deprecated
-----------
-
-Features added
---------------
-
-Bugs fixed
-----------
-
* #8520: C, fix copying of AliasNode.
-Testing
---------
-
Release 3.3.1 (released Nov 12, 2020)
=====================================
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index a20192679..bd164694d 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -14,4 +14,5 @@ You can also browse it from this repository from
``doc/internals/contributing.rst``
Sphinx uses GitHub to host source code, track patches and bugs, and more.
-Please make an effort to provide as much possible when filing bugs.
+Please make an effort to provide as much detail as possible when filing
+bugs.
diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst
index 73429938f..363c936bf 100644
--- a/doc/extdev/deprecated.rst
+++ b/doc/extdev/deprecated.rst
@@ -102,11 +102,21 @@ The following is a list of deprecated interfaces.
- 4.0
- ``sphinx.util.inspect.getannotations()``
+ * - ``sphinx.ext.autodoc.importer._getmro()``
+ - 3.4
+ - 4.0
+ - ``sphinx.util.inspect.getmro()``
+
* - ``sphinx.pycode.ModuleAnalyzer.parse()``
- 3.4
- 5.0
- ``sphinx.pycode.ModuleAnalyzer.analyze()``
+ * - ``sphinx.util.osutil.movefile()``
+ - 3.4
+ - 5.0
+ - ``os.replace()``
+
* - ``sphinx.util.requests.is_ssl_error()``
- 3.4
- 5.0
diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py
index 439c8512d..44009fb72 100644
--- a/sphinx/builders/gettext.py
+++ b/sphinx/builders/gettext.py
@@ -290,7 +290,7 @@ class MessageCatalogBuilder(I18nBuilder):
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_builder(MessageCatalogBuilder)
- app.add_config_value('gettext_compact', True, 'gettext', Any)
+ app.add_config_value('gettext_compact', True, 'gettext', {bool, str})
app.add_config_value('gettext_location', True, 'gettext')
app.add_config_value('gettext_uuid', False, 'gettext')
app.add_config_value('gettext_auto_build', True, 'env')
diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py
index 01acc33ed..fd80c4970 100644
--- a/sphinx/builders/html/__init__.py
+++ b/sphinx/builders/html/__init__.py
@@ -9,6 +9,7 @@
"""
import html
+import os
import posixpath
import re
import sys
@@ -43,7 +44,7 @@ from sphinx.util.fileutil import copy_asset
from sphinx.util.i18n import format_date
from sphinx.util.inventory import InventoryFile
from sphinx.util.matching import DOTFILES, Matcher, patmatch
-from sphinx.util.osutil import copyfile, ensuredir, movefile, os_path, relative_uri
+from sphinx.util.osutil import copyfile, ensuredir, os_path, relative_uri
from sphinx.util.tags import Tags
from sphinx.writers.html import HTMLTranslator, HTMLWriter
@@ -1064,7 +1065,7 @@ class StandaloneHTMLBuilder(Builder):
else:
with open(searchindexfn + '.tmp', 'wb') as fb:
self.indexer.dump(fb, self.indexer_format)
- movefile(searchindexfn + '.tmp', searchindexfn)
+ os.replace(searchindexfn + '.tmp', searchindexfn)
def convert_html_css_files(app: Sphinx, config: Config) -> None:
diff --git a/sphinx/config.py b/sphinx/config.py
index 005bc812f..02c70cea0 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -173,6 +173,14 @@ class Config:
defvalue = self.values[name][0]
if self.values[name][2] == Any:
return value
+ elif self.values[name][2] == {bool, str}:
+ if value == '0':
+ # given falsy string from command line option
+ return False
+ elif value == '1':
+ return True
+ else:
+ return value
elif type(defvalue) is bool or self.values[name][2] == [bool]:
if value == '0':
# given falsy string from command line option
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index d71a4891d..3bbf68005 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -271,6 +271,8 @@ class PyXrefMixin:
result = super().make_xref(rolename, domain, target, # type: ignore
innernode, contnode, env)
result['refspecific'] = True
+ result['py:module'] = env.ref_context.get('py:module')
+ result['py:class'] = env.ref_context.get('py:class')
if target.startswith(('.', '~')):
prefix, result['reftarget'] = target[0], target[1:]
if prefix == '.':
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index 5f610a365..eec738a75 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -25,7 +25,8 @@ from sphinx.application import Sphinx
from sphinx.config import ENUM, Config
from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warning
from sphinx.environment import BuildEnvironment
-from sphinx.ext.autodoc.importer import get_module_members, get_object_members, import_object
+from sphinx.ext.autodoc.importer import (get_class_members, get_module_members,
+ get_object_members, import_object)
from sphinx.ext.autodoc.mock import mock
from sphinx.locale import _, __
from sphinx.pycode import ModuleAnalyzer, PycodeError
@@ -270,9 +271,11 @@ class ObjectMember(tuple):
def __new__(cls, name: str, obj: Any, **kwargs: Any) -> Any:
return super().__new__(cls, (name, obj)) # type: ignore
- def __init__(self, name: str, obj: Any, skipped: bool = False) -> None:
+ def __init__(self, name: str, obj: Any, docstring: Optional[str] = None,
+ skipped: bool = False) -> None:
self.__name__ = name
self.object = obj
+ self.docstring = docstring
self.skipped = skipped
@@ -700,6 +703,11 @@ class Documenter:
cls_doc = self.get_attr(cls, '__doc__', None)
if cls_doc == doc:
doc = None
+
+ if isinstance(obj, ObjectMember) and obj.docstring:
+ # hack for ClassDocumenter to inject docstring via ObjectMember
+ doc = obj.docstring
+
has_doc = bool(doc)
metadata = extract_metadata(doc)
@@ -1559,7 +1567,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
self.add_line(' ' + _('Bases: %s') % ', '.join(bases), sourcename)
def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]:
- members = get_object_members(self.object, self.objpath, self.get_attr, self.analyzer)
+ members = get_class_members(self.object, self.objpath, self.get_attr)
if not want_all:
if not self.options.members:
return False, [] # type: ignore
@@ -1567,16 +1575,18 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
selected = []
for name in self.options.members: # type: str
if name in members:
- selected.append((name, members[name].value))
+ selected.append(ObjectMember(name, members[name].value,
+ docstring=members[name].docstring))
else:
logger.warning(__('missing attribute %s in object %s') %
(name, self.fullname), type='autodoc')
return False, selected
elif self.options.inherited_members:
- return False, [(m.name, m.value) for m in members.values()]
+ return False, [ObjectMember(m.name, m.value, docstring=m.docstring)
+ for m in members.values()]
else:
- return False, [(m.name, m.value) for m in members.values()
- if m.directly_defined]
+ return False, [ObjectMember(m.name, m.value, docstring=m.docstring)
+ for m in members.values() if m.class_ == self.object]
def get_doc(self, ignore: int = None) -> List[List[str]]:
if self.doc_as_attr:
@@ -1822,6 +1832,26 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin,
) -> bool:
return isinstance(parent, ModuleDocumenter) and isattr
+ def update_annotations(self, parent: Any) -> None:
+ """Update __annotations__ to support type_comment and so on."""
+ try:
+ annotations = inspect.getannotations(parent)
+
+ analyzer = ModuleAnalyzer.for_module(self.modname)
+ analyzer.analyze()
+ for (classname, attrname), annotation in analyzer.annotations.items():
+ if classname == '' and attrname not in annotations:
+ annotations[attrname] = annotation # type: ignore
+ except AttributeError:
+ pass
+
+ def import_object(self, raiseerror: bool = False) -> bool:
+ ret = super().import_object(raiseerror)
+ if self.parent:
+ self.update_annotations(self.parent)
+
+ return ret
+
def add_directive_header(self, sig: str) -> None:
super().add_directive_header(sig)
sourcename = self.get_sourcename()
@@ -1836,11 +1866,6 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin,
if self.objpath[-1] in annotations:
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
self.add_line(' :type: ' + objrepr, sourcename)
- else:
- key = ('.'.join(self.objpath[:-1]), self.objpath[-1])
- if self.analyzer and key in self.analyzer.annotations:
- self.add_line(' :type: ' + self.analyzer.annotations[key],
- sourcename)
try:
if self.options.no_value or self.should_suppress_value_header():
@@ -2033,6 +2058,28 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
return
+class NonDataDescriptorMixin(DataDocumenterMixinBase):
+ """
+ Mixin for AttributeDocumenter to provide the feature for supporting non
+ data-descriptors.
+
+ .. note:: This mix-in must be inherited after other mix-ins. Otherwise, docstring
+ and :value: header will be suppressed unexpectedly.
+ """
+
+ def should_suppress_value_header(self) -> bool:
+ return (inspect.isattributedescriptor(self.object) or
+ super().should_suppress_directive_header())
+
+ def get_doc(self, ignore: int = None) -> List[List[str]]:
+ if not inspect.isattributedescriptor(self.object):
+ # the docstring of non datadescriptor is very probably the wrong thing
+ # to display
+ return []
+ else:
+ return super().get_doc(ignore) # type: ignore
+
+
class SlotsMixin(DataDocumenterMixinBase):
"""
Mixin for AttributeDocumenter to provide the feature for supporting __slots__.
@@ -2080,8 +2127,96 @@ class SlotsMixin(DataDocumenterMixinBase):
return super().get_doc(ignore) # type: ignore
+class UninitializedInstanceAttributeMixin(DataDocumenterMixinBase):
+ """
+ Mixin for AttributeDocumenter to provide the feature for supporting uninitialized
+ instance attributes (that are defined in __init__() methods with doc-comments).
+
+ Example:
+
+ class Foo:
+ def __init__(self):
+ self.attr = None #: This is a target of this mix-in.
+ """
+
+ def get_attribute_comment(self, parent: Any) -> Optional[List[str]]:
+ try:
+ for cls in inspect.getmro(parent):
+ try:
+ module = safe_getattr(cls, '__module__')
+ qualname = safe_getattr(cls, '__qualname__')
+
+ analyzer = ModuleAnalyzer.for_module(module)
+ analyzer.analyze()
+ if qualname and self.objpath:
+ key = (qualname, self.objpath[-1])
+ if key in analyzer.attr_docs:
+ return list(analyzer.attr_docs[key])
+ except (AttributeError, PycodeError):
+ pass
+ except (AttributeError, PycodeError):
+ pass
+
+ return None
+
+ def is_uninitialized_instance_attribute(self, parent: Any) -> bool:
+ """Check the subject is an attribute defined in __init__()."""
+ # An instance variable defined in __init__().
+ if self.get_attribute_comment(parent):
+ return True
+ else:
+ return False
+
+ def import_object(self, raiseerror: bool = False) -> bool:
+ """Check the exisitence of uninitizlied instance attribute when failed to import
+ the attribute.
+ """
+ try:
+ return super().import_object(raiseerror=True) # type: ignore
+ except ImportError as exc:
+ try:
+ ret = import_object(self.modname, self.objpath[:-1], 'class',
+ attrgetter=self.get_attr, # type: ignore
+ warningiserror=self.config.autodoc_warningiserror)
+ parent = ret[3]
+ if self.is_uninitialized_instance_attribute(parent):
+ self.object = UNINITIALIZED_ATTR
+ self.parent = parent
+ return True
+ except ImportError:
+ pass
+
+ if raiseerror:
+ raise
+ else:
+ logger.warning(exc.args[0], type='autodoc', subtype='import_object')
+ self.env.note_reread()
+ return False
+
+ def should_suppress_value_header(self) -> bool:
+ return (self.object is UNINITIALIZED_ATTR or
+ super().should_suppress_value_header())
+
+ def get_doc(self, ignore: int = None) -> List[List[str]]:
+ if self.object is UNINITIALIZED_ATTR:
+ comment = self.get_attribute_comment(self.parent)
+ if comment:
+ return [comment]
+
+ return super().get_doc(ignore) # type: ignore
+
+ def add_content(self, more_content: Optional[StringList], no_docstring: bool = False
+ ) -> None:
+ if self.object is UNINITIALIZED_ATTR:
+ self.analyzer = None
+
+ super().add_content(more_content, no_docstring=no_docstring) # type: ignore
+
+
class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: ignore
- TypeVarMixin, DocstringStripSignatureMixin, ClassLevelDocumenter):
+ TypeVarMixin, UninitializedInstanceAttributeMixin,
+ NonDataDescriptorMixin, DocstringStripSignatureMixin,
+ ClassLevelDocumenter):
"""
Specialized Documenter subclass for attributes.
"""
@@ -2131,35 +2266,36 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type:
except ImportError:
pass
- # An instance variable defined inside __init__().
+ return False
+
+ def update_annotations(self, parent: Any) -> None:
+ """Update __annotations__ to support type_comment and so on."""
try:
- analyzer = ModuleAnalyzer.for_module(self.modname)
- attr_docs = analyzer.find_attr_docs()
- if self.objpath:
- key = ('.'.join(self.objpath[:-1]), self.objpath[-1])
- if key in attr_docs:
- return True
+ annotations = inspect.getannotations(parent)
- return False
- except PycodeError:
- pass
+ for cls in inspect.getmro(parent):
+ try:
+ module = safe_getattr(cls, '__module__')
+ qualname = safe_getattr(cls, '__qualname__')
- return False
+ analyzer = ModuleAnalyzer.for_module(module)
+ analyzer.analyze()
+ for (classname, attrname), annotation in analyzer.annotations.items():
+ if classname == qualname and attrname not in annotations:
+ annotations[attrname] = annotation # type: ignore
+ except (AttributeError, PycodeError):
+ pass
+ except AttributeError:
+ pass
def import_object(self, raiseerror: bool = False) -> bool:
try:
ret = super().import_object(raiseerror=True)
if inspect.isenumattribute(self.object):
self.object = self.object.value
- if inspect.isattributedescriptor(self.object):
- self._datadescriptor = True
- else:
- # if it's not a data descriptor
- self._datadescriptor = False
except ImportError as exc:
if self.isinstanceattribute():
self.object = INSTANCEATTR
- self._datadescriptor = False
ret = True
elif raiseerror:
raise
@@ -2168,6 +2304,9 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type:
self.env.note_reread()
ret = False
+ if self.parent:
+ self.update_annotations(self.parent)
+
return ret
def get_real_modname(self) -> str:
@@ -2187,27 +2326,19 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type:
if self.objpath[-1] in annotations:
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
self.add_line(' :type: ' + objrepr, sourcename)
- else:
- key = ('.'.join(self.objpath[:-1]), self.objpath[-1])
- if self.analyzer and key in self.analyzer.annotations:
- self.add_line(' :type: ' + self.analyzer.annotations[key],
- sourcename)
- # data descriptors do not have useful values
- if not self._datadescriptor:
- try:
- if self.object is INSTANCEATTR or self.options.no_value:
- pass
- else:
- objrepr = object_description(self.object)
- self.add_line(' :value: ' + objrepr, sourcename)
- except ValueError:
+ try:
+ if (self.object is INSTANCEATTR or self.options.no_value or
+ self.should_suppress_value_header()):
pass
+ else:
+ objrepr = object_description(self.object)
+ self.add_line(' :value: ' + objrepr, sourcename)
+ except ValueError:
+ pass
def get_doc(self, ignore: int = None) -> List[List[str]]:
- if not self._datadescriptor:
- # if it's not a data descriptor, its docstring is very probably the
- # wrong thing to display
+ if self.object is INSTANCEATTR:
return []
try:
diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py
index 8513255c1..b7f2d558b 100644
--- a/sphinx/ext/autodoc/importer.py
+++ b/sphinx/ext/autodoc/importer.py
@@ -13,9 +13,10 @@ import traceback
import warnings
from typing import Any, Callable, Dict, List, NamedTuple, Optional, Tuple
-from sphinx.pycode import ModuleAnalyzer
+from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.util import logging
-from sphinx.util.inspect import getannotations, getslots, isclass, isenumclass, safe_getattr
+from sphinx.util.inspect import (getannotations, getmro, getslots, isclass, isenumclass,
+ safe_getattr)
if False:
# For type annotation
@@ -165,12 +166,9 @@ class Attribute(NamedTuple):
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()
+ warnings.warn('sphinx.ext.autodoc.importer._getmro() is deprecated.',
+ RemovedInSphinx40Warning)
+ return getmro(obj)
def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
@@ -218,7 +216,7 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
continue
# annotation only member (ex. attr: int)
- for i, cls in enumerate(_getmro(subject)):
+ for i, cls in enumerate(getmro(subject)):
try:
for name in getannotations(cls):
name = unmangle(cls, name)
@@ -235,3 +233,88 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
members[name] = Attribute(name, True, INSTANCEATTR)
return members
+
+
+class ClassAttribute:
+ """The attribute of the class."""
+
+ def __init__(self, cls: Any, name: str, value: Any, docstring: Optional[str] = None):
+ self.class_ = cls
+ self.name = name
+ self.value = value
+ self.docstring = docstring
+
+
+def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable
+ ) -> Dict[str, ClassAttribute]:
+ """Get members and attributes of target class."""
+ from sphinx.ext.autodoc import INSTANCEATTR
+
+ # the members directly defined in the class
+ obj_dict = attrgetter(subject, '__dict__', {})
+
+ members = {} # type: Dict[str, ClassAttribute]
+
+ # enum members
+ if isenumclass(subject):
+ for name, value in subject.__members__.items():
+ if name not in members:
+ members[name] = ClassAttribute(subject, name, value)
+
+ superclass = subject.__mro__[1]
+ for name in obj_dict:
+ if name not in superclass.__dict__:
+ value = safe_getattr(subject, name)
+ members[name] = ClassAttribute(subject, name, value)
+
+ # members in __slots__
+ try:
+ __slots__ = getslots(subject)
+ if __slots__:
+ from sphinx.ext.autodoc import SLOTSATTR
+
+ for name, docstring in __slots__.items():
+ members[name] = ClassAttribute(subject, name, SLOTSATTR, docstring)
+ except (AttributeError, TypeError, ValueError):
+ pass
+
+ # other members
+ for name in dir(subject):
+ try:
+ value = attrgetter(subject, name)
+ unmangled = unmangle(subject, name)
+ if unmangled and unmangled not in members:
+ if name in obj_dict:
+ members[unmangled] = ClassAttribute(subject, unmangled, value)
+ else:
+ members[unmangled] = ClassAttribute(None, unmangled, value)
+ except AttributeError:
+ continue
+
+ try:
+ for cls in getmro(subject):
+ # annotation only member (ex. attr: int)
+ try:
+ for name in getannotations(cls):
+ name = unmangle(cls, name)
+ if name and name not in members:
+ members[name] = ClassAttribute(cls, name, INSTANCEATTR)
+ except AttributeError:
+ pass
+
+ # append instance attributes (cf. self.attr1) if analyzer knows
+ try:
+ modname = safe_getattr(cls, '__module__')
+ qualname = safe_getattr(cls, '__qualname__')
+ analyzer = ModuleAnalyzer.for_module(modname)
+ analyzer.analyze()
+ for (ns, name), docstring in analyzer.attr_docs.items():
+ if ns == qualname and name not in members:
+ members[name] = ClassAttribute(cls, name, INSTANCEATTR,
+ '\n'.join(docstring))
+ except (AttributeError, PycodeError):
+ pass
+ except AttributeError:
+ pass
+
+ return members
diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py
index 2cadcdcb3..5f799e9cf 100644
--- a/sphinx/ext/napoleon/docstring.py
+++ b/sphinx/ext/napoleon/docstring.py
@@ -174,6 +174,8 @@ class GoogleDocstring:
'notes': self._parse_notes_section,
'other parameters': self._parse_other_parameters_section,
'parameters': self._parse_parameters_section,
+ 'receive': self._parse_receives_section,
+ 'receives': self._parse_receives_section,
'return': self._parse_returns_section,
'returns': self._parse_returns_section,
'raise': self._parse_raises_section,
@@ -709,6 +711,15 @@ class GoogleDocstring:
lines.append('')
return lines
+ def _parse_receives_section(self, section: str) -> List[str]:
+ 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(_('Receives'), fields)
+
def _parse_references_section(self, section: str) -> List[str]:
use_admonition = self._config.napoleon_use_admonition_for_references
return self._parse_generic_section(_('References'), use_admonition)
diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html
index 131d2c533..a7604ba8d 100644
--- a/sphinx/themes/basic/layout.html
+++ b/sphinx/themes/basic/layout.html
@@ -18,7 +18,7 @@
{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and
(sidebars != []) %}
{%- set url_root = pathto('', 1) %}
-{# XXX necessary? #}
+{# URL root should never be #, then all links are fragments #}
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
{%- if not embedded and docstitle %}
{%- set titlesuffix = " — "|safe + docstitle|e %}
@@ -88,7 +88,7 @@
{%- endmacro %}
{%- macro script() %}
- <script id="documentation_options" data-url_root="{{ pathto('', 1) }}" src="{{ pathto('_static/documentation_options.js', 1) }}"></script>
+ <script id="documentation_options" data-url_root="{{ url_root }}" src="{{ pathto('_static/documentation_options.js', 1) }}"></script>
{%- for js in script_files %}
{{ js_tag(js) }}
{%- endfor %}
diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py
index 4865aa615..a70023436 100644
--- a/sphinx/transforms/post_transforms/images.py
+++ b/sphinx/transforms/post_transforms/images.py
@@ -20,7 +20,7 @@ from sphinx.locale import __
from sphinx.transforms import SphinxTransform
from sphinx.util import epoch_to_rfc1123, logging, requests, rfc1123_to_epoch, sha1
from sphinx.util.images import get_image_extension, guess_mimetype, parse_data_uri
-from sphinx.util.osutil import ensuredir, movefile
+from sphinx.util.osutil import ensuredir
logger = logging.getLogger(__name__)
@@ -99,7 +99,7 @@ class ImageDownloader(BaseImageConverter):
# append a suffix if URI does not contain suffix
ext = get_image_extension(mimetype)
newpath = os.path.join(self.imagedir, dirname, basename + ext)
- movefile(path, newpath)
+ os.replace(path, newpath)
self.app.env.original_image_uri.pop(path)
self.app.env.original_image_uri[newpath] = node['uri']
path = newpath
diff --git a/sphinx/util/docfields.py b/sphinx/util/docfields.py
index 3fa76271f..82463ad0e 100644
--- a/sphinx/util/docfields.py
+++ b/sphinx/util/docfields.py
@@ -271,6 +271,7 @@ class DocFieldTransformer:
self.directive.domain,
target,
contnode=content[0],
+ env=self.directive.state.document.settings.env
)
if _is_single_paragraph(field_body):
paragraph = cast(nodes.paragraph, field_body[0])
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index d390da9fc..aa0d4aca6 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -20,7 +20,7 @@ import warnings
from functools import partial, partialmethod
from inspect import Parameter, isclass, ismethod, ismethoddescriptor, ismodule # NOQA
from io import StringIO
-from typing import Any, Callable, Dict, Mapping, Optional, Sequence, cast
+from typing import Any, Callable, Dict, Mapping, Optional, Sequence, Tuple, cast
from sphinx.deprecation import RemovedInSphinx50Warning
from sphinx.pycode.ast import ast # for py36-37
@@ -36,6 +36,10 @@ else:
MethodDescriptorType = type(str.join)
WrapperDescriptorType = type(dict.__dict__['fromkeys'])
+if False:
+ # For type annotation
+ from typing import Type # NOQA
+
logger = logging.getLogger(__name__)
memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE)
@@ -166,6 +170,18 @@ def getannotations(obj: Any) -> Mapping[str, Any]:
return {}
+def getmro(obj: Any) -> Tuple["Type", ...]:
+ """Get __mro__ from given *obj* safely.
+
+ Raises AttributeError if given *obj* raises an error on accessing __mro__.
+ """
+ __mro__ = safe_getattr(obj, '__mro__', None)
+ if isinstance(__mro__, tuple):
+ return __mro__
+ else:
+ return tuple()
+
+
def getslots(obj: Any) -> Optional[Dict]:
"""Get __slots__ attribute of the class as dict.
diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py
index 153808186..4672d5f04 100644
--- a/sphinx/util/osutil.py
+++ b/sphinx/util/osutil.py
@@ -18,6 +18,8 @@ from io import StringIO
from os import path
from typing import Any, Generator, Iterator, List, Optional, Type
+from sphinx.deprecation import RemovedInSphinx50Warning
+
try:
# for ALT Linux (#6712)
from sphinx.testing.path import path as Path
@@ -83,6 +85,9 @@ def mtimes_of_files(dirnames: List[str], suffix: str) -> Iterator[float]:
def movefile(source: str, dest: str) -> None:
"""Move a file, removing the destination if it exists."""
+ warnings.warn('sphinx.util.osutil.movefile() is deprecated for removal. '
+ 'Please use os.replace() instead.',
+ RemovedInSphinx50Warning, stacklevel=2)
if os.path.exists(dest):
try:
os.unlink(dest)
diff --git a/tests/roots/test-ext-autodoc/target/annotations.py b/tests/roots/test-ext-autodoc/target/annotations.py
index e9ff2f604..ef600e2af 100644
--- a/tests/roots/test-ext-autodoc/target/annotations.py
+++ b/tests/roots/test-ext-autodoc/target/annotations.py
@@ -7,6 +7,9 @@ myint = int
#: docstring
variable: myint
+#: docstring
+variable2 = None # type: myint
+
def sum(x: myint, y: myint) -> myint:
"""docstring"""
@@ -32,4 +35,7 @@ class Foo:
"""docstring"""
#: docstring
- attr: myint
+ attr1: myint
+
+ def __init__(self):
+ self.attr2: myint = None #: docstring
diff --git a/tests/roots/test-ext-autodoc/target/instance_variable.py b/tests/roots/test-ext-autodoc/target/instance_variable.py
new file mode 100644
index 000000000..ae86d1edb
--- /dev/null
+++ b/tests/roots/test-ext-autodoc/target/instance_variable.py
@@ -0,0 +1,10 @@
+class Foo:
+ def __init__(self):
+ self.attr1 = None #: docstring foo
+ self.attr2 = None #: docstring foo
+
+
+class Bar(Foo):
+ def __init__(self):
+ self.attr2 = None #: docstring bar
+ self.attr3 = None #: docstring bar
diff --git a/tests/roots/test-ext-autodoc/target/slots.py b/tests/roots/test-ext-autodoc/target/slots.py
index 144f97c95..32822fd38 100644
--- a/tests/roots/test-ext-autodoc/target/slots.py
+++ b/tests/roots/test-ext-autodoc/target/slots.py
@@ -1,8 +1,12 @@
class Foo:
+ """docstring"""
+
__slots__ = ['attr']
class Bar:
+ """docstring"""
+
__slots__ = {'attr1': 'docstring of attr1',
'attr2': 'docstring of attr2',
'attr3': None}
@@ -12,4 +16,6 @@ class Bar:
class Baz:
+ """docstring"""
+
__slots__ = 'attr'
diff --git a/tests/roots/test-ext-autodoc/target/typed_vars.py b/tests/roots/test-ext-autodoc/target/typed_vars.py
index ba9657f18..f909b80f1 100644
--- a/tests/roots/test-ext-autodoc/target/typed_vars.py
+++ b/tests/roots/test-ext-autodoc/target/typed_vars.py
@@ -29,3 +29,6 @@ class Class:
class Derived(Class):
attr7: int
+
+
+Alias = Derived
diff --git a/tests/test_build.py b/tests/test_build.py
index 9dcf78165..756a56e6d 100644
--- a/tests/test_build.py
+++ b/tests/test_build.py
@@ -36,7 +36,11 @@ def nonascii_srcdir(request, rootdir, sphinx_test_tempdir):
if not srcdir.exists():
(rootdir / 'test-root').copytree(srcdir)
except UnicodeEncodeError:
+ # Now Python 3.7+ follows PEP-540 and uses utf-8 encoding for filesystem by default.
+ # So this error handling will be no longer used (after dropping python 3.6 support).
srcdir = basedir / 'all'
+ if not srcdir.exists():
+ (rootdir / 'test-root').copytree(srcdir)
else:
# add a doc with a non-ASCII file name to the source dir
(srcdir / (test_name + '.txt')).write_text(dedent("""
diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py
index ce4b49bc5..56a986a91 100644
--- a/tests/test_domain_py.py
+++ b/tests/test_domain_py.py
@@ -791,6 +791,53 @@ def test_canonical(app):
assert domain.objects['_io.StringIO'] == ('index', 'io.StringIO', 'class', True)
+def test_info_field_list(app):
+ text = (".. py:module:: example\n"
+ ".. py:class:: Class\n"
+ "\n"
+ " :param str name: blah blah\n"
+ " :param age: blah blah\n"
+ " :type age: int\n")
+ doctree = restructuredtext.parse(app, text)
+ print(doctree)
+
+ assert_node(doctree, (nodes.target,
+ addnodes.index,
+ addnodes.index,
+ [desc, ([desc_signature, ([desc_annotation, "class "],
+ [desc_addname, "example."],
+ [desc_name, "Class"])],
+ [desc_content, nodes.field_list, nodes.field])]))
+ assert_node(doctree[3][1][0][0],
+ ([nodes.field_name, "Parameters"],
+ [nodes.field_body, nodes.bullet_list, ([nodes.list_item, nodes.paragraph],
+ [nodes.list_item, nodes.paragraph])]))
+
+ # :param str name:
+ assert_node(doctree[3][1][0][0][1][0][0][0],
+ ([addnodes.literal_strong, "name"],
+ " (",
+ [pending_xref, addnodes.literal_emphasis, "str"],
+ ")",
+ " -- ",
+ "blah blah"))
+ assert_node(doctree[3][1][0][0][1][0][0][0][2], pending_xref,
+ refdomain="py", reftype="class", reftarget="str",
+ **{"py:module": "example", "py:class": "Class"})
+
+ # :param age: + :type age:
+ assert_node(doctree[3][1][0][0][1][0][1][0],
+ ([addnodes.literal_strong, "age"],
+ " (",
+ [pending_xref, addnodes.literal_emphasis, "int"],
+ ")",
+ " -- ",
+ "blah blah"))
+ assert_node(doctree[3][1][0][0][1][0][1][0][2], pending_xref,
+ refdomain="py", reftype="class", reftarget="int",
+ **{"py:module": "example", "py:class": "Class"})
+
+
@pytest.mark.sphinx(freshenv=True)
def test_module_index(app):
text = (".. py:module:: docutils\n"
diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py
index ae280a627..9e1d9961f 100644
--- a/tests/test_ext_autodoc.py
+++ b/tests/test_ext_autodoc.py
@@ -1171,6 +1171,8 @@ def test_slots(app):
'.. py:class:: Bar()',
' :module: target.slots',
'',
+ ' docstring',
+ '',
'',
' .. py:attribute:: Bar.attr1',
' :module: target.slots',
@@ -1191,6 +1193,8 @@ def test_slots(app):
'.. py:class:: Baz()',
' :module: target.slots',
'',
+ ' docstring',
+ '',
'',
' .. py:attribute:: Baz.attr',
' :module: target.slots',
@@ -1199,6 +1203,8 @@ def test_slots(app):
'.. py:class:: Foo()',
' :module: target.slots',
'',
+ ' docstring',
+ '',
'',
' .. py:attribute:: Foo.attr',
' :module: target.slots',
@@ -1559,6 +1565,11 @@ def test_autodoc_typed_instance_variables(app):
'.. py:module:: target.typed_vars',
'',
'',
+ '.. py:attribute:: Alias',
+ ' :module: target.typed_vars',
+ '',
+ ' alias of :class:`target.typed_vars.Derived`',
+ '',
'.. py:class:: Class()',
' :module: target.typed_vars',
'',
@@ -1667,9 +1678,31 @@ def test_autodoc_typed_inherited_instance_variables(app):
'',
' .. py:attribute:: Derived.attr3',
' :module: target.typed_vars',
+ ' :type: int',
' :value: 0',
'',
'',
+ ' .. py:attribute:: Derived.attr4',
+ ' :module: target.typed_vars',
+ ' :type: int',
+ '',
+ ' attr4',
+ '',
+ '',
+ ' .. py:attribute:: Derived.attr5',
+ ' :module: target.typed_vars',
+ ' :type: int',
+ '',
+ ' attr5',
+ '',
+ '',
+ ' .. py:attribute:: Derived.attr6',
+ ' :module: target.typed_vars',
+ ' :type: int',
+ '',
+ ' attr6',
+ '',
+ '',
' .. py:attribute:: Derived.attr7',
' :module: target.typed_vars',
' :type: int',
diff --git a/tests/test_ext_autodoc_autoattribute.py b/tests/test_ext_autodoc_autoattribute.py
index e44395ee3..7f79ca332 100644
--- a/tests/test_ext_autodoc_autoattribute.py
+++ b/tests/test_ext_autodoc_autoattribute.py
@@ -59,6 +59,19 @@ def test_autoattribute_typed_variable(app):
@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autoattribute_typed_variable_in_alias(app):
+ actual = do_autodoc(app, 'attribute', 'target.typed_vars.Alias.attr2')
+ assert list(actual) == [
+ '',
+ '.. py:attribute:: Alias.attr2',
+ ' :module: target.typed_vars',
+ ' :type: int',
+ '',
+ ]
+
+
+@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autoattribute_instance_variable(app):
actual = do_autodoc(app, 'attribute', 'target.typed_vars.Class.attr4')
assert list(actual) == [
@@ -72,6 +85,21 @@ def test_autoattribute_instance_variable(app):
]
+@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autoattribute_instance_variable_in_alias(app):
+ actual = do_autodoc(app, 'attribute', 'target.typed_vars.Alias.attr4')
+ assert list(actual) == [
+ '',
+ '.. py:attribute:: Alias.attr4',
+ ' :module: target.typed_vars',
+ ' :type: int',
+ '',
+ ' attr4',
+ '',
+ ]
+
+
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autoattribute_slots_variable_list(app):
actual = do_autodoc(app, 'attribute', 'target.slots.Foo.attr')
diff --git a/tests/test_ext_autodoc_autoclass.py b/tests/test_ext_autodoc_autoclass.py
index 17c7f8944..6130f233a 100644
--- a/tests/test_ext_autodoc_autoclass.py
+++ b/tests/test_ext_autodoc_autoclass.py
@@ -51,6 +51,61 @@ def test_classes(app):
]
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_instance_variable(app):
+ options = {'members': True}
+ actual = do_autodoc(app, 'class', 'target.instance_variable.Bar', options)
+ assert list(actual) == [
+ '',
+ '.. py:class:: Bar()',
+ ' :module: target.instance_variable',
+ '',
+ '',
+ ' .. py:attribute:: Bar.attr2',
+ ' :module: target.instance_variable',
+ '',
+ ' docstring bar',
+ '',
+ '',
+ ' .. py:attribute:: Bar.attr3',
+ ' :module: target.instance_variable',
+ '',
+ ' docstring bar',
+ '',
+ ]
+
+
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_inherited_instance_variable(app):
+ options = {'members': True,
+ 'inherited-members': True}
+ actual = do_autodoc(app, 'class', 'target.instance_variable.Bar', options)
+ assert list(actual) == [
+ '',
+ '.. py:class:: Bar()',
+ ' :module: target.instance_variable',
+ '',
+ '',
+ ' .. py:attribute:: Bar.attr1',
+ ' :module: target.instance_variable',
+ '',
+ ' docstring foo',
+ '',
+ '',
+ ' .. py:attribute:: Bar.attr2',
+ ' :module: target.instance_variable',
+ '',
+ ' docstring bar',
+ '',
+ '',
+ ' .. py:attribute:: Bar.attr3',
+ ' :module: target.instance_variable',
+ '',
+ ' docstring bar',
+ '',
+ ]
+
+
def test_decorators(app):
actual = do_autodoc(app, 'class', 'target.decorator.Baz')
assert list(actual) == [
@@ -77,6 +132,32 @@ def test_decorators(app):
]
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_slots_attribute(app):
+ options = {"members": None}
+ actual = do_autodoc(app, 'class', 'target.slots.Bar', options)
+ assert list(actual) == [
+ '',
+ '.. py:class:: Bar()',
+ ' :module: target.slots',
+ '',
+ ' docstring',
+ '',
+ '',
+ ' .. py:attribute:: Bar.attr1',
+ ' :module: target.slots',
+ '',
+ ' docstring of attr1',
+ '',
+ '',
+ ' .. py:attribute:: Bar.attr2',
+ ' :module: target.slots',
+ '',
+ ' docstring of instance attr2',
+ '',
+ ]
+
+
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_show_inheritance_for_subclass_of_generic_type(app):
diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py
index d76ed25e3..ac0a2c11c 100644
--- a/tests/test_ext_autodoc_configs.py
+++ b/tests/test_ext_autodoc_configs.py
@@ -707,7 +707,14 @@ def test_autodoc_type_aliases(app):
' docstring',
'',
'',
- ' .. py:attribute:: Foo.attr',
+ ' .. py:attribute:: Foo.attr1',
+ ' :module: target.annotations',
+ ' :type: int',
+ '',
+ ' docstring',
+ '',
+ '',
+ ' .. py:attribute:: Foo.attr2',
' :module: target.annotations',
' :type: int',
'',
@@ -733,6 +740,14 @@ def test_autodoc_type_aliases(app):
'',
' docstring',
'',
+ '',
+ '.. py:data:: variable2',
+ ' :module: target.annotations',
+ ' :type: int',
+ ' :value: None',
+ '',
+ ' docstring',
+ '',
]
# define aliases
@@ -749,7 +764,14 @@ def test_autodoc_type_aliases(app):
' docstring',
'',
'',
- ' .. py:attribute:: Foo.attr',
+ ' .. py:attribute:: Foo.attr1',
+ ' :module: target.annotations',
+ ' :type: myint',
+ '',
+ ' docstring',
+ '',
+ '',
+ ' .. py:attribute:: Foo.attr2',
' :module: target.annotations',
' :type: myint',
'',
@@ -775,6 +797,14 @@ def test_autodoc_type_aliases(app):
'',
' docstring',
'',
+ '',
+ '.. py:data:: variable2',
+ ' :module: target.annotations',
+ ' :type: myint',
+ ' :value: None',
+ '',
+ ' docstring',
+ '',
]
diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py
index 38ff6e79c..e6127fe55 100644
--- a/tests/test_ext_napoleon_docstring.py
+++ b/tests/test_ext_napoleon_docstring.py
@@ -303,6 +303,34 @@ class GoogleDocstringTest(BaseDocstringTest):
"""
Single line summary
+ Receive:
+ arg1 (list(int)): Description
+ arg2 (list[int]): Description
+ """,
+ """
+ Single line summary
+
+ :Receives: * **arg1** (*list(int)*) -- Description
+ * **arg2** (*list[int]*) -- Description
+ """
+ ), (
+ """
+ Single line summary
+
+ Receives:
+ arg1 (list(int)): Description
+ arg2 (list[int]): Description
+ """,
+ """
+ Single line summary
+
+ :Receives: * **arg1** (*list(int)*) -- Description
+ * **arg2** (*list[int]*) -- Description
+ """
+ ), (
+ """
+ Single line summary
+
Yield:
str:Extended
description of yielded value
@@ -1263,6 +1291,48 @@ class NumpyDocstringTest(BaseDocstringTest):
"""
Single line summary
+ Receive
+ -------
+ arg1:str
+ Extended
+ description of arg1
+ arg2 : int
+ Extended
+ description of arg2
+ """,
+ """
+ Single line summary
+
+ :Receives: * **arg1** (:class:`str`) -- Extended
+ description of arg1
+ * **arg2** (:class:`int`) -- Extended
+ description of arg2
+ """
+ ), (
+ """
+ Single line summary
+
+ Receives
+ --------
+ arg1:str
+ Extended
+ description of arg1
+ arg2 : int
+ Extended
+ description of arg2
+ """,
+ """
+ Single line summary
+
+ :Receives: * **arg1** (:class:`str`) -- Extended
+ description of arg1
+ * **arg2** (:class:`int`) -- Extended
+ description of arg2
+ """
+ ), (
+ """
+ Single line summary
+
Yield
-----
str