mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #3449 from njsmith/getargspec-__wrapped__
On py3, use inspect.signature for more accurate signature calculation
This commit is contained in:
commit
6cb582fdd3
@ -28,41 +28,73 @@ memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE)
|
|||||||
|
|
||||||
|
|
||||||
if PY3:
|
if PY3:
|
||||||
from functools import partial
|
# 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):
|
def getargspec(func):
|
||||||
"""Like inspect.getargspec but supports functools.partial as well."""
|
"""Like inspect.getfullargspec but supports bound methods, and wrapped
|
||||||
if inspect.ismethod(func):
|
methods."""
|
||||||
func = func.__func__
|
# On 3.5+, signature(int) or similar raises ValueError. On 3.4, it
|
||||||
if type(func) is partial:
|
# succeeds with a bogus signature. We want a TypeError uniformly, to
|
||||||
orig_func = func.func
|
# match historical behavior.
|
||||||
argspec = getargspec(orig_func)
|
if (isinstance(func, type) and
|
||||||
args = list(argspec[0])
|
is_builtin_class_method(func, "__new__") and
|
||||||
defaults = list(argspec[3] or ())
|
is_builtin_class_method(func, "__init__")):
|
||||||
kwoargs = list(argspec[4])
|
raise TypeError(
|
||||||
kwodefs = dict(argspec[5] or {})
|
"can't compute signature for built-in type {}".format(func))
|
||||||
if func.args:
|
|
||||||
args = args[len(func.args):]
|
sig = inspect.signature(func)
|
||||||
for arg in func.keywords or ():
|
|
||||||
try:
|
args = []
|
||||||
i = args.index(arg) - len(args)
|
varargs = None
|
||||||
del args[i]
|
varkw = None
|
||||||
try:
|
kwonlyargs = []
|
||||||
del defaults[i]
|
defaults = ()
|
||||||
except IndexError:
|
annotations = {}
|
||||||
pass
|
defaults = ()
|
||||||
except ValueError: # must be a kwonly arg
|
kwdefaults = {}
|
||||||
i = kwoargs.index(arg)
|
|
||||||
del kwoargs[i]
|
if sig.return_annotation is not sig.empty:
|
||||||
del kwodefs[arg]
|
annotations['return'] = sig.return_annotation
|
||||||
return inspect.FullArgSpec(args, argspec[1], argspec[2],
|
|
||||||
tuple(defaults), kwoargs,
|
for param in sig.parameters.values():
|
||||||
kwodefs, argspec[6])
|
kind = param.kind
|
||||||
while hasattr(func, '__wrapped__'):
|
name = param.name
|
||||||
func = func.__wrapped__
|
|
||||||
if not inspect.isfunction(func):
|
if kind is inspect.Parameter.POSITIONAL_ONLY:
|
||||||
raise TypeError('%r is not a Python function' % func)
|
args.append(name)
|
||||||
return inspect.getfullargspec(func)
|
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
|
else: # 2.7
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from six import PY3
|
||||||
|
|
||||||
from util import SphinxTestApp, Struct # NOQA
|
from util import SphinxTestApp, Struct # NOQA
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -752,6 +754,10 @@ def test_generate():
|
|||||||
|
|
||||||
# test autodoc_member_order == 'source'
|
# test autodoc_member_order == 'source'
|
||||||
directive.env.ref_context['py:module'] = 'test_autodoc'
|
directive.env.ref_context['py:module'] = 'test_autodoc'
|
||||||
|
if PY3:
|
||||||
|
roger_line = ' .. py:classmethod:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)'
|
||||||
|
else:
|
||||||
|
roger_line = ' .. py:classmethod:: Class.roger(a, e=5, f=6)'
|
||||||
assert_order(['.. py:class:: Class(arg)',
|
assert_order(['.. py:class:: Class(arg)',
|
||||||
' .. py:attribute:: Class.descr',
|
' .. py:attribute:: Class.descr',
|
||||||
' .. py:method:: Class.meth()',
|
' .. py:method:: Class.meth()',
|
||||||
@ -761,7 +767,7 @@ def test_generate():
|
|||||||
' .. py:attribute:: Class.docattr',
|
' .. py:attribute:: Class.docattr',
|
||||||
' .. py:attribute:: Class.udocattr',
|
' .. py:attribute:: Class.udocattr',
|
||||||
' .. py:attribute:: Class.mdocattr',
|
' .. py:attribute:: Class.mdocattr',
|
||||||
' .. py:classmethod:: Class.roger(a, e=5, f=6)',
|
roger_line,
|
||||||
' .. py:classmethod:: Class.moore(a, e, f) -> happiness',
|
' .. py:classmethod:: Class.moore(a, e, f) -> happiness',
|
||||||
' .. py:attribute:: Class.inst_attr_comment',
|
' .. py:attribute:: Class.inst_attr_comment',
|
||||||
' .. py:attribute:: Class.inst_attr_string',
|
' .. py:attribute:: Class.inst_attr_string',
|
||||||
|
@ -10,8 +10,68 @@
|
|||||||
"""
|
"""
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from six import PY3
|
||||||
|
import functools
|
||||||
|
from textwrap import dedent
|
||||||
|
import pytest
|
||||||
|
|
||||||
from sphinx.util import inspect
|
from sphinx.util import inspect
|
||||||
|
|
||||||
|
class TestGetArgSpec(TestCase):
|
||||||
|
def test_getargspec_builtin_type(self):
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
inspect.getargspec(int)
|
||||||
|
|
||||||
|
def test_getargspec_partial(self):
|
||||||
|
def fun(a, b, c=1, d=2):
|
||||||
|
pass
|
||||||
|
p = functools.partial(fun, 10, c=11)
|
||||||
|
|
||||||
|
if PY3:
|
||||||
|
# Python 3's partial is rather cleverer than Python 2's, and we
|
||||||
|
# have to jump through some hoops to define an equivalent function
|
||||||
|
# in a way that won't confuse Python 2's parser:
|
||||||
|
ns = {}
|
||||||
|
exec(dedent("""
|
||||||
|
def f_expected(b, *, c=11, d=2):
|
||||||
|
pass
|
||||||
|
"""), ns)
|
||||||
|
f_expected = ns["f_expected"]
|
||||||
|
else:
|
||||||
|
def f_expected(b, d=2):
|
||||||
|
pass
|
||||||
|
expected = inspect.getargspec(f_expected)
|
||||||
|
|
||||||
|
assert expected == inspect.getargspec(p)
|
||||||
|
|
||||||
|
def test_getargspec_bound_methods(self):
|
||||||
|
def f_expected_unbound(self, arg1, **kwargs):
|
||||||
|
pass
|
||||||
|
expected_unbound = inspect.getargspec(f_expected_unbound)
|
||||||
|
|
||||||
|
def f_expected_bound(arg1, **kwargs):
|
||||||
|
pass
|
||||||
|
expected_bound = inspect.getargspec(f_expected_bound)
|
||||||
|
|
||||||
|
class Foo:
|
||||||
|
def method(self, arg1, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
bound_method = Foo().method
|
||||||
|
|
||||||
|
@functools.wraps(bound_method)
|
||||||
|
def wrapped_bound_method(*args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert expected_unbound == inspect.getargspec(Foo.method)
|
||||||
|
if PY3:
|
||||||
|
# On py2, the inspect functions don't properly handle bound
|
||||||
|
# methods (they include a spurious 'self' argument)
|
||||||
|
assert expected_bound == inspect.getargspec(bound_method)
|
||||||
|
# On py2, the inspect functions can't properly handle wrapped
|
||||||
|
# functions (no __wrapped__ support)
|
||||||
|
assert expected_bound == inspect.getargspec(wrapped_bound_method)
|
||||||
|
|
||||||
|
|
||||||
class TestSafeGetAttr(TestCase):
|
class TestSafeGetAttr(TestCase):
|
||||||
def test_safe_getattr_with_default(self):
|
def test_safe_getattr_with_default(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user