Skip to content

Commit ff300d6

Browse files
Zeroto521pre-commit-ci[bot]ericmjlthatlittleboy
authored
[EHN] New decorator deprecated_kwargs (#1103)
* lint importing * [EHN] New decorator `deprecated_kwargs` * Test `deprecated_kwargs` * Update CHANGELOG.md * lint codes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add return section * lint codes * Adjust the sequence of section * Revert "lint codes" This reverts commit 710c70a. * Test normal case * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review Co-authored-by: Jeremy Goh <[email protected]> * Drop duplicated `"` * Update example in docstring * Correct variable name * Title more clearer names * Rename function names * Test `message` * Fix testsuits * [EHN] New option `error` to raise error or warning * Lint importing * Ignore DAR402 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Eric Ma <[email protected]> Co-authored-by: Jeremy Goh <[email protected]>
1 parent 4067119 commit ff300d6

File tree

3 files changed

+161
-13
lines changed

3 files changed

+161
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- [DOC] Updated developer guide docs.
66
- [ENH] Allow column selection/renaming within conditional_join. #1102 @samukweku.
7+
- [EHN] New decorator `deprecated_kwargs` for breaking API. #1103 @Zeroto521
78

89
## [v0.23.1] - 2022-05-03
910

janitor/utils.py

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
"""Miscellaneous internal PyJanitor helper functions."""
22

3-
import functools
43
import os
54
import socket
65
import sys
7-
import warnings
8-
from typing import (
9-
Callable,
10-
Dict,
11-
Iterable,
12-
Union,
13-
)
6+
from warnings import warn
7+
from functools import singledispatch, wraps
8+
from typing import Callable, Dict, Iterable, Union
149

1510
import numpy as np
1611
import pandas as pd
@@ -46,7 +41,7 @@ def check(varname: str, value, expected_types: list):
4641
raise TypeError(f"{varname} should be one of {expected_types}.")
4742

4843

49-
@functools.singledispatch
44+
@singledispatch
5045
def _expand_grid(value, grid_index, key):
5146
"""
5247
Base function for dispatch of `_expand_grid`.
@@ -231,6 +226,59 @@ def idempotent(func: Callable, df: pd.DataFrame, *args, **kwargs):
231226
)
232227

233228

229+
def deprecated_kwargs(
230+
*arguments: list[str],
231+
message: str = (
232+
"The keyword argument '{argument}' of '{func_name}' is deprecated."
233+
),
234+
error: bool = True,
235+
) -> Callable:
236+
"""
237+
Used as a decorator when deprecating function's keyword arguments.
238+
239+
Example:
240+
241+
```python
242+
from janitor.utils import deprecated_kwargs
243+
244+
@deprecated_kwargs('x', 'y')
245+
def plus(a, b, x=0, y=0):
246+
return a + b
247+
```
248+
249+
:param arguments: The list of deprecated keyword arguments.
250+
:param message: The message of `ValueError` or `DeprecationWarning`.
251+
It should be a string or a string template. If a string template
252+
defaults input `func_name` and `argument`.
253+
:param error: If True raises `ValueError` else returns
254+
`DeprecationWarning`.
255+
:return: The original function wrapped with the deprecated `kwargs`
256+
checking function.
257+
:raises ValueError: If one of `arguments` is in the decorated function's
258+
keyword arguments. # noqa: DAR402
259+
"""
260+
261+
def decorator(func):
262+
@wraps(func)
263+
def wrapper(*args, **kwargs):
264+
for argument in arguments:
265+
if argument in kwargs:
266+
msg = message.format(
267+
func_name=func.__name__,
268+
argument=argument,
269+
)
270+
if error:
271+
raise ValueError(msg)
272+
else:
273+
warn(msg, DeprecationWarning)
274+
275+
return func(*args, **kwargs)
276+
277+
return wrapper
278+
279+
return decorator
280+
281+
234282
def deprecated_alias(**aliases) -> Callable:
235283
"""
236284
Used as a decorator when deprecating old function argument names, while
@@ -252,7 +300,7 @@ def simple_sum(alpha, beta):
252300
""" # noqa: E501
253301

254302
def decorator(func):
255-
@functools.wraps(func)
303+
@wraps(func)
256304
def wrapper(*args, **kwargs):
257305
rename_kwargs(func.__name__, kwargs, aliases)
258306
return func(*args, **kwargs)
@@ -287,7 +335,7 @@ def simple_sum(alpha, beta):
287335

288336
def decorator(func):
289337
def emit_warning(*args, **kwargs):
290-
warnings.warn(message, FutureWarning)
338+
warn(message, FutureWarning)
291339
return func(*args, **kwargs)
292340

293341
return emit_warning
@@ -315,7 +363,7 @@ def rename_kwargs(func_name: str, kwargs: Dict, aliases: Dict):
315363
raise TypeError(
316364
f"{func_name} received both {old_alias} and {new_alias}"
317365
)
318-
warnings.warn(
366+
warn(
319367
f"{old_alias} is deprecated; use {new_alias}",
320368
DeprecationWarning,
321369
)
@@ -449,7 +497,7 @@ def is_connected(url: str) -> bool:
449497
return True
450498
except OSError as e:
451499

452-
warnings.warn(
500+
warn(
453501
"There was an issue connecting to the internet. "
454502
"Please see original error below."
455503
)

tests/utils/test_deprecated_kwargs.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import pytest
2+
3+
from janitor.utils import deprecated_kwargs
4+
5+
6+
@pytest.mark.utils
7+
@pytest.mark.parametrize(
8+
"arguments, message, func_kwargs, msg_expected",
9+
[
10+
(
11+
["a"],
12+
"The keyword argument '{argument}' of '{func_name}' is deprecated",
13+
dict(a=1),
14+
"The keyword argument 'a' of 'simple_sum' is deprecated",
15+
),
16+
(
17+
["b"],
18+
"The keyword argument '{argument}' of '{func_name}' is deprecated",
19+
dict(b=2),
20+
"The keyword argument 'b' of 'simple_sum' is deprecated",
21+
),
22+
(
23+
["a", "b"],
24+
"The option '{argument}' of '{func_name}' is deprecated.",
25+
dict(a=1, b=2),
26+
"The option 'a' of 'simple_sum' is deprecated.",
27+
),
28+
(
29+
["b", "a"],
30+
"The keyword of function is deprecated.",
31+
dict(a=1, b=2),
32+
"The keyword of function is deprecated.",
33+
),
34+
],
35+
)
36+
def test_error(arguments, message, func_kwargs, msg_expected):
37+
@deprecated_kwargs(*arguments, message=message)
38+
def simple_sum(alpha, beta, a=0, b=0):
39+
return alpha + beta
40+
41+
with pytest.raises(ValueError, match=msg_expected):
42+
simple_sum(1, 2, **func_kwargs)
43+
44+
45+
@pytest.mark.utils
46+
@pytest.mark.parametrize(
47+
"arguments, message, func_kwargs, msg_expected",
48+
[
49+
(
50+
["a"],
51+
"The keyword argument '{argument}' of '{func_name}' is deprecated",
52+
dict(a=1),
53+
"The keyword argument 'a' of 'simple_sum' is deprecated",
54+
),
55+
(
56+
["b"],
57+
"The keyword argument '{argument}' of '{func_name}' is deprecated",
58+
dict(b=2),
59+
"The keyword argument 'b' of 'simple_sum' is deprecated",
60+
),
61+
(
62+
["a", "b"],
63+
"The option '{argument}' of '{func_name}' is deprecated.",
64+
dict(a=1, b=2),
65+
"The option 'a' of 'simple_sum' is deprecated.",
66+
),
67+
(
68+
["b", "a"],
69+
"The keyword of function is deprecated.",
70+
dict(a=1, b=2),
71+
"The keyword of function is deprecated.",
72+
),
73+
],
74+
)
75+
def test_warning(arguments, message, func_kwargs, msg_expected):
76+
@deprecated_kwargs(*arguments, message=message, error=False)
77+
def simple_sum(alpha, beta, a=0, b=0):
78+
return alpha + beta
79+
80+
with pytest.warns(DeprecationWarning, match=msg_expected):
81+
simple_sum(1, 2, **func_kwargs)
82+
83+
84+
@pytest.mark.utils
85+
@pytest.mark.parametrize(
86+
"arguments, func_args, expected",
87+
[
88+
(["a"], [0, 0], 0),
89+
(["b"], [1, 1], 2),
90+
(["a", "b"], [0, 1], 1),
91+
(["b", "a"], [0, 1], 1),
92+
],
93+
)
94+
def test_without_error(arguments, func_args, expected):
95+
@deprecated_kwargs(*arguments)
96+
def simple_sum(alpha, beta, a=0, b=0):
97+
return alpha + beta
98+
99+
assert simple_sum(*func_args) == expected

0 commit comments

Comments
 (0)