summaryrefslogtreecommitdiff
path: root/sphinx/ext/autodoc.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/ext/autodoc.py')
-rw-r--r--sphinx/ext/autodoc.py154
1 files changed, 139 insertions, 15 deletions
diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py
index 27cd54f93..13621c118 100644
--- a/sphinx/ext/autodoc.py
+++ b/sphinx/ext/autodoc.py
@@ -17,7 +17,8 @@ import inspect
import traceback
from types import FunctionType, BuiltinFunctionType, MethodType
-from six import iteritems, itervalues, text_type, class_types
+from six import iterkeys, iteritems, itervalues, text_type, class_types, \
+ string_types, StringIO
from docutils import nodes
from docutils.utils import assemble_option_dict
from docutils.statemachine import ViewList
@@ -33,6 +34,10 @@ from sphinx.util.inspect import getargspec, isdescriptor, safe_getmembers, \
safe_getattr, object_description, is_builtin_class_method
from sphinx.util.docstrings import prepare_docstring
+try:
+ import typing
+except ImportError:
+ typing = None
#: extended signature RE: with explicit module name separated by ::
py_ext_sig_re = re.compile(
@@ -62,7 +67,9 @@ class DefDict(dict):
return True
__nonzero__ = __bool__ # for python2 compatibility
-identity = lambda x: x
+
+def identity(x):
+ return x
class Options(dict):
@@ -243,9 +250,126 @@ def between(marker, what=None, keepempty=False, exclude=False):
return process
-def formatargspec(*argspec):
- return inspect.formatargspec(*argspec,
- formatvalue=lambda x: '=' + object_description(x))
+def format_annotation(annotation):
+ """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.
+ """
+ qualified_name = (annotation.__module__ + '.' + annotation.__qualname__
+ if annotation else repr(annotation))
+
+ if not isinstance(annotation, type):
+ return repr(annotation)
+ elif annotation.__module__ == 'builtins':
+ return annotation.__qualname__
+ elif typing:
+ if isinstance(annotation, typing.TypeVar):
+ return annotation.__name__
+ elif hasattr(typing, 'GenericMeta') and \
+ isinstance(annotation, typing.GenericMeta) and \
+ hasattr(annotation, '__parameters__'):
+ params = annotation.__parameters__
+ 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 \
+ hasattr(annotation, '__union_params__'):
+ params = annotation.__union_params__
+ 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 \
+ hasattr(annotation, '__args__') and \
+ hasattr(annotation, '__result__'):
+ args = annotation.__args__
+ if 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__))
+ elif hasattr(typing, 'TupleMeta') and \
+ isinstance(annotation, typing.TupleMeta) and \
+ hasattr(annotation, '__tuple_params__') and \
+ hasattr(annotation, '__tuple_use_ellipsis__'):
+ params = annotation.__tuple_params__
+ if params is not None:
+ param_strings = [format_annotation(p) for p in params]
+ if annotation.__tuple_use_ellipsis__:
+ 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={}):
+ """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):
+ if name in annotations:
+ return '%s: %s' % (name, format_annotation(get_annotation(name)))
+ return name
+
+ def get_annotation(name):
+ 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()
+ 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]))
+ formatted.append(arg_fd.getvalue())
+
+ if varargs:
+ formatted.append('*' + format_arg_with_annotation(varargs))
+
+ if kwonlyargs:
+ 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]))
+ 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()
class Documenter(object):
@@ -596,7 +720,7 @@ class Documenter(object):
# __dict__ contains only the members directly defined in
# the class (but get them via getattr anyway, to e.g. get
# unbound method objects instead of function objects);
- # using keys() because apparently there are objects for which
+ # using list(iterkeys()) because apparently there are objects for which
# __dict__ changes while getting attributes
try:
obj_dict = self.get_attr(self.object, '__dict__')
@@ -604,7 +728,7 @@ class Documenter(object):
members = []
else:
members = [(mname, self.get_attr(self.object, mname, None))
- for mname in obj_dict.keys()]
+ for mname in list(iterkeys(obj_dict))]
membernames = set(m[0] for m in members)
# add instance attributes from the analyzer
for aname in analyzed_member_names:
@@ -886,7 +1010,7 @@ class ModuleDocumenter(Documenter):
memberlist = self.object.__all__
# Sometimes __all__ is broken...
if not isinstance(memberlist, (list, tuple)) or not \
- all(isinstance(entry, str) for entry in memberlist):
+ all(isinstance(entry, string_types) for entry in memberlist):
self.directive.warn(
'__all__ should be a list of strings, not %r '
'(in module %s) -- ignoring __all__' %
@@ -1059,7 +1183,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
argspec = getargspec(self.object.__init__)
if argspec[0]:
del argspec[0][0]
- args = formatargspec(*argspec)
+ args = formatargspec(self.object, *argspec)
# escape backslashes for reST
args = args.replace('\\', '\\\\')
return args
@@ -1114,7 +1238,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
return None
if argspec[0] and argspec[0][0] in ('cls', 'self'):
del argspec[0][0]
- return formatargspec(*argspec)
+ return formatargspec(initmeth, *argspec)
def format_signature(self):
if self.doc_as_attr:
@@ -1281,7 +1405,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter):
argspec = getargspec(self.object)
if argspec[0] and argspec[0][0] in ('cls', 'self'):
del argspec[0][0]
- args = formatargspec(*argspec)
+ args = formatargspec(self.object, *argspec)
# escape backslashes for reST
args = args.replace('\\', '\\\\')
return args
@@ -1311,9 +1435,9 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
isinstance(member, cls.method_types) and not \
type(member).__name__ in ("type", "method_descriptor",
"instancemethod")
- return isdatadesc or (not isinstance(parent, ModuleDocumenter)
- and not inspect.isroutine(member)
- and not isinstance(member, class_types))
+ return isdatadesc or (not isinstance(parent, ModuleDocumenter) and
+ not inspect.isroutine(member) and
+ not isinstance(member, class_types))
def document_members(self, all_members=False):
pass
@@ -1523,7 +1647,7 @@ def setup(app):
app.add_event('autodoc-process-signature')
app.add_event('autodoc-skip-member')
- return {'version': sphinx.__version__, 'parallel_read_safe': True}
+ return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
class testcls: