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.py115
1 files changed, 109 insertions, 6 deletions
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index f199e6748..60745be61 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -315,6 +315,112 @@ def is_builtin_class_method(obj: Any, attr_name: str) -> bool:
return getattr(builtins, safe_getattr(cls, '__name__', '')) is cls
+def signature(subject: Callable, bound_method: bool = False) -> inspect.Signature:
+ """Return a Signature object for the given *subject*.
+
+ :param bound_method: Specify *subject* is a bound method or not
+ """
+ # check subject is not a built-in class (ex. int, str)
+ if (isinstance(subject, type) and
+ is_builtin_class_method(subject, "__new__") and
+ is_builtin_class_method(subject, "__init__")):
+ raise TypeError("can't compute signature for built-in type {}".format(subject))
+
+ try:
+ signature = inspect.signature(subject)
+ parameters = list(signature.parameters.values())
+ return_annotation = signature.return_annotation
+ except IndexError:
+ # Until python 3.6.4, cpython has been crashed on inspection for
+ # partialmethods not having any arguments.
+ # https://bugs.python.org/issue33009
+ if hasattr(subject, '_partialmethod'):
+ parameters = []
+ return_annotation = inspect.Parameter.empty
+ else:
+ raise
+
+ try:
+ # Update unresolved annotations using ``get_type_hints()``.
+ annotations = typing.get_type_hints(subject)
+ for i, param in enumerate(parameters):
+ if isinstance(param.annotation, str) and param.name in annotations:
+ parameters[i] = param.replace(annotation=annotations[param.name])
+ if 'return' in annotations:
+ return_annotation = annotations['return']
+ except Exception:
+ # ``get_type_hints()`` does not support some kind of objects like partial,
+ # ForwardRef and so on.
+ pass
+
+ if bound_method:
+ if inspect.ismethod(subject):
+ # ``inspect.signature()`` considers the subject is a bound method and removes
+ # first argument from signature. Therefore no skips are needed here.
+ pass
+ else:
+ if len(parameters) > 0:
+ parameters.pop(0)
+
+ return inspect.Signature(parameters, return_annotation=return_annotation)
+
+
+def stringify_signature(sig: inspect.Signature, show_annotation: bool = True,
+ show_return_annotation: bool = True) -> str:
+ """Stringify a Signature object.
+
+ :param show_annotation: Show annotation in result
+ """
+ args = []
+ last_kind = None
+ for param in sig.parameters.values():
+ # insert '*' between POSITIONAL args and KEYWORD_ONLY args::
+ # func(a, b, *, c, d):
+ if param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD,
+ param.POSITIONAL_ONLY,
+ None):
+ args.append('*')
+
+ arg = StringIO()
+ if param.kind in (param.POSITIONAL_ONLY,
+ param.POSITIONAL_OR_KEYWORD,
+ param.KEYWORD_ONLY):
+ arg.write(param.name)
+ if show_annotation and param.annotation is not param.empty:
+ arg.write(': ')
+ arg.write(stringify_annotation(param.annotation))
+ if param.default is not param.empty:
+ if show_annotation and param.annotation is not param.empty:
+ arg.write(' = ')
+ arg.write(object_description(param.default))
+ else:
+ arg.write('=')
+ arg.write(object_description(param.default))
+ 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(stringify_annotation(param.annotation))
+ 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(stringify_annotation(param.annotation))
+
+ args.append(arg.getvalue())
+ last_kind = param.kind
+
+ if (sig.return_annotation is inspect.Parameter.empty or
+ show_annotation is False or
+ show_return_annotation is False):
+ return '(%s)' % ', '.join(args)
+ else:
+ annotation = stringify_annotation(sig.return_annotation)
+ return '(%s) -> %s' % (', '.join(args), annotation)
+
+
class Signature:
"""The Signature object represents the call signature of a callable object and
its return annotation.
@@ -322,6 +428,9 @@ class Signature:
def __init__(self, subject: Callable, bound_method: bool = False,
has_retval: bool = True) -> None:
+ warnings.warn('sphinx.util.inspect.Signature() is deprecated',
+ RemovedInSphinx40Warning)
+
# check subject is not a built-in class (ex. int, str)
if (isinstance(subject, type) and
is_builtin_class_method(subject, "__new__") and
@@ -447,20 +556,14 @@ class Signature:
def format_annotation(self, annotation: Any) -> str:
"""Return formatted representation of a type annotation."""
- warnings.warn('format_annotation() is deprecated',
- RemovedInSphinx40Warning)
return stringify_annotation(annotation)
def format_annotation_new(self, annotation: Any) -> str:
"""format_annotation() for py37+"""
- warnings.warn('format_annotation_new() is deprecated',
- RemovedInSphinx40Warning)
return stringify_annotation(annotation)
def format_annotation_old(self, annotation: Any) -> str:
"""format_annotation() for py36 or below"""
- warnings.warn('format_annotation_old() is deprecated',
- RemovedInSphinx40Warning)
return stringify_annotation(annotation)