Skip to content

Commit

Permalink
Merge pull request #308 from eriknw/fix_property_introspection
Browse files Browse the repository at this point in the history
Use our introspection registry when `inspect.signature` fails due to inspecting properties
  • Loading branch information
eriknw committed May 30, 2016
2 parents 2faca0e + c723427 commit 2fc057d
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 8 deletions.
1 change: 1 addition & 0 deletions toolz/compatibility.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import operator
import sys
PY3 = sys.version_info[0] > 2
PY33 = sys.version_info[0] == 3 and sys.version_info[1] == 3
PY34 = sys.version_info[0] == 3 and sys.version_info[1] == 4
PYPY = hasattr(sys, 'pypy_version_info')

Expand Down
20 changes: 16 additions & 4 deletions toolz/functoolz.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from operator import attrgetter
from textwrap import dedent

from .compatibility import PY3, PY34, PYPY
from .compatibility import PY3, PY33, PY34, PYPY
from .utils import no_default


Expand Down Expand Up @@ -718,10 +718,22 @@ def _check_sigspec(sigspec, func, builtin_func, *builtin_args):
sigspec = e
if isinstance(sigspec, ValueError):
return None, builtin_func(*builtin_args)
elif isinstance(sigspec, TypeError):
return None, False
elif not isinstance(sigspec, inspect.Signature):
return None, False # pragma: no cover
if (
func in _sigs.signatures
and ((
hasattr(func, '__signature__')
and hasattr(func.__signature__, '__get__')
) or (
PY33
and hasattr(func, '__wrapped__')
and hasattr(func.__wrapped__, '__get__')
and not callable(func.__wrapped__)
))
): # pragma: no cover (not covered in Python 3.4)
val = builtin_func(*builtin_args)
return None, val
return None, False
return sigspec, None

else: # pragma: py3 no cover
Expand Down
4 changes: 2 additions & 2 deletions toolz/tests/test_functoolz.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,10 +579,10 @@ def f(a, b):
def test_excepts():
# These are descriptors, make sure this works correctly.
assert excepts.__name__ == 'excepts'
assert excepts.__doc__.startswith(
assert (
'A wrapper around a function to catch exceptions and\n'
' dispatch to a handler.\n'
)
) in excepts.__doc__

def idx(a):
"""idx docstring
Expand Down
65 changes: 63 additions & 2 deletions toolz/tests/test_inspect_args.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import functools
import inspect
import itertools
import operator
import toolz
from toolz.functoolz import (curry, is_valid_args, is_partial_args, is_arity,
num_required_args, has_varargs, has_keywords)
from toolz._signatures import builtins
from toolz.compatibility import PY3
import toolz._signatures as _sigs
from toolz.compatibility import PY3, PY33
from toolz.utils import raises


Expand Down Expand Up @@ -413,7 +415,9 @@ def is_missing(modname, name, func):
except TypeError:
pass
try:
return (callable(func) and modname in func.__module__
return (callable(func)
and func.__module__ is not None
and modname in func.__module__
and is_partial_args(func, (), {}) is not True
and func not in blacklist)
except AttributeError:
Expand All @@ -435,3 +439,60 @@ def is_missing(modname, name, func):
message = 'Missing introspection for the following callables:\n\n'
raise AssertionError(message + '\n\n'.join(messages))


def test_inspect_signature_property():
if not PY3:
return

# By adding AddX to our signature registry, we can inspect the class
# itself and objects of the class. `inspect.signature` doesn't like
# it when `obj.__signature__` is a property.
class AddX(object):
def __init__(self, func):
self.func = func

def __call__(self, addx, *args, **kwargs):
return addx + self.func(*args, **kwargs)

@property
def __signature__(self):
sig = inspect.signature(self.func)
params = list(sig.parameters.values())
kind = inspect.Parameter.POSITIONAL_OR_KEYWORD
newparam = inspect.Parameter('addx', kind)
params = [newparam] + params
return sig.replace(parameters=params)

addx = AddX(lambda x: x)
sig = inspect.signature(addx)
assert sig == inspect.Signature(parameters=[
inspect.Parameter('addx', inspect.Parameter.POSITIONAL_OR_KEYWORD),
inspect.Parameter('x', inspect.Parameter.POSITIONAL_OR_KEYWORD)])

assert num_required_args(AddX) is False
_sigs.signatures[AddX] = (_sigs.expand_sig((0, lambda func: None)),)
assert num_required_args(AddX) == 1
del _sigs.signatures[AddX]


def test_inspect_wrapped_property():
class Wrapped(object):
def __init__(self, func):
self.func = func

def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)

@property
def __wrapped__(self):
return self.func

func = lambda x: x
wrapped = Wrapped(func)
if PY3:
assert inspect.signature(func) == inspect.signature(wrapped)

assert num_required_args(Wrapped) == (False if PY33 else None)
_sigs.signatures[Wrapped] = (_sigs.expand_sig((0, lambda func: None)),)
assert num_required_args(Wrapped) == 1

0 comments on commit 2fc057d

Please sign in to comment.