This commit is contained in:
gpotter2
2020-02-13 15:27:58 +00:00
parent 2e89b66e7a
commit 51b80ab121
3 changed files with 85 additions and 11 deletions

View File

@@ -9,6 +9,7 @@
"""
import ast
import itertools
from inspect import getsource
from typing import Any, Dict
from typing import cast
@@ -23,11 +24,11 @@ from sphinx.util import logging
logger = logging.getLogger(__name__)
def get_type_comment(obj: Any) -> ast.FunctionDef:
def get_type_comment(obj: Any) -> Dict[str, Any]:
"""Get type_comment'ed FunctionDef object from living object.
This tries to parse original code for living object and returns
AST node for given *obj*. It requires py38+ or typed_ast module.
parameter dictionary for given *obj*. It requires py38+ or typed_ast module.
"""
try:
source = getsource(obj)
@@ -41,7 +42,34 @@ def get_type_comment(obj: Any) -> ast.FunctionDef:
subject = cast(ast.FunctionDef, module.body[0]) # type: ignore
if getattr(subject, "type_comment", None):
return ast_parse(subject.type_comment, mode='func_type') # type: ignore
comment = subject.type_comment # type: ignore
if not comment.startswith("(...)"):
node = cast(
ast.FunctionDef,
ast_parse(comment, mode='func_type')
)
results = {
'returns': node.returns,
'explicit': False,
'args': node.argtypes # type: ignore
}
else:
node = subject
results = {
'returns': node.returns,
'explicit': True,
'args': {
arg.arg: arg.type_comment
for arg in itertools.chain(
getattr(node.args, "posonlyargs", []) or [],
getattr(node.args, "args", []) or [],
getattr(node.args, "vararg", []) or [],
getattr(node.args, "kwonlyargs", []) or [],
getattr(node.args, "kwarg", []) or [],
)
}
}
return results
else:
return None
except (OSError, TypeError): # failed to load source code
@@ -54,16 +82,18 @@ def update_annotations_using_type_comments(app: Sphinx, obj: Any, bound_method:
"""Update annotations info of *obj* using type_comments."""
try:
function = get_type_comment(obj)
if function and hasattr(function, 'argtypes'):
if function.argtypes != [ast.Ellipsis]: # type: ignore
sig = inspect.signature(obj, bound_method)
for i, param in enumerate(sig.parameters.values()):
if param.name not in obj.__annotations__:
annotation = ast_unparse(function.argtypes[i]) # type: ignore
obj.__annotations__[param.name] = annotation
if function:
explicit = function['explicit']
args = function['args']
sig = inspect.signature(obj, bound_method)
for i, param in enumerate(sig.parameters.values()):
if param.name not in obj.__annotations__:
type_hint = args[param.name if explicit else i]
annotation = ast_unparse(type_hint)
obj.__annotations__[param.name] = annotation
if 'return' not in obj.__annotations__:
obj.__annotations__['return'] = ast_unparse(function.returns) # type: ignore
obj.__annotations__['return'] = ast_unparse(function['returns'])
except NotImplementedError as exc: # failed to ast.unparse()
logger.warning("Failed to parse type_comment for %r: %s", obj, exc)

View File

@@ -18,7 +18,27 @@ class Math:
# type: (int, int) -> int
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):
# type: (str, List[int], Tuple[int, Union[str, Unknown]], *str, **str) -> None
pass
def missing_attr(c,
a, # type: str
b=None # type: Optional[str]
):
# type: (...) -> str
return a + (b or "")

View File

@@ -483,9 +483,17 @@ def test_autodoc_typehints_signature(app):
' :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',
' :module: target.typehints',
' ',
' ',
' .. py:method:: Math.nothing() -> None',
' :module: target.typehints',
' ',
'',
'.. py:function:: complex_func(arg1: str, arg2: List[int], arg3: Tuple[int, '
'Union[str, Unknown]] = None, *args: str, **kwargs: str) -> None',
@@ -498,6 +506,10 @@ def test_autodoc_typehints_signature(app):
'',
'.. py:function:: incr(a: int, b: int = 1) -> int',
' :module: target.typehints',
'',
'',
'.. py:function:: missing_attr(c: None, a: str, b: Optional[str] = None) -> None',
' :module: target.typehints',
''
]
@@ -522,9 +534,17 @@ def test_autodoc_typehints_none(app):
' :module: target.typehints',
' ',
' ',
' .. py:method:: Math.horse(a, b)',
' :module: target.typehints',
' ',
' ',
' .. py:method:: Math.incr(a, b=1)',
' :module: target.typehints',
' ',
' ',
' .. py:method:: Math.nothing()',
' :module: target.typehints',
' ',
'',
'.. py:function:: complex_func(arg1, arg2, arg3=None, *args, **kwargs)',
' :module: target.typehints',
@@ -536,6 +556,10 @@ def test_autodoc_typehints_none(app):
'',
'.. py:function:: incr(a, b=1)',
' :module: target.typehints',
'',
'',
'.. py:function:: missing_attr(c, a, b=None)',
' :module: target.typehints',
''
]