Merge branch '3.x' into 8200_typealias_break_type_annotation

This commit is contained in:
Takeshi KOMIYA 2020-10-04 22:55:42 +09:00 committed by GitHub
commit d8cdad919b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 141 additions and 23 deletions

12
CHANGES
View File

@ -12,6 +12,8 @@ Deprecated
* ``sphinx.builders.latex.LaTeXBuilder.usepackages``
* ``sphinx.builders.latex.LaTeXBuilder.usepackages_afger_hyperref``
* ``sphinx.ext.autodoc.SingledispatchFunctionDocumenter``
* ``sphinx.ext.autodoc.SingledispatchMethodDocumenter``
Features added
--------------
@ -22,11 +24,14 @@ Features added
nested declarations.
* #8081: LaTeX: Allow to add LaTeX package via ``app.add_latex_package()`` until
just before writing .tex file
* #7996: manpage: Add :confval:`man_make_section_directory` to make a section
directory on build man page
Bugs fixed
----------
* #8085: i18n: Add support for having single text domain
* #6640: i18n: Failed to override system message translation
* #8143: autodoc: AttributeError is raised when False value is passed to
autodoc_default_options
* #8103: autodoc: functools.cached_property is not considered as a property
@ -34,15 +39,22 @@ Bugs fixed
by string not ending with blank lines
* #8142: autodoc: Wrong constructor signature for the class derived from
typing.Generic
* #8157: autodoc: TypeError is raised when annotation has invalid __args__
* #7964: autodoc: Tuple in default value is wrongly rendered
* #8200: autodoc: type aliases break type formatting of autoattribute
* #8192: napoleon: description is disappeared when it contains inline literals
* #8142: napoleon: Potential of regex denial of service in google style docs
* #8169: LaTeX: pxjahyper loaded even when latex_engine is not platex
* #8175: intersphinx: Potential of regex denial of service by broken inventory
* #8277: sphinx-build: missing and redundant spacing (and etc) for console
output on building
* #7973: imgconverter: Check availability of imagemagick many times
* #8093: The highlight warning has wrong location in some builders (LaTeX,
singlehtml and so on)
* #8239: Failed to refer a token in productionlist if it is indented
* #8268: linkcheck: Report HTTP errors when ``linkcheck_anchors`` is ``True``
* #8245: linkcheck: take source directory into account for local files
* #6914: figure numbers are unexpectedly assigned to uncaptioned items
Testing
--------

View File

@ -36,6 +36,16 @@ The following is a list of deprecated interfaces.
- 5.0
- N/A
* - ``sphinx.ext.autodoc.SingledispatchFunctionDocumenter``
- 3.3
- 5.0
- ``sphinx.ext.autodoc.FunctionDocumenter``
* - ``sphinx.ext.autodoc.SingledispatchMethodDocumenter``
- 3.3
- 5.0
- ``sphinx.ext.autodoc.MethodDocumenter``
* - ``sphinx.ext.autodoc.members_set_option()``
- 3.2
- 5.0

View File

@ -2245,6 +2245,12 @@ These options influence manual page output.
.. versionadded:: 1.1
.. confval:: man_make_section_directory
If true, make a section directory on build man page. Default is False.
.. versionadded:: 3.3
.. _texinfo-options:

View File

@ -18,7 +18,7 @@ import warnings
from collections import deque
from io import StringIO
from os import path
from typing import Any, Callable, Dict, IO, List, Tuple, Union
from typing import Any, Callable, Dict, IO, List, Optional, Tuple, Union
from docutils import nodes
from docutils.nodes import Element, TextElement
@ -293,7 +293,10 @@ class Sphinx:
if catalog.domain == 'sphinx' and catalog.is_outdated():
catalog.write_mo(self.config.language)
locale_dirs = [None, path.join(package_dir, 'locale')] + list(repo.locale_dirs)
locale_dirs = [None] # type: List[Optional[str]]
locale_dirs += list(repo.locale_dirs)
locale_dirs += [path.join(package_dir, 'locale')]
self.translator, has_translation = locale.init(locale_dirs, self.config.language)
if has_translation or self.config.language == 'en':
# "en" never needs to be translated

View File

@ -641,7 +641,7 @@ class StandaloneHTMLBuilder(Builder):
def gen_additional_pages(self) -> None:
# additional pages from conf.py
for pagename, template in self.config.html_additional_pages.items():
logger.info(' ' + pagename, nonl=True)
logger.info(pagename + ' ', nonl=True)
self.handle_page(pagename, {}, template)
# the search page
@ -691,7 +691,7 @@ class StandaloneHTMLBuilder(Builder):
'content': content,
'collapse_index': collapse,
}
logger.info(' ' + indexname, nonl=True)
logger.info(indexname + ' ', nonl=True)
self.handle_page(indexname, indexcontext, 'domainindex.html')
def copy_image_files(self) -> None:
@ -785,7 +785,7 @@ class StandaloneHTMLBuilder(Builder):
def copy_static_files(self) -> None:
try:
with progress_message(__('copying static files... ')):
with progress_message(__('copying static files')):
ensuredir(path.join(self.outdir, '_static'))
# prepare context for templates

View File

@ -211,7 +211,7 @@ class CheckExternalLinksBuilder(Builder):
else:
return 'redirected', new_url, 0
def check() -> Tuple[str, str, int]:
def check(docname: str) -> Tuple[str, str, int]:
# check for various conditions without bothering the network
if len(uri) == 0 or uri.startswith(('#', 'mailto:')):
return 'unchecked', '', 0
@ -220,7 +220,8 @@ class CheckExternalLinksBuilder(Builder):
# non supported URI schemes (ex. ftp)
return 'unchecked', '', 0
else:
if path.exists(path.join(self.srcdir, uri)):
srcdir = path.dirname(self.env.doc2path(docname))
if path.exists(path.join(srcdir, uri)):
return 'working', '', 0
else:
for rex in self.to_ignore:
@ -257,7 +258,7 @@ class CheckExternalLinksBuilder(Builder):
uri, docname, lineno = self.wqueue.get()
if uri is None:
break
status, info, code = check()
status, info, code = check(docname)
self.rqueue.put((uri, docname, lineno, status, info, code))
def process_result(self, result: Tuple[str, str, int, str, str, int]) -> None:

View File

@ -24,7 +24,7 @@ from sphinx.util import logging
from sphinx.util import progress_message
from sphinx.util.console import darkgreen # type: ignore
from sphinx.util.nodes import inline_all_toctrees
from sphinx.util.osutil import make_filename_from_project
from sphinx.util.osutil import ensuredir, make_filename_from_project
from sphinx.writers.manpage import ManualPageWriter, ManualPageTranslator
@ -80,7 +80,12 @@ class ManualPageBuilder(Builder):
docsettings.authors = authors
docsettings.section = section
if self.config.man_make_section_directory:
ensuredir(path.join(self.outdir, str(section)))
targetname = '%s/%s.%s' % (section, name, section)
else:
targetname = '%s.%s' % (name, section)
logger.info(darkgreen(targetname) + ' { ', nonl=True)
destination = FileOutput(
destination_path=path.join(self.outdir, targetname),
@ -115,6 +120,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('man_pages', default_man_pages, None)
app.add_config_value('man_show_urls', False, None)
app.add_config_value('man_make_section_directory', False, None)
return {
'version': 'builtin',

View File

@ -224,6 +224,10 @@ class TocTreeCollector(EnvironmentCollector):
def get_figtype(node: Node) -> str:
for domain in env.domains.values():
figtype = domain.get_enumerable_node_type(node)
if domain.name == 'std' and not domain.get_numfig_title(node): # type: ignore
# Skip if uncaptioned node
continue
if figtype:
return figtype

View File

@ -1302,6 +1302,11 @@ class SingledispatchFunctionDocumenter(FunctionDocumenter):
Retained for backwards compatibility, now does the same as the FunctionDocumenter
"""
def __init__(self, *args: Any, **kwargs: Any) -> None:
warnings.warn("%s is deprecated." % self.__class__.__name__,
RemovedInSphinx50Warning, stacklevel=2)
super().__init__(*args, **kwargs)
class DecoratorDocumenter(FunctionDocumenter):
"""
@ -1938,6 +1943,11 @@ class SingledispatchMethodDocumenter(MethodDocumenter):
Retained for backwards compatibility, now does the same as the MethodDocumenter
"""
def __init__(self, *args: Any, **kwargs: Any) -> None:
warnings.warn("%s is deprecated." % self.__class__.__name__,
RemovedInSphinx50Warning, stacklevel=2)
super().__init__(*args, **kwargs)
class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore
"""

View File

@ -106,7 +106,7 @@ class _TranslationProxy(UserString):
translators = defaultdict(NullTranslations) # type: Dict[Tuple[str, str], NullTranslations]
def init(locale_dirs: List[str], language: str,
def init(locale_dirs: List[Optional[str]], language: str,
catalog: str = 'sphinx', namespace: str = 'general') -> Tuple[NullTranslations, bool]:
"""Look for message catalogs in `locale_dirs` and *ensure* that there is at
least a NullTranslations catalog set in `translators`. If called multiple

View File

@ -166,6 +166,20 @@ class _UnparseVisitor(ast.NodeVisitor):
return "{" + ", ".join(self.visit(e) for e in node.elts) + "}"
def visit_Subscript(self, node: ast.Subscript) -> str:
def is_simple_tuple(value: ast.AST) -> bool:
return (
isinstance(value, ast.Tuple) and
bool(value.elts) and
not any(isinstance(elt, ast.Starred) for elt in value.elts)
)
if is_simple_tuple(node.slice):
elts = ", ".join(self.visit(e) for e in node.slice.elts) # type: ignore
return "%s[%s]" % (self.visit(node.value), elts)
elif isinstance(node.slice, ast.Index) and is_simple_tuple(node.slice.value):
elts = ", ".join(self.visit(e) for e in node.slice.value.elts) # type: ignore
return "%s[%s]" % (self.visit(node.value), elts)
else:
return "%s[%s]" % (self.visit(node.value), self.visit(node.slice))
def visit_UnaryOp(self, node: ast.UnaryOp) -> str:
@ -173,7 +187,7 @@ class _UnparseVisitor(ast.NodeVisitor):
def visit_Tuple(self, node: ast.Tuple) -> str:
if node.elts:
return ", ".join(self.visit(e) for e in node.elts)
return "(" + ", ".join(self.visit(e) for e in node.elts) + ")"
else:
return "()"

View File

@ -11,7 +11,7 @@
import os
import re
from math import ceil
from typing import Any, Dict, List, Tuple
from typing import Any, Dict, List, Optional, Tuple
from docutils import nodes
@ -175,6 +175,13 @@ class ImageConverter(BaseImageConverter):
"""
default_priority = 200
#: The converter is available or not. Will be filled at the first call of
#: the build. The result is shared in the same process.
#:
#: .. todo:: This should be refactored not to store the state without class
#: variable.
available = None # type: Optional[bool]
#: A conversion rules the image converter supports.
#: It is represented as a list of pair of source image format (mimetype) and
#: destination one::
@ -187,16 +194,14 @@ class ImageConverter(BaseImageConverter):
conversion_rules = [] # type: List[Tuple[str, str]]
def __init__(self, *args: Any, **kwargs: Any) -> None:
self.available = None # type: bool
# the converter is available or not.
# Will be checked at first conversion
super().__init__(*args, **kwargs)
def match(self, node: nodes.image) -> bool:
if not self.app.builder.supported_image_types:
return False
elif self.available is None:
self.available = self.is_available()
# store the value to the class variable to share it during the build
self.__class__.available = self.is_available()
if not self.available:
return False

View File

@ -109,7 +109,10 @@ def _stringify_py37(annotation: Any) -> str:
return repr(annotation)
if getattr(annotation, '__args__', None):
if qualname == 'Union':
if not isinstance(annotation.__args__, (list, tuple)):
# broken __args__ found
pass
elif qualname == 'Union':
if len(annotation.__args__) > 1 and annotation.__args__[-1] is NoneType:
if len(annotation.__args__) > 2:
args = ', '.join(stringify(a) for a in annotation.__args__[:-1])

View File

@ -30,6 +30,13 @@ def test_all(app, status, warning):
assert 'Footnotes' not in content
@pytest.mark.sphinx('man', testroot='basic',
confoverrides={'man_make_section_directory': True})
def test_man_make_section_directory(app, status, warning):
app.build()
assert (app.outdir / '1' / 'python.1').exists()
@pytest.mark.sphinx('man', testroot='directive-code')
def test_captioned_code_block(app, status, warning):
app.builder.build_all()

View File

@ -14,8 +14,10 @@ import re
import pytest
from babel.messages import pofile, mofile
from babel.messages.catalog import Catalog
from docutils import nodes
from sphinx import locale
from sphinx.testing.util import (
path, etree_parse, strip_escseq,
assert_re_search, assert_not_re_search, assert_startswith, assert_node
@ -1289,3 +1291,30 @@ def test_image_glob_intl_using_figure_language_filename(app):
def getwarning(warnings):
return strip_escseq(warnings.getvalue().replace(os.sep, '/'))
@pytest.mark.sphinx('html', testroot='basic', confoverrides={'language': 'de'})
def test_customize_system_message(make_app, app_params, sphinx_test_tempdir):
try:
# clear translators cache
locale.translators.clear()
# prepare message catalog (.po)
locale_dir = sphinx_test_tempdir / 'basic' / 'locales' / 'de' / 'LC_MESSAGES'
locale_dir.makedirs()
with (locale_dir / 'sphinx.po').open('wb') as f:
catalog = Catalog()
catalog.add('Quick search', 'QUICK SEARCH')
pofile.write_po(f, catalog)
# construct application and convert po file to .mo
args, kwargs = app_params
app = make_app(*args, **kwargs)
assert (locale_dir / 'sphinx.mo').exists()
assert app.translator.gettext('Quick search') == 'QUICK SEARCH'
app.build()
content = (app.outdir / 'index.html').read_text()
assert 'QUICK SEARCH' in content
finally:
locale.translators.clear()

View File

@ -53,7 +53,7 @@ from sphinx.pycode import ast
("+ a", "+ a"), # UAdd
("- 1", "- 1"), # UnaryOp
("- a", "- a"), # USub
("(1, 2, 3)", "1, 2, 3"), # Tuple
("(1, 2, 3)", "(1, 2, 3)"), # Tuple
("()", "()"), # Tuple (empty)
])
def test_unparse(source, expected):

View File

@ -32,6 +32,10 @@ class MyList(List[T]):
pass
class BrokenType:
__args__ = int
def test_stringify():
assert stringify(int) == "int"
assert stringify(str) == "str"
@ -113,3 +117,7 @@ def test_stringify_type_hints_alias():
MyTuple = Tuple[str, str]
assert stringify(MyStr) == "str"
assert stringify(MyTuple) == "Tuple[str, str]" # type: ignore
def test_stringify_broken_type_hints():
assert stringify(BrokenType) == 'test_util_typing.BrokenType'