mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-10-12 16:32:10 -05:00
Merge branch '2.0'
This commit is contained in:
commit
954db2bd27
11 files changed
+160
-26
No files matched your search
+1
-1
@@ -24,7 +24,7 @@ matrix:
|
|||||||
env:
|
env:
|
||||||
- TOXENV=du15
|
- TOXENV=du15
|
||||||
- PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg"
|
- PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg"
|
||||||
- python: '3.8'
|
- python: 'nightly'
|
||||||
env:
|
env:
|
||||||
- TOXENV=du16
|
- TOXENV=du16
|
||||||
- python: '3.6'
|
- python: '3.6'
|
||||||
|
|||||||
@@ -85,6 +85,12 @@ Features added
|
|||||||
Bugs fixed
|
Bugs fixed
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
* #7138: autodoc: ``autodoc.typehints`` crashed when variable has unbound object
|
||||||
|
as a value
|
||||||
|
* #7156: autodoc: separator for keyword only arguments is not shown
|
||||||
|
* #7146: autodoc: IndexError is raised on suppressed type_comment found
|
||||||
|
* #7151: crashed when extension assigns a value to ``env.indexentries``
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ templates_path = ['_templates']
|
|||||||
exclude_patterns = ['_build']
|
exclude_patterns = ['_build']
|
||||||
|
|
||||||
project = 'Sphinx'
|
project = 'Sphinx'
|
||||||
copyright = '2007-2019, Georg Brandl and the Sphinx team'
|
copyright = '2007-2020, Georg Brandl and the Sphinx team'
|
||||||
version = sphinx.__display_version__
|
version = sphinx.__display_version__
|
||||||
release = version
|
release = version
|
||||||
show_authors = True
|
show_authors = True
|
||||||
|
|||||||
@@ -644,3 +644,11 @@ class BuildEnvironment:
|
|||||||
from sphinx.domains.index import IndexDomain
|
from sphinx.domains.index import IndexDomain
|
||||||
domain = cast(IndexDomain, self.get_domain('index'))
|
domain = cast(IndexDomain, self.get_domain('index'))
|
||||||
return domain.entries
|
return domain.entries
|
||||||
|
|
||||||
|
@indexentries.setter
|
||||||
|
def indexentries(self, entries: Dict[str, List[Tuple[str, str, str, str, str]]]) -> None:
|
||||||
|
warnings.warn('env.indexentries() is deprecated. Please use IndexDomain instead.',
|
||||||
|
RemovedInSphinx40Warning, stacklevel=2)
|
||||||
|
from sphinx.domains.index import IndexDomain
|
||||||
|
domain = cast(IndexDomain, self.get_domain('index'))
|
||||||
|
domain.data['entries'] = entries
|
||||||
@@ -8,13 +8,13 @@
|
|||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ast
|
from inspect import Parameter, Signature, getsource
|
||||||
from inspect import getsource
|
from typing import Any, Dict, List
|
||||||
from typing import Any, Dict
|
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
import sphinx
|
import sphinx
|
||||||
from sphinx.application import Sphinx
|
from sphinx.application import Sphinx
|
||||||
|
from sphinx.pycode.ast import ast
|
||||||
from sphinx.pycode.ast import parse as ast_parse
|
from sphinx.pycode.ast import parse as ast_parse
|
||||||
from sphinx.pycode.ast import unparse as ast_unparse
|
from sphinx.pycode.ast import unparse as ast_unparse
|
||||||
from sphinx.util import inspect
|
from sphinx.util import inspect
|
||||||
@@ -23,11 +23,73 @@ from sphinx.util import logging
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_type_comment(obj: Any) -> ast.FunctionDef:
|
def not_suppressed(argtypes: List[ast.AST] = []) -> bool:
|
||||||
|
"""Check given *argtypes* is suppressed type_comment or not."""
|
||||||
|
if len(argtypes) == 0: # no argtypees
|
||||||
|
return False
|
||||||
|
elif len(argtypes) == 1 and ast_unparse(argtypes[0]) == "...": # suppressed
|
||||||
|
# Note: To support multiple versions of python, this uses ``ast_unparse()`` for
|
||||||
|
# comparison with Ellipsis. Since 3.8, ast.Constant has been used to represent
|
||||||
|
# Ellipsis node instead of ast.Ellipsis.
|
||||||
|
return False
|
||||||
|
else: # not suppressed
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def signature_from_ast(node: ast.FunctionDef, bound_method: bool,
|
||||||
|
type_comment: ast.FunctionDef) -> Signature:
|
||||||
|
"""Return a Signature object for the given *node*.
|
||||||
|
|
||||||
|
:param bound_method: Specify *node* is a bound method or not
|
||||||
|
"""
|
||||||
|
params = []
|
||||||
|
if hasattr(node.args, "posonlyargs"): # for py38+
|
||||||
|
for arg in node.args.posonlyargs: # type: ignore
|
||||||
|
param = Parameter(arg.arg, Parameter.POSITIONAL_ONLY, annotation=arg.type_comment)
|
||||||
|
params.append(param)
|
||||||
|
|
||||||
|
for arg in node.args.args:
|
||||||
|
param = Parameter(arg.arg, Parameter.POSITIONAL_OR_KEYWORD,
|
||||||
|
annotation=arg.type_comment or Parameter.empty)
|
||||||
|
params.append(param)
|
||||||
|
|
||||||
|
if node.args.vararg:
|
||||||
|
param = Parameter(node.args.vararg.arg, Parameter.VAR_POSITIONAL,
|
||||||
|
annotation=arg.type_comment or Parameter.empty)
|
||||||
|
params.append(param)
|
||||||
|
|
||||||
|
for arg in node.args.kwonlyargs:
|
||||||
|
param = Parameter(arg.arg, Parameter.KEYWORD_ONLY,
|
||||||
|
annotation=arg.type_comment or Parameter.empty)
|
||||||
|
params.append(param)
|
||||||
|
|
||||||
|
if node.args.kwarg:
|
||||||
|
param = Parameter(node.args.kwarg.arg, Parameter.VAR_KEYWORD,
|
||||||
|
annotation=arg.type_comment or Parameter.empty)
|
||||||
|
params.append(param)
|
||||||
|
|
||||||
|
# Remove first parameter when *obj* is bound_method
|
||||||
|
if bound_method and params:
|
||||||
|
params.pop(0)
|
||||||
|
|
||||||
|
# merge type_comment into signature
|
||||||
|
if not_suppressed(type_comment.argtypes): # type: ignore
|
||||||
|
for i, param in enumerate(params):
|
||||||
|
params[i] = param.replace(annotation=type_comment.argtypes[i]) # type: ignore
|
||||||
|
|
||||||
|
if node.returns:
|
||||||
|
return Signature(params, return_annotation=node.returns)
|
||||||
|
elif type_comment.returns:
|
||||||
|
return Signature(params, return_annotation=ast_unparse(type_comment.returns))
|
||||||
|
else:
|
||||||
|
return Signature(params)
|
||||||
|
|
||||||
|
|
||||||
|
def get_type_comment(obj: Any, bound_method: bool = False) -> Signature:
|
||||||
"""Get type_comment'ed FunctionDef object from living object.
|
"""Get type_comment'ed FunctionDef object from living object.
|
||||||
|
|
||||||
This tries to parse original code for living object and returns
|
This tries to parse original code for living object and returns
|
||||||
AST node for given *obj*. It requires py38+ or typed_ast module.
|
Signature for given *obj*. It requires py38+ or typed_ast module.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
source = getsource(obj)
|
source = getsource(obj)
|
||||||
@@ -41,7 +103,8 @@ def get_type_comment(obj: Any) -> ast.FunctionDef:
|
|||||||
subject = cast(ast.FunctionDef, module.body[0]) # type: ignore
|
subject = cast(ast.FunctionDef, module.body[0]) # type: ignore
|
||||||
|
|
||||||
if getattr(subject, "type_comment", None):
|
if getattr(subject, "type_comment", None):
|
||||||
return ast_parse(subject.type_comment, mode='func_type') # type: ignore
|
function = ast_parse(subject.type_comment, mode='func_type')
|
||||||
|
return signature_from_ast(subject, bound_method, function) # type: ignore
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
except (OSError, TypeError): # failed to load source code
|
except (OSError, TypeError): # failed to load source code
|
||||||
@@ -53,17 +116,17 @@ def get_type_comment(obj: Any) -> ast.FunctionDef:
|
|||||||
def update_annotations_using_type_comments(app: Sphinx, obj: Any, bound_method: bool) -> None:
|
def update_annotations_using_type_comments(app: Sphinx, obj: Any, bound_method: bool) -> None:
|
||||||
"""Update annotations info of *obj* using type_comments."""
|
"""Update annotations info of *obj* using type_comments."""
|
||||||
try:
|
try:
|
||||||
function = get_type_comment(obj)
|
type_sig = get_type_comment(obj, bound_method)
|
||||||
if function and hasattr(function, 'argtypes'):
|
if type_sig:
|
||||||
if function.argtypes != [ast.Ellipsis]: # type: ignore
|
sig = inspect.signature(obj, bound_method)
|
||||||
sig = inspect.signature(obj, bound_method)
|
for param in sig.parameters.values():
|
||||||
for i, param in enumerate(sig.parameters.values()):
|
if param.name not in obj.__annotations__:
|
||||||
if param.name not in obj.__annotations__:
|
annotation = type_sig.parameters[param.name].annotation
|
||||||
annotation = ast_unparse(function.argtypes[i]) # type: ignore
|
if annotation is not Parameter.empty:
|
||||||
obj.__annotations__[param.name] = annotation
|
obj.__annotations__[param.name] = ast_unparse(annotation)
|
||||||
|
|
||||||
if 'return' not in obj.__annotations__:
|
if 'return' not in obj.__annotations__:
|
||||||
obj.__annotations__['return'] = ast_unparse(function.returns) # type: ignore
|
obj.__annotations__['return'] = type_sig.return_annotation
|
||||||
except NotImplementedError as exc: # failed to ast.unparse()
|
except NotImplementedError as exc: # failed to ast.unparse()
|
||||||
logger.warning("Failed to parse type_comment for %r: %s", obj, exc)
|
logger.warning("Failed to parse type_comment for %r: %s", obj, exc)
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any,
|
|||||||
annotation[param.name] = typing.stringify(param.annotation)
|
annotation[param.name] = typing.stringify(param.annotation)
|
||||||
if sig.return_annotation is not sig.empty:
|
if sig.return_annotation is not sig.empty:
|
||||||
annotation['return'] = typing.stringify(sig.return_annotation)
|
annotation['return'] = typing.stringify(sig.return_annotation)
|
||||||
except TypeError:
|
except (TypeError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -389,9 +389,9 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True,
|
|||||||
if param.kind != param.POSITIONAL_ONLY and last_kind == param.POSITIONAL_ONLY:
|
if param.kind != param.POSITIONAL_ONLY and last_kind == param.POSITIONAL_ONLY:
|
||||||
# PEP-570: Separator for Positional Only Parameter: /
|
# PEP-570: Separator for Positional Only Parameter: /
|
||||||
args.append('/')
|
args.append('/')
|
||||||
elif param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD,
|
if param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD,
|
||||||
param.POSITIONAL_ONLY,
|
param.POSITIONAL_ONLY,
|
||||||
None):
|
None):
|
||||||
# PEP-3102: Separator for Keyword Only Parameter: *
|
# PEP-3102: Separator for Keyword Only Parameter: *
|
||||||
args.append('*')
|
args.append('*')
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
def foo(a, b, /, c, d):
|
def foo(*, a, b):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def bar(a, b, /):
|
def bar(a, b, /, c, d):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def baz(a, /, *, b):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def qux(a, b, /):
|
||||||
pass
|
pass
|
||||||
@@ -18,7 +18,27 @@ class Math:
|
|||||||
# type: (int, int) -> int
|
# type: (int, int) -> int
|
||||||
return a - b
|
return a - b
|
||||||
|
|
||||||
|
def nothing(self):
|
||||||
|
# type: () -> None
|
||||||
|
pass
|
||||||
|
|
||||||
|
def horse(self,
|
||||||
|
a, # type: str
|
||||||
|
b, # type: int
|
||||||
|
):
|
||||||
|
# type: (...) -> None
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def complex_func(arg1, arg2, arg3=None, *args, **kwargs):
|
def complex_func(arg1, arg2, arg3=None, *args, **kwargs):
|
||||||
# type: (str, List[int], Tuple[int, Union[str, Unknown]], *str, **str) -> None
|
# type: (str, List[int], Tuple[int, Union[str, Unknown]], *str, **str) -> None
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def missing_attr(c,
|
||||||
|
a, # type: str
|
||||||
|
b=None # type: Optional[str]
|
||||||
|
):
|
||||||
|
# type: (...) -> str
|
||||||
|
return a + (b or "")
|
||||||
|
|
||||||
@@ -482,9 +482,17 @@ def test_autodoc_typehints_signature(app):
|
|||||||
' :module: target.typehints',
|
' :module: target.typehints',
|
||||||
' ',
|
' ',
|
||||||
' ',
|
' ',
|
||||||
|
' .. py:method:: Math.horse(a: str, b: int) -> None',
|
||||||
|
' :module: target.typehints',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
' .. py:method:: Math.incr(a: int, b: int = 1) -> int',
|
' .. py:method:: Math.incr(a: int, b: int = 1) -> int',
|
||||||
' :module: target.typehints',
|
' :module: target.typehints',
|
||||||
' ',
|
' ',
|
||||||
|
' ',
|
||||||
|
' .. py:method:: Math.nothing() -> None',
|
||||||
|
' :module: target.typehints',
|
||||||
|
' ',
|
||||||
'',
|
'',
|
||||||
'.. py:function:: complex_func(arg1: str, arg2: List[int], arg3: Tuple[int, '
|
'.. py:function:: complex_func(arg1: str, arg2: List[int], arg3: Tuple[int, '
|
||||||
'Union[str, Unknown]] = None, *args: str, **kwargs: str) -> None',
|
'Union[str, Unknown]] = None, *args: str, **kwargs: str) -> None',
|
||||||
@@ -497,6 +505,10 @@ def test_autodoc_typehints_signature(app):
|
|||||||
'',
|
'',
|
||||||
'.. py:function:: incr(a: int, b: int = 1) -> int',
|
'.. py:function:: incr(a: int, b: int = 1) -> int',
|
||||||
' :module: target.typehints',
|
' :module: target.typehints',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'.. py:function:: missing_attr(c, a: str, b: Optional[str] = None) -> str',
|
||||||
|
' :module: target.typehints',
|
||||||
''
|
''
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -521,9 +533,17 @@ def test_autodoc_typehints_none(app):
|
|||||||
' :module: target.typehints',
|
' :module: target.typehints',
|
||||||
' ',
|
' ',
|
||||||
' ',
|
' ',
|
||||||
|
' .. py:method:: Math.horse(a, b)',
|
||||||
|
' :module: target.typehints',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
' .. py:method:: Math.incr(a, b=1)',
|
' .. py:method:: Math.incr(a, b=1)',
|
||||||
' :module: target.typehints',
|
' :module: target.typehints',
|
||||||
' ',
|
' ',
|
||||||
|
' ',
|
||||||
|
' .. py:method:: Math.nothing()',
|
||||||
|
' :module: target.typehints',
|
||||||
|
' ',
|
||||||
'',
|
'',
|
||||||
'.. py:function:: complex_func(arg1, arg2, arg3=None, *args, **kwargs)',
|
'.. py:function:: complex_func(arg1, arg2, arg3=None, *args, **kwargs)',
|
||||||
' :module: target.typehints',
|
' :module: target.typehints',
|
||||||
@@ -535,6 +555,10 @@ def test_autodoc_typehints_none(app):
|
|||||||
'',
|
'',
|
||||||
'.. py:function:: incr(a, b=1)',
|
'.. py:function:: incr(a, b=1)',
|
||||||
' :module: target.typehints',
|
' :module: target.typehints',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'.. py:function:: missing_attr(c, a, b=None)',
|
||||||
|
' :module: target.typehints',
|
||||||
''
|
''
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -298,14 +298,21 @@ def test_signature_annotations():
|
|||||||
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
|
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
|
||||||
@pytest.mark.sphinx(testroot='ext-autodoc')
|
@pytest.mark.sphinx(testroot='ext-autodoc')
|
||||||
def test_signature_annotations_py38(app):
|
def test_signature_annotations_py38(app):
|
||||||
from target.pep570 import foo, bar
|
from target.pep570 import foo, bar, baz, qux
|
||||||
|
|
||||||
|
# case: separator at head
|
||||||
|
sig = inspect.signature(foo)
|
||||||
|
assert stringify_signature(sig) == '(*, a, b)'
|
||||||
|
|
||||||
# case: separator in the middle
|
# case: separator in the middle
|
||||||
sig = inspect.signature(foo)
|
sig = inspect.signature(bar)
|
||||||
assert stringify_signature(sig) == '(a, b, /, c, d)'
|
assert stringify_signature(sig) == '(a, b, /, c, d)'
|
||||||
|
|
||||||
|
sig = inspect.signature(baz)
|
||||||
|
assert stringify_signature(sig) == '(a, /, *, b)'
|
||||||
|
|
||||||
# case: separator at tail
|
# case: separator at tail
|
||||||
sig = inspect.signature(bar)
|
sig = inspect.signature(qux)
|
||||||
assert stringify_signature(sig) == '(a, b, /)'
|
assert stringify_signature(sig) == '(a, b, /)'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user