From 568b0d5baa09ed8af4d79fba9f26431e47f20138 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 9 Sep 2021 22:36:25 +0200 Subject: [PATCH] Make date.{converter,interval_multiples} rcvalidators side-effect free. Register a custom unit conversion interface for dates that reads the relevant rc values when needed. This will allow having multiple rcParams instances existing at the same time, with only one of them actually being active and affecting the library's behavior. --- .../next_api_changes/behavior/21031-AL.rst | 3 + lib/matplotlib/dates.py | 62 ++++++++----------- lib/matplotlib/rcsetup.py | 25 +------- lib/matplotlib/tests/test_dates.py | 2 +- 4 files changed, 31 insertions(+), 61 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/21031-AL.rst diff --git a/doc/api/next_api_changes/behavior/21031-AL.rst b/doc/api/next_api_changes/behavior/21031-AL.rst new file mode 100644 index 000000000000..ec1a80f9478a --- /dev/null +++ b/doc/api/next_api_changes/behavior/21031-AL.rst @@ -0,0 +1,3 @@ +Setting invalid ``rcParams["date.converter"]`` now raises ValueError +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Previously, invalid values would be ignored with a UserWarning. diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 67aff6270815..2b03ff81678c 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -1890,44 +1890,32 @@ def axisinfo(self, unit, axis): default_limits=(datemin, datemax)) -class _rcParam_helper: +class _SwitchableDateConverter: """ - This helper class is so that we can set the converter for dates - via the validator for the rcParams `date.converter` and - `date.interval_multiples`. Never instatiated. + Helper converter-like object that generates and dispatches to + temporary ConciseDateConverter or DateConverter instances based on + :rc:`date.converter` and :rc:`date.interval_multiples`. """ - conv_st = 'auto' - int_mult = True - - @classmethod - def set_converter(cls, s): - """Called by validator for rcParams date.converter""" - if s not in ['concise', 'auto']: - raise ValueError('Converter must be one of "concise" or "auto"') - cls.conv_st = s - cls.register_converters() - - @classmethod - def set_int_mult(cls, b): - """Called by validator for rcParams date.interval_multiples""" - cls.int_mult = b - cls.register_converters() - - @classmethod - def register_converters(cls): - """ - Helper to register the date converters when rcParams `date.converter` - and `date.interval_multiples` are changed. Called by the helpers - above. - """ - if cls.conv_st == 'concise': - converter = ConciseDateConverter - else: - converter = DateConverter + @staticmethod + def _get_converter(): + converter_cls = { + "concise": ConciseDateConverter, "auto": DateConverter}[ + mpl.rcParams["date.converter"]] + interval_multiples = mpl.rcParams["date.interval_multiples"] + return converter_cls(interval_multiples=interval_multiples) + + def axisinfo(self, *args, **kwargs): + return self._get_converter().axisinfo(*args, **kwargs) + + def default_units(self, *args, **kwargs): + return self._get_converter().default_units(*args, **kwargs) + + def convert(self, *args, **kwargs): + return self._get_converter().convert(*args, **kwargs) + - interval_multiples = cls.int_mult - convert = converter(interval_multiples=interval_multiples) - units.registry[np.datetime64] = convert - units.registry[datetime.date] = convert - units.registry[datetime.datetime] = convert +units.registry[np.datetime64] = \ + units.registry[datetime.date] = \ + units.registry[datetime.datetime] = \ + _SwitchableDateConverter() diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index a8a54c10dac6..9f6b9d53ca62 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -149,26 +149,6 @@ def validate_bool(b): raise ValueError('Could not convert "%s" to bool' % b) -def _validate_date_converter(s): - if s is None: - return - s = validate_string(s) - if s not in ['auto', 'concise']: - _api.warn_external(f'date.converter string must be "auto" or ' - f'"concise", not "{s}". Check your matplotlibrc') - return - import matplotlib.dates as mdates - mdates._rcParam_helper.set_converter(s) - - -def _validate_date_int_mult(s): - if s is None: - return - s = validate_bool(s) - import matplotlib.dates as mdates - mdates._rcParam_helper.set_int_mult(s) - - def validate_axisbelow(s): try: return validate_bool(s) @@ -1036,10 +1016,9 @@ def _convert_validator_spec(key, conv): "date.autoformatter.second": validate_string, "date.autoformatter.microsecond": validate_string, - # 'auto', 'concise', 'auto-noninterval' - 'date.converter': _validate_date_converter, + 'date.converter': ['auto', 'concise'], # for auto date locator, choose interval_multiples - 'date.interval_multiples': _validate_date_int_mult, + 'date.interval_multiples': validate_bool, # legend properties "legend.fancybox": validate_bool, diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index 3a38516c7272..96a3db173053 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -1152,7 +1152,7 @@ def test_change_converter(): fig.canvas.draw() assert ax.get_xticklabels()[0].get_text() == 'Jan 01 2020' assert ax.get_xticklabels()[1].get_text() == 'Jan 15 2020' - with pytest.warns(UserWarning) as rec: + with pytest.raises(ValueError): plt.rcParams['date.converter'] = 'boo'