diff --git a/control/config.py b/control/config.py index 99245dd2f..afd7615ca 100644 --- a/control/config.py +++ b/control/config.py @@ -7,6 +7,8 @@ # files. For now, you can just choose between MATLAB and FBS default # values + tweak a few other things. + +import collections import warnings __all__ = ['defaults', 'set_defaults', 'reset_defaults', @@ -20,7 +22,43 @@ 'control.squeeze_time_response': None, 'forced_response.return_x': False, } -defaults = dict(_control_defaults) + + +class DefaultDict(collections.UserDict): + """Map names for settings from older version to their renamed ones. + + If a user wants to write to an old setting, issue a warning and write to + the renamed setting instead. Accessing the old setting returns the value + from the new name. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def __setitem__(self, key, value): + super().__setitem__(self._check_deprecation(key), value) + + def __missing__(self, key): + # An old key should never have been set. If it is being accessed + # through __getitem__, return the value from the new name. + repl = self._check_deprecation(key) + if self.__contains__(repl): + return self[repl] + else: + raise KeyError(key) + + def _check_deprecation(self, key): + if self.__contains__(f"deprecated.{key}"): + repl = self[f"deprecated.{key}"] + warnings.warn(f"config.defaults['{key}'] has been renamed to " + f"config.defaults['{repl}'].", + FutureWarning, stacklevel=3) + return repl + else: + return key + + +defaults = DefaultDict(_control_defaults) def set_defaults(module, **keywords): diff --git a/control/freqplot.py b/control/freqplot.py index f6e995bee..8dbf998d3 100644 --- a/control/freqplot.py +++ b/control/freqplot.py @@ -68,8 +68,16 @@ 'freqplot.Hz': False, # Plot frequency in Hertz 'freqplot.grid': True, # Turn on grid for gain and phase 'freqplot.wrap_phase': False, # Wrap the phase plot at a given value + + # deprecations + 'deprecated.bode.dB': 'freqplot.dB', + 'deprecated.bode.deg': 'freqplot.deg', + 'deprecated.bode.Hz': 'freqplot.Hz', + 'deprecated.bode.grid': 'freqplot.grid', + 'deprecated.bode.wrap_phase': 'freqplot.wrap_phase', } + # # Main plotting functions # diff --git a/control/tests/config_test.py b/control/tests/config_test.py index 45fd8de22..e198254bf 100644 --- a/control/tests/config_test.py +++ b/control/tests/config_test.py @@ -49,6 +49,49 @@ def test_get_param(self): assert ct.config._get_param('config', 'test4', {'test4': 1}, None) == 1 + def test_default_deprecation(self): + ct.config.defaults['deprecated.config.oldkey'] = 'config.newkey' + ct.config.defaults['deprecated.config.oldmiss'] = 'config.newmiss' + + msgpattern = r'config\.oldkey.* has been renamed to .*config\.newkey' + + ct.config.defaults['config.newkey'] = 1 + with pytest.warns(FutureWarning, match=msgpattern): + assert ct.config.defaults['config.oldkey'] == 1 + with pytest.warns(FutureWarning, match=msgpattern): + ct.config.defaults['config.oldkey'] = 2 + with pytest.warns(FutureWarning, match=msgpattern): + assert ct.config.defaults['config.oldkey'] == 2 + assert ct.config.defaults['config.newkey'] == 2 + + ct.config.set_defaults('config', newkey=3) + with pytest.warns(FutureWarning, match=msgpattern): + assert ct.config._get_param('config', 'oldkey') == 3 + with pytest.warns(FutureWarning, match=msgpattern): + ct.config.set_defaults('config', oldkey=4) + with pytest.warns(FutureWarning, match=msgpattern): + assert ct.config.defaults['config.oldkey'] == 4 + assert ct.config.defaults['config.newkey'] == 4 + + ct.config.defaults.update({'config.newkey': 5}) + with pytest.warns(FutureWarning, match=msgpattern): + ct.config.defaults.update({'config.oldkey': 6}) + with pytest.warns(FutureWarning, match=msgpattern): + assert ct.config.defaults.get('config.oldkey') == 6 + + with pytest.raises(KeyError): + with pytest.warns(FutureWarning, match=msgpattern): + ct.config.defaults['config.oldmiss'] + with pytest.raises(KeyError): + ct.config.defaults['config.neverdefined'] + + # assert that reset defaults keeps the custom type + ct.config.reset_defaults() + with pytest.warns(FutureWarning, + match='bode.* has been renamed to.*freqplot'): + assert ct.config.defaults['bode.Hz'] \ + == ct.config.defaults['freqplot.Hz'] + @mplcleanup def test_fbs_bode(self): ct.use_fbs_defaults()