Skip to content

Commit dea7bb0

Browse files
authored
Bump version 1.8.1 (with deprecation cleanup and a bug fix)
* chg: Deprecation util cleaned up and expanded a bit. More forgiving of unexpected inputs. * fix: Bug in v1.8.0 deprecation util - deepcopy inadvertently replacing things like default_authenticator * Bump version: 1.8.0 → 1.8.1
1 parent e8605ab commit dea7bb0

File tree

5 files changed

+118
-31
lines changed

5 files changed

+118
-31
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 1.8.0
2+
current_version = 1.8.1
33
commit = True
44
tag = True
55

CHANGELOG.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@ Changelog
22
=========
33

44

5+
v1.8.1 (2019-06-14)
6+
-------------------
7+
8+
Changes
9+
~~~~~~~
10+
- Deprecation util cleaned up and expanded a bit. More forgiving of unexpected inputs. [Rick Riensche]
11+
12+
Fix
13+
~~~
14+
- Bug in v1.8.0 deprecation util - deepcopy inadvertently replacing things like default_authenticator
15+
16+
517
v1.8.0 (2019-06-12)
618
-------------------
719

flask_rebar/utils/deprecation.py

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
:license: MIT, see LICENSE for details.
99
"""
1010

11-
import copy
1211
import functools
1312
import warnings
13+
from collections import namedtuple
1414

1515
from werkzeug.local import LocalProxy as module_property # noqa
1616

@@ -55,21 +55,19 @@ def deprecated(new_func=None, eol_version=None):
5555
:param Union[str, (str, str)] new_func: Name (or name and end-of-life version) of replacement
5656
:param str eol_version: Version in which this function may no longer work
5757
:return:
58+
Raise a deprecation warning for decorated function.
59+
Tuple is supported for new_func just in case somebody infers it as an option based on the way we
60+
deprecate params..
61+
If tuple form is used for new_func AND eol_version is provided, eol_version will trump whatever is
62+
found in the tuple; caveat emptor
5863
"""
5964

6065
def decorator(f):
6166
@functools.wraps(f)
6267
def wrapper(*args, **kwargs):
63-
new = None
64-
eol = eol_version
65-
if (
66-
type(new_func) is tuple
67-
): # in case somebody inferred this from the way we deprecate params..
68-
new = str(new_func[0])
69-
eol = str(new_func[1])
70-
elif new_func:
71-
new = str(new_func)
72-
_deprecation_warning(f.__name__, new, eol)
68+
new, eol = _validated_deprecation_spec(new_func)
69+
eol = eol_version or eol
70+
_deprecation_warning(f.__name__, new, eol, stacklevel=3)
7371
return f(*args, **kwargs)
7472

7573
return wrapper
@@ -96,35 +94,54 @@ def wrapper(*args, **kwargs):
9694
return decorator
9795

9896

97+
def _validated_deprecation_spec(spec):
98+
"""
99+
:param Union[new_name, (new_name, eol_version)] spec: new name and/or expected end-of-life version
100+
:return: (str new_name, str eol_version), normalized to tuple and sanitized to deal with malformed inputs
101+
Parse a deprecation spec (string or tuple) to a standardized namedtuple form.
102+
If spec is provided as a bare value (presumably string), we'll treat as new name with no end-of-life version
103+
If spec is provided (likely on accident) as a 1-element tuple, we'll treat same as a bare value
104+
If spec is provided as a tuple with more than 2 elements, we'll simply ignore the extraneous
105+
"""
106+
new_name = None
107+
eol_version = None
108+
if type(spec) is tuple:
109+
if len(spec) > 0:
110+
new_name = str(spec[0]) if spec[0] else None
111+
if len(spec) > 1:
112+
eol_version = str(spec[1]) if spec[1] else None
113+
elif spec:
114+
new_name = str(spec)
115+
validated = namedtuple("deprecation_spec", ["new_name", "eol_version"])(
116+
new_name, eol_version
117+
)
118+
return validated
119+
120+
99121
def _remap_kwargs(func_name, kwargs, aliases):
100122
"""
101123
Adapted from https://stackoverflow.com/a/49802489/977046
102124
"""
103-
remapped_args = copy.deepcopy(kwargs)
125+
remapped_args = dict(kwargs)
104126
for alias, new_spec in aliases.items():
105127
if alias in remapped_args:
106-
eol_version = None
107-
if type(new_spec) is tuple:
108-
new = str(new_spec[0])
109-
if len(new_spec) >= 2:
110-
eol_version = str(new_spec[1]) if new_spec[1] else None
111-
else:
112-
new = str(new_spec)
128+
new, eol_version = _validated_deprecation_spec(new_spec)
113129
if new in remapped_args:
114130
raise TypeError(
115131
"{} received both {} and {}".format(func_name, alias, new)
116132
)
117133
else:
118-
_deprecation_warning(alias, new, eol_version)
119-
remapped_args[new] = remapped_args.pop(alias)
134+
_deprecation_warning(alias, new, eol_version, stacklevel=4)
135+
if new:
136+
remapped_args[new] = remapped_args.pop(alias)
120137

121138
return remapped_args
122139

123140

124-
def _deprecation_warning(old_name, new_name, eol_version):
141+
def _deprecation_warning(old_name, new_name, eol_version, stacklevel=1):
125142
eol_clause = (
126143
" and may be removed in version {}".format(eol_version) if eol_version else ""
127144
)
128145
replacement_clause = "; use {}".format(new_name) if new_name else ""
129146
msg = "{} is deprecated{}{}".format(old_name, eol_clause, replacement_clause)
130-
warnings.warn(msg, config.warning_type)
147+
warnings.warn(message=msg, category=config.warning_type, stacklevel=stacklevel)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
if __name__ == "__main__":
66
setup(
77
name="flask-rebar",
8-
version="1.8.0",
8+
version="1.8.1",
99
author="Barak Alon",
1010
author_email="[email protected]",
1111
description="Flask-Rebar combines flask, marshmallow, and swagger for robust REST services.",

tests/test_deprecation_utils.py

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,35 @@
1414

1515

1616
@deprecated_parameters(
17-
old_param1="new_param1",
18-
old_param2=("new_param2", "v99"),
19-
old_param3=("new_param3",),
20-
old_param4=("new_param4", None),
17+
old_param1="new_param1", # rename with no predicted end-of-life version
18+
old_param2=("new_param2", "v99"), # rename with predicted end-of-life version
19+
old_param3=("new_param3",), # rename with a poorly formed tuple
20+
old_param4=("new_param4", None), # rename with explicitly None end-of-life version
21+
old_param5=(None, "v99.5"), # no rename with explicit end-of-life version
22+
old_param6=None, # deprecated param with no replacement, no specific end-of-life-version
23+
old_param7=(None, None), # same as 6, but for the truly pedantic
24+
old_param8=(), # could imagine someone accidentally doing this.. :P
2125
)
22-
def _add(new_param1=0, new_param2=0, new_param3=0, new_param4=0):
23-
return new_param1 + new_param2 + new_param3 + new_param4
26+
def _add(
27+
new_param1=0,
28+
new_param2=0,
29+
new_param3=0,
30+
new_param4=0,
31+
old_param5=0,
32+
old_param6=0,
33+
old_param7=0,
34+
old_param8=0,
35+
):
36+
return (
37+
new_param1
38+
+ new_param2
39+
+ new_param3
40+
+ new_param4
41+
+ old_param5
42+
+ old_param6
43+
+ old_param7
44+
+ old_param8
45+
)
2446

2547

2648
@deprecated()
@@ -103,6 +125,42 @@ def test_parameter_deprecation_warnings(self):
103125
)
104126
self.assertIs(w[0].category, FutureWarning)
105127

128+
# with no replacement but specific expiration
129+
with warnings.catch_warnings(record=True) as w:
130+
warnings.simplefilter("always")
131+
result = _add(new_param1=1, old_param5=5)
132+
self.assertEqual(result, 6)
133+
self.assertEqual(len(w), 1)
134+
msg = str(w[0].message)
135+
self.assertIn("old_param5 is deprecated", msg)
136+
self.assertIn("v99.5", msg)
137+
self.assertNotIn("new_param", msg)
138+
self.assertIs(w[0].category, FutureWarning)
139+
140+
# with no replacement (specified as None)
141+
with warnings.catch_warnings(record=True) as w:
142+
warnings.simplefilter("always")
143+
result = _add(new_param1=1, old_param5=5)
144+
self.assertEqual(result, 6)
145+
self.assertEqual(len(w), 1)
146+
msg = str(w[0].message)
147+
self.assertIn("old_param5 is deprecated", msg)
148+
self.assertIn("v99.5", msg)
149+
self.assertNotIn("new_param", msg)
150+
self.assertIs(w[0].category, FutureWarning)
151+
152+
# with no replacement -- specified as explicit (None, None) and implicit ()
153+
with warnings.catch_warnings(record=True) as w:
154+
warnings.simplefilter("always")
155+
result = _add(old_param7=7, old_param8=8)
156+
self.assertEqual(result, 15)
157+
self.assertEqual(len(w), 2)
158+
msgs = {str(w[0].message), str(w[1].message)}
159+
expected_msgs = {"old_param7 is deprecated", "old_param8 is deprecated"}
160+
self.assertEqual(expected_msgs, msgs)
161+
self.assertIn("v99.5", msg)
162+
self.assertNotIn("new_param", msg)
163+
106164
def test_parameter_deprecation_warning_type(self):
107165
"""Deprecation supports specifying type of warning"""
108166
deprecation_config.warning_type = DeprecationWarning

0 commit comments

Comments
 (0)