if self.legacy_signatures:
try:
argspec = util.get_callable_argspec(fn, no_self=True)
- except ValueError:
+ except TypeError:
pass
else:
fn = legacy._wrap_fn_for_legacy(self, fn, argspec)
def _maybe_wrap_callable(self, fn):
"""Wrap callables that don't accept a context.
- The alternative here is to require that
- a simple callable passed to "default" would need
- to be of the form "default=lambda ctx: datetime.now".
- That is the more "correct" way to go, but the case
- of using a zero-arg callable for "default" is so
- much more prominent than the context-specific one
- I'm having trouble justifying putting that inconvenience
- on everyone.
+ This is to allow easy compatiblity with default callables
+ that aren't specific to accepting of a context.
"""
- # TODO: why aren't we using a util.langhelpers function
- # for this? e.g. get_callable_argspec
-
- if isinstance(fn, (types.BuiltinMethodType, types.BuiltinFunctionType)):
- return lambda ctx: fn()
- elif inspect.isfunction(fn) or inspect.ismethod(fn):
- inspectable = fn
- elif inspect.isclass(fn):
- inspectable = fn.__init__
- elif hasattr(fn, '__call__'):
- inspectable = fn.__call__
- else:
- # probably not inspectable, try anyways.
- inspectable = fn
try:
- argspec = inspect.getargspec(inspectable)
+ argspec = util.get_callable_argspec(fn, no_self=True)
except TypeError:
return lambda ctx: fn()
defaulted = argspec[3] is not None and len(argspec[3]) or 0
positionals = len(argspec[0]) - defaulted
- # Py3K compat - no unbound methods
- if inspect.ismethod(inspectable) or inspect.isclass(fn):
- positionals -= 1
-
if positionals == 0:
return lambda ctx: fn()
elif positionals == 1:
return compat.inspect_getargspec(func)[0]
-def get_callable_argspec(fn, no_self=False):
- if isinstance(fn, types.FunctionType):
- return compat.inspect_getargspec(fn)
- elif isinstance(fn, types.MethodType) and no_self:
- spec = compat.inspect_getargspec(fn.__func__)
- return compat.ArgSpec(spec.args[1:], spec.varargs, spec.keywords, spec.defaults)
+def get_callable_argspec(fn, no_self=False, _is_init=False):
+ """Return the argument signature for any callable.
+
+ All pure-Python callables are accepted, including
+ functions, methods, classes, objects with __call__;
+ builtins and other edge cases like functools.partial() objects
+ raise a TypeError.
+
+ """
+ if inspect.isbuiltin(fn):
+ raise TypeError("Can't inspect builtin: %s" % fn)
+ elif inspect.isfunction(fn):
+ if _is_init and no_self:
+ spec = compat.inspect_getargspec(fn)
+ return compat.ArgSpec(spec.args[1:], spec.varargs,
+ spec.keywords, spec.defaults)
+ else:
+ return compat.inspect_getargspec(fn)
+ elif inspect.ismethod(fn):
+ if no_self and (_is_init or fn.__self__):
+ spec = compat.inspect_getargspec(fn.__func__)
+ return compat.ArgSpec(spec.args[1:], spec.varargs,
+ spec.keywords, spec.defaults)
+ else:
+ return compat.inspect_getargspec(fn.__func__)
+ elif inspect.isclass(fn):
+ return get_callable_argspec(fn.__init__, no_self=no_self, _is_init=True)
elif hasattr(fn, '__func__'):
return compat.inspect_getargspec(fn.__func__)
- elif hasattr(fn, '__call__') and \
- not hasattr(fn.__call__, '__call__'): # functools.partial does this;
- # not much we can do
- return get_callable_argspec(fn.__call__)
+ elif hasattr(fn, '__call__'):
+ if inspect.ismethod(fn.__call__):
+ return get_callable_argspec(fn.__call__, no_self=no_self)
+ else:
+ raise TypeError("Can't inspect callable: %s" % fn)
else:
- raise ValueError("Can't inspect function: %s" % fn)
+ raise TypeError("Can't inspect callable: %s" % fn)
def format_argspec_plus(fn, grouped=True):
"""Returns a dictionary of formatted, introspected function arguments.
(['x', 'y'], None, 'kw', None)
)
+ def test_callable_argspec_fn_no_self(self):
+ def foo(x, y, **kw):
+ pass
+ eq_(
+ get_callable_argspec(foo, no_self=True),
+ (['x', 'y'], None, 'kw', None)
+ )
+
+ def test_callable_argspec_fn_no_self_but_self(self):
+ def foo(self, x, y, **kw):
+ pass
+ eq_(
+ get_callable_argspec(foo, no_self=True),
+ (['self', 'x', 'y'], None, 'kw', None)
+ )
+
+ def test_callable_argspec_py_builtin(self):
+ import datetime
+ assert_raises(
+ TypeError,
+ get_callable_argspec, datetime.datetime.now
+ )
+
+ def test_callable_argspec_obj_init(self):
+ assert_raises(
+ TypeError,
+ get_callable_argspec, object
+ )
+
def test_callable_argspec_method(self):
class Foo(object):
def foo(self, x, y, **kw):
(['self', 'x', 'y'], None, 'kw', None)
)
+ def test_callable_argspec_instance_method_no_self(self):
+ class Foo(object):
+ def foo(self, x, y, **kw):
+ pass
+ eq_(
+ get_callable_argspec(Foo().foo, no_self=True),
+ (['x', 'y'], None, 'kw', None)
+ )
+
+ def test_callable_argspec_unbound_method_no_self(self):
+ class Foo(object):
+ def foo(self, x, y, **kw):
+ pass
+ eq_(
+ get_callable_argspec(Foo.foo, no_self=True),
+ (['self', 'x', 'y'], None, 'kw', None)
+ )
+
+ def test_callable_argspec_init(self):
+ class Foo(object):
+ def __init__(self, x, y):
+ pass
+
+ eq_(
+ get_callable_argspec(Foo),
+ (['self', 'x', 'y'], None, None, None)
+ )
+
+ def test_callable_argspec_init_no_self(self):
+ class Foo(object):
+ def __init__(self, x, y):
+ pass
+
+ eq_(
+ get_callable_argspec(Foo, no_self=True),
+ (['x', 'y'], None, None, None)
+ )
+
+ def test_callable_argspec_call(self):
+ class Foo(object):
+ def __call__(self, x, y):
+ pass
+ eq_(
+ get_callable_argspec(Foo()),
+ (['self', 'x', 'y'], None, None, None)
+ )
+
+ def test_callable_argspec_call_no_self(self):
+ class Foo(object):
+ def __call__(self, x, y):
+ pass
+ eq_(
+ get_callable_argspec(Foo(), no_self=True),
+ (['x', 'y'], None, None, None)
+ )
+
def test_callable_argspec_partial(self):
from functools import partial
def foo(x, y, z, **kw):
bar = partial(foo, 5)
assert_raises(
- ValueError,
+ TypeError,
get_callable_argspec, bar
)