diff options
-rw-r--r-- | sphinx/ext/autodoc/__init__.py (renamed from sphinx/ext/autodoc.py) | 268 | ||||
-rw-r--r-- | sphinx/ext/autodoc/importer.py | 118 | ||||
-rw-r--r-- | sphinx/ext/autodoc/inspector.py | 176 |
3 files changed, 298 insertions, 264 deletions
diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc/__init__.py index c81909668..98dd03eef 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc/__init__.py @@ -16,10 +16,8 @@ import sys import inspect import traceback import warnings -from types import FunctionType, MethodType, ModuleType -from six import PY2, iterkeys, iteritems, itervalues, text_type, class_types, \ - string_types, StringIO +from six import PY2, iterkeys, iteritems, itervalues, text_type, class_types, string_types from docutils import nodes from docutils.utils import assemble_option_dict @@ -27,6 +25,8 @@ from docutils.parsers.rst import Directive from docutils.statemachine import ViewList import sphinx +from sphinx.ext.autodoc.importer import _MockImporter +from sphinx.ext.autodoc.inspector import format_annotation, formatargspec # NOQA # to keep compatibility from sphinx.util import rpartition, force_decode from sphinx.locale import _ from sphinx.pycode import ModuleAnalyzer, PycodeError @@ -40,18 +40,11 @@ from sphinx.util.docstrings import prepare_docstring if False: # For type annotation + from types import ModuleType # NOQA from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union # NOQA from docutils.utils import Reporter # NOQA from sphinx.application import Sphinx # NOQA -try: - if sys.version_info >= (3,): - import typing - else: - typing = None -except ImportError: - typing = None - logger = logging.getLogger(__name__) # This type isn't exposed directly in any modules, but can be found @@ -106,103 +99,6 @@ class Options(dict): return None -class _MockObject(object): - """Used by autodoc_mock_imports.""" - - def __init__(self, *args, **kwargs): - # type: (Any, Any) -> None - pass - - def __len__(self): - # type: () -> int - return 0 - - def __contains__(self, key): - # type: (str) -> bool - return False - - def __iter__(self): - # type: () -> None - pass - - def __getitem__(self, key): - # type: (str) -> _MockObject - return self - - def __getattr__(self, key): - # type: (str) -> _MockObject - return self - - def __call__(self, *args, **kw): - # type: (Any, Any) -> Any - if args and type(args[0]) in [FunctionType, MethodType]: - # Appears to be a decorator, pass through unchanged - return args[0] - return self - - -class _MockModule(ModuleType): - """Used by autodoc_mock_imports.""" - __file__ = '/dev/null' - - def __init__(self, name, loader): - # type: (str, _MockImporter) -> None - self.__name__ = self.__package__ = name - self.__loader__ = loader - self.__all__ = [] # type: List[str] - self.__path__ = [] # type: List[str] - - def __getattr__(self, name): - # type: (str) -> _MockObject - o = _MockObject() - o.__module__ = self.__name__ - return o - - -class _MockImporter(object): - - def __init__(self, names): - # type: (List[str]) -> None - self.base_packages = set() # type: Set[str] - for n in names: - # Convert module names: - # ['a.b.c', 'd.e'] - # to a set of base packages: - # set(['a', 'd']) - self.base_packages.add(n.split('.')[0]) - self.mocked_modules = [] # type: List[str] - # enable hook by adding itself to meta_path - sys.meta_path = sys.meta_path + [self] - - def disable(self): - # remove `self` from `sys.meta_path` to disable import hook - sys.meta_path = [i for i in sys.meta_path if i is not self] - # remove mocked modules from sys.modules to avoid side effects after - # running auto-documenter - for m in self.mocked_modules: - if m in sys.modules: - del sys.modules[m] - - def find_module(self, name, path=None): - # type: (str, str) -> Any - base_package = name.split('.')[0] - if base_package in self.base_packages: - return self - return None - - def load_module(self, name): - # type: (str) -> ModuleType - if name in sys.modules: - # module has already been imported, return it - return sys.modules[name] - else: - logger.debug('[autodoc] adding a mock module %s!', name) - module = _MockModule(name, self) - sys.modules[name] = module - self.mocked_modules.append(name) - return module - - ALL = object() INSTANCEATTR = object() @@ -359,162 +255,6 @@ def between(marker, what=None, keepempty=False, exclude=False): return process -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 typing and 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 - elif typing: - 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: - params = annotation.__args__ - else: # typing.Callable - args = ', '.join(format_annotation(a) for a in annotation.__args__[:-1]) - result = format_annotation(annotation.__args__[-1]) - return '%s[[%s], %s]' % (qualified_name, args, result) - elif 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 \ - getattr(annotation, '__args__', None) is not None and \ - hasattr(annotation, '__result__'): - # Skipped in the case of plain typing.Callable - args = annotation.__args__ - 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__)) - 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={}): - # 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) # type: ignore - 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() - - class Documenter(object): """ A Documenter knows how to autodocument a single object type. When diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py new file mode 100644 index 000000000..1234d716a --- /dev/null +++ b/sphinx/ext/autodoc/importer.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +""" + sphinx.ext.autodoc.importer + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Importer utilities for autodoc + + :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import sys +from types import FunctionType, MethodType, ModuleType + +from sphinx.util import logging + +if False: + # For type annotation + from typing import Any, List, Set # NOQA + +logger = logging.getLogger(__name__) + + +class _MockObject(object): + """Used by autodoc_mock_imports.""" + + def __init__(self, *args, **kwargs): + # type: (Any, Any) -> None + pass + + def __len__(self): + # type: () -> int + return 0 + + def __contains__(self, key): + # type: (str) -> bool + return False + + def __iter__(self): + # type: () -> None + pass + + def __getitem__(self, key): + # type: (str) -> _MockObject + return self + + def __getattr__(self, key): + # type: (str) -> _MockObject + return self + + def __call__(self, *args, **kw): + # type: (Any, Any) -> Any + if args and type(args[0]) in [FunctionType, MethodType]: + # Appears to be a decorator, pass through unchanged + return args[0] + return self + + +class _MockModule(ModuleType): + """Used by autodoc_mock_imports.""" + __file__ = '/dev/null' + + def __init__(self, name, loader): + # type: (str, _MockImporter) -> None + self.__name__ = self.__package__ = name + self.__loader__ = loader + self.__all__ = [] # type: List[str] + self.__path__ = [] # type: List[str] + + def __getattr__(self, name): + # type: (str) -> _MockObject + o = _MockObject() + o.__module__ = self.__name__ + return o + + +class _MockImporter(object): + + def __init__(self, names): + # type: (List[str]) -> None + self.base_packages = set() # type: Set[str] + for n in names: + # Convert module names: + # ['a.b.c', 'd.e'] + # to a set of base packages: + # set(['a', 'd']) + self.base_packages.add(n.split('.')[0]) + self.mocked_modules = [] # type: List[str] + # enable hook by adding itself to meta_path + sys.meta_path = sys.meta_path + [self] + + def disable(self): + # remove `self` from `sys.meta_path` to disable import hook + sys.meta_path = [i for i in sys.meta_path if i is not self] + # remove mocked modules from sys.modules to avoid side effects after + # running auto-documenter + for m in self.mocked_modules: + if m in sys.modules: + del sys.modules[m] + + def find_module(self, name, path=None): + # type: (str, str) -> Any + base_package = name.split('.')[0] + if base_package in self.base_packages: + return self + return None + + def load_module(self, name): + # type: (str) -> ModuleType + if name in sys.modules: + # module has already been imported, return it + return sys.modules[name] + else: + logger.debug('[autodoc] adding a mock module %s!', name) + module = _MockModule(name, self) + sys.modules[name] = module + self.mocked_modules.append(name) + return module 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() |