diff options
Diffstat (limited to 'sphinx/util/inspect.py')
-rw-r--r-- | sphinx/util/inspect.py | 418 |
1 files changed, 125 insertions, 293 deletions
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index c1e446c43..d4c0f1948 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ sphinx.util.inspect ~~~~~~~~~~~~~~~~~~~ @@ -8,152 +7,108 @@ :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ -from __future__ import absolute_import +import builtins +import enum import inspect import re import sys import typing -from collections import OrderedDict +import warnings from functools import partial +from io import StringIO -from six import PY2, PY3, StringIO, binary_type, string_types, itervalues -from six.moves import builtins - -from sphinx.util import force_decode +from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.util import logging from sphinx.util.pycompat import NoneType if False: # For type annotation - from typing import Any, Callable, Dict, List, Tuple, Type # NOQA + from typing import Any, Callable, Mapping, List, Tuple, Type # NOQA logger = logging.getLogger(__name__) memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE) -if PY3: - # Copied from the definition of inspect.getfullargspec from Python master, - # and modified to remove the use of special flags that break decorated - # callables and bound methods in the name of backwards compatibility. Used - # under the terms of PSF license v2, which requires the above statement - # and the following: - # - # 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): - """Like inspect.getfullargspec but supports bound methods, and wrapped - methods.""" - # 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. - if (isinstance(func, type) and - is_builtin_class_method(func, "__new__") and - is_builtin_class_method(func, "__init__")): - raise TypeError( - "can't compute signature for built-in type {}".format(func)) - - sig = inspect.signature(func) - - args = [] - varargs = None - varkw = None - kwonlyargs = [] - defaults = () - annotations = {} - defaults = () - kwdefaults = {} - - if sig.return_annotation is not sig.empty: - annotations['return'] = sig.return_annotation - - for param in sig.parameters.values(): - kind = param.kind - name = param.name - - if kind is inspect.Parameter.POSITIONAL_ONLY: - args.append(name) - elif kind is inspect.Parameter.POSITIONAL_OR_KEYWORD: - args.append(name) - if param.default is not param.empty: - defaults += (param.default,) - elif kind is inspect.Parameter.VAR_POSITIONAL: - varargs = name - elif kind is inspect.Parameter.KEYWORD_ONLY: - kwonlyargs.append(name) - if param.default is not param.empty: - kwdefaults[name] = param.default - elif kind is inspect.Parameter.VAR_KEYWORD: - varkw = name - - if param.annotation is not param.empty: - annotations[name] = param.annotation - - if not kwdefaults: - # compatibility with 'func.__kwdefaults__' - kwdefaults = None - - if not defaults: - # compatibility with 'func.__defaults__' - defaults = None - - return inspect.FullArgSpec(args, varargs, varkw, defaults, - kwonlyargs, kwdefaults, annotations) - -else: # 2.7 - def getargspec(func): - # type: (Any) -> Any - """Like inspect.getargspec but supports functools.partial as well.""" - if inspect.ismethod(func): - func = func.__func__ - parts = 0, () # type: Tuple[int, Tuple[unicode, ...]] - if type(func) is partial: - keywords = func.keywords - if keywords is None: - keywords = {} - parts = len(func.args), keywords.keys() - func = func.func - if not inspect.isfunction(func): - raise TypeError('%r is not a Python function' % func) - args, varargs, varkw = inspect.getargs(func.__code__) - func_defaults = func.__defaults__ - if func_defaults is None: - func_defaults = [] - else: - func_defaults = list(func_defaults) - if parts[0]: - args = args[parts[0]:] - if parts[1]: - for arg in parts[1]: - i = args.index(arg) - len(args) # type: ignore - del args[i] - try: - del func_defaults[i] - except IndexError: - pass - return inspect.ArgSpec(args, varargs, varkw, func_defaults) # type: ignore - -try: - import enum -except ImportError: - enum = None +# Copied from the definition of inspect.getfullargspec from Python master, +# and modified to remove the use of special flags that break decorated +# callables and bound methods in the name of backwards compatibility. Used +# under the terms of PSF license v2, which requires the above statement +# and the following: +# +# 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): + """Like inspect.getfullargspec but supports bound methods, and wrapped + methods.""" + # 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. + if (isinstance(func, type) and + is_builtin_class_method(func, "__new__") and + is_builtin_class_method(func, "__init__")): + raise TypeError( + "can't compute signature for built-in type {}".format(func)) + + sig = inspect.signature(func) + + args = [] + varargs = None + varkw = None + kwonlyargs = [] + defaults = () + annotations = {} + defaults = () + kwdefaults = {} + + if sig.return_annotation is not sig.empty: + annotations['return'] = sig.return_annotation + + for param in sig.parameters.values(): + kind = param.kind + name = param.name + + if kind is inspect.Parameter.POSITIONAL_ONLY: + args.append(name) + elif kind is inspect.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: + varargs = name + elif kind is inspect.Parameter.KEYWORD_ONLY: + kwonlyargs.append(name) + if param.default is not param.empty: + kwdefaults[name] = param.default + elif kind is inspect.Parameter.VAR_KEYWORD: + varkw = name + + if param.annotation is not param.empty: + annotations[name] = param.annotation + + if not kwdefaults: + # compatibility with 'func.__kwdefaults__' + kwdefaults = None + + if not defaults: + # compatibility with 'func.__defaults__' + defaults = None + + return inspect.FullArgSpec(args, varargs, varkw, defaults, + kwonlyargs, kwdefaults, annotations) def isenumclass(x): # type: (Type) -> bool """Check if the object is subclass of enum.""" - if enum is None: - return False return inspect.isclass(x) and issubclass(x, enum.Enum) def isenumattribute(x): # type: (Any) -> bool """Check if the object is attribute of enum.""" - if enum is None: - return False return isinstance(x, enum.Enum) @@ -168,17 +123,14 @@ def isclassmethod(obj): """Check if the object is classmethod.""" if isinstance(obj, classmethod): return True - elif inspect.ismethod(obj): - if getattr(obj, 'im_self', None): # py2 - return True - elif getattr(obj, '__self__', None): # py3 - return True + elif inspect.ismethod(obj) and obj.__self__ is not None: + return True return False def isstaticmethod(obj, cls=None, name=None): - # type: (Any, Any, unicode) -> bool + # type: (Any, Any, str) -> bool """Check if the object is staticmethod.""" if isinstance(obj, staticmethod): return True @@ -219,7 +171,7 @@ def isbuiltin(obj): def safe_getattr(obj, name, *defargs): - # type: (Any, unicode, unicode) -> object + # type: (Any, str, str) -> object """A getattr() that turns all exceptions into AttributeErrors.""" try: return getattr(obj, name, *defargs) @@ -242,9 +194,9 @@ def safe_getattr(obj, name, *defargs): def safe_getmembers(object, predicate=None, attr_getter=safe_getattr): - # type: (Any, Callable[[unicode], bool], Callable) -> List[Tuple[unicode, Any]] + # type: (Any, Callable[[str], bool], Callable) -> List[Tuple[str, Any]] """A version of inspect.getmembers() that uses safe_getattr().""" - results = [] # type: List[Tuple[unicode, Any]] + results = [] # type: List[Tuple[str, Any]] for key in dir(object): try: value = attr_getter(object, key, None) @@ -257,7 +209,7 @@ def safe_getmembers(object, predicate=None, attr_getter=safe_getattr): def object_description(object): - # type: (Any) -> unicode + # type: (Any) -> str """A repr() implementation that returns text safe to use in reST context.""" if isinstance(object, dict): try: @@ -275,15 +227,19 @@ def object_description(object): except TypeError: pass # Cannot sort set values, fall back to generic repr else: - template = "{%s}" if PY3 else "set([%s])" - return template % ", ".join(object_description(x) - for x in sorted_values) + return "{%s}" % ", ".join(object_description(x) for x in sorted_values) + if isinstance(object, frozenset): + try: + sorted_values = sorted(object) + except TypeError: + pass # Cannot sort frozenset values, fall back to generic repr + else: + return "frozenset({%s})" % ", ".join(object_description(x) + for x in sorted_values) try: s = repr(object) except Exception: raise ValueError - if isinstance(s, binary_type): - s = force_decode(s, None) # type: ignore # Strip non-deterministic memory addresses such as # ``<__main__.A at 0x7f68cb685710>`` s = memory_address_re.sub('', s) @@ -291,7 +247,7 @@ def object_description(object): def is_builtin_class_method(obj, attr_name): - # type: (Any, unicode) -> bool + # type: (Any, str) -> bool """If attr_name is implemented at builtin class, return True. >>> is_builtin_class_method(int, '__init__') @@ -308,7 +264,7 @@ def is_builtin_class_method(obj, attr_name): return getattr(builtins, safe_getattr(cls, '__name__', '')) is cls # type: ignore -class Parameter(object): +class Parameter: """Fake parameter class for python2.""" POSITIONAL_ONLY = 0 POSITIONAL_OR_KEYWORD = 1 @@ -324,8 +280,11 @@ class Parameter(object): self.default = default self.annotation = self.empty + warnings.warn('sphinx.util.inspect.Parameter is deprecated.', + RemovedInSphinx30Warning, stacklevel=2) + -class Signature(object): +class Signature: """The Signature object represents the call signature of a callable object and its return annotation. """ @@ -342,23 +301,20 @@ class Signature(object): self.has_retval = has_retval self.partialmethod_with_noargs = False - if PY3: - try: - self.signature = inspect.signature(subject) - 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'): - self.signature = None - self.partialmethod_with_noargs = True - else: - raise - else: - self.argspec = getargspec(subject) + try: + self.signature = inspect.signature(subject) + 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'): + self.signature = None + self.partialmethod_with_noargs = True + else: + raise try: - self.annotations = typing.get_type_hints(subject) # type: ignore + self.annotations = typing.get_type_hints(subject) except Exception: # get_type_hints() does not support some kind of objects like partial, # ForwardRef and so on. For them, it raises an exception. In that case, @@ -368,49 +324,28 @@ class Signature(object): if bound_method: # client gives a hint that the subject is a bound method - if PY3 and inspect.ismethod(subject): + if inspect.ismethod(subject): # inspect.signature already considers the subject is bound method. # So it is not need to skip first argument. self.skip_first_argument = False else: self.skip_first_argument = True else: - if PY3: - # inspect.signature recognizes type of method properly without any hints - self.skip_first_argument = False - else: - # check the subject is bound method or not - self.skip_first_argument = inspect.ismethod(subject) and subject.__self__ # type: ignore # NOQA + # inspect.signature recognizes type of method properly without any hints + self.skip_first_argument = False @property def parameters(self): - # type: () -> Dict - if PY3: - if self.partialmethod_with_noargs: - return {} - else: - return self.signature.parameters + # type: () -> Mapping + if self.partialmethod_with_noargs: + return {} else: - params = OrderedDict() # type: Dict - positionals = len(self.argspec.args) - len(self.argspec.defaults) - for i, arg in enumerate(self.argspec.args): - if i < positionals: - params[arg] = Parameter(arg) - else: - default = self.argspec.defaults[i - positionals] - params[arg] = Parameter(arg, default=default) - if self.argspec.varargs: - params[self.argspec.varargs] = Parameter(self.argspec.varargs, - Parameter.VAR_POSITIONAL) - if self.argspec.keywords: - params[self.argspec.keywords] = Parameter(self.argspec.keywords, - Parameter.VAR_KEYWORD) - return params + return self.signature.parameters @property def return_annotation(self): # type: () -> Any - if PY3 and self.signature: + if self.signature: if self.has_retval: return self.signature.return_annotation else: @@ -419,10 +354,10 @@ class Signature(object): return None def format_args(self): - # type: () -> unicode + # type: () -> str args = [] last_kind = None - for i, param in enumerate(itervalues(self.parameters)): + for i, param in enumerate(self.parameters.values()): # skip first argument if subject is bound method if self.skip_first_argument and i == 0: continue @@ -441,8 +376,7 @@ class Signature(object): param.KEYWORD_ONLY): arg.write(param.name) if param.annotation is not param.empty: - if isinstance(param.annotation, string_types) and \ - param.name in self.annotations: + if isinstance(param.annotation, str) and param.name in self.annotations: arg.write(': ') arg.write(self.format_annotation(self.annotations[param.name])) else: @@ -451,10 +385,10 @@ class Signature(object): if param.default is not param.empty: if param.annotation is param.empty: arg.write('=') - arg.write(object_description(param.default)) # type: ignore + arg.write(object_description(param.default)) else: arg.write(' = ') - arg.write(object_description(param.default)) # type: ignore + arg.write(object_description(param.default)) elif param.kind == param.VAR_POSITIONAL: arg.write('*') arg.write(param.name) @@ -465,7 +399,7 @@ class Signature(object): args.append(arg.getvalue()) last_kind = param.kind - if PY2 or self.return_annotation is inspect.Parameter.empty: + if self.return_annotation is inspect.Parameter.empty: return '(%s)' % ', '.join(args) else: if 'return' in self.annotations: @@ -484,8 +418,8 @@ class Signature(object): Displaying complex types from ``typing`` relies on its private API. """ - if isinstance(annotation, string_types): - return annotation # type: ignore + if isinstance(annotation, str): + return annotation elif isinstance(annotation, typing.TypeVar): # type: ignore return annotation.__name__ elif not annotation: @@ -636,110 +570,8 @@ class Signature(object): return qualname -if sys.version_info >= (3, 5): - _getdoc = inspect.getdoc -else: - # code copied from the inspect.py module of the standard library - # of Python 3.5 - - def _findclass(func): - # type: (Any) -> Any - cls = sys.modules.get(func.__module__) - if cls is None: - return None - if hasattr(func, 'im_class'): - cls = func.im_class - else: - for name in func.__qualname__.split('.')[:-1]: - cls = getattr(cls, name) - if not inspect.isclass(cls): - return None - return cls - - def _finddoc(obj): - # type: (Any) -> unicode - if inspect.isclass(obj): - for base in obj.__mro__: - if base is not object: - try: - doc = base.__doc__ - except AttributeError: - continue - if doc is not None: - return doc - return None - - if inspect.ismethod(obj) and getattr(obj, '__self__', None): - name = obj.__func__.__name__ - self = obj.__self__ - if (inspect.isclass(self) and - getattr(getattr(self, name, None), '__func__') - is obj.__func__): - # classmethod - cls = self - else: - cls = self.__class__ - elif inspect.isfunction(obj) or inspect.ismethod(obj): - name = obj.__name__ - cls = _findclass(obj) - if cls is None or getattr(cls, name) != obj: - return None - elif inspect.isbuiltin(obj): - name = obj.__name__ - self = obj.__self__ - if (inspect.isclass(self) and - self.__qualname__ + '.' + name == obj.__qualname__): - # classmethod - cls = self - else: - cls = self.__class__ - # Should be tested before isdatadescriptor(). - elif isinstance(obj, property): - func = obj.fget - name = func.__name__ - cls = _findclass(func) - if cls is None or getattr(cls, name) is not obj: - return None - elif inspect.ismethoddescriptor(obj) or inspect.isdatadescriptor(obj): - name = obj.__name__ - cls = obj.__objclass__ - if getattr(cls, name) is not obj: - return None - else: - return None - - for base in cls.__mro__: - try: - doc = getattr(base, name).__doc__ - except AttributeError: - continue - if doc is not None: - return doc - return None - - def _getdoc(object): - # type: (Any) -> unicode - """Get the documentation string for an object. - - All tabs are expanded to spaces. To clean up docstrings that are - indented to line up with blocks of code, any whitespace than can be - uniformly removed from the second line onwards is removed.""" - try: - doc = object.__doc__ - except AttributeError: - return None - if doc is None: - try: - doc = _finddoc(object) - except (AttributeError, TypeError): - return None - if not isinstance(doc, str): - return None - return inspect.cleandoc(doc) - - def getdoc(obj, attrgetter=safe_getattr, allow_inherited=False): - # type: (Any, Callable, bool) -> unicode + # type: (Any, Callable, bool) -> str """Get the docstring for the object. This tries to obtain the docstring for some kind of objects additionally: @@ -751,6 +583,6 @@ def getdoc(obj, attrgetter=safe_getattr, allow_inherited=False): if ispartial(obj) and doc == obj.__class__.__doc__: return getdoc(obj.func) elif doc is None and allow_inherited: - doc = _getdoc(obj) + doc = inspect.getdoc(obj) return doc |