Merge branch '3.x' into 7901_resolve_types_for_overloaded_funcs

This commit is contained in:
Takeshi KOMIYA
2020-07-12 14:15:36 +09:00
committed by GitHub
18 changed files with 122 additions and 30 deletions

View File

@@ -13,10 +13,14 @@ Deprecated
Features added Features added
-------------- --------------
* #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. * #7853: C and C++, support parameterized GNU style attributes.
* #7888: napoleon: Add aliases Warn and Raise. * #7888: napoleon: Add aliases Warn and Raise.
* C, added :rst:dir:`c:alias` directive for inserting copies * C, added :rst:dir:`c:alias` directive for inserting copies
of existing declarations. of existing declarations.
* #7902: html theme: Add a new option :confval:`globaltoc_maxdepth` to control
the behavior of globaltoc in sidebar
* #7052: add ``:noindexentry:`` to the Python, C, C++, and Javascript domains. * #7052: add ``:noindexentry:`` to the Python, C, C++, and Javascript domains.
Update the documentation to better reflect the relationship between this option Update the documentation to better reflect the relationship between this option
and the ``:noindex:`` option. and the ``:noindex:`` option.
@@ -25,6 +29,8 @@ Bugs fixed
---------- ----------
* #7886: autodoc: TypeError is raised on mocking generic-typed classes * #7886: autodoc: TypeError is raised on mocking generic-typed classes
* #7935: autodoc: function signature is not shown when the function has a
parameter having ``inspect._empty`` as its default value
* #7901: autodoc: type annotations for overloaded functions are not resolved * #7901: autodoc: type annotations for overloaded functions are not resolved
* #7839: autosummary: cannot handle umlauts in function names * #7839: autosummary: cannot handle umlauts in function names
* #7865: autosummary: Failed to extract summary line when abbreviations found * #7865: autosummary: Failed to extract summary line when abbreviations found
@@ -34,6 +40,8 @@ Bugs fixed
* #7715: LaTeX: ``numfig_secnum_depth > 1`` leads to wrong figure links * #7715: LaTeX: ``numfig_secnum_depth > 1`` leads to wrong figure links
* #7846: html theme: XML-invalid files were generated * #7846: html theme: XML-invalid files were generated
* #7894: gettext: Wrong source info is shown when using rst_epilog * #7894: gettext: Wrong source info is shown when using rst_epilog
* #7691: linkcheck: HEAD requests are not used for checking
* #7928: py domain: failed to resolve a type annotation for the attribute
* #7869: :rst:role:`abbr` role without an explanation will show the explanation * #7869: :rst:role:`abbr` role without an explanation will show the explanation
from the previous abbr role from the previous abbr role
* C and C++, removed ``noindex`` directive option as it did * C and C++, removed ``noindex`` directive option as it did

View File

@@ -926,6 +926,15 @@ that use Sphinx's HTMLWriter class.
.. versionadded:: 1.8 .. versionadded:: 1.8
.. confval:: html_codeblock_linenos_style
The style of line numbers for code-blocks.
* ``'table'`` -- display line numbers using ``<table>`` tag (default)
* ``'inline'`` -- display line numbers using ``<span>`` tag
.. versionadded:: 3.2
.. confval:: html_context .. confval:: html_context
A dictionary of values to pass into the template engine's context for all A dictionary of values to pass into the template engine's context for all

View File

@@ -172,6 +172,12 @@ These themes are:
.. versionadded:: 3.1 .. versionadded:: 3.1
- **globaltoc_maxdepth** (int): The maximum depth of the toctree in
``globaltoc.html`` (see :confval:`html_sidebars`). Set it to -1 to allow
unlimited depth. Defaults to the max depth selected in the toctree directive.
.. versionadded:: 3.2
**alabaster** **alabaster**
`Alabaster theme`_ is a modified "Kr" Sphinx theme from @kennethreitz `Alabaster theme`_ is a modified "Kr" Sphinx theme from @kennethreitz
(especially as used in his Requests project), which was itself originally (especially as used in his Requests project), which was itself originally

View File

@@ -26,7 +26,7 @@ from docutils.utils import relative_path
from sphinx import package_dir, __display_version__ from sphinx import package_dir, __display_version__
from sphinx.application import Sphinx from sphinx.application import Sphinx
from sphinx.builders import Builder from sphinx.builders import Builder
from sphinx.config import Config from sphinx.config import Config, ENUM
from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.domains import Domain, Index, IndexEntry from sphinx.domains import Domain, Index, IndexEntry
from sphinx.environment.adapters.asset import ImageAdapter from sphinx.environment.adapters.asset import ImageAdapter
@@ -886,6 +886,8 @@ class StandaloneHTMLBuilder(Builder):
def _get_local_toctree(self, docname: str, collapse: bool = True, **kwargs: Any) -> str: def _get_local_toctree(self, docname: str, collapse: bool = True, **kwargs: Any) -> str:
if 'includehidden' not in kwargs: if 'includehidden' not in kwargs:
kwargs['includehidden'] = False kwargs['includehidden'] = False
if kwargs.get('maxdepth') == '':
kwargs.pop('maxdepth')
return self.render_partial(TocTree(self.env).get_toctree_for( return self.render_partial(TocTree(self.env).get_toctree_for(
docname, self, collapse, **kwargs))['fragment'] docname, self, collapse, **kwargs))['fragment']
@@ -1226,6 +1228,8 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('html_search_scorer', '', None) app.add_config_value('html_search_scorer', '', None)
app.add_config_value('html_scaled_image_link', True, 'html') app.add_config_value('html_scaled_image_link', True, 'html')
app.add_config_value('html_baseurl', '', 'html') app.add_config_value('html_baseurl', '', 'html')
app.add_config_value('html_codeblock_linenos_style', 'table', 'html',
ENUM('table', 'inline'))
app.add_config_value('html_math_renderer', None, 'env') app.add_config_value('html_math_renderer', None, 'env')
app.add_config_value('html4_writer', False, 'html') app.add_config_value('html4_writer', False, 'html')

View File

@@ -77,18 +77,24 @@ ModuleEntry = NamedTuple('ModuleEntry', [('docname', str),
('deprecated', bool)]) ('deprecated', bool)])
def type_to_xref(text: str) -> addnodes.pending_xref: def type_to_xref(text: str, env: BuildEnvironment = None) -> addnodes.pending_xref:
"""Convert a type string to a cross reference node.""" """Convert a type string to a cross reference node."""
if text == 'None': if text == 'None':
reftype = 'obj' reftype = 'obj'
else: else:
reftype = 'class' reftype = 'class'
if env:
kwargs = {'py:module': env.ref_context.get('py:module'),
'py:class': env.ref_context.get('py:class')}
else:
kwargs = {}
return pending_xref('', nodes.Text(text), return pending_xref('', nodes.Text(text),
refdomain='py', reftype=reftype, reftarget=text) refdomain='py', reftype=reftype, reftarget=text, **kwargs)
def _parse_annotation(annotation: str) -> List[Node]: def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Node]:
"""Parse type annotation.""" """Parse type annotation."""
def unparse(node: ast.AST) -> List[Node]: def unparse(node: ast.AST) -> List[Node]:
if isinstance(node, ast.Attribute): if isinstance(node, ast.Attribute):
@@ -130,18 +136,22 @@ def _parse_annotation(annotation: str) -> List[Node]:
else: else:
raise SyntaxError # unsupported syntax raise SyntaxError # unsupported syntax
if env is None:
warnings.warn("The env parameter for _parse_annotation becomes required now.",
RemovedInSphinx50Warning, stacklevel=2)
try: try:
tree = ast_parse(annotation) tree = ast_parse(annotation)
result = unparse(tree) result = unparse(tree)
for i, node in enumerate(result): for i, node in enumerate(result):
if isinstance(node, nodes.Text): if isinstance(node, nodes.Text):
result[i] = type_to_xref(str(node)) result[i] = type_to_xref(str(node), env)
return result return result
except SyntaxError: except SyntaxError:
return [type_to_xref(annotation)] return [type_to_xref(annotation, env)]
def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist: def _parse_arglist(arglist: str, env: BuildEnvironment = None) -> addnodes.desc_parameterlist:
"""Parse a list of arguments using AST parser""" """Parse a list of arguments using AST parser"""
params = addnodes.desc_parameterlist(arglist) params = addnodes.desc_parameterlist(arglist)
sig = signature_from_str('(%s)' % arglist) sig = signature_from_str('(%s)' % arglist)
@@ -167,7 +177,7 @@ def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist:
node += addnodes.desc_sig_name('', param.name) node += addnodes.desc_sig_name('', param.name)
if param.annotation is not param.empty: if param.annotation is not param.empty:
children = _parse_annotation(param.annotation) children = _parse_annotation(param.annotation, env)
node += addnodes.desc_sig_punctuation('', ':') node += addnodes.desc_sig_punctuation('', ':')
node += nodes.Text(' ') node += nodes.Text(' ')
node += addnodes.desc_sig_name('', '', *children) # type: ignore node += addnodes.desc_sig_name('', '', *children) # type: ignore
@@ -415,7 +425,7 @@ class PyObject(ObjectDescription):
signode += addnodes.desc_name(name, name) signode += addnodes.desc_name(name, name)
if arglist: if arglist:
try: try:
signode += _parse_arglist(arglist) signode += _parse_arglist(arglist, self.env)
except SyntaxError: except SyntaxError:
# fallback to parse arglist original parser. # fallback to parse arglist original parser.
# it supports to represent optional arguments (ex. "func(foo [, bar])") # it supports to represent optional arguments (ex. "func(foo [, bar])")
@@ -430,7 +440,7 @@ class PyObject(ObjectDescription):
signode += addnodes.desc_parameterlist() signode += addnodes.desc_parameterlist()
if retann: if retann:
children = _parse_annotation(retann) children = _parse_annotation(retann, self.env)
signode += addnodes.desc_returns(retann, '', *children) signode += addnodes.desc_returns(retann, '', *children)
anno = self.options.get('annotation') anno = self.options.get('annotation')
@@ -626,7 +636,7 @@ class PyVariable(PyObject):
typ = self.options.get('type') typ = self.options.get('type')
if typ: if typ:
annotations = _parse_annotation(typ) annotations = _parse_annotation(typ, self.env)
signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations) signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations)
value = self.options.get('value') value = self.options.get('value')
@@ -872,7 +882,7 @@ class PyAttribute(PyObject):
typ = self.options.get('type') typ = self.options.get('type')
if typ: if typ:
annotations = _parse_annotation(typ) annotations = _parse_annotation(typ, self.env)
signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations) signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations)
value = self.options.get('value') value = self.options.get('value')

View File

@@ -115,7 +115,7 @@ class EventManager:
raise raise
except Exception as exc: except Exception as exc:
raise ExtensionError(__("Handler %r for event %r threw an exception") % raise ExtensionError(__("Handler %r for event %r threw an exception") %
(listener.handler, name)) from exc (listener.handler, name), exc) from exc
return results return results
def emit_firstresult(self, name: str, *args: Any, def emit_firstresult(self, name: str, *args: Any,

View File

@@ -8,4 +8,4 @@
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
#} #}
<h3><a href="{{ pathto(master_doc)|e }}">{{ _('Table of Contents') }}</a></h3> <h3><a href="{{ pathto(master_doc)|e }}">{{ _('Table of Contents') }}</a></h3>
{{ toctree(includehidden=theme_globaltoc_includehidden, collapse=theme_globaltoc_collapse) }} {{ toctree(includehidden=theme_globaltoc_includehidden, collapse=theme_globaltoc_collapse, maxdepth=theme_globaltoc_maxdepth) }}

View File

@@ -12,3 +12,4 @@ body_max_width = 800
navigation_with_keys = False navigation_with_keys = False
globaltoc_collapse = true globaltoc_collapse = true
globaltoc_includehidden = false globaltoc_includehidden = false
globaltoc_maxdepth =

View File

@@ -485,7 +485,13 @@ def signature(subject: Callable, bound_method: bool = False, follow_wrapped: boo
if len(parameters) > 0: if len(parameters) > 0:
parameters.pop(0) parameters.pop(0)
return inspect.Signature(parameters, return_annotation=return_annotation) # To allow to create signature object correctly for pure python functions,
# pass an internal parameter __validate_parameters__=False to Signature
#
# For example, this helps a function having a default value `inspect._empty`.
# refs: https://github.com/sphinx-doc/sphinx/issues/7935
return inspect.Signature(parameters, return_annotation=return_annotation, # type: ignore
__validate_parameters__=False)
def evaluate_signature(sig: inspect.Signature, globalns: Dict = None, localns: Dict = None def evaluate_signature(sig: inspect.Signature, globalns: Dict = None, localns: Dict = None

View File

@@ -124,4 +124,4 @@ def head(url: str, **kwargs: Any) -> requests.Response:
headers.setdefault('User-Agent', useragent_header[0][1]) headers.setdefault('User-Agent', useragent_header[0][1])
with ignore_insecure_warning(**kwargs): with ignore_insecure_warning(**kwargs):
return requests.get(url, **kwargs) return requests.head(url, **kwargs)

View File

@@ -445,6 +445,9 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
else: else:
opts = {} opts = {}
if linenos and self.builder.config.html_codeblock_linenos_style:
linenos = self.builder.config.html_codeblock_linenos_style
highlighted = self.highlighter.highlight_block( highlighted = self.highlighter.highlight_block(
node.rawsource, lang, opts=opts, linenos=linenos, node.rawsource, lang, opts=opts, linenos=linenos,
location=(self.builder.current_docname, node.line), **highlight_args location=(self.builder.current_docname, node.line), **highlight_args

View File

@@ -397,6 +397,9 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
else: else:
opts = {} opts = {}
if linenos and self.builder.config.html_codeblock_linenos_style:
linenos = self.builder.config.html_codeblock_linenos_style
highlighted = self.highlighter.highlight_block( highlighted = self.highlighter.highlight_block(
node.rawsource, lang, opts=opts, linenos=linenos, node.rawsource, lang, opts=opts, linenos=linenos,
location=(self.builder.current_docname, node.line), **highlight_args location=(self.builder.current_docname, node.line), **highlight_args

View File

@@ -0,0 +1,7 @@
.. code-block::
:linenos:
def hello(name)
print("hello", name)
hello("Sphinx")

View File

@@ -1575,3 +1575,21 @@ def test_html_scaled_image_link(app):
assert re.search('\n<img alt="_images/img.png" class="no-scaled-link"' assert re.search('\n<img alt="_images/img.png" class="no-scaled-link"'
' src="_images/img.png" style="[^"]+" />', ' src="_images/img.png" style="[^"]+" />',
context) context)
@pytest.mark.sphinx('html', testroot='reST-code-block',
confoverrides={'html_codeblock_linenos_style': 'table'})
def test_html_codeblock_linenos_style_table(app):
app.build()
content = (app.outdir / 'index.html').read_text()
assert '<div class="linenodiv"><pre>1\n2\n3\n4</pre></div>' in content
@pytest.mark.sphinx('html', testroot='reST-code-block',
confoverrides={'html_codeblock_linenos_style': 'inline'})
def test_html_codeblock_linenos_style_inline(app):
app.build()
content = (app.outdir / 'index.html').read_text()
assert '<span class="lineno">1 </span>' in content

View File

@@ -236,18 +236,18 @@ def test_get_full_qualified_name():
assert domain.get_full_qualified_name(node) == 'module1.Class.func' assert domain.get_full_qualified_name(node) == 'module1.Class.func'
def test_parse_annotation(): def test_parse_annotation(app):
doctree = _parse_annotation("int") doctree = _parse_annotation("int", app.env)
assert_node(doctree, ([pending_xref, "int"],)) assert_node(doctree, ([pending_xref, "int"],))
assert_node(doctree[0], pending_xref, refdomain="py", reftype="class", reftarget="int") assert_node(doctree[0], pending_xref, refdomain="py", reftype="class", reftarget="int")
doctree = _parse_annotation("List[int]") doctree = _parse_annotation("List[int]", app.env)
assert_node(doctree, ([pending_xref, "List"], assert_node(doctree, ([pending_xref, "List"],
[desc_sig_punctuation, "["], [desc_sig_punctuation, "["],
[pending_xref, "int"], [pending_xref, "int"],
[desc_sig_punctuation, "]"])) [desc_sig_punctuation, "]"]))
doctree = _parse_annotation("Tuple[int, int]") doctree = _parse_annotation("Tuple[int, int]", app.env)
assert_node(doctree, ([pending_xref, "Tuple"], assert_node(doctree, ([pending_xref, "Tuple"],
[desc_sig_punctuation, "["], [desc_sig_punctuation, "["],
[pending_xref, "int"], [pending_xref, "int"],
@@ -255,14 +255,14 @@ def test_parse_annotation():
[pending_xref, "int"], [pending_xref, "int"],
[desc_sig_punctuation, "]"])) [desc_sig_punctuation, "]"]))
doctree = _parse_annotation("Tuple[()]") doctree = _parse_annotation("Tuple[()]", app.env)
assert_node(doctree, ([pending_xref, "Tuple"], assert_node(doctree, ([pending_xref, "Tuple"],
[desc_sig_punctuation, "["], [desc_sig_punctuation, "["],
[desc_sig_punctuation, "("], [desc_sig_punctuation, "("],
[desc_sig_punctuation, ")"], [desc_sig_punctuation, ")"],
[desc_sig_punctuation, "]"])) [desc_sig_punctuation, "]"]))
doctree = _parse_annotation("Callable[[int, int], int]") doctree = _parse_annotation("Callable[[int, int], int]", app.env)
assert_node(doctree, ([pending_xref, "Callable"], assert_node(doctree, ([pending_xref, "Callable"],
[desc_sig_punctuation, "["], [desc_sig_punctuation, "["],
[desc_sig_punctuation, "["], [desc_sig_punctuation, "["],
@@ -275,12 +275,11 @@ def test_parse_annotation():
[desc_sig_punctuation, "]"])) [desc_sig_punctuation, "]"]))
# None type makes an object-reference (not a class reference) # None type makes an object-reference (not a class reference)
doctree = _parse_annotation("None") doctree = _parse_annotation("None", app.env)
assert_node(doctree, ([pending_xref, "None"],)) assert_node(doctree, ([pending_xref, "None"],))
assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="None") assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="None")
def test_pyfunction_signature(app): def test_pyfunction_signature(app):
text = ".. py:function:: hello(name: str) -> str" text = ".. py:function:: hello(name: str) -> str"
doctree = restructuredtext.parse(app, text) doctree = restructuredtext.parse(app, text)
@@ -458,14 +457,22 @@ def test_pyobject_prefix(app):
def test_pydata(app): def test_pydata(app):
text = ".. py:data:: var\n" text = (".. py:module:: example\n"
".. py:data:: var\n"
" :type: int\n")
domain = app.env.get_domain('py') domain = app.env.get_domain('py')
doctree = restructuredtext.parse(app, text) doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index, assert_node(doctree, (nodes.target,
[desc, ([desc_signature, desc_name, "var"], addnodes.index,
addnodes.index,
[desc, ([desc_signature, ([desc_addname, "example."],
[desc_name, "var"],
[desc_annotation, (": ",
[pending_xref, "int"])])],
[desc_content, ()])])) [desc_content, ()])]))
assert 'var' in domain.objects assert_node(doctree[3][0][2][1], pending_xref, **{"py:module": "example"})
assert domain.objects['var'] == ('index', 'var', 'data') assert 'example.var' in domain.objects
assert domain.objects['example.var'] == ('index', 'example.var', 'data')
def test_pyfunction(app): def test_pyfunction(app):
@@ -698,6 +705,8 @@ def test_pyattribute(app):
[desc_sig_punctuation, "]"])], [desc_sig_punctuation, "]"])],
[desc_annotation, " = ''"])], [desc_annotation, " = ''"])],
[desc_content, ()])) [desc_content, ()]))
assert_node(doctree[1][1][1][0][1][1], pending_xref, **{"py:class": "Class"})
assert_node(doctree[1][1][1][0][1][3], pending_xref, **{"py:class": "Class"})
assert 'Class.attr' in domain.objects assert 'Class.attr' in domain.objects
assert domain.objects['Class.attr'] == ('index', 'Class.attr', 'attribute') assert domain.objects['Class.attr'] == ('index', 'Class.attr', 'attribute')

View File

@@ -130,7 +130,7 @@ def test_signature_partialmethod():
def test_signature_annotations(): def test_signature_annotations():
from typing_test_data import (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, from typing_test_data import (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10,
f11, f12, f13, f14, f15, f16, f17, f18, f19, f20, Node) f11, f12, f13, f14, f15, f16, f17, f18, f19, f20, f21, Node)
# Class annotations # Class annotations
sig = inspect.signature(f0) sig = inspect.signature(f0)
@@ -214,6 +214,10 @@ def test_signature_annotations():
sig = inspect.signature(f19) sig = inspect.signature(f19)
assert stringify_signature(sig) == '(*args: int, **kwargs: str)' assert stringify_signature(sig) == '(*args: int, **kwargs: str)'
# default value is inspect.Signature.empty
sig = inspect.signature(f21)
assert stringify_signature(sig) == "(arg1='whatever', arg2)"
# type hints by string # type hints by string
sig = inspect.signature(Node.children) sig = inspect.signature(Node.children)
if (3, 5, 0) <= sys.version_info < (3, 5, 3): if (3, 5, 0) <= sys.version_info < (3, 5, 3):

View File

@@ -1,3 +1,4 @@
from inspect import Signature
from numbers import Integral from numbers import Integral
from typing import Any, Dict, List, TypeVar, Union, Callable, Tuple, Optional from typing import Any, Dict, List, TypeVar, Union, Callable, Tuple, Optional
@@ -100,6 +101,9 @@ def f20() -> Optional[Union[int, str]]:
pass pass
def f21(arg1='whatever', arg2=Signature.empty):
pass
class Node: class Node:
def __init__(self, parent: Optional['Node']) -> None: def __init__(self, parent: Optional['Node']) -> None: