diff options
Diffstat (limited to 'sphinx/ext/autodoc/inspector.py')
-rw-r--r-- | sphinx/ext/autodoc/inspector.py | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/sphinx/ext/autodoc/inspector.py b/sphinx/ext/autodoc/inspector.py new file mode 100644 index 000000000..da779d531 --- /dev/null +++ b/sphinx/ext/autodoc/inspector.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +""" + sphinx.ext.autodoc.inspector + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Inspect utilities for autodoc + + :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import typing + +from six import StringIO, string_types + +from sphinx.util.inspect import object_description + +if False: + # For type annotation + from typing import Any, Callable, Dict, Tuple # NOQA + + +def format_annotation(annotation): + # type: (Any) -> str + """Return formatted representation of a type annotation. + + Show qualified names for types and additional details for types from + the ``typing`` module. + + Displaying complex types from ``typing`` relies on its private API. + """ + if isinstance(annotation, typing.TypeVar): # type: ignore + return annotation.__name__ + if annotation == Ellipsis: + return '...' + if not isinstance(annotation, type): + return repr(annotation) + + qualified_name = (annotation.__module__ + '.' + annotation.__qualname__ # type: ignore + if annotation else repr(annotation)) + + if annotation.__module__ == 'builtins': + return annotation.__qualname__ # type: ignore + else: + if hasattr(typing, 'GenericMeta') and \ + isinstance(annotation, typing.GenericMeta): + # In Python 3.5.2+, all arguments are stored in __args__, + # whereas __parameters__ only contains generic parameters. + # + # Prior to Python 3.5.2, __args__ is not available, and all + # arguments are in __parameters__. + params = None + if hasattr(annotation, '__args__'): + if annotation.__args__ is None or len(annotation.__args__) <= 2: # type: ignore # NOQA + params = annotation.__args__ # type: ignore + else: # typing.Callable + args = ', '.join(format_annotation(a) for a in annotation.__args__[:-1]) # type: ignore # NOQA + result = format_annotation(annotation.__args__[-1]) # type: ignore + return '%s[[%s], %s]' % (qualified_name, args, result) + elif hasattr(annotation, '__parameters__'): + params = annotation.__parameters__ # type: ignore + if params is not None: + param_str = ', '.join(format_annotation(p) for p in params) + return '%s[%s]' % (qualified_name, param_str) + elif (hasattr(typing, 'UnionMeta') and + isinstance(annotation, typing.UnionMeta) and # type: ignore + hasattr(annotation, '__union_params__')): + params = annotation.__union_params__ # type: ignore + if params is not None: + param_str = ', '.join(format_annotation(p) for p in params) + return '%s[%s]' % (qualified_name, param_str) + elif (hasattr(typing, 'CallableMeta') and + isinstance(annotation, typing.CallableMeta) and # type: ignore + getattr(annotation, '__args__', None) is not None and + hasattr(annotation, '__result__')): + # Skipped in the case of plain typing.Callable + args = annotation.__args__ # type: ignore + if args is None: + return qualified_name + elif args is Ellipsis: + args_str = '...' + else: + formatted_args = (format_annotation(a) for a in args) + args_str = '[%s]' % ', '.join(formatted_args) + return '%s[%s, %s]' % (qualified_name, + args_str, + format_annotation(annotation.__result__)) # type: ignore + elif (hasattr(typing, 'TupleMeta') and + isinstance(annotation, typing.TupleMeta) and # type: ignore + hasattr(annotation, '__tuple_params__') and + hasattr(annotation, '__tuple_use_ellipsis__')): + params = annotation.__tuple_params__ # type: ignore + if params is not None: + param_strings = [format_annotation(p) for p in params] + if annotation.__tuple_use_ellipsis__: # type: ignore + param_strings.append('...') + return '%s[%s]' % (qualified_name, + ', '.join(param_strings)) + return qualified_name + + +def formatargspec(function, args, varargs=None, varkw=None, defaults=None, + kwonlyargs=(), kwonlydefaults={}, annotations={}): + # type: (Callable, Tuple[str, ...], str, str, Any, Tuple, Dict, Dict[str, Any]) -> str + """Return a string representation of an ``inspect.FullArgSpec`` tuple. + + An enhanced version of ``inspect.formatargspec()`` that handles typing + annotations better. + """ + + def format_arg_with_annotation(name): + # type: (str) -> str + if name in annotations: + return '%s: %s' % (name, format_annotation(get_annotation(name))) + return name + + def get_annotation(name): + # type: (str) -> str + value = annotations[name] + if isinstance(value, string_types): + return introspected_hints.get(name, value) + else: + return value + + introspected_hints = (typing.get_type_hints(function) + if typing and hasattr(function, '__code__') else {}) + + fd = StringIO() + fd.write('(') + + formatted = [] + defaults_start = len(args) - len(defaults) if defaults else len(args) + + for i, arg in enumerate(args): + arg_fd = StringIO() + if isinstance(arg, list): + # support tupled arguments list (only for py2): def foo((x, y)) + arg_fd.write('(') + arg_fd.write(format_arg_with_annotation(arg[0])) + for param in arg[1:]: + arg_fd.write(', ') + arg_fd.write(format_arg_with_annotation(param)) + arg_fd.write(')') + else: + arg_fd.write(format_arg_with_annotation(arg)) + if defaults and i >= defaults_start: + arg_fd.write(' = ' if arg in annotations else '=') + arg_fd.write(object_description(defaults[i - defaults_start])) # type: ignore + formatted.append(arg_fd.getvalue()) + + if varargs: + formatted.append('*' + format_arg_with_annotation(varargs)) + + if kwonlyargs: + if not varargs: + formatted.append('*') + + for kwarg in kwonlyargs: + arg_fd = StringIO() + arg_fd.write(format_arg_with_annotation(kwarg)) + if kwonlydefaults and kwarg in kwonlydefaults: + arg_fd.write(' = ' if kwarg in annotations else '=') + arg_fd.write(object_description(kwonlydefaults[kwarg])) # type: ignore + formatted.append(arg_fd.getvalue()) + + if varkw: + formatted.append('**' + format_arg_with_annotation(varkw)) + + fd.write(', '.join(formatted)) + fd.write(')') + + if 'return' in annotations: + fd.write(' -> ') + fd.write(format_annotation(get_annotation('return'))) + + return fd.getvalue() |