summaryrefslogtreecommitdiff
path: root/sphinx/util/inspect.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/util/inspect.py')
-rw-r--r--sphinx/util/inspect.py122
1 files changed, 92 insertions, 30 deletions
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index 2bcccdd60..855b11d83 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -17,12 +17,15 @@ import typing
import warnings
from functools import partial, partialmethod
from inspect import ( # NOQA
- isclass, ismethod, ismethoddescriptor, isroutine
+ Parameter, isclass, ismethod, ismethoddescriptor, isroutine
)
from io import StringIO
from typing import Any, Callable, Mapping, List, Tuple
+from typing import cast
-from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
+from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
+from sphinx.pycode.ast import ast # for py35-37
+from sphinx.pycode.ast import unparse as ast_unparse
from sphinx.util import logging
from sphinx.util.typing import stringify as stringify_annotation
@@ -51,9 +54,11 @@ memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE)
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
# 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software
# Foundation; All Rights Reserved
-def getargspec(func):
+def getargspec(func: Callable) -> Any:
"""Like inspect.getfullargspec but supports bound methods, and wrapped
methods."""
+ warnings.warn('sphinx.ext.inspect.getargspec() is deprecated',
+ RemovedInSphinx50Warning)
# On 3.5+, signature(int) or similar raises ValueError. On 3.4, it
# succeeds with a bogus signature. We want a TypeError uniformly, to
# match historical behavior.
@@ -81,19 +86,19 @@ def getargspec(func):
kind = param.kind
name = param.name
- if kind is inspect.Parameter.POSITIONAL_ONLY:
+ if kind is Parameter.POSITIONAL_ONLY:
args.append(name)
- elif kind is inspect.Parameter.POSITIONAL_OR_KEYWORD:
+ elif kind is Parameter.POSITIONAL_OR_KEYWORD:
args.append(name)
if param.default is not param.empty:
defaults += (param.default,) # type: ignore
- elif kind is inspect.Parameter.VAR_POSITIONAL:
+ elif kind is Parameter.VAR_POSITIONAL:
varargs = name
- elif kind is inspect.Parameter.KEYWORD_ONLY:
+ elif kind is Parameter.KEYWORD_ONLY:
kwonlyargs.append(name)
if param.default is not param.empty:
kwdefaults[name] = param.default
- elif kind is inspect.Parameter.VAR_KEYWORD:
+ elif kind is Parameter.VAR_KEYWORD:
varkw = name
if param.annotation is not param.empty:
@@ -192,6 +197,14 @@ def isabstractmethod(obj: Any) -> bool:
return safe_getattr(obj, '__isabstractmethod__', False) is True
+def is_cython_function_or_method(obj: Any) -> bool:
+ """Check if the object is a function or method in cython."""
+ try:
+ return obj.__class__.__name__ == 'cython_function_or_method'
+ except AttributeError:
+ return False
+
+
def isattributedescriptor(obj: Any) -> bool:
"""Check if the object is an attribute like descriptor."""
if inspect.isdatadescriptor(object):
@@ -202,6 +215,9 @@ def isattributedescriptor(obj: Any) -> bool:
if isfunction(obj) or isbuiltin(obj) or inspect.ismethod(obj):
# attribute must not be either function, builtin and method
return False
+ elif is_cython_function_or_method(obj):
+ # attribute must not be either function and method (for cython)
+ return False
elif inspect.isclass(obj):
# attribute must not be a class
return False
@@ -219,6 +235,26 @@ def isattributedescriptor(obj: Any) -> bool:
return False
+def is_singledispatch_function(obj: Any) -> bool:
+ """Check if the object is singledispatch function."""
+ if (inspect.isfunction(obj) and
+ hasattr(obj, 'dispatch') and
+ hasattr(obj, 'register') and
+ obj.dispatch.__module__ == 'functools'):
+ return True
+ else:
+ return False
+
+
+def is_singledispatch_method(obj: Any) -> bool:
+ """Check if the object is singledispatch method."""
+ try:
+ from functools import singledispatchmethod # type: ignore
+ return isinstance(obj, singledispatchmethod)
+ except ImportError: # py35-37
+ return False
+
+
def isfunction(obj: Any) -> bool:
"""Check if the object is function."""
return inspect.isfunction(unwrap(obj))
@@ -359,7 +395,7 @@ def signature(subject: Callable, bound_method: bool = False) -> inspect.Signatur
# https://bugs.python.org/issue33009
if hasattr(subject, '_partialmethod'):
parameters = []
- return_annotation = inspect.Parameter.empty
+ return_annotation = Parameter.empty
else:
raise
@@ -427,11 +463,11 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True,
args.append(arg.getvalue())
last_kind = param.kind
- if last_kind == inspect.Parameter.POSITIONAL_ONLY:
+ if last_kind == Parameter.POSITIONAL_ONLY:
# PEP-570: Separator for Positional Only Parameter: /
args.append('/')
- if (sig.return_annotation is inspect.Parameter.empty or
+ if (sig.return_annotation is Parameter.empty or
show_annotation is False or
show_return_annotation is False):
return '(%s)' % ', '.join(args)
@@ -440,24 +476,50 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True,
return '(%s) -> %s' % (', '.join(args), annotation)
-class Parameter:
- """Fake parameter class for python2."""
- POSITIONAL_ONLY = 0
- POSITIONAL_OR_KEYWORD = 1
- VAR_POSITIONAL = 2
- KEYWORD_ONLY = 3
- VAR_KEYWORD = 4
- empty = object()
+def signature_from_str(signature: str) -> inspect.Signature:
+ """Create a Signature object from string."""
+ module = ast.parse('def func' + signature + ': pass')
+ definition = cast(ast.FunctionDef, module.body[0]) # type: ignore
+
+ # parameters
+ args = definition.args
+ params = []
+
+ if hasattr(args, "posonlyargs"):
+ for arg in args.posonlyargs: # type: ignore
+ annotation = ast_unparse(arg.annotation) or Parameter.empty
+ params.append(Parameter(arg.arg, Parameter.POSITIONAL_ONLY,
+ annotation=annotation))
+
+ for i, arg in enumerate(args.args):
+ if len(args.args) - i <= len(args.defaults):
+ default = ast_unparse(args.defaults[-len(args.args) + i])
+ else:
+ default = Parameter.empty
+
+ annotation = ast_unparse(arg.annotation) or Parameter.empty
+ params.append(Parameter(arg.arg, Parameter.POSITIONAL_OR_KEYWORD,
+ default=default, annotation=annotation))
+
+ if args.vararg:
+ annotation = ast_unparse(args.vararg.annotation) or Parameter.empty
+ params.append(Parameter(args.vararg.arg, Parameter.VAR_POSITIONAL,
+ annotation=annotation))
+
+ for i, arg in enumerate(args.kwonlyargs):
+ default = ast_unparse(args.kw_defaults[i])
+ annotation = ast_unparse(arg.annotation) or Parameter.empty
+ params.append(Parameter(arg.arg, Parameter.KEYWORD_ONLY, default=default,
+ annotation=annotation))
+
+ if args.kwarg:
+ annotation = ast_unparse(args.kwarg.annotation) or Parameter.empty
+ params.append(Parameter(args.kwarg.arg, Parameter.VAR_KEYWORD,
+ annotation=annotation))
- def __init__(self, name: str, kind: int = POSITIONAL_OR_KEYWORD,
- default: Any = empty) -> None:
- self.name = name
- self.kind = kind
- self.default = default
- self.annotation = self.empty
+ return_annotation = ast_unparse(definition.returns) or Parameter.empty
- warnings.warn('sphinx.util.inspect.Parameter is deprecated.',
- RemovedInSphinx30Warning, stacklevel=2)
+ return inspect.Signature(params, return_annotation=return_annotation)
class Signature:
@@ -528,12 +590,12 @@ class Signature:
if self.has_retval:
return self.signature.return_annotation
else:
- return inspect.Parameter.empty
+ return Parameter.empty
else:
return None
def format_args(self, show_annotation: bool = True) -> str:
- def get_annotation(param: inspect.Parameter) -> Any:
+ def get_annotation(param: Parameter) -> Any:
if isinstance(param.annotation, str) and param.name in self.annotations:
return self.annotations[param.name]
else:
@@ -585,7 +647,7 @@ class Signature:
args.append(arg.getvalue())
last_kind = param.kind
- if self.return_annotation is inspect.Parameter.empty or show_annotation is False:
+ if self.return_annotation is Parameter.empty or show_annotation is False:
return '(%s)' % ', '.join(args)
else:
if 'return' in self.annotations: