Merge branch '4.x'

This commit is contained in:
Takeshi KOMIYA 2021-05-13 01:55:22 +09:00
commit 8350736c27
20 changed files with 189 additions and 32 deletions

51
.github/workflows/transifex.yml vendored Normal file
View File

@ -0,0 +1,51 @@
name: Sync translations on repository and transifex.com
on:
schedule:
- cron: "0 0 * * SUN"
workflow_dispatch:
jobs:
push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: 4.x
- name: Set up Python
uses: actions/setup-python@v2
- name: Install dependencies
run: pip install -U babel jinja2 transifex-client
- name: Extract translations from source code
run: python setup.py extract_messages
- name: Push translations to transifex.com
run: cd sphinx/locale && tx push -s --no-interactive --parallel
env:
TX_TOKEN: ${{ secrets.TX_TOKEN }}
pull:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: 4.x
- name: Set up Python
uses: actions/setup-python@v2
- name: Install dependencies
run: pip install -U babel jinja2 transifex-client
- name: Extract translations from source code
run: python setup.py extract_messages
- name: Pull translations to transifex.com
run: cd sphinx/locale && tx pull -a -f --no-interactive --parallel
env:
TX_TOKEN: ${{ secrets.TX_TOKEN }}
- name: Compile message catalogs
run: python setup.py compile_catalog
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
commit-message: 'Update message catalogs'
branch: bot/pull-translations
title: Update message catalogs

View File

@ -33,6 +33,7 @@ Incompatible changes
Deprecated
----------
* ``sphinx.application.Sphinx.html_theme``
* ``sphinx.util.docstrings.extract_metadata()``
Features added
@ -45,6 +46,9 @@ Features added
allows you to define an alias for a class with module name like
``foo.bar.BazClass``
* #9175: autodoc: Special member is not documented in the module
* #9195: autodoc: The arguments of ``typing.Literal`` are wrongly rendered
* #9185: autodoc: :confval:`autodoc_typehints` allows ``'both'`` setting to
allow typehints to be included both in the signature and description
* #3257: autosummary: Support instance attributes for classes
* #9129: html search: Show search summaries when html_copy_source = False
* #9120: html theme: Eliminate prompt characters of code-block from copyable
@ -62,6 +66,7 @@ Bugs fixed
* #8872: autodoc: stacked singledispatches are wrongly rendered
* #8597: autodoc: a docsting having metadata only should be treated as
undocumented
* #9185: autodoc: typehints for overloaded functions and methods are inaccurate
Testing
--------
@ -84,6 +89,8 @@ Features added
Bugs fixed
----------
* #9210: viewcode: crashed if non importable modules found on parallel build
Testing
--------

View File

@ -22,6 +22,11 @@ The following is a list of deprecated interfaces.
- (will be) Removed
- Alternatives
* - ``sphinx.application.Sphinx.html_theme``
- 4.1
- 6.0
- ``sphinx.registry.SphinxComponentRegistry.html_themes``
* - ``sphinx.util.docstrings.extract_metadata()``
- 4.1
- 6.0

View File

@ -573,15 +573,27 @@ There are also config values that you can set:
This value controls how to represent typehints. The setting takes the
following values:
* ``'signature'`` -- Show typehints as its signature (default)
* ``'description'`` -- Show typehints as content of function or method
* ``'signature'`` -- Show typehints in the signature (default)
* ``'description'`` -- Show typehints as content of the function or method
The typehints of overloaded functions or methods will still be represented
in the signature.
* ``'none'`` -- Do not show typehints
* ``'both'`` -- Show typehints in the signature and as content of
the function or method
Overloaded functions or methods will not have typehints included in the
description because it is impossible to accurately represent all possible
overloads as a list of parameters.
.. versionadded:: 2.1
.. versionadded:: 3.0
New option ``'description'`` is added.
.. versionadded:: 4.1
New option ``'both'`` is added.
.. confval:: autodoc_typehints_description_target
This value controls whether the types of undocumented parameters and return

View File

@ -14,6 +14,7 @@ import os
import pickle
import platform
import sys
import warnings
from collections import deque
from io import StringIO
from os import path
@ -29,6 +30,7 @@ from pygments.lexer import Lexer
import sphinx
from sphinx import locale, package_dir
from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx60Warning
from sphinx.domains import Domain, Index
from sphinx.environment import BuildEnvironment
from sphinx.environment.collectors import EnvironmentCollector
@ -145,7 +147,6 @@ class Sphinx:
self.env: Optional[BuildEnvironment] = None
self.project: Optional[Project] = None
self.registry = SphinxComponentRegistry()
self.html_themes: Dict[str, str] = {}
# validate provided directories
self.srcdir = abspath(srcdir)
@ -1184,13 +1185,13 @@ class Sphinx:
def add_html_theme(self, name: str, theme_path: str) -> None:
"""Register a HTML Theme.
The *name* is a name of theme, and *path* is a full path to the theme
(refs: :ref:`distribute-your-theme`).
The *name* is a name of theme, and *theme_path* is a full path to the
theme (refs: :ref:`distribute-your-theme`).
.. versionadded:: 1.6
"""
logger.debug('[app] adding HTML theme: %r, %r', name, theme_path)
self.html_themes[name] = theme_path
self.registry.add_html_theme(name, theme_path)
def add_html_math_renderer(self, name: str,
inline_renderers: Tuple[Callable, Callable] = None,
@ -1257,6 +1258,12 @@ class Sphinx:
return True
@property
def html_themes(self) -> Dict[str, str]:
warnings.warn('app.html_themes is deprecated.',
RemovedInSphinx60Warning)
return self.registry.html_themes
class TemplateBridge:
"""

View File

@ -1315,7 +1315,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
sigs = []
if (self.analyzer and
'.'.join(self.objpath) in self.analyzer.overloads and
self.config.autodoc_typehints == 'signature'):
self.config.autodoc_typehints != 'none'):
# Use signatures for overloaded functions instead of the implementation function.
overloaded = True
else:
@ -1561,7 +1561,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
sigs = []
overloads = self.get_overloaded_signatures()
if overloads and self.config.autodoc_typehints == 'signature':
if overloads and self.config.autodoc_typehints != 'none':
# Use signatures for overloaded methods instead of the implementation method.
method = safe_getattr(self._signature_class, self._signature_method_name, None)
__globals__ = safe_getattr(method, '__globals__', {})
@ -2109,7 +2109,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
sigs = []
if (self.analyzer and
'.'.join(self.objpath) in self.analyzer.overloads and
self.config.autodoc_typehints == 'signature'):
self.config.autodoc_typehints != 'none'):
# Use signatures for overloaded methods instead of the implementation method.
overloaded = True
else:
@ -2666,7 +2666,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('autodoc_docstring_signature', True, True)
app.add_config_value('autodoc_mock_imports', [], True)
app.add_config_value('autodoc_typehints', "signature", True,
ENUM("signature", "description", "none"))
ENUM("signature", "description", "none", "both"))
app.add_config_value('autodoc_typehints_description_target', 'all', True,
ENUM('all', 'documented'))
app.add_config_value('autodoc_type_aliases', {}, True)

View File

@ -40,7 +40,7 @@ def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any,
def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element) -> None:
if domain != 'py':
return
if app.config.autodoc_typehints != 'description':
if app.config.autodoc_typehints not in ('both', 'description'):
return
try:

View File

@ -150,10 +150,11 @@ def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str],
if modname not in env._viewcode_modules: # type: ignore
env._viewcode_modules[modname] = entry # type: ignore
else:
used = env._viewcode_modules[modname][2] # type: ignore
for fullname, docname in entry[2].items():
if fullname not in used:
used[fullname] = docname
if env._viewcode_modules[modname]: # type: ignore
used = env._viewcode_modules[modname][2] # type: ignore
for fullname, docname in entry[2].items():
if fullname not in used:
used[fullname] = docname
def env_purge_doc(app: Sphinx, env: BuildEnvironment, docname: str) -> None:

View File

@ -23,7 +23,7 @@ from sphinx.util import logging
from sphinx.util.osutil import mtimes_of_files
try:
from jinja2.utils import pass_context # type: ignore # jinja2-3.0 or above
from jinja2.utils import pass_context
except ImportError:
from jinja2 import contextfunction as pass_context

View File

@ -93,6 +93,9 @@ class SphinxComponentRegistry:
self.html_inline_math_renderers: Dict[str, Tuple[Callable, Callable]] = {}
self.html_block_math_renderers: Dict[str, Tuple[Callable, Callable]] = {}
#: HTML themes
self.html_themes: Dict[str, str] = {}
#: js_files; list of JS paths or URLs
self.js_files: List[Tuple[str, Dict[str, Any]]] = []
@ -403,6 +406,9 @@ class SphinxComponentRegistry:
self.html_inline_math_renderers[name] = inline_renderers
self.html_block_math_renderers[name] = block_renderers
def add_html_theme(self, name: str, theme_path: str) -> None:
self.html_themes[name] = theme_path
def load_extension(self, app: "Sphinx", extname: str) -> None:
"""Load a Sphinx extension."""
if extname in app.extensions: # already loaded

View File

@ -155,7 +155,7 @@ class HTMLThemeFactory:
def __init__(self, app: "Sphinx") -> None:
self.app = app
self.themes = app.html_themes
self.themes = app.registry.html_themes
self.load_builtin_themes()
if getattr(app.config, 'html_theme_path', None):
self.load_additional_themes(app.config.html_theme_path)

View File

@ -235,7 +235,7 @@ def save_traceback(app: "Sphinx") -> str:
platform.python_version(),
platform.python_implementation(),
docutils.__version__, docutils.__version_details__,
jinja2.__version__, # type: ignore
jinja2.__version__,
last_msgs)).encode())
if app is not None:
for ext in app.extensions.values():

View File

@ -24,7 +24,7 @@ from sphinx.locale import __
from sphinx.util import docutils, logging
try:
from jinja2.utils import pass_environment # type: ignore # jinja2-3.0 or above
from jinja2.utils import pass_environment
except ImportError:
from jinja2 import environmentfilter as pass_environment
@ -61,7 +61,7 @@ def textwidth(text: str, widechars: str = 'WF') -> int:
def heading(env: Environment, text: str, level: int = 1) -> str:
"""Create a heading for *level*."""
assert level <= 3
width = textwidth(text, WIDECHARS[env.language]) # type: ignore
width = textwidth(text, WIDECHARS[env.language])
sectioning_char = SECTIONING_CHARS[level - 1]
return '%s\n%s' % (text, sectioning_char * width)

View File

@ -69,18 +69,18 @@ class Tags:
def eval_node(node: Node) -> bool:
if isinstance(node, nodes.CondExpr):
if eval_node(node.test): # type: ignore
return eval_node(node.expr1) # type: ignore
if eval_node(node.test):
return eval_node(node.expr1)
else:
return eval_node(node.expr2) # type: ignore
return eval_node(node.expr2)
elif isinstance(node, nodes.And):
return eval_node(node.left) and eval_node(node.right) # type: ignore
return eval_node(node.left) and eval_node(node.right)
elif isinstance(node, nodes.Or):
return eval_node(node.left) or eval_node(node.right) # type: ignore
return eval_node(node.left) or eval_node(node.right)
elif isinstance(node, nodes.Not):
return not eval_node(node.node) # type: ignore
return not eval_node(node.node)
elif isinstance(node, nodes.Name):
return self.tags.get(node.name, False) # type: ignore
return self.tags.get(node.name, False)
else:
raise ValueError('invalid node, check parsing')

View File

@ -153,6 +153,7 @@ def _restify_py37(cls: Optional[Type]) -> str:
else:
text = restify(cls.__origin__)
origin = getattr(cls, '__origin__', None)
if not hasattr(cls, '__args__'):
pass
elif all(is_system_TypeVar(a) for a in cls.__args__):
@ -161,6 +162,8 @@ def _restify_py37(cls: Optional[Type]) -> str:
elif cls.__module__ == 'typing' and cls._name == 'Callable':
args = ', '.join(restify(a) for a in cls.__args__[:-1])
text += r"\ [[%s], %s]" % (args, restify(cls.__args__[-1]))
elif cls.__module__ == 'typing' and getattr(origin, '_name', None) == 'Literal':
text += r"\ [%s]" % ', '.join(repr(a) for a in cls.__args__)
elif cls.__args__:
text += r"\ [%s]" % ", ".join(restify(a) for a in cls.__args__)
@ -362,6 +365,9 @@ def _stringify_py37(annotation: Any) -> str:
args = ', '.join(stringify(a) for a in annotation.__args__[:-1])
returns = stringify(annotation.__args__[-1])
return '%s[[%s], %s]' % (qualname, args, returns)
elif qualname == 'Literal':
args = ', '.join(repr(a) for a in annotation.__args__)
return '%s[%s]' % (qualname, args)
elif str(annotation).startswith('typing.Annotated'): # for py39+
return stringify(annotation.__args__[0])
elif all(is_system_TypeVar(a) for a in annotation.__args__):

View File

@ -10,4 +10,6 @@
.. autofunction:: target.typehints.incr
.. autofunction:: target.overload.sum
.. autofunction:: target.typehints.tuple_args

View File

@ -695,6 +695,14 @@ def test_autodoc_typehints_description(app):
' Tuple[int, int]\n'
in context)
# Overloads still get displyed in the signature
assert ('target.overload.sum(x: int, y: int = 0) -> int\n'
'target.overload.sum(x: float, y: float = 0.0) -> float\n'
'target.overload.sum(x: str, y: str = None) -> str\n'
'\n'
' docstring\n'
in context)
@pytest.mark.sphinx('text', testroot='ext-autodoc',
confoverrides={'autodoc_typehints': "description",
@ -787,6 +795,46 @@ def test_autodoc_typehints_description_for_invalid_node(app):
restructuredtext.parse(app, text) # raises no error
@pytest.mark.sphinx('text', testroot='ext-autodoc',
confoverrides={'autodoc_typehints': "both"})
def test_autodoc_typehints_both(app):
(app.srcdir / 'index.rst').write_text(
'.. autofunction:: target.typehints.incr\n'
'\n'
'.. autofunction:: target.typehints.tuple_args\n'
'\n'
'.. autofunction:: target.overload.sum\n'
)
app.build()
context = (app.outdir / 'index.txt').read_text()
assert ('target.typehints.incr(a: int, b: int = 1) -> int\n'
'\n'
' Parameters:\n'
' * **a** (*int*) --\n'
'\n'
' * **b** (*int*) --\n'
'\n'
' Return type:\n'
' int\n'
in context)
assert ('target.typehints.tuple_args(x: Tuple[int, Union[int, str]]) -> Tuple[int, int]\n'
'\n'
' Parameters:\n'
' **x** (*Tuple**[**int**, **Union**[**int**, **str**]**]*) --\n'
'\n'
' Return type:\n'
' Tuple[int, int]\n'
in context)
# Overloads still get displyed in the signature
assert ('target.overload.sum(x: int, y: int = 0) -> int\n'
'target.overload.sum(x: float, y: float = 0.0) -> float\n'
'target.overload.sum(x: str, y: str = None) -> str\n'
'\n'
' docstring\n'
in context)
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
@pytest.mark.sphinx('text', testroot='ext-autodoc')
def test_autodoc_type_aliases(app):

View File

@ -214,7 +214,11 @@ def test_signature_annotations():
# optional union
sig = inspect.signature(f20)
assert stringify_signature(sig) == '() -> Optional[Union[int, str]]'
if sys.version_info < (3, 7):
assert stringify_signature(sig) in ('() -> Optional[Union[int, str]]',
'() -> Optional[Union[str, int]]')
else:
assert stringify_signature(sig) == '() -> Optional[Union[int, str]]'
# Any
sig = inspect.signature(f14)

View File

@ -133,6 +133,12 @@ def test_restify_type_ForwardRef():
assert restify(ForwardRef("myint")) == ":class:`myint`"
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
def test_restify_type_Literal():
from typing import Literal # type: ignore
assert restify(Literal[1, "2", "\r"]) == ":obj:`~typing.Literal`\\ [1, '2', '\\r']"
@pytest.mark.skipif(sys.version_info < (3, 10), reason='python 3.10+ is required.')
def test_restify_type_union_operator():
assert restify(int | None) == "Optional[:class:`int`]" # type: ignore
@ -237,6 +243,12 @@ def test_stringify_type_hints_alias():
assert stringify(MyTuple) == "Tuple[str, str]" # type: ignore
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
def test_stringify_type_Literal():
from typing import Literal # type: ignore
assert stringify(Literal[1, "2", "\r"]) == "Literal[1, '2', '\\r']"
@pytest.mark.skipif(sys.version_info < (3, 10), reason='python 3.10+ is required.')
def test_stringify_type_union_operator():
assert stringify(int | None) == "Optional[int]" # type: ignore

View File

@ -30,8 +30,6 @@ for first beta releases
* open "https://github.com/sphinx-doc/sphinx/actions?query=branch:master" and all tests has passed
* Run ``git fetch; git status`` and check nothing changed
* Run ``python setup.py extract_messages``
* Run ``(cd sphinx/locale; tx push -s)``
* ``python utils/bump_version.py X.Y.0b1``
* Check diff by ``git diff``
* ``git commit -am 'Bump to X.Y.0 beta1'``
@ -83,8 +81,6 @@ for major releases
* open "https://github.com/sphinx-doc/sphinx/actions?query=branch:X.x" and all tests has passed
* Run ``git fetch; git status`` and check nothing changed
* Run ``(cd sphinx/locale; tx pull -a -f)``
* Run ``python setup.py compile_catalog``
* Run ``git add sphinx``
* Run ``git commit -am 'Update message catalogs'``
* ``python utils/bump_version.py X.Y.0``