Merge branch '2.0'

This commit is contained in:
Takeshi KOMIYA
2019-08-19 00:37:30 +09:00
28 changed files with 1021 additions and 613 deletions

View File

@@ -19,7 +19,7 @@ matrix:
- TOXENV=du13
- python: '3.7'
env:
- TOXENV=py37
- TOXENV=du14
- PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg"
- python: 'nightly'
env: TOXENV=py38

54
CHANGES
View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
Release 3.0.0 (in development)
==============================
@@ -26,7 +27,7 @@ Bugs fixed
Testing
--------
Release 2.2.0 (in development)
Release 2.3.0 (in development)
==============================
Dependencies
@@ -35,7 +36,26 @@ Dependencies
Incompatible changes
--------------------
Deprecated
----------
Features added
--------------
Bugs fixed
----------
Testing
--------
Release 2.2.0 (released Aug 19, 2019)
=====================================
Incompatible changes
--------------------
* apidoc: template files are renamed to ``.rst_t``
* html: Field lists will be styled by grid layout
Deprecated
----------
@@ -63,6 +83,9 @@ Features added
* #6310: imgmath: let :confval:`imgmath_use_preview` work also with the SVG
format for images rendering inline math
* #6533: LaTeX: refactor visit_enumerated_list() to use ``\sphinxsetlistlabels``
* #6628: quickstart: Use ``https://docs.python.org/3/`` for default setting of
:confval:`intersphinx_mapping`
* #6419: sphinx-build: give reasons why rebuilded
Bugs fixed
----------
@@ -78,6 +101,9 @@ Bugs fixed
``__init__()`` and ``__new__()``
* #6574: autodoc: :confval:`autodoc_member_order` does not refer order of
imports when ``'bysource'`` order
* #6574: autodoc: missing type annotation for variadic and keyword parameters
* #6589: autodoc: Formatting issues with autodoc_typehints='none'
* #6605: autodoc: crashed when target code contains custom method-like objects
* #6498: autosummary: crashed with wrong autosummary_generate setting
* #6507: autosummary: crashes without no autosummary_generate setting
* #6511: LaTeX: autonumbered list can not be customized in LaTeX
@@ -92,30 +118,8 @@ Bugs fixed
* #6561: glossary: Wrong hyperlinks are generated for non alphanumeric terms
* #6620: i18n: classifiers of definition list are not translated with
docutils-0.15
Testing
--------
Release 2.1.3 (in development)
==============================
Dependencies
------------
Incompatible changes
--------------------
Deprecated
----------
Features added
--------------
Bugs fixed
----------
Testing
--------
* #6474: ``DocFieldTransformer`` raises AttributeError when given directive is
not a subclass of ObjectDescription
Release 2.1.2 (released Jun 19, 2019)
=====================================

View File

@@ -80,6 +80,14 @@ are built:
This value should only contain the path to the latex executable, not further
arguments; use :confval:`imgmath_latex_args` for that purpose.
.. hint::
Some fancy LaTeX mark-up (an example was reported which used TikZ to add
various decorations to the equation) require multiple runs of the LaTeX
executable. To handle this, set this configuration setting to
``'latexmk'`` (or a full path to it) as this Perl script reliably
chooses dynamically how many latex runs are needed.
.. confval:: imgmath_latex_args
Additional arguments to give to latex, as a list. The default is an empty

View File

@@ -388,9 +388,11 @@ class Builder:
# ... but not those that already were removed
changed.update(self.env.glob_toctrees & self.env.found_docs)
if changed:
reason = CONFIG_CHANGED_REASON.get(self.env.config_status, '')
if updated: # explain the change iff build config status was not ok
reason = (CONFIG_CHANGED_REASON.get(self.env.config_status, '') +
(self.env.config_status_extra or ''))
logger.info('[%s] ', reason, nonl=True)
logger.info(__('%s added, %s changed, %s removed'),
len(added), len(changed), len(removed))

View File

@@ -31,10 +31,7 @@ RemovedInNextVersionWarning = RemovedInSphinx40Warning
def deprecated_alias(modname, objects, warning):
# type: (str, Dict, Type[Warning]) -> None
module = sys.modules.get(modname)
if module is None:
module = import_module(modname)
module = import_module(modname)
sys.modules[modname] = _ModuleWrapper(module, modname, objects, warning) # type: ignore

View File

@@ -93,14 +93,15 @@ class BuildEnvironment:
# --------- ENVIRONMENT INITIALIZATION -------------------------------------
def __init__(self, app: "Sphinx" = None):
self.app = None # type: Sphinx
self.doctreedir = None # type: str
self.srcdir = None # type: str
self.config = None # type: Config
self.config_status = None # type: int
self.events = None # type: EventManager
self.project = None # type: Project
self.version = None # type: Dict[str, str]
self.app = None # type: Sphinx
self.doctreedir = None # type: str
self.srcdir = None # type: str
self.config = None # type: Config
self.config_status = None # type: int
self.config_status_extra = None # type: str
self.events = None # type: EventManager
self.project = None # type: Project
self.version = None # type: Dict[str, str]
# the method of doctree versioning; see set_versioning_method
self.versioning_condition = None # type: Union[bool, Callable]
@@ -230,16 +231,25 @@ class BuildEnvironment:
def _update_config(self, config: Config) -> None:
"""Update configurations by new one."""
self.config_status = CONFIG_OK
self.config_status_extra = ''
if self.config is None:
self.config_status = CONFIG_NEW
elif self.config.extensions != config.extensions:
self.config_status = CONFIG_EXTENSIONS_CHANGED
extensions = sorted(
set(self.config.extensions) ^ set(config.extensions))
if len(extensions) == 1:
extension = extensions[0]
else:
extension = '%d' % (len(extensions),)
self.config_status_extra = ' (%r)' % (extension,)
else:
# check if a config value was changed that affects how
# doctrees are read
for item in config.filter('env'):
if self.config[item.name] != item.value:
self.config_status = CONFIG_CHANGED
self.config_status_extra = ' (%r)' % (item.name,)
break
self.config = config

View File

@@ -8,7 +8,7 @@
:license: BSD, see LICENSE for details.
"""
import sys
import importlib
import traceback
import warnings
from collections import namedtuple
@@ -23,14 +23,13 @@ logger = logging.getLogger(__name__)
def import_module(modname: str, warningiserror: bool = False) -> Any:
"""
Call __import__(modname), convert exceptions to ImportError
Call importlib.import_module(modname), convert exceptions to ImportError
"""
try:
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=ImportWarning)
with logging.skip_warningiserror(not warningiserror):
__import__(modname)
return sys.modules[modname]
return importlib.import_module(modname)
except BaseException as exc:
# Importing modules may cause any side effects, including
# SystemExit, so we need to catch all errors.

View File

@@ -13,6 +13,7 @@ import glob
import inspect
import pickle
import re
from importlib import import_module
from os import path
from typing import Any, Dict, IO, List, Pattern, Set, Tuple
@@ -144,7 +145,7 @@ class CoverageBuilder(Builder):
continue
try:
mod = __import__(mod_name, fromlist=['foo'])
mod = import_module(mod_name)
except ImportError as err:
logger.warning(__('module %s could not be imported: %s'), mod_name, err)
self.py_undoc[mod_name] = {'error': err}

View File

@@ -38,8 +38,8 @@ r"""
import builtins
import inspect
import re
import sys
from hashlib import md5
from importlib import import_module
from typing import Any, Dict, Iterable, List, Tuple
from typing import cast
@@ -74,8 +74,10 @@ def try_import(objname: str) -> Any:
Returns imported object or module. If failed, returns None value.
"""
try:
__import__(objname)
return sys.modules.get(objname)
return import_module(objname)
except TypeError:
# Relative import
return None
except ImportError:
matched = module_sig_re.match(objname)
@@ -87,8 +89,8 @@ def try_import(objname: str) -> Any:
if modname is None:
return None
try:
__import__(modname)
return getattr(sys.modules.get(modname), attrname, None)
module = import_module(modname)
return getattr(module, attrname, None)
except ImportError:
return None

View File

@@ -9,6 +9,7 @@
"""
from functools import partial
from importlib import import_module
from pygments import highlight
from pygments.filters import ErrorToken
@@ -83,7 +84,7 @@ class PygmentsBridge:
return NoneStyle
elif '.' in stylename:
module, stylename = stylename.rsplit('.', 1)
return getattr(__import__(module, None, None, ['__name__']), stylename)
return getattr(import_module(module), stylename)
else:
return get_style_by_name(stylename)

View File

@@ -9,6 +9,7 @@
"""
import traceback
from importlib import import_module
from types import MethodType
from docutils.parsers.rst import Directive
@@ -419,18 +420,19 @@ class SphinxComponentRegistry:
prefix = __('while setting up extension %s:') % extname
with prefixed_warnings(prefix):
try:
mod = __import__(extname, None, None, ['setup'])
mod = import_module(extname)
except ImportError as err:
logger.verbose(__('Original exception:\n') + traceback.format_exc())
raise ExtensionError(__('Could not import extension %s') % extname, err)
if not hasattr(mod, 'setup'):
setup = getattr(mod, 'setup', None)
if setup is None:
logger.warning(__('extension %r has no setup() function; is it really '
'a Sphinx extension module?'), extname)
metadata = {} # type: Dict[str, Any]
else:
try:
metadata = mod.setup(app)
metadata = setup(app)
except VersionRequirementError as err:
# add the extension name to the version required
raise VersionRequirementError(

View File

@@ -11,6 +11,7 @@ import html
import pickle
import re
import warnings
from importlib import import_module
from os import path
from docutils import nodes
@@ -278,8 +279,7 @@ class IndexBuilder:
self.lang = SearchEnglish(options) # type: SearchLanguage
elif isinstance(lang_class, str):
module, classname = lang_class.rsplit('.', 1)
lang_class = getattr(__import__(module, None, None, [classname]),
classname)
lang_class = getattr(import_module(module), classname)
self.lang = lang_class(options)
else:
# it's directly a class (e.g. added by app.add_search_language)

View File

@@ -2,7 +2,7 @@
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
@@ -105,7 +105,7 @@ html_static_path = ['{{ dot }}static']
# -- Options for intersphinx extension ---------------------------------------
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/': None}
intersphinx_mapping = {'https://docs.python.org/3/': None}
{%- endif %}
{%- if 'sphinx.ext.todo' in extensions %}
@@ -114,4 +114,3 @@ intersphinx_mapping = {'https://docs.python.org/': None}
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
{%- endif %}

View File

@@ -520,14 +520,15 @@ dl.citation > dd:after {
}
dl.field-list {
display: flex;
flex-wrap: wrap;
display: grid;
grid-template-columns: fit-content(30%) auto;
}
dl.field-list > dt {
flex-basis: 20%;
font-weight: bold;
word-break: break-word;
padding-left: 0.5em;
padding-right: 5px;
}
dl.field-list > dt:after {
@@ -535,8 +536,8 @@ dl.field-list > dt:after {
}
dl.field-list > dd {
flex-basis: 70%;
padding-left: 1em;
padding-left: 0.5em;
margin-top: 0em;
margin-left: 0em;
margin-bottom: 0em;
}

File diff suppressed because one or more lines are too long

View File

@@ -412,15 +412,10 @@ p.versionchanged span.versionmodified {
dl.field-list > dt {
color: white;
padding-left: 0.5em;
padding-right: 5px;
background-color: #82A0BE;
}
dl.field-list > dd {
padding-left: 0.5em;
margin-top: 0em;
margin-left: 0em;
background-color: #f7f7f7;
}

View File

@@ -292,7 +292,7 @@ code {
font-size: 0.95em;
}
th {
th, dl.field-list > dt {
background-color: #ede;
}

View File

@@ -22,6 +22,7 @@ from codecs import BOM_UTF8
from collections import deque
from datetime import datetime
from hashlib import md5
from importlib import import_module
from os import path
from time import mktime, strptime
from typing import Any, Callable, Dict, IO, Iterable, Iterator, List, Pattern, Set, Tuple
@@ -238,12 +239,10 @@ def get_module_source(modname: str) -> Tuple[str, str]:
Can return ('file', 'filename') in which case the source is in the given
file, or ('string', 'source') which which case the source is the string.
"""
if modname not in sys.modules:
try:
__import__(modname)
except Exception as err:
raise PycodeError('error importing %r' % modname, err)
mod = sys.modules[modname]
try:
mod = import_module(modname)
except Exception as err:
raise PycodeError('error importing %r' % modname, err)
filename = getattr(mod, '__file__', None)
loader = getattr(mod, '__loader__', None)
if loader and getattr(loader, 'get_filename', None):
@@ -284,8 +283,7 @@ def get_full_modname(modname: str, attribute: str) -> str:
# Prevents a TypeError: if the last getattr() call will return None
# then it's better to return it directly
return None
__import__(modname)
module = sys.modules[modname]
module = import_module(modname)
# Allow an attribute to have multiple parts and incidentially allow
# repeated .s in the attribute.
@@ -540,14 +538,13 @@ def import_object(objname: str, source: str = None) -> Any:
try:
objpath = objname.split('.')
modname = objpath.pop(0)
obj = __import__(modname)
obj = import_module(modname)
for name in objpath:
modname += '.' + name
try:
obj = getattr(obj, name)
except AttributeError:
__import__(modname)
obj = getattr(obj, name)
obj = import_module(modname)
return obj
except (AttributeError, ImportError) as exc:

View File

@@ -218,7 +218,14 @@ class DocFieldTransformer:
def __init__(self, directive: "ObjectDescription") -> None:
self.directive = directive
self.typemap = directive.get_field_type_map()
try:
self.typemap = directive.get_field_type_map()
except Exception:
# for 3rd party extensions directly calls this transformer.
warnings.warn('DocFieldTransformer expects given directive object is a subclass '
'of ObjectDescription.', RemovedInSphinx40Warning)
self.typemap = self.preprocess_fieldtypes(directive.__class__.doc_field_types)
def preprocess_fieldtypes(self, types: List[Field]) -> Dict[str, Tuple[Field, bool]]:
warnings.warn('DocFieldTransformer.preprocess_fieldtypes() is deprecated.',

View File

@@ -205,9 +205,12 @@ def isbuiltin(obj: Any) -> bool:
def iscoroutinefunction(obj: Any) -> bool:
"""Check if the object is coroutine-function."""
if inspect.iscoroutinefunction(obj):
if hasattr(obj, '__code__') and inspect.iscoroutinefunction(obj):
# check obj.__code__ because iscoroutinefunction() crashes for custom method-like
# objects (see https://github.com/sphinx-doc/sphinx/issues/6605)
return True
elif ispartial(obj) and inspect.iscoroutinefunction(obj.func):
elif (ispartial(obj) and hasattr(obj.func, '__code__') and
inspect.iscoroutinefunction(obj.func)):
# partialed
return True
else:
@@ -378,6 +381,12 @@ class Signature:
return None
def format_args(self, show_annotation: bool = True) -> str:
def format_param_annotation(param: inspect.Parameter) -> str:
if isinstance(param.annotation, str) and param.name in self.annotations:
return self.format_annotation(self.annotations[param.name])
else:
return self.format_annotation(param.annotation)
args = []
last_kind = None
for i, param in enumerate(self.parameters.values()):
@@ -399,14 +408,10 @@ class Signature:
param.KEYWORD_ONLY):
arg.write(param.name)
if show_annotation and param.annotation is not param.empty:
if isinstance(param.annotation, str) and param.name in self.annotations:
arg.write(': ')
arg.write(self.format_annotation(self.annotations[param.name]))
else:
arg.write(': ')
arg.write(self.format_annotation(param.annotation))
arg.write(': ')
arg.write(format_param_annotation(param))
if param.default is not param.empty:
if param.annotation is param.empty:
if param.annotation is param.empty or show_annotation is False:
arg.write('=')
arg.write(object_description(param.default))
else:
@@ -415,14 +420,20 @@ class Signature:
elif param.kind == param.VAR_POSITIONAL:
arg.write('*')
arg.write(param.name)
if show_annotation and param.annotation is not param.empty:
arg.write(': ')
arg.write(format_param_annotation(param))
elif param.kind == param.VAR_KEYWORD:
arg.write('**')
arg.write(param.name)
if show_annotation and param.annotation is not param.empty:
arg.write(': ')
arg.write(format_param_annotation(param))
args.append(arg.getvalue())
last_kind = param.kind
if self.return_annotation is inspect.Parameter.empty:
if self.return_annotation is inspect.Parameter.empty or show_annotation is False:
return '(%s)' % ', '.join(args)
else:
if 'return' in self.annotations:

View File

@@ -7,6 +7,8 @@
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import os
import shutil
import pytest
from sphinx.builders.html import StandaloneHTMLBuilder
@@ -23,18 +25,30 @@ def test_config_status(make_app, app_params):
app1 = make_app(*args, freshenv=True, **kwargs)
assert app1.env.config_status == CONFIG_NEW
app1.build()
assert '[new config] 1 added' in app1._status.getvalue()
# incremental build (no config changed)
app2 = make_app(*args, **kwargs)
assert app2.env.config_status == CONFIG_OK
app2.build()
assert "0 added, 0 changed, 0 removed" in app2._status.getvalue()
# incremental build (config entry changed)
app3 = make_app(*args, confoverrides={'master_doc': 'content'}, **kwargs)
app3 = make_app(*args, confoverrides={'master_doc': 'indexx'}, **kwargs)
fname = os.path.join(app3.srcdir, 'index.rst')
assert os.path.isfile(fname)
shutil.move(fname, fname[:-4] + 'x.rst')
assert app3.env.config_status == CONFIG_CHANGED
app3.build()
shutil.move(fname[:-4] + 'x.rst', fname)
assert "[config changed ('master_doc')] 1 added" in app3._status.getvalue()
# incremental build (extension changed)
app4 = make_app(*args, confoverrides={'extensions': ['sphinx.ext.autodoc']}, **kwargs)
assert app4.env.config_status == CONFIG_EXTENSIONS_CHANGED
app4.build()
want_str = "[extensions changed ('sphinx.ext.autodoc')] 1 added"
assert want_str in app4._status.getvalue()
@pytest.mark.sphinx('dummy')

View File

@@ -500,15 +500,15 @@ def test_autodoc_typehints_none(app):
'.. py:module:: target.typehints',
'',
'',
'.. py:class:: Math(s, o = None)',
'.. py:class:: Math(s, o=None)',
' :module: target.typehints',
'',
' ',
' .. py:method:: Math.incr(a, b = 1) -> int',
' .. py:method:: Math.incr(a, b=1)',
' :module: target.typehints',
' ',
'',
'.. py:function:: incr(a, b = 1) -> int',
'.. py:function:: incr(a, b=1)',
' :module: target.typehints',
''
]

View File

@@ -10,6 +10,7 @@
import abc
import sys
from importlib import import_module
import pytest
@@ -56,27 +57,27 @@ def test_mock():
submodule = modname + '.submodule'
assert modname not in sys.modules
with pytest.raises(ImportError):
__import__(modname)
import_module(modname)
with mock([modname]):
__import__(modname)
import_module(modname)
assert modname in sys.modules
assert isinstance(sys.modules[modname], _MockModule)
# submodules are also mocked
__import__(submodule)
import_module(submodule)
assert submodule in sys.modules
assert isinstance(sys.modules[submodule], _MockModule)
assert modname not in sys.modules
with pytest.raises(ImportError):
__import__(modname)
import_module(modname)
def test_mock_does_not_follow_upper_modules():
with mock(['sphinx.unknown.module']):
with pytest.raises(ImportError):
__import__('sphinx.unknown')
import_module('sphinx.unknown')
@pytest.mark.skipif(sys.version_info < (3, 7), reason='Only for py37 or above')

View File

@@ -74,7 +74,7 @@ def test_js_source(app, status, warning):
app.builder.build(['contents'])
v = '3.2.1'
v = '3.4.1'
msg = 'jquery.js version does not match to {v}'.format(v=v)
jquery_min = (app.outdir / '_static' / 'jquery.js').text()
assert 'jQuery v{v}'.format(v=v) in jquery_min, msg

View File

@@ -195,7 +195,7 @@ def test_Signature_partialmethod():
def test_Signature_annotations():
from typing_test_data import (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10,
f11, f12, f13, f14, f15, f16, f17, f18, Node)
f11, f12, f13, f14, f15, f16, f17, f18, f19, Node)
# Class annotations
sig = inspect.Signature(f0).format_args()
@@ -275,6 +275,10 @@ def test_Signature_annotations():
sig = inspect.Signature(f18).format_args()
assert sig == '(self, arg1: Union[int, Tuple] = 10) -> List[Dict]'
# annotations for variadic and keyword parameters
sig = inspect.Signature(f19).format_args()
assert sig == '(*args: int, **kwargs: str)'
# type hints by string
sig = inspect.Signature(Node.children).format_args()
if (3, 5, 0) <= sys.version_info < (3, 5, 3):
@@ -285,6 +289,10 @@ def test_Signature_annotations():
sig = inspect.Signature(Node.__init__).format_args()
assert sig == '(self, parent: Optional[Node]) -> None'
# show_annotation is False
sig = inspect.Signature(f7).format_args(show_annotation=False)
assert sig == '(x=None, y={})'
def test_safe_getattr_with_default():
class Foo:

View File

@@ -92,6 +92,11 @@ def f18(self, arg1: Union[int, Tuple] = 10) -> List[Dict]:
pass
def f19(*args: int, **kwargs: str):
pass
class Node:
def __init__(self, parent: Optional['Node']) -> None:
pass

View File

@@ -1,6 +1,6 @@
[tox]
minversion = 2.4.0
envlist = docs,flake8,mypy,coverage,py{35,36,37,38},du{12,13,14}
envlist = docs,flake8,mypy,coverage,py{35,36,37,38},du{12,13,14,15}
[testenv]
usedevelop = True
@@ -13,6 +13,7 @@ deps =
du12: docutils==0.12
du13: docutils==0.13.1
du14: docutils==0.14
du15: docutils==0.15
extras =
test
setenv =