Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@kchawla-pi
Last active April 20, 2019 05:53
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kchawla-pi/40a08a18dc04f39cd338a4cdc15eb6a9 to your computer and use it in GitHub Desktop.
Save kchawla-pi/40a08a18dc04f39cd338a4cdc15eb6a9 to your computer and use it in GitHub Desktop.
A python decorator for deprecating parameters from a function/method
"""
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