Last active
April 20, 2019 05:53
-
-
Save kchawla-pi/40a08a18dc04f39cd338a4cdc15eb6a9 to your computer and use it in GitHub Desktop.
A python decorator for deprecating parameters from a function/method
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Gist made by Kshitij Chawla (Github name: kchawla-pi) for the Nilearn library in Feb/March 2019. | |
GPLv3 | |
""" | |
def replace_parameters(replacement_params, | |
end_version='future', | |
lib_name='Nilearn', | |
): | |
""" | |
Decorator to deprecate & replace specificied parameters | |
in the decorated functions and methods | |
without changing function definition or signature. | |
Parameters | |
---------- | |
replacement_params : Dict[string, string] | |
Dict where the key-value pairs represent the old parameters | |
and their corresponding new parameters. | |
Example: {old_param1: new_param1, old_param2: new_param2,...} | |
end_version : str (optional) {'future' (default) | 'next' | <version>} | |
Version when using the deprecated parameters will raise an error. | |
For informational purpose in the warning text. | |
lib_name: str (optional) (Default: 'Nilearn') | |
Name of the library to which the decoratee belongs. | |
For informational purpose in the warning text. | |
""" | |
def _replace_params(func): | |
@functools.wraps(func) | |
def wrapper(*args, **kwargs): | |
_warn_deprecated_params(replacement_params, end_version, lib_name, | |
kwargs | |
) | |
kwargs = _transfer_deprecated_param_vals(replacement_params, | |
kwargs | |
) | |
return func(*args, **kwargs) | |
return wrapper | |
return _replace_params | |
def _warn_deprecated_params(replacement_params, end_version, lib_name, kwargs): | |
""" For the decorator replace_parameters(), | |
raises warnings about deprecated parameters. | |
Parameters | |
---------- | |
replacement_params: Dict[str, str] | |
Dictionary of old_parameters as keys with replacement parameters | |
as their corresponding values. | |
end_version: str | |
The version where use of the deprecated parameters will raise an error. | |
For informational purpose in the warning text. | |
lib_name: str | |
Name of the library. For informational purpose in the warning text. | |
kwargs: Dict[str, any] | |
Dictionary of all the keyword args passed on the decorated function. | |
""" | |
used_deprecated_params = set(kwargs).intersection(replacement_params) | |
for deprecated_param_ in used_deprecated_params: | |
replacement_param = replacement_params[deprecated_param_] | |
param_deprecation_msg = ( | |
'The parameter "{}" will be removed in {} release of {}. ' | |
'Please use the parameter "{}" instead.'.format(deprecated_param_, | |
end_version, | |
lib_name, | |
replacement_param, | |
) | |
) | |
warnings.filterwarnings('always', message=param_deprecation_msg) | |
warnings.warn(category=DeprecationWarning, | |
message=param_deprecation_msg, | |
stacklevel=3) | |
def _transfer_deprecated_param_vals(replacement_params, kwargs): | |
""" For the decorator replace_parameters(), reassigns new parameters | |
the values passed to their corresponding deprecated parameters. | |
Parameters | |
---------- | |
replacement_params: Dict[str, str] | |
Dictionary of old_parameters as keys with replacement parameters | |
as their corresponding values. | |
kwargs: Dict[str, any] | |
Dictionary of all the keyword args passed on the decorated function. | |
Returns | |
------- | |
kwargs: Dict[str, any] | |
Dictionary of all the keyword args to be passed on | |
to the decorated function, with old parameter names | |
replaced by new parameters, with their values intact. | |
""" | |
for old_param, new_param in replacement_params.items(): | |
old_param_val = kwargs.setdefault(old_param, None) | |
if old_param_val is not None: | |
kwargs[new_param] = old_param_val | |
kwargs.pop(old_param) | |
return kwargs | |
''' | |
Tests: | |
''' | |
def test_replace_parameters(): | |
""" Integration tests that deprecates mock parameters in a mock function | |
and checks that the deprecated parameters transfer their values correctly | |
to replacement parameters and all deprecation warning are raised as | |
expected. | |
""" | |
mock_input, replacement_params = _mock_args_for_testing_replace_parameter() | |
expected_output = ('dp0', 'dp1', 'up0', 'up1') | |
expected_warnings = [ | |
('The parameter "deprecated_param_0" will be removed in 0.6.1rc ' | |
'release of other_lib. Please use the parameter "replacement_param_0"' | |
' instead.' | |
), | |
('The parameter "deprecated_param_1" will be removed in 0.6.1rc ' | |
'release of other_lib. Please use the parameter "replacement_param_1"' | |
' instead.' | |
), | |
] | |
@replace_parameters(replacement_params, '0.6.1rc', 'other_lib', ) | |
def mock_function(replacement_param_0, replacement_param_1, | |
unchanged_param_0, unchanged_param_1): | |
return (replacement_param_0, replacement_param_1, unchanged_param_0, | |
unchanged_param_1 | |
) | |
with warnings.catch_warnings(record=True) as raised_warnings: | |
actual_output = mock_function(deprecated_param_0='dp0', | |
deprecated_param_1='dp1', | |
unchanged_param_0='up0', | |
unchanged_param_1='up1', | |
) | |
assert actual_output == expected_output | |
expected_warnings.sort() | |
raised_warnings.sort(key=lambda mem: str(mem.message)) | |
for raised_warning_, expected_warning_ in zip(raised_warnings, | |
expected_warnings): | |
assert str(raised_warning_.message) == expected_warning_ | |
def test_transfer_deprecated_param_vals(): | |
""" Unit test to check that values assigned to deprecated parameters are | |
correctly reassigned to the replacement parameters. | |
""" | |
mock_input, replacement_params = _mock_args_for_testing_replace_parameter() | |
expected_output = { | |
'unchanged_param_0': 'unchanged_param_0_val', | |
'replacement_param_0': 'deprecated_param_0_val', | |
'replacement_param_1': 'deprecated_param_1_val', | |
'unchanged_param_1': 'unchanged_param_1_val', | |
} | |
actual_ouput = helpers._transfer_deprecated_param_vals( | |
replacement_params, | |
mock_input, | |
) | |
assert actual_ouput == expected_output | |
def test_future_warn_deprecated_params(): | |
""" Unit test to check that the correct warning is displayed. | |
""" | |
mock_input, replacement_params = _mock_args_for_testing_replace_parameter() | |
expected_warnings = [ | |
('The parameter "deprecated_param_0" will be removed in sometime ' | |
'release of somelib. Please use the parameter "replacement_param_0" ' | |
'instead.' | |
), | |
('The parameter "deprecated_param_1" will be removed in sometime ' | |
'release of somelib. Please use the parameter "replacement_param_1" ' | |
'instead.' | |
), | |
] | |
with warnings.catch_warnings(record=True) as raised_warnings: | |
helpers._warn_deprecated_params( | |
replacement_params, | |
end_version='sometime', | |
lib_name='somelib', | |
kwargs=mock_input, | |
) | |
expected_warnings.sort() | |
raised_warnings.sort(key=lambda mem: str(mem.message)) | |
for raised_warning_, expected_warning_ in zip(raised_warnings, | |
expected_warnings | |
): | |
assert str(raised_warning_.message) == expected_warning_ | |
''' | |
Usage example: | |
''' | |
def _replacement_params_view_connectome(): | |
""" Returns a dict containing deprecated & replacement parameters | |
as key-value pair for view_connectome(). | |
Avoids cluttering the global namespace. | |
""" | |
return { | |
'coords': 'node_coords', | |
'threshold': 'edge_threshold', | |
'cmap': 'edge_cmap', | |
'marker_size': 'node_size', | |
} | |
@replace_parameters(replacement_params=_replacement_params_view_connectome(), | |
end_version='0.6.0', | |
lib_name='Nilearn', | |
) | |
def view_connectome(adjacency_matrix, node_coords, edge_threshold=None, | |
edge_cmap=cm.bwr, symmetric_cmap=True, | |
linewidth=6., node_size=3., | |
): | |
connectome_info = _get_connectome( | |
adjacency_matrix, node_coords, threshold=edge_threshold, cmap=edge_cmap, | |
symmetric_cmap=symmetric_cmap, marker_size=node_size) | |
return _make_connectome_html(connectome_info) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment