Skip to content

Commit f96c378

Browse files
author
Joe Jevnik
committed
ENH: py2 compat wrappers
1 parent d7d6701 commit f96c378

File tree

4 files changed

+68
-7
lines changed

4 files changed

+68
-7
lines changed

interface/compat.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import functools
12
import sys
23
from itertools import repeat
34

@@ -6,17 +7,42 @@
67
PY2 = version_info.major == 2
78
PY3 = version_info.major == 3
89

9-
if PY2: # pragma: nocover
10+
if PY2: # pragma: nocover-py3
1011
from funcsigs import signature, Parameter
1112

13+
@functools.wraps(functools.wraps)
14+
def wraps(func, *args, **kwargs):
15+
outer_decorator = functools.wraps(func, *args, **kwargs)
16+
17+
def decorator(f):
18+
wrapped = outer_decorator(f)
19+
wrapped.__wrapped__ = func
20+
return wrapped
21+
22+
return decorator
23+
1224
def raise_from(e, from_):
1325
raise e
1426

1527
def viewkeys(d):
1628
return d.viewkeys()
1729

18-
else: # pragma: nocover
19-
from inspect import signature, Parameter
30+
def unwrap(func, stop=None):
31+
if stop is None:
32+
def stop(f):
33+
return False
34+
35+
while not stop(func):
36+
try:
37+
func = func.__wrapped__
38+
except AttributeError:
39+
return func
40+
41+
else: # pragma: nocover-py2
42+
from inspect import signature, Parameter, unwrap
43+
44+
wraps = functools.wraps
45+
2046
exec("def raise_from(e, from_):" # pragma: nocover
2147
" raise e from from_")
2248

interface/tests/test_interface.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22
from textwrap import dedent
33

4-
from ..compat import PY3
4+
from ..compat import PY3, wraps
55
from ..interface import implements, InvalidImplementation, Interface, default
66

77

@@ -656,3 +656,21 @@ def default_classmethod(cls, x):
656656
657657
Consider changing the implementation of default_method or making these attributes part of HasDefault.""" # noqa
658658
assert second == expected_second
659+
660+
661+
def test_wrapped_implementation():
662+
class I(Interface): # pragma: nocover
663+
def f(self, a, b, c):
664+
pass
665+
666+
def wrapping_decorator(f):
667+
@wraps(f)
668+
def inner(*args, **kwargs): # pragma: nocover
669+
pass
670+
671+
return inner
672+
673+
class C(implements(I)): # pragma: nocover
674+
@wrapping_decorator
675+
def f(self, a, b, c):
676+
pass

interface/tests/test_utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from ..utils import is_a, unique
22

3+
from ..compat import wraps, unwrap
4+
35

46
def test_unique():
57
assert list(unique(iter([1, 3, 1, 2, 3]))) == [1, 3, 2]
@@ -8,3 +10,14 @@ def test_unique():
810
def test_is_a():
911
assert is_a(int)(5)
1012
assert not is_a(str)(5)
13+
14+
15+
def test_wrap_and_unwrap():
16+
def f(a, b, c): # pragma: nocover
17+
pass
18+
19+
@wraps(f)
20+
def g(*args): # pragma: nocover
21+
pass
22+
23+
assert unwrap(g) is f

interface/typed_signature.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"""
77
import types
88

9-
from .compat import signature
9+
from .compat import signature, unwrap
1010
from .default import default
1111

1212

@@ -45,7 +45,7 @@ def __str__(self):
4545
BUILTIN_FUNCTION_TYPES = (types.FunctionType, types.BuiltinFunctionType)
4646

4747

48-
def extract_func(obj):
48+
def _inner_extract_func(obj):
4949
if isinstance(obj, BUILTIN_FUNCTION_TYPES):
5050
# Fast path, since this is the most likely case.
5151
return obj
@@ -54,6 +54,10 @@ def extract_func(obj):
5454
elif isinstance(obj, property):
5555
return obj.fget
5656
elif isinstance(obj, default):
57-
return extract_func(obj.implementation)
57+
return _inner_extract_func(obj.implementation)
5858
else:
5959
return obj
60+
61+
62+
def extract_func(obj):
63+
return unwrap(_inner_extract_func(obj))

0 commit comments

Comments
 (0)