Merge branch '3.x' into 8172_napoleon_redos

This commit is contained in:
Takeshi KOMIYA
2020-09-28 01:15:40 +09:00
committed by GitHub
16 changed files with 178 additions and 39 deletions

14
CHANGES
View File

@@ -10,17 +10,31 @@ Incompatible changes
Deprecated Deprecated
---------- ----------
* ``sphinx.builders.latex.LaTeXBuilder.usepackages``
* ``sphinx.builders.latex.LaTeXBuilder.usepackages_afger_hyperref``
Features added Features added
-------------- --------------
* #8100: html: Show a better error message for failures on copying
html_static_files
* #8141: C: added a ``maxdepth`` option to :rst:dir:`c:alias` to insert
nested declarations.
* #8081: LaTeX: Allow to add LaTeX package via ``app.add_latex_package()`` until
just before writing .tex file
Bugs fixed Bugs fixed
---------- ----------
* #8085: i18n: Add support for having single text domain * #8085: i18n: Add support for having single text domain
* #8143: autodoc: AttributeError is raised when False value is passed to * #8143: autodoc: AttributeError is raised when False value is passed to
autodoc_default_options autodoc_default_options
* #8103: autodoc: functools.cached_property is not considered as a property
* #8190: autodoc: parsing error is raised if some extension replaces docstring
by string not ending with blank lines
* #8192: napoleon: description is disappeared when it contains inline literals * #8192: napoleon: description is disappeared when it contains inline literals
* #8172: napoleon: Potential of regex denial of service in google style docs * #8172: napoleon: Potential of regex denial of service in google style docs
* #8169: LaTeX: pxjahyper loaded even when latex_engine is not platex
* #8093: The highlight warning has wrong location in some builders (LaTeX, * #8093: The highlight warning has wrong location in some builders (LaTeX,
singlehtml and so on) singlehtml and so on)

View File

@@ -330,6 +330,7 @@ Documentation using a custom theme or integrated in a website
* `Lasso <http://lassoguide.com/>`__ * `Lasso <http://lassoguide.com/>`__
* `Mako <http://docs.makotemplates.org/>`__ * `Mako <http://docs.makotemplates.org/>`__
* `MirrorBrain <http://mirrorbrain.org/docs/>`__ * `MirrorBrain <http://mirrorbrain.org/docs/>`__
* `Mitiq <https://mitiq.readthedocs.io/>`__
* `MongoDB <https://docs.mongodb.com/>`__ * `MongoDB <https://docs.mongodb.com/>`__
* `Music21 <https://web.mit.edu/music21/doc/>`__ * `Music21 <https://web.mit.edu/music21/doc/>`__
* `MyHDL <http://docs.myhdl.org/>`__ * `MyHDL <http://docs.myhdl.org/>`__

View File

@@ -26,6 +26,16 @@ The following is a list of deprecated interfaces.
- (will be) Removed - (will be) Removed
- Alternatives - Alternatives
* - ``sphinx.builders.latex.LaTeXBuilder.usepackages``
- 3.3
- 5.0
- N/A
* - ``sphinx.builders.latex.LaTeXBuilder.usepackages_afger_hyperref``
- 3.3
- 5.0
- N/A
* - ``sphinx.ext.autodoc.members_set_option()`` * - ``sphinx.ext.autodoc.members_set_option()``
- 3.2 - 3.2
- 5.0 - 5.0

View File

@@ -229,7 +229,7 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
.. versionchanged:: 3.0 .. versionchanged:: 3.0
It takes an anchestor class name as an argument. It takes an ancestor class name as an argument.
* It's possible to override the signature for explicitly documented callable * It's possible to override the signature for explicitly documented callable
objects (functions, methods, classes) with the regular syntax that will objects (functions, methods, classes) with the regular syntax that will

View File

@@ -744,6 +744,18 @@ The following directive can be used for this purpose.
.. versionadded:: 3.2 .. versionadded:: 3.2
.. rubric:: Options
.. rst:directive:option:: maxdepth: int
Insert nested declarations as well, up to the total depth given.
Use 0 for infinite depth and 1 for just the mentioned declaration.
Defaults to 1.
.. versionadded:: 3.3
.. c:namespace-pop:: .. c:namespace-pop::

View File

@@ -751,18 +751,27 @@ class StandaloneHTMLBuilder(Builder):
copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js')) copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js'))
def copy_theme_static_files(self, context: Dict) -> None: def copy_theme_static_files(self, context: Dict) -> None:
def onerror(filename: str, error: Exception) -> None:
logger.warning(__('Failed to copy a file in html_static_file: %s: %r'),
filename, error)
if self.theme: if self.theme:
for entry in self.theme.get_theme_dirs()[::-1]: for entry in self.theme.get_theme_dirs()[::-1]:
copy_asset(path.join(entry, 'static'), copy_asset(path.join(entry, 'static'),
path.join(self.outdir, '_static'), path.join(self.outdir, '_static'),
excluded=DOTFILES, context=context, renderer=self.templates) excluded=DOTFILES, context=context,
renderer=self.templates, onerror=onerror)
def copy_html_static_files(self, context: Dict) -> None: def copy_html_static_files(self, context: Dict) -> None:
def onerror(filename: str, error: Exception) -> None:
logger.warning(__('Failed to copy a file in html_static_file: %s: %r'),
filename, error)
excluded = Matcher(self.config.exclude_patterns + ["**/.*"]) excluded = Matcher(self.config.exclude_patterns + ["**/.*"])
for entry in self.config.html_static_path: for entry in self.config.html_static_path:
copy_asset(path.join(self.confdir, entry), copy_asset(path.join(self.confdir, entry),
path.join(self.outdir, '_static'), path.join(self.outdir, '_static'),
excluded, context=context, renderer=self.templates) excluded, context=context, renderer=self.templates, onerror=onerror)
def copy_html_logo(self) -> None: def copy_html_logo(self) -> None:
if self.config.html_logo: if self.config.html_logo:

View File

@@ -24,7 +24,7 @@ from sphinx.builders.latex.constants import ADDITIONAL_SETTINGS, DEFAULT_SETTING
from sphinx.builders.latex.theming import Theme, ThemeFactory from sphinx.builders.latex.theming import Theme, ThemeFactory
from sphinx.builders.latex.util import ExtBabel from sphinx.builders.latex.util import ExtBabel
from sphinx.config import Config, ENUM from sphinx.config import Config, ENUM
from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
from sphinx.environment.adapters.asset import ImageAdapter from sphinx.environment.adapters.asset import ImageAdapter
from sphinx.errors import NoUri, SphinxError from sphinx.errors import NoUri, SphinxError
from sphinx.locale import _, __ from sphinx.locale import _, __
@@ -128,8 +128,6 @@ class LaTeXBuilder(Builder):
self.docnames = [] # type: Iterable[str] self.docnames = [] # type: Iterable[str]
self.document_data = [] # type: List[Tuple[str, str, str, str, str, bool]] self.document_data = [] # type: List[Tuple[str, str, str, str, str, bool]]
self.themes = ThemeFactory(self.app) self.themes = ThemeFactory(self.app)
self.usepackages = self.app.registry.latex_packages
self.usepackages_after_hyperref = self.app.registry.latex_packages_after_hyperref
texescape.init() texescape.init()
self.init_context() self.init_context()
@@ -179,10 +177,6 @@ class LaTeXBuilder(Builder):
key = (self.config.latex_engine, self.config.language[:2]) key = (self.config.latex_engine, self.config.language[:2])
self.context.update(ADDITIONAL_SETTINGS.get(key, {})) self.context.update(ADDITIONAL_SETTINGS.get(key, {}))
# Apply extension settings to context
self.context['packages'] = self.usepackages
self.context['packages_after_hyperref'] = self.usepackages_after_hyperref
# Apply user settings to context # Apply user settings to context
self.context.update(self.config.latex_elements) self.context.update(self.config.latex_elements)
self.context['release'] = self.config.release self.context['release'] = self.config.release
@@ -203,6 +197,13 @@ class LaTeXBuilder(Builder):
# Show the release label only if release value exists # Show the release label only if release value exists
self.context.setdefault('releasename', _('Release')) self.context.setdefault('releasename', _('Release'))
def update_context(self) -> None:
"""Update template variables for .tex file just before writing."""
# Apply extension settings to context
registry = self.app.registry
self.context['packages'] = registry.latex_packages
self.context['packages_after_hyperref'] = registry.latex_packages_after_hyperref
def init_babel(self) -> None: def init_babel(self) -> None:
self.babel = ExtBabel(self.config.language, not self.context['babel']) self.babel = ExtBabel(self.config.language, not self.context['babel'])
if self.config.language and not self.babel.is_supported_language(): if self.config.language and not self.babel.is_supported_language():
@@ -290,6 +291,7 @@ class LaTeXBuilder(Builder):
doctree['tocdepth'] = tocdepth doctree['tocdepth'] = tocdepth
self.post_process_images(doctree) self.post_process_images(doctree)
self.update_doc_context(title, author, theme) self.update_doc_context(title, author, theme)
self.update_context()
with progress_message(__("writing")): with progress_message(__("writing")):
docsettings._author = author docsettings._author = author
@@ -448,6 +450,18 @@ class LaTeXBuilder(Builder):
filename = path.join(package_dir, 'templates', 'latex', 'sphinxmessages.sty_t') filename = path.join(package_dir, 'templates', 'latex', 'sphinxmessages.sty_t')
copy_asset_file(filename, self.outdir, context=context, renderer=LaTeXRenderer()) copy_asset_file(filename, self.outdir, context=context, renderer=LaTeXRenderer())
@property
def usepackages(self) -> List[Tuple[str, str]]:
warnings.warn('LaTeXBuilder.usepackages is deprecated.',
RemovedInSphinx50Warning, stacklevel=2)
return self.app.registry.latex_packages
@property
def usepackages_after_hyperref(self) -> List[Tuple[str, str]]:
warnings.warn('LaTeXBuilder.usepackages_after_hyperref is deprecated.',
RemovedInSphinx50Warning, stacklevel=2)
return self.app.registry.latex_packages_after_hyperref
def patch_settings(settings: Any) -> Any: def patch_settings(settings: Any) -> Any:
"""Make settings object to show deprecation messages.""" """Make settings object to show deprecation messages."""
@@ -505,7 +519,7 @@ def validate_latex_theme_options(app: Sphinx, config: Config) -> None:
def install_pakcages_for_ja(app: Sphinx) -> None: def install_pakcages_for_ja(app: Sphinx) -> None:
"""Install packages for Japanese.""" """Install packages for Japanese."""
if app.config.language == 'ja': if app.config.language == 'ja' and app.config.latex_engine in ('platex', 'uplatex'):
app.add_latex_package('pxjahyper', after_hyperref=True) app.add_latex_package('pxjahyper', after_hyperref=True)

View File

@@ -136,8 +136,8 @@ class ASTIdentifier(ASTBaseBase):
reftype='identifier', reftype='identifier',
reftarget=targetText, modname=None, reftarget=targetText, modname=None,
classname=None) classname=None)
# key = symbol.get_lookup_key() key = symbol.get_lookup_key()
# pnode['c:parent_key'] = key pnode['c:parent_key'] = key
if self.is_anon(): if self.is_anon():
pnode += nodes.strong(text="[anonymous]") pnode += nodes.strong(text="[anonymous]")
else: else:
@@ -1562,6 +1562,11 @@ class Symbol:
for s in sChild.get_all_symbols(): for s in sChild.get_all_symbols():
yield s yield s
@property
def children(self) -> Iterator["Symbol"]:
for c in self._children:
yield c
@property @property
def children_recurse_anon(self) -> Iterator["Symbol"]: def children_recurse_anon(self) -> Iterator["Symbol"]:
for c in self._children: for c in self._children:
@@ -3408,10 +3413,13 @@ class CNamespacePopObject(SphinxDirective):
class AliasNode(nodes.Element): class AliasNode(nodes.Element):
def __init__(self, sig: str, env: "BuildEnvironment" = None, def __init__(self, sig: str, maxdepth: int, document: Any, env: "BuildEnvironment" = None,
parentKey: LookupKey = None) -> None: parentKey: LookupKey = None) -> None:
super().__init__() super().__init__()
self.sig = sig self.sig = sig
self.maxdepth = maxdepth
assert maxdepth >= 0
self.document = document
if env is not None: if env is not None:
if 'c:parent_symbol' not in env.temp_data: if 'c:parent_symbol' not in env.temp_data:
root = env.domaindata['c']['root_symbol'] root = env.domaindata['c']['root_symbol']
@@ -3428,6 +3436,37 @@ class AliasNode(nodes.Element):
class AliasTransform(SphinxTransform): class AliasTransform(SphinxTransform):
default_priority = ReferencesResolver.default_priority - 1 default_priority = ReferencesResolver.default_priority - 1
def _render_symbol(self, s: Symbol, maxdepth: int, document: Any) -> List[Node]:
nodes = [] # type: List[Node]
options = dict() # type: ignore
signode = addnodes.desc_signature('', '')
nodes.append(signode)
s.declaration.describe_signature(signode, 'markName', self.env, options)
if maxdepth == 0:
recurse = True
elif maxdepth == 1:
recurse = False
else:
maxdepth -= 1
recurse = True
if recurse:
content = addnodes.desc_content()
desc = addnodes.desc()
content.append(desc)
desc.document = document
desc['domain'] = 'c'
# 'desctype' is a backwards compatible attribute
desc['objtype'] = desc['desctype'] = 'alias'
desc['noindex'] = True
for sChild in s.children:
childNodes = self._render_symbol(sChild, maxdepth, document)
desc.extend(childNodes)
if len(desc.children) != 0:
nodes.append(content)
return nodes
def apply(self, **kwargs: Any) -> None: def apply(self, **kwargs: Any) -> None:
for node in self.document.traverse(AliasNode): for node in self.document.traverse(AliasNode):
sig = node.sig sig = node.sig
@@ -3468,17 +3507,16 @@ class AliasTransform(SphinxTransform):
logger.warning("Could not find C declaration for alias '%s'." % name, logger.warning("Could not find C declaration for alias '%s'." % name,
location=node) location=node)
node.replace_self(signode) node.replace_self(signode)
else: continue
nodes = []
options = dict() # type: ignore nodes = self._render_symbol(s, maxdepth=node.maxdepth, document=node.document)
signode = addnodes.desc_signature(sig, '') node.replace_self(nodes)
nodes.append(signode)
s.declaration.describe_signature(signode, 'markName', self.env, options)
node.replace_self(nodes)
class CAliasObject(ObjectDescription): class CAliasObject(ObjectDescription):
option_spec = {} # type: Dict option_spec = {
'maxdepth': directives.nonnegative_int
} # type: Dict
def run(self) -> List[Node]: def run(self) -> List[Node]:
if ':' in self.name: if ':' in self.name:
@@ -3494,16 +3532,10 @@ class CAliasObject(ObjectDescription):
node['noindex'] = True node['noindex'] = True
self.names = [] # type: List[str] self.names = [] # type: List[str]
maxdepth = self.options.get('maxdepth', 1)
signatures = self.get_signatures() signatures = self.get_signatures()
for i, sig in enumerate(signatures): for i, sig in enumerate(signatures):
node.append(AliasNode(sig, env=self.env)) node.append(AliasNode(sig, maxdepth, self.state.document, env=self.env))
contentnode = addnodes.desc_content()
node.append(contentnode)
self.before_content()
self.state.nested_parse(self.content, self.content_offset, contentnode)
self.env.temp_data['object'] = None
self.after_content()
return [node] return [node]

View File

@@ -535,6 +535,11 @@ class Documenter:
self.env.app.emit('autodoc-process-docstring', self.env.app.emit('autodoc-process-docstring',
self.objtype, self.fullname, self.object, self.objtype, self.fullname, self.object,
self.options, docstringlines) self.options, docstringlines)
if docstringlines and docstringlines[-1] != '':
# append a blank line to the end of the docstring
docstringlines.append('')
yield from docstringlines yield from docstringlines
def get_sourcename(self) -> str: def get_sourcename(self) -> str:

View File

@@ -66,6 +66,10 @@ module_sig_re = re.compile(r'''^(?:([\w.]*)\.)? # module names
''', re.VERBOSE) ''', re.VERBOSE)
py_builtins = [obj for obj in vars(builtins).values()
if inspect.isclass(obj)]
def try_import(objname: str) -> Any: def try_import(objname: str) -> Any:
"""Import a object or module using *name* and *currentmodule*. """Import a object or module using *name* and *currentmodule*.
*name* should be a relative name from *currentmodule* or *name* should be a relative name from *currentmodule* or
@@ -178,7 +182,6 @@ class InheritanceGraph:
traverse to. Multiple names can be specified separated by comma. traverse to. Multiple names can be specified separated by comma.
""" """
all_classes = {} all_classes = {}
py_builtins = vars(builtins).values()
def recurse(cls: Any) -> None: def recurse(cls: Any) -> None:
if not show_builtins and cls in py_builtins: if not show_builtins and cls in py_builtins:

View File

@@ -21,7 +21,6 @@ from docutils.nodes import Node
from docutils.parsers.rst import directives, roles from docutils.parsers.rst import directives, roles
from sphinx import application, locale from sphinx import application, locale
from sphinx.builders.latex import LaTeXBuilder
from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.pycode import ModuleAnalyzer from sphinx.pycode import ModuleAnalyzer
from sphinx.testing.path import path from sphinx.testing.path import path
@@ -141,7 +140,6 @@ class SphinxTestApp(application.Sphinx):
def cleanup(self, doctrees: bool = False) -> None: def cleanup(self, doctrees: bool = False) -> None:
ModuleAnalyzer.cache.clear() ModuleAnalyzer.cache.clear()
LaTeXBuilder.usepackages = []
locale.translators.clear() locale.translators.clear()
sys.path[:] = self._saved_path sys.path[:] = self._saved_path
sys.modules.pop('autodoc_fodder', None) sys.modules.pop('autodoc_fodder', None)

View File

@@ -10,7 +10,7 @@
import os import os
import posixpath import posixpath
from typing import Dict from typing import Callable, Dict
from docutils.utils import relative_path from docutils.utils import relative_path
@@ -56,7 +56,8 @@ def copy_asset_file(source: str, destination: str,
def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda path: False, def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda path: False,
context: Dict = None, renderer: "BaseRenderer" = None) -> None: context: Dict = None, renderer: "BaseRenderer" = None,
onerror: Callable[[str, Exception], None] = None) -> None:
"""Copy asset files to destination recursively. """Copy asset files to destination recursively.
On copying, it expands the template variables if context argument is given and On copying, it expands the template variables if context argument is given and
@@ -67,6 +68,7 @@ def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda pat
:param excluded: The matcher to determine the given path should be copied or not :param excluded: The matcher to determine the given path should be copied or not
:param context: The template variables. If not given, template files are simply copied :param context: The template variables. If not given, template files are simply copied
:param renderer: The template engine. If not given, SphinxRenderer is used by default :param renderer: The template engine. If not given, SphinxRenderer is used by default
:param onerror: The error handler.
""" """
if not os.path.exists(source): if not os.path.exists(source):
return return
@@ -90,6 +92,12 @@ def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda pat
for filename in files: for filename in files:
if not excluded(posixpath.join(reldir, filename)): if not excluded(posixpath.join(reldir, filename)):
copy_asset_file(posixpath.join(root, filename), try:
posixpath.join(destination, reldir), copy_asset_file(posixpath.join(root, filename),
context, renderer) posixpath.join(destination, reldir),
context, renderer)
except Exception as exc:
if onerror:
onerror(posixpath.join(root, filename), exc)
else:
raise

View File

@@ -304,6 +304,11 @@ def iscoroutinefunction(obj: Any) -> bool:
def isproperty(obj: Any) -> bool: def isproperty(obj: Any) -> bool:
"""Check if the object is property.""" """Check if the object is property."""
if sys.version_info > (3, 8):
from functools import cached_property # cached_property is available since py3.8
if isinstance(obj, cached_property):
return True
return isinstance(obj, property) return isinstance(obj, property)

View File

@@ -0,0 +1,7 @@
from functools import cached_property
class Foo:
@cached_property
def prop(self) -> int:
return 1

View File

@@ -881,6 +881,26 @@ def test_autodoc_descriptor(app):
] ]
@pytest.mark.skipif(sys.version_info < (3, 8),
reason='cached_property is available since python3.8.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_cached_property(app):
options = {"members": None,
"undoc-members": True}
actual = do_autodoc(app, 'class', 'target.cached_property.Foo', options)
assert list(actual) == [
'',
'.. py:class:: Foo()',
' :module: target.cached_property',
'',
'',
' .. py:method:: Foo.prop',
' :module: target.cached_property',
' :property:',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_member_order(app): def test_autodoc_member_order(app):
# case member-order='bysource' # case member-order='bysource'

View File

@@ -28,7 +28,8 @@ def test_process_docstring(app):
'.. py:function:: func()', '.. py:function:: func()',
' :module: target.process_docstring', ' :module: target.process_docstring',
'', '',
' my docstring' ' my docstring',
'',
] ]