Skip to content

Commit 0997722

Browse files
authored
Deprecated decorator: add support for class methods. (#27)
1 parent 166b5d4 commit 0997722

File tree

2 files changed

+73
-35
lines changed

2 files changed

+73
-35
lines changed

pywisetransfer/deprecation.py

+35-13
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,44 @@ class deprecated:
1111
https://blog.miguelgrinberg.com/post/the-ultimate-guide-to-python-decorators-part-iii-decorators-with-arguments
1212
"""
1313

14+
@staticmethod
15+
def _message(message=None, *args, **kwargs):
16+
return message
17+
1418
def __init__(self, *args, **kwargs):
19+
self.f = None
1520
if len(args) == 1 and callable(args[0]):
16-
self.f = args[0]
17-
message = args[1] if len(args) > 1 else None
18-
else:
19-
self.f = None
20-
message = args[0] if len(args) == 1 else None
21-
self.message = kwargs.get("message", message)
21+
global f
22+
f = orig = args[0]
23+
exec(
24+
f"""
25+
class deprecated(deprecated):
26+
@staticmethod
27+
def {f.__name__}(*args, **kwargs):
28+
self._emit_warning()
29+
return orig(*args, **kwargs)
30+
31+
f = deprecated.{f.__name__}
32+
""",
33+
locals(),
34+
globals(),
35+
)
36+
self.f = f
37+
args = args[1:]
38+
self.message = self._message(*args, **kwargs)
39+
40+
def _emit_warning(self):
41+
warnings.warn(self.message, DeprecationWarning, stacklevel=3)
2242

2343
def __call__(self, *args, **kwargs):
24-
if self.f is None and len(args) == 1 and callable(args[0]):
25-
self.f = args[0]
26-
return self
27-
28-
warnings.warn(self.message, DeprecationWarning, stacklevel=2)
29-
return self.f(*args, **kwargs)
44+
if self.f:
45+
return self.f(*args, **kwargs)
46+
else:
47+
assert len(args) == 1 and callable(args[0])
48+
return deprecated(args[0], message=self.message).f
3049

3150
def __repr__(self):
32-
return f"<deprecated {repr(self.f)}>"
51+
if self.f:
52+
return repr(self.f)
53+
else:
54+
return f"<deprecation decorator ({self.message!r})>"

test/test_deprecation.py

+38-22
Original file line numberDiff line numberDiff line change
@@ -4,56 +4,72 @@
44

55
from pywisetransfer.deprecation import deprecated
66

7-
record_warnings = lambda: catch_warnings(record=True)
87

8+
def record_warnings():
9+
return catch_warnings(record=True)
910

10-
def undecorated():
11-
return 1
11+
12+
def undecorated(n):
13+
return n
1214

1315

1416
@deprecated
15-
def bare_decorator():
16-
return 1
17+
def bare_decorator(n):
18+
return n + 1
1719

1820

1921
@deprecated()
20-
def zero_args_decorator():
21-
return 1
22+
def zero_args_decorator(n):
23+
return n + 2
2224

2325

2426
@deprecated("positional")
25-
def posarg_decorator():
26-
return 1
27+
def posarg_decorator(n):
28+
return n + 3
2729

2830

2931
@deprecated(message="keyword")
30-
def kwarg_decorator():
31-
return 1
32+
def kwarg_decorator(n):
33+
return n + 4
34+
35+
36+
class Class:
37+
base = 2
38+
39+
@deprecated(message="method")
40+
def method_decorator(self, n):
41+
return self.base + n + 3
3242

3343

3444
@pytest.mark.parametrize(
35-
"func, name, deprecated, message",
45+
"func, name, deprecated, result, message",
3646
[
37-
(undecorated, "undecorated", False, None),
38-
(bare_decorator, "bare_decorator", True, None),
39-
(zero_args_decorator, "zero_args_decorator", True, None),
40-
(posarg_decorator, "posarg_decorator", True, "positional"),
41-
(kwarg_decorator, "kwarg_decorator", True, "keyword"),
47+
(undecorated, "undecorated", False, 0, None),
48+
(bare_decorator, "bare_decorator", True, 1, None),
49+
(zero_args_decorator, "zero_args_decorator", True, 2, None),
50+
(posarg_decorator, "posarg_decorator", True, 3, "positional"),
51+
(kwarg_decorator, "kwarg_decorator", True, 4, "keyword"),
52+
(Class().method_decorator, "method_decorator", True, 5, "method"),
4253
],
4354
)
44-
def test_no_decorator(func, name, deprecated, message):
55+
def test_decorator_variants(func, name, deprecated, result, message):
4556
actual_repr = repr(func)
4657
with record_warnings() as ws:
47-
result = func()
58+
actual_result = func(0)
4859

4960
# Check the Python repr of the function
5061
assert name in actual_repr
51-
actual_deprecated = "deprecated" in actual_repr
62+
actual_deprecated = "deprecated." in actual_repr
5263
assert deprecated == actual_deprecated
5364

5465
# Check the behaviour of the function
55-
assert result == 1
66+
assert result == actual_result
5667

5768
# Check the warnings emitted by the function
58-
assert ws if deprecated else not ws
69+
assert len(ws) == 1 if deprecated else not ws
5970
assert any([message in str(w.message) for w in ws] if message else [True])
71+
72+
73+
def test_standalone_decorator_repr():
74+
decorator = deprecated(message="standalone")
75+
assert repr(decorator) == "<deprecation decorator ('standalone')>"

0 commit comments

Comments
 (0)