mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge tag 'v3.1.1'
This commit is contained in:
30
CHANGES
30
CHANGES
@@ -36,6 +36,33 @@ Bugs fixed
|
|||||||
Testing
|
Testing
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
Release 3.1.1 (released Jun 14, 2020)
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
Incompatible changes
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
* #7808: napoleon: a type for attribute are represented as typed field
|
||||||
|
|
||||||
|
Features added
|
||||||
|
--------------
|
||||||
|
|
||||||
|
* #7807: autodoc: Show detailed warning when type_comment is mismatched with its
|
||||||
|
signature
|
||||||
|
|
||||||
|
Bugs fixed
|
||||||
|
----------
|
||||||
|
|
||||||
|
* #7808: autodoc: Warnings raised on variable and attribute type annotations
|
||||||
|
* #7802: autodoc: EOFError is raised on parallel build
|
||||||
|
* #7821: autodoc: TypeError is raised for overloaded C-ext function
|
||||||
|
* #7805: autodoc: an object which descriptors returns is unexpectedly documented
|
||||||
|
* #7807: autodoc: wrong signature is shown for the function using contextmanager
|
||||||
|
* #7812: autosummary: generates broken stub files if the target code contains
|
||||||
|
an attribute and module that are same name
|
||||||
|
* #7808: napoleon: Warnings raised on variable and attribute type annotations
|
||||||
|
* #7811: sphinx.util.inspect causes circular import problem
|
||||||
|
|
||||||
Release 3.1.0 (released Jun 08, 2020)
|
Release 3.1.0 (released Jun 08, 2020)
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
@@ -194,9 +221,6 @@ Bugs fixed
|
|||||||
* #7763: C and C++, don't crash during display stringification of unary
|
* #7763: C and C++, don't crash during display stringification of unary
|
||||||
expressions and fold expressions.
|
expressions and fold expressions.
|
||||||
|
|
||||||
Release 3.0.5 (in development)
|
|
||||||
==============================
|
|
||||||
|
|
||||||
Release 3.0.4 (released May 27, 2020)
|
Release 3.0.4 (released May 27, 2020)
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|||||||
@@ -592,7 +592,8 @@ class PyVariable(PyObject):
|
|||||||
|
|
||||||
typ = self.options.get('type')
|
typ = self.options.get('type')
|
||||||
if typ:
|
if typ:
|
||||||
signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), type_to_xref(typ))
|
annotations = _parse_annotation(typ)
|
||||||
|
signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations)
|
||||||
|
|
||||||
value = self.options.get('value')
|
value = self.options.get('value')
|
||||||
if value:
|
if value:
|
||||||
@@ -752,7 +753,8 @@ class PyAttribute(PyObject):
|
|||||||
|
|
||||||
typ = self.options.get('type')
|
typ = self.options.get('type')
|
||||||
if typ:
|
if typ:
|
||||||
signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), type_to_xref(typ))
|
annotations = _parse_annotation(typ)
|
||||||
|
signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations)
|
||||||
|
|
||||||
value = self.options.get('value')
|
value = self.options.get('value')
|
||||||
if value:
|
if value:
|
||||||
|
|||||||
@@ -421,9 +421,9 @@ class Documenter:
|
|||||||
if matched:
|
if matched:
|
||||||
args = matched.group(1)
|
args = matched.group(1)
|
||||||
retann = matched.group(2)
|
retann = matched.group(2)
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
logger.warning(__('error while formatting arguments for %s:') %
|
logger.warning(__('error while formatting arguments for %s: %s'),
|
||||||
self.fullname, type='autodoc', exc_info=True)
|
self.fullname, exc, type='autodoc')
|
||||||
args = None
|
args = None
|
||||||
|
|
||||||
result = self.env.events.emit_firstresult('autodoc-process-signature',
|
result = self.env.events.emit_firstresult('autodoc-process-signature',
|
||||||
@@ -790,8 +790,8 @@ class Documenter:
|
|||||||
# parse right now, to get PycodeErrors on parsing (results will
|
# parse right now, to get PycodeErrors on parsing (results will
|
||||||
# be cached anyway)
|
# be cached anyway)
|
||||||
self.analyzer.find_attr_docs()
|
self.analyzer.find_attr_docs()
|
||||||
except PycodeError:
|
except PycodeError as exc:
|
||||||
logger.debug('[autodoc] module analyzer failed:', exc_info=True)
|
logger.debug('[autodoc] module analyzer failed: %s', exc)
|
||||||
# no source file -- e.g. for builtin and C modules
|
# no source file -- e.g. for builtin and C modules
|
||||||
self.analyzer = None
|
self.analyzer = None
|
||||||
# at least add the module.__file__ as a dependency
|
# at least add the module.__file__ as a dependency
|
||||||
@@ -1223,7 +1223,11 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
|||||||
params = list(sig.parameters.values())
|
params = list(sig.parameters.values())
|
||||||
if params[0].annotation is Parameter.empty:
|
if params[0].annotation is Parameter.empty:
|
||||||
params[0] = params[0].replace(annotation=typ)
|
params[0] = params[0].replace(annotation=typ)
|
||||||
func.__signature__ = sig.replace(parameters=params) # type: ignore
|
try:
|
||||||
|
func.__signature__ = sig.replace(parameters=params) # type: ignore
|
||||||
|
except TypeError:
|
||||||
|
# failed to update signature (ex. built-in or extension types)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
class SingledispatchFunctionDocumenter(FunctionDocumenter):
|
class SingledispatchFunctionDocumenter(FunctionDocumenter):
|
||||||
@@ -1815,7 +1819,11 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
|||||||
params = list(sig.parameters.values())
|
params = list(sig.parameters.values())
|
||||||
if params[1].annotation is Parameter.empty:
|
if params[1].annotation is Parameter.empty:
|
||||||
params[1] = params[1].replace(annotation=typ)
|
params[1] = params[1].replace(annotation=typ)
|
||||||
func.__signature__ = sig.replace(parameters=params) # type: ignore
|
try:
|
||||||
|
func.__signature__ = sig.replace(parameters=params) # type: ignore
|
||||||
|
except TypeError:
|
||||||
|
# failed to update signature (ex. built-in or extension types)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
class SingledispatchMethodDocumenter(MethodDocumenter):
|
class SingledispatchMethodDocumenter(MethodDocumenter):
|
||||||
@@ -1903,6 +1911,17 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
|||||||
else:
|
else:
|
||||||
self.add_line(' :annotation: %s' % self.options.annotation, sourcename)
|
self.add_line(' :annotation: %s' % self.options.annotation, sourcename)
|
||||||
|
|
||||||
|
def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]:
|
||||||
|
try:
|
||||||
|
# Disable `autodoc_inherit_docstring` temporarily to avoid to obtain
|
||||||
|
# a docstring from the value which descriptor returns unexpectedly.
|
||||||
|
# ref: https://github.com/sphinx-doc/sphinx/issues/7805
|
||||||
|
orig = self.env.config.autodoc_inherit_docstrings
|
||||||
|
self.env.config.autodoc_inherit_docstrings = False # type: ignore
|
||||||
|
return super().get_doc(encoding, ignore)
|
||||||
|
finally:
|
||||||
|
self.env.config.autodoc_inherit_docstrings = orig # type: ignore
|
||||||
|
|
||||||
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
|
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
|
||||||
if not self._datadescriptor:
|
if not self._datadescriptor:
|
||||||
# if it's not a data descriptor, its docstring is very probably the
|
# if it's not a data descriptor, its docstring is very probably the
|
||||||
|
|||||||
@@ -128,6 +128,9 @@ def update_annotations_using_type_comments(app: Sphinx, obj: Any, bound_method:
|
|||||||
|
|
||||||
if 'return' not in obj.__annotations__:
|
if 'return' not in obj.__annotations__:
|
||||||
obj.__annotations__['return'] = type_sig.return_annotation
|
obj.__annotations__['return'] = type_sig.return_annotation
|
||||||
|
except KeyError as exc:
|
||||||
|
logger.warning(__("Failed to update signature for %r: parameter not found: %s"),
|
||||||
|
obj, exc)
|
||||||
except NotImplementedError as exc: # failed to ast.unparse()
|
except NotImplementedError as exc: # failed to ast.unparse()
|
||||||
logger.warning(__("Failed to parse type_comment for %r: %s"), obj, exc)
|
logger.warning(__("Failed to parse type_comment for %r: %s"), obj, exc)
|
||||||
|
|
||||||
|
|||||||
@@ -168,10 +168,11 @@ class Config:
|
|||||||
**If False**::
|
**If False**::
|
||||||
|
|
||||||
.. attribute:: attr1
|
.. attribute:: attr1
|
||||||
:type: int
|
|
||||||
|
|
||||||
Description of `attr1`
|
Description of `attr1`
|
||||||
|
|
||||||
|
:type: int
|
||||||
|
|
||||||
napoleon_use_param : :obj:`bool` (Defaults to True)
|
napoleon_use_param : :obj:`bool` (Defaults to True)
|
||||||
True to use a ``:param:`` role for each function parameter. False to
|
True to use a ``:param:`` role for each function parameter. False to
|
||||||
use a single ``:parameters:`` role for all the parameters.
|
use a single ``:parameters:`` role for all the parameters.
|
||||||
|
|||||||
@@ -584,12 +584,13 @@ class GoogleDocstring:
|
|||||||
lines.append('.. attribute:: ' + _name)
|
lines.append('.. attribute:: ' + _name)
|
||||||
if self._opt and 'noindex' in self._opt:
|
if self._opt and 'noindex' in self._opt:
|
||||||
lines.append(' :noindex:')
|
lines.append(' :noindex:')
|
||||||
if _type:
|
|
||||||
lines.extend(self._indent([':type: %s' % _type], 3))
|
|
||||||
lines.append('')
|
lines.append('')
|
||||||
|
|
||||||
fields = self._format_field('', '', _desc)
|
fields = self._format_field('', '', _desc)
|
||||||
lines.extend(self._indent(fields, 3))
|
lines.extend(self._indent(fields, 3))
|
||||||
|
if _type:
|
||||||
|
lines.append('')
|
||||||
|
lines.extend(self._indent([':type: %s' % _type], 3))
|
||||||
lines.append('')
|
lines.append('')
|
||||||
if self._config.napoleon_use_ivar:
|
if self._config.napoleon_use_ivar:
|
||||||
lines.append('')
|
lines.append('')
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ from typing import Any, Dict, List, Optional, Tuple
|
|||||||
|
|
||||||
from sphinx.pycode.ast import ast # for py37 or older
|
from sphinx.pycode.ast import ast # for py37 or older
|
||||||
from sphinx.pycode.ast import parse, unparse
|
from sphinx.pycode.ast import parse, unparse
|
||||||
from sphinx.util.inspect import signature_from_ast
|
|
||||||
|
|
||||||
|
|
||||||
comment_re = re.compile('^\\s*#: ?(.*)\r?\n?$')
|
comment_re = re.compile('^\\s*#: ?(.*)\r?\n?$')
|
||||||
@@ -265,6 +264,8 @@ class VariableCommentPicker(ast.NodeVisitor):
|
|||||||
self.finals.append(".".join(qualname))
|
self.finals.append(".".join(qualname))
|
||||||
|
|
||||||
def add_overload_entry(self, func: ast.FunctionDef) -> None:
|
def add_overload_entry(self, func: ast.FunctionDef) -> None:
|
||||||
|
# avoid circular import problem
|
||||||
|
from sphinx.util.inspect import signature_from_ast
|
||||||
qualname = self.get_qualname_for(func.name)
|
qualname = self.get_qualname_for(func.name)
|
||||||
if qualname:
|
if qualname:
|
||||||
overloads = self.overloads.setdefault(".".join(qualname), [])
|
overloads = self.overloads.setdefault(".".join(qualname), [])
|
||||||
|
|||||||
@@ -425,13 +425,28 @@ def split_full_qualified_name(name: str) -> Tuple[str, str]:
|
|||||||
Therefore you need to mock 3rd party modules if needed before
|
Therefore you need to mock 3rd party modules if needed before
|
||||||
calling this function.
|
calling this function.
|
||||||
"""
|
"""
|
||||||
|
from sphinx.util import inspect
|
||||||
|
|
||||||
parts = name.split('.')
|
parts = name.split('.')
|
||||||
for i, part in enumerate(parts, 1):
|
for i, part in enumerate(parts, 1):
|
||||||
try:
|
try:
|
||||||
modname = ".".join(parts[:i])
|
modname = ".".join(parts[:i])
|
||||||
import_module(modname)
|
module = import_module(modname)
|
||||||
|
|
||||||
|
# check the module has a member named as attrname
|
||||||
|
#
|
||||||
|
# Note: This is needed to detect the attribute having the same name
|
||||||
|
# as the module.
|
||||||
|
# ref: https://github.com/sphinx-doc/sphinx/issues/7812
|
||||||
|
attrname = parts[i]
|
||||||
|
if hasattr(module, attrname):
|
||||||
|
value = inspect.safe_getattr(module, attrname)
|
||||||
|
if not inspect.ismodule(value):
|
||||||
|
return ".".join(parts[:i]), ".".join(parts[i:])
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return ".".join(parts[:i - 1]), ".".join(parts[i - 1:])
|
return ".".join(parts[:i - 1]), ".".join(parts[i - 1:])
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
return name, ""
|
return name, ""
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import builtins
|
import builtins
|
||||||
|
import contextlib
|
||||||
import enum
|
import enum
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
@@ -18,7 +19,7 @@ import typing
|
|||||||
import warnings
|
import warnings
|
||||||
from functools import partial, partialmethod
|
from functools import partial, partialmethod
|
||||||
from inspect import ( # NOQA
|
from inspect import ( # NOQA
|
||||||
Parameter, isclass, ismethod, ismethoddescriptor
|
Parameter, isclass, ismethod, ismethoddescriptor, ismodule
|
||||||
)
|
)
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
@@ -404,6 +405,17 @@ def is_builtin_class_method(obj: Any, attr_name: str) -> bool:
|
|||||||
return getattr(builtins, name, None) is cls
|
return getattr(builtins, name, None) is cls
|
||||||
|
|
||||||
|
|
||||||
|
def _should_unwrap(subject: Callable) -> bool:
|
||||||
|
"""Check the function should be unwrapped on getting signature."""
|
||||||
|
if (safe_getattr(subject, '__globals__', None) and
|
||||||
|
subject.__globals__.get('__name__') == 'contextlib' and # type: ignore
|
||||||
|
subject.__globals__.get('__file__') == contextlib.__file__): # type: ignore
|
||||||
|
# contextmanger should be unwrapped
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def signature(subject: Callable, bound_method: bool = False, follow_wrapped: bool = False
|
def signature(subject: Callable, bound_method: bool = False, follow_wrapped: bool = False
|
||||||
) -> inspect.Signature:
|
) -> inspect.Signature:
|
||||||
"""Return a Signature object for the given *subject*.
|
"""Return a Signature object for the given *subject*.
|
||||||
@@ -414,7 +426,10 @@ def signature(subject: Callable, bound_method: bool = False, follow_wrapped: boo
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
signature = inspect.signature(subject, follow_wrapped=follow_wrapped)
|
if _should_unwrap(subject):
|
||||||
|
signature = inspect.signature(subject)
|
||||||
|
else:
|
||||||
|
signature = inspect.signature(subject, follow_wrapped=follow_wrapped)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# follow built-in wrappers up (ex. functools.lru_cache)
|
# follow built-in wrappers up (ex. functools.lru_cache)
|
||||||
signature = inspect.signature(subject)
|
signature = inspect.signature(subject)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ if TYPE_CHECKING:
|
|||||||
from sphinx.builders import Builder
|
from sphinx.builders import Builder
|
||||||
from sphinx.domain import IndexEntry
|
from sphinx.domain import IndexEntry
|
||||||
from sphinx.environment import BuildEnvironment
|
from sphinx.environment import BuildEnvironment
|
||||||
from sphinx.utils.tags import Tags
|
from sphinx.util.tags import Tags
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
# for py32 or above
|
from contextlib import contextmanager
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=None)
|
@lru_cache(maxsize=None)
|
||||||
def slow_function(message, timeout):
|
def slow_function(message, timeout):
|
||||||
"""This function is slow."""
|
"""This function is slow."""
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def feeling_good(x: int, y: int) -> Generator:
|
||||||
|
"""You'll feel better in this context!"""
|
||||||
|
yield
|
||||||
|
|||||||
@@ -681,7 +681,7 @@ def test_pyattribute(app):
|
|||||||
text = (".. py:class:: Class\n"
|
text = (".. py:class:: Class\n"
|
||||||
"\n"
|
"\n"
|
||||||
" .. py:attribute:: attr\n"
|
" .. py:attribute:: attr\n"
|
||||||
" :type: str\n"
|
" :type: Optional[str]\n"
|
||||||
" :value: ''\n")
|
" :value: ''\n")
|
||||||
domain = app.env.get_domain('py')
|
domain = app.env.get_domain('py')
|
||||||
doctree = restructuredtext.parse(app, text)
|
doctree = restructuredtext.parse(app, text)
|
||||||
@@ -694,7 +694,10 @@ def test_pyattribute(app):
|
|||||||
entries=[('single', 'attr (Class attribute)', 'Class.attr', '', None)])
|
entries=[('single', 'attr (Class attribute)', 'Class.attr', '', None)])
|
||||||
assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "attr"],
|
assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "attr"],
|
||||||
[desc_annotation, (": ",
|
[desc_annotation, (": ",
|
||||||
[pending_xref, "str"])],
|
[pending_xref, "Optional"],
|
||||||
|
[desc_sig_punctuation, "["],
|
||||||
|
[pending_xref, "str"],
|
||||||
|
[desc_sig_punctuation, "]"])],
|
||||||
[desc_annotation, " = ''"])],
|
[desc_annotation, " = ''"])],
|
||||||
[desc_content, ()]))
|
[desc_content, ()]))
|
||||||
assert 'Class.attr' in domain.objects
|
assert 'Class.attr' in domain.objects
|
||||||
|
|||||||
@@ -146,3 +146,16 @@ def test_wrapped_function(app):
|
|||||||
' This function is slow.',
|
' This function is slow.',
|
||||||
'',
|
'',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||||
|
def test_wrapped_function_contextmanager(app):
|
||||||
|
actual = do_autodoc(app, 'function', 'target.wrappedfunction.feeling_good')
|
||||||
|
assert list(actual) == [
|
||||||
|
'',
|
||||||
|
'.. py:function:: feeling_good(x: int, y: int) -> Generator',
|
||||||
|
' :module: target.wrappedfunction',
|
||||||
|
'',
|
||||||
|
" You'll feel better in this context!",
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
|||||||
@@ -53,19 +53,22 @@ class NamedtupleSubclassTest(BaseDocstringTest):
|
|||||||
Sample namedtuple subclass
|
Sample namedtuple subclass
|
||||||
|
|
||||||
.. attribute:: attr1
|
.. attribute:: attr1
|
||||||
:type: Arbitrary type
|
|
||||||
|
|
||||||
Quick description of attr1
|
Quick description of attr1
|
||||||
|
|
||||||
|
:type: Arbitrary type
|
||||||
|
|
||||||
.. attribute:: attr2
|
.. attribute:: attr2
|
||||||
:type: Another arbitrary type
|
|
||||||
|
|
||||||
Quick description of attr2
|
Quick description of attr2
|
||||||
|
|
||||||
|
:type: Another arbitrary type
|
||||||
|
|
||||||
.. attribute:: attr3
|
.. attribute:: attr3
|
||||||
:type: Type
|
|
||||||
|
|
||||||
Adds a newline after the type
|
Adds a newline after the type
|
||||||
|
|
||||||
|
:type: Type
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
@@ -409,9 +412,10 @@ Attributes:
|
|||||||
actual = str(GoogleDocstring(docstring))
|
actual = str(GoogleDocstring(docstring))
|
||||||
expected = """\
|
expected = """\
|
||||||
.. attribute:: in_attr
|
.. attribute:: in_attr
|
||||||
:type: :class:`numpy.ndarray`
|
|
||||||
|
|
||||||
super-dooper attribute
|
super-dooper attribute
|
||||||
|
|
||||||
|
:type: :class:`numpy.ndarray`
|
||||||
"""
|
"""
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
@@ -423,9 +427,10 @@ Attributes:
|
|||||||
actual = str(GoogleDocstring(docstring))
|
actual = str(GoogleDocstring(docstring))
|
||||||
expected = """\
|
expected = """\
|
||||||
.. attribute:: in_attr
|
.. attribute:: in_attr
|
||||||
:type: numpy.ndarray
|
|
||||||
|
|
||||||
super-dooper attribute
|
super-dooper attribute
|
||||||
|
|
||||||
|
:type: numpy.ndarray
|
||||||
"""
|
"""
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user