From ab1ea99718adf4eb5df73979acdafa86e2cf77f9 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Tue, 4 Apr 2023 16:36:04 -0500 Subject: [PATCH 01/22] Implement RcParams using ChainMaps. --- lib/matplotlib/__init__.py | 251 +++++++++++++++++++------- lib/matplotlib/mpl-data/matplotlibrc | 10 +- lib/matplotlib/pyplot.py | 14 +- lib/matplotlib/rcsetup.py | 10 +- lib/matplotlib/style/core.py | 6 +- lib/matplotlib/tests/test_rcparams.py | 2 +- lib/matplotlib/tests/test_widgets.py | 2 +- 7 files changed, 210 insertions(+), 85 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 53f27c46314a..a55deeb912de 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -134,8 +134,8 @@ import atexit -from collections import namedtuple -from collections.abc import MutableMapping +from collections import namedtuple, ChainMap, defaultdict +from collections.abc import MutableMapping, Mapping import contextlib import functools import importlib @@ -155,6 +155,7 @@ import numpy from packaging.version import parse as parse_version +from copy import deepcopy # cbook must import matplotlib only within function # definitions, so it is safe to import from it here. @@ -650,7 +651,7 @@ def gen_candidates(): @_docstring.Substitution( "\n".join(map("- {}".format, sorted(rcsetup._validators, key=str.lower))) ) -class RcParams(MutableMapping, dict): +class RcParams(MutableMapping): """ A dict-like key-value store for config parameters, including validation. @@ -665,14 +666,71 @@ class RcParams(MutableMapping, dict): -------- :ref:`customizing-with-matplotlibrc-files` """ - validate = rcsetup._validators + namespaces = ( + "backends", + "lines", + "patches", + "hatches", + "boxplot", + "font", + "text", + "latex", + "axes", + "date", + "xtick", + "ytick", + "grid", + "legend", + "figure", + "image", + "contour", + "errorbar", + "hist", + "scatter", + "agg", + "path", + "savefig", + "tk", + "ps", + "pdf", + "svg", + "pgf", + "docstring", + "keymap", + "animation", + "_internal", + "webagg", + "markers", + "pcolor", + "pcolormesh", + "patch", + "hatch", + "mathtext", + "polaraxes", + "axes3d", + "xaxis", + "yaxis", + "default" + ) + + single_key_set = {"backend", "toolbar", "interactive", + "timezone", "backend_fallback"} - # validate values on the way in def __init__(self, *args, **kwargs): + self._namespace_maps = {name: ChainMap({}) for name in self.namespaces} self.update(*args, **kwargs) + self._namespace_maps = { + name: mapping.new_child() + for name, mapping in self._namespace_maps.items() + } + self._mapping = ChainMap(*self._namespace_maps.values()) - def _set(self, key, val): + def _split_key(self, key, sep="."): + keys = key.split(sep, maxsplit=1) + return keys, len(keys) + + def _set(self, key, value): """ Directly write data bypassing deprecation and validation logic. @@ -690,7 +748,13 @@ def _set(self, key, val): :meta public: """ - dict.__setitem__(self, key, val) + keys, depth = self._split_key(key) + if depth == 1: + if key in self.single_key_set: + self._namespace_maps["default"][key] = value + self._namespace_maps[key] = value + elif depth == 2: + self._namespace_maps[keys[0]][keys[1]] = value def _get(self, key): """ @@ -711,7 +775,13 @@ def _get(self, key): :meta public: """ - return dict.__getitem__(self, key) + keys, depth = self._split_key(key) + if depth == 1: + if key in self.single_key_set: + return self._namespace_maps["default"].get(key) + return self._namespace_maps[key] + elif depth == 2: + return self._namespace_maps[keys[0]].get(keys[1]) def __setitem__(self, key, val): try: @@ -733,10 +803,13 @@ def __setitem__(self, key, val): if val is rcsetup._auto_backend_sentinel: if 'backend' in self: return + if key in self.single_key_set: + key = f"default.{key}" try: cval = self.validate[key](val) except ValueError as ve: raise ValueError(f"Key {key}: {ve}") from None + # breakpoint() self._set(key, cval) except KeyError as err: raise KeyError( @@ -768,51 +841,97 @@ def __getitem__(self, key): def _get_backend_or_none(self): """Get the requested backend, if any, without triggering resolution.""" - backend = self._get("backend") + backend = self._get("default.backend") return None if backend is rcsetup._auto_backend_sentinel else backend - def __repr__(self): - class_name = self.__class__.__name__ - indent = len(class_name) + 1 - with _api.suppress_matplotlib_deprecation_warning(): - repr_split = pprint.pformat(dict(self), indent=1, - width=80 - indent).split('\n') - repr_indented = ('\n' + ' ' * indent).join(repr_split) - return f'{class_name}({repr_indented})' - - def __str__(self): - return '\n'.join(map('{0[0]}: {0[1]}'.format, sorted(self.items()))) + def __delitem__(self, key): + keys, depth = self._split_key(key) + if depth == 1: + del self._namespace_maps[key] + elif depth == 2: + del self._namespace_maps[keys[0]][keys[1]] + + def __contains__(self, key): + keys, depth = self._split_key(key) + if depth == 1: + if key in self.single_key_set: + return key in self._namespace_maps["default"] + return key in self._namespace_maps + elif depth == 2: + return any(key in mapping for mapping in self._namespace_maps) def __iter__(self): - """Yield sorted list of keys.""" + """Yield from sorted list of keys""" with _api.suppress_matplotlib_deprecation_warning(): - yield from sorted(dict.__iter__(self)) + yield from sorted(self.keys()) def __len__(self): - return dict.__len__(self) - - def find_all(self, pattern): - """ - Return the subset of this RcParams dictionary whose keys match, - using :func:`re.search`, the given ``pattern``. - - .. note:: - - Changes to the returned dictionary are *not* propagated to - the parent RcParams dictionary. + return sum(len(mapping) for mapping in self._namespace_maps) + def __repr__(self): + return repr(dict(self.items())) + + def keys(self): + keys = ( + ".".join((space, key)) + for space, mapping in self._namespace_maps.items() + for key in mapping.keys() + ) + return keys + + def values(self): + for key in self.keys(): + yield self[key] + + def items(self): + for key, value in zip(self.keys(), self.values()): + yield key, value + + def pop(self, key): + keys, depth = self._split_key(key) + if depth == 1: + self._mapping.pop() + elif depth == 2: + self._namespace_mapping[keys[0]].pop(keys[1]) + + def popitem(self): + return self._mapping.popitem() + + def clear(self): + self._mapping.clear() + + def setdefault(self, key, default=None): + self[key] = default + return default + + def get(self, key, default=None): + try: + return self[key] + except KeyError as e: + return default + + def update(self, other=(), /, **kwds): + """D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. + If E present and has a .keys() method, does: for k in E: D[k] = E[k] + If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v + In either case, this is followed by: for k, v in F.items(): D[k] = v """ - pattern_re = re.compile(pattern) - return RcParams((key, value) - for key, value in self.items() - if pattern_re.search(key)) + if isinstance(other, Mapping): + for key in other: + self[key] = other[key] + elif hasattr(other, "keys"): + for key in other.keys(): + self[key] = other[key] + else: + for key, value in other: + self[key] = value + for key, value in kwds.items(): + self[key] = value def copy(self): - """Copy this RcParams instance.""" - rccopy = RcParams() - for k in self: # Skip deprecations and revalidation. - rccopy._set(k, self._get(k)) - return rccopy + return deepcopy(self) + +# MutableMapping.register(RcParams) def rc_params(fail_on_error=False): @@ -962,36 +1081,34 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): return config -# When constructing the global instances, we need to perform certain updates -# by explicitly calling the superclass (dict.update, dict.items) to avoid -# triggering resolution of _auto_backend_sentinel. rcParamsDefault = _rc_params_in_file( cbook._get_data_path("matplotlibrc"), # Strip leading comment. transform=lambda line: line[1:] if line.startswith("#") else line, fail_on_error=True) -dict.update(rcParamsDefault, rcsetup._hardcoded_defaults) +rcParamsDefault.update(rcsetup._hardcoded_defaults) # Normally, the default matplotlibrc file contains *no* entry for backend (the # corresponding line starts with ##, not #; we fill on _auto_backend_sentinel # in that case. However, packagers can set a different default backend # (resulting in a normal `#backend: foo` line) in which case we should *not* # fill in _auto_backend_sentinel. -dict.setdefault(rcParamsDefault, "backend", rcsetup._auto_backend_sentinel) -rcParams = RcParams() # The global instance. -dict.update(rcParams, dict.items(rcParamsDefault)) -dict.update(rcParams, _rc_params_in_file(matplotlib_fname())) -rcParamsOrig = rcParams.copy() +rcParamsDefault.setdefault("default.backend", rcsetup._auto_backend_sentinel) +params_dict = RcParams() +params_dict.update(rcParamsDefault.items()) +params_dict.update(_rc_params_in_file(matplotlib_fname())) +rcParamsOrig = params_dict.copy() with _api.suppress_matplotlib_deprecation_warning(): # This also checks that all rcParams are indeed listed in the template. # Assigning to rcsetup.defaultParams is left only for backcompat. defaultParams = rcsetup.defaultParams = { # We want to resolve deprecated rcParams, but not backend... - key: [(rcsetup._auto_backend_sentinel if key == "backend" else + key: [(rcsetup._auto_backend_sentinel if key == "default.backend" else rcParamsDefault[key]), validator] for key, validator in rcsetup._validators.items()} -if rcParams['axes.formatter.use_locale']: +if params_dict['axes.formatter.use_locale']: locale.setlocale(locale.LC_ALL, '') +rcParams = RcParams(params_dict) def rc(group, **kwargs): @@ -1133,7 +1250,7 @@ def rc_file(fname, *, use_default_template=True): from .style.core import STYLE_BLACKLIST rc_from_file = rc_params_from_file( fname, use_default_template=use_default_template) - rcParams.update({k: rc_from_file[k] for k in rc_from_file + rcParams.update({k: rc_from_file[k] for k in rc_from_file.keys() if k not in STYLE_BLACKLIST}) @@ -1182,16 +1299,24 @@ def rc_context(rc=None, fname=None): plt.plot(x, y) """ - orig = dict(rcParams.copy()) - del orig['backend'] try: + for space in rcParams._namespace_maps.keys(): + rcParams._namespace_maps[space] = rcParams._namespace_maps[ + space + ].new_child() if fname: rc_file(fname) if rc: rcParams.update(rc) yield finally: - dict.update(rcParams, orig) # Revert to the original rcs. + # Revert to the original rcs. + backend = rcParams["backend"] + for space in rcParams._namespace_maps.keys(): + rcParams._namespace_maps[space] = rcParams._namespace_maps[ + space + ].parents + rcParams["backend"] = backend def use(backend, *, force=True): @@ -1259,14 +1384,14 @@ def use(backend, *, force=True): # value which will be respected when the user finally imports # pyplot else: - rcParams['backend'] = backend + rcParams['default.backend'] = backend # if the user has asked for a given backend, do not helpfully # fallback - rcParams['backend_fallback'] = False + rcParams['default.backend_fallback'] = False if os.environ.get('MPLBACKEND'): - rcParams['backend'] = os.environ.get('MPLBACKEND') + rcParams['default.backend'] = os.environ.get('MPLBACKEND') def get_backend(): @@ -1277,14 +1402,14 @@ def get_backend(): -------- matplotlib.use """ - return rcParams['backend'] + return rcParams['default.backend'] def interactive(b): """ Set whether to redraw after every plotting command (e.g. `.pyplot.xlabel`). """ - rcParams['interactive'] = b + rcParams['default.interactive'] = b def is_interactive(): @@ -1296,7 +1421,7 @@ def is_interactive(): This function is only intended for use in backends. End users should use `.pyplot.isinteractive` instead. """ - return rcParams['interactive'] + return rcParams['default.interactive'] def _val_or_rc(val, rc_name): diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index 9bd8a622092e..25e6aaa00d4d 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -82,7 +82,7 @@ ## PS PDF SVG Template ## You can also deploy your own backend outside of Matplotlib by referring to ## the module name (which must be in the PYTHONPATH) as 'module://my_backend'. -##backend: Agg +##default.backend: Agg ## The port to use for the web server in the WebAgg backend. #webagg.port: 8988 @@ -100,12 +100,12 @@ ## If you are running pyplot inside a GUI and your backend choice ## conflicts, we will automatically try to find a compatible one for ## you if backend_fallback is True -#backend_fallback: True +#default.backend_fallback: True -#interactive: False +#default.interactive: False #figure.hooks: # list of dotted.module.name:dotted.callable.name -#toolbar: toolbar2 # {None, toolbar2, toolmanager} -#timezone: UTC # a pytz timezone string, e.g., US/Central or Europe/Paris +#default.toolbar: toolbar2 # {None, toolbar2, toolmanager} +#default.timezone: UTC # a pytz timezone string, e.g., US/Central or Europe/Paris ## *************************************************************************** diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 7f4aa12c9ed6..e815ddb1b347 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -327,16 +327,16 @@ def switch_backend(newbackend: str) -> None: except ImportError: continue else: - rcParamsOrig['backend'] = candidate + rcParamsOrig['default.backend'] = candidate return else: # Switching to Agg should always succeed; if it doesn't, let the # exception propagate out. switch_backend("agg") - rcParamsOrig["backend"] = "agg" + rcParamsOrig["default.backend"] = "agg" return # have to escape the switch on access logic - old_backend = dict.__getitem__(rcParams, 'backend') + old_backend = rcParams['default.backend'] module = importlib.import_module(cbook._backend_module_name(newbackend)) canvas_class = module.FigureCanvas @@ -413,7 +413,7 @@ def draw_if_interactive() -> None: _log.debug("Loaded backend %s version %s.", newbackend, backend_mod.backend_version) - rcParams['backend'] = rcParamsDefault['backend'] = newbackend + rcParams['default.backend'] = rcParamsDefault['default.backend'] = newbackend _backend_mod = backend_mod for func_name in ["new_figure_manager", "draw_if_interactive", "show"]: globals()[func_name].__signature__ = inspect.signature( @@ -745,7 +745,7 @@ def xkcd( "xkcd mode is not compatible with text.usetex = True") stack = ExitStack() - stack.callback(dict.update, rcParams, rcParams.copy()) # type: ignore + stack.callback(rcParams.update, rcParams.copy()) # type: ignore from matplotlib import patheffects rcParams.update({ @@ -2472,11 +2472,11 @@ def polar(*args, **kwargs) -> list[Line2D]: # If rcParams['backend_fallback'] is true, and an interactive backend is # requested, ignore rcParams['backend'] and force selection of a backend that # is compatible with the current running interactive framework. -if (rcParams["backend_fallback"] +if (rcParams["default.backend_fallback"] and rcParams._get_backend_or_none() in ( # type: ignore set(rcsetup.interactive_bk) - {'WebAgg', 'nbAgg'}) and cbook._get_running_interactive_framework()): # type: ignore - rcParams._set("backend", rcsetup._auto_backend_sentinel) # type: ignore + rcParams._set("default.backend", rcsetup._auto_backend_sentinel) # type: ignore # fmt: on diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 38d4606024d3..bee0204a46b9 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -892,12 +892,12 @@ def _convert_validator_spec(key, conv): # The rcParams defaults are defined in lib/matplotlib/mpl-data/matplotlibrc, which # gets copied to matplotlib/mpl-data/matplotlibrc by the setup script. _validators = { - "backend": validate_backend, - "backend_fallback": validate_bool, + "default.backend": validate_backend, + "default.backend_fallback": validate_bool, "figure.hooks": validate_stringlist, - "toolbar": _validate_toolbar, - "interactive": validate_bool, - "timezone": validate_string, + "default.toolbar": _validate_toolbar, + "default.interactive": validate_bool, + "default.timezone": validate_string, "webagg.port": validate_int, "webagg.address": validate_string, diff --git a/lib/matplotlib/style/core.py b/lib/matplotlib/style/core.py index 7e9008c56165..57ea5fa5df1d 100644 --- a/lib/matplotlib/style/core.py +++ b/lib/matplotlib/style/core.py @@ -39,9 +39,9 @@ STYLE_EXTENSION = 'mplstyle' # A list of rcParams that should not be applied from styles STYLE_BLACKLIST = { - 'interactive', 'backend', 'webagg.port', 'webagg.address', - 'webagg.port_retries', 'webagg.open_in_browser', 'backend_fallback', - 'toolbar', 'timezone', 'figure.max_open_warning', + 'default.interactive', 'default.backend', 'webagg.port', 'webagg.address', + 'webagg.port_retries', 'webagg.open_in_browser', 'default.backend_fallback', + 'default.toolbar', 'default.timezone', 'figure.max_open_warning', 'figure.raise_window', 'savefig.directory', 'tk.window_focus', 'docstring.hardcopy', 'date.epoch'} diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 65cd823f13a9..609e80bb4138 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -199,7 +199,7 @@ def test_axes_titlecolor_rcparams(): def test_Issue_1713(tmpdir): rcpath = Path(tmpdir) / 'test_rcparams.rc' - rcpath.write_text('timezone: UTC', encoding='utf-8') + rcpath.write_text('default.timezone: UTC', encoding='utf-8') with mock.patch('locale.getpreferredencoding', return_value='UTF-32-BE'): rc = mpl.rc_params_from_file(rcpath, True, False) assert rc.get('timezone') == 'UTC' diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index de17bb79bd4b..b96c5693f6a2 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -1029,7 +1029,7 @@ def test_CheckButtons(ax): @pytest.mark.parametrize("toolbar", ["none", "toolbar2", "toolmanager"]) def test_TextBox(ax, toolbar): # Avoid "toolmanager is provisional" warning. - plt.rcParams._set("toolbar", toolbar) + plt.rcParams._set("default.toolbar", toolbar) submit_event = mock.Mock(spec=noop, return_value=None) text_change_event = mock.Mock(spec=noop, return_value=None) From 517750871872d7404f8e296d39dc2bb3cb625287 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Tue, 4 Apr 2023 16:52:51 -0500 Subject: [PATCH 02/22] Make namespaces variable compact --- lib/matplotlib/__init__.py | 53 +++++--------------------------------- 1 file changed, 7 insertions(+), 46 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index a55deeb912de..22cc9a3cec8d 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -667,52 +667,13 @@ class RcParams(MutableMapping): :ref:`customizing-with-matplotlibrc-files` """ validate = rcsetup._validators - namespaces = ( - "backends", - "lines", - "patches", - "hatches", - "boxplot", - "font", - "text", - "latex", - "axes", - "date", - "xtick", - "ytick", - "grid", - "legend", - "figure", - "image", - "contour", - "errorbar", - "hist", - "scatter", - "agg", - "path", - "savefig", - "tk", - "ps", - "pdf", - "svg", - "pgf", - "docstring", - "keymap", - "animation", - "_internal", - "webagg", - "markers", - "pcolor", - "pcolormesh", - "patch", - "hatch", - "mathtext", - "polaraxes", - "axes3d", - "xaxis", - "yaxis", - "default" - ) + namespaces = ("backends", "lines", "patches", "hatches", "boxplot", "font", "text", + "latex", "axes", "date", "xtick", "ytick", "grid", "legend", + "figure", "image", "contour", "errorbar", "hist", "scatter", "agg", + "path", "savefig", "tk", "ps", "pdf", "svg", "pgf", "docstring", + "keymap", "animation", "_internal", "webagg", "markers", "pcolor", + "pcolormesh", "patch", "hatch", "mathtext", "polaraxes", "axes3d", + "xaxis", "yaxis", "default") single_key_set = {"backend", "toolbar", "interactive", "timezone", "backend_fallback"} From 45d4743b960c00bfc65b183ba30f2e70a0fcca71 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Thu, 13 Apr 2023 20:43:26 -0500 Subject: [PATCH 03/22] Raise KeyError when accessing namespace for now. --- lib/matplotlib/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 22cc9a3cec8d..e71e351bd4ce 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -740,7 +740,13 @@ def _get(self, key): if depth == 1: if key in self.single_key_set: return self._namespace_maps["default"].get(key) - return self._namespace_maps[key] + # Comment the following line and remove the raise statement + # to enable getting namespace parameters. + # return self._namespace_maps[key] + else: + raise KeyError( + f"{key} is not a valid rc parameter (see rcParams.keys() for " + f"a list of valid parameters)") elif depth == 2: return self._namespace_maps[keys[0]].get(keys[1]) @@ -770,7 +776,6 @@ def __setitem__(self, key, val): cval = self.validate[key](val) except ValueError as ve: raise ValueError(f"Key {key}: {ve}") from None - # breakpoint() self._set(key, cval) except KeyError as err: raise KeyError( From 133f2af557178644f8e83474872f69869ed2063b Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Thu, 13 Apr 2023 20:54:24 -0500 Subject: [PATCH 04/22] Make linter happy --- lib/matplotlib/pyplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index e815ddb1b347..a2b5190fff67 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -745,7 +745,7 @@ def xkcd( "xkcd mode is not compatible with text.usetex = True") stack = ExitStack() - stack.callback(rcParams.update, rcParams.copy()) # type: ignore + stack.callback(rcParams.update, rcParams.copy()) # type: ignore from matplotlib import patheffects rcParams.update({ From b7f4fb5cb8878fba9528808d4660c184d1529c7c Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Fri, 14 Apr 2023 11:46:47 -0500 Subject: [PATCH 05/22] Update repr, str and add find_all --- lib/matplotlib/__init__.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index e71e351bd4ce..c4f93d99f131 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -835,7 +835,16 @@ def __len__(self): return sum(len(mapping) for mapping in self._namespace_maps) def __repr__(self): - return repr(dict(self.items())) + class_name = self.__class__.__name__ + indent = len(class_name) + 1 + with _api.suppress_matplotlib_deprecation_warning(): + repr_split = pprint.pformat(dict(self.items()), indent=1, + width=80 - indent).split('\n') + repr_indented = ('\n' + ' ' * indent).join(repr_split) + return f'{class_name}({repr_indented})' + + def __str__(self): + return '\n'.join(map('{0[0]}: {0[1]}'.format, sorted(self.items()))) def keys(self): keys = ( @@ -897,7 +906,21 @@ def update(self, other=(), /, **kwds): def copy(self): return deepcopy(self) -# MutableMapping.register(RcParams) + def find_all(self, pattern): + """ + Return the subset of this RcParams dictionary whose keys match, + using :func:`re.search`, the given ``pattern``. + + .. note:: + + Changes to the returned dictionary are *not* propagated to + the parent RcParams dictionary. + + """ + pattern_re = re.compile(pattern) + return RcParams((key, value) + for key, value in self.items() + if pattern_re.search(key)) def rc_params(fail_on_error=False): From d135342b1c448bd7b82806c863662b4071f896c1 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Sun, 16 Apr 2023 22:08:07 -0500 Subject: [PATCH 06/22] Fix issue resolve testing backend --- lib/matplotlib/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index c4f93d99f131..14ee36f05d7a 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -766,7 +766,7 @@ def __setitem__(self, key, val): _api.warn_deprecated( version, name=key, obj_type="rcparam", alternative=alt_key) return - elif key == 'backend': + elif key == 'backend' or key == "default.backend": if val is rcsetup._auto_backend_sentinel: if 'backend' in self: return @@ -1391,7 +1391,7 @@ def get_backend(): -------- matplotlib.use """ - return rcParams['default.backend'] + return rcParams['backend'] def interactive(b): From 47b48ee2282f5e66ac1f5990a9c69fa679d239a3 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Mon, 17 Apr 2023 16:51:32 -0500 Subject: [PATCH 07/22] Update a few functions in RcParams --- lib/matplotlib/__init__.py | 62 +++++++++++++++++++++------- lib/matplotlib/mpl-data/matplotlibrc | 10 ++--- 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 14ee36f05d7a..4a4cbd6ca061 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -134,7 +134,7 @@ import atexit -from collections import namedtuple, ChainMap, defaultdict +from collections import namedtuple, ChainMap from collections.abc import MutableMapping, Mapping import contextlib import functools @@ -691,7 +691,7 @@ def _split_key(self, key, sep="."): keys = key.split(sep, maxsplit=1) return keys, len(keys) - def _set(self, key, value): + def _set(self, key, val): """ Directly write data bypassing deprecation and validation logic. @@ -712,10 +712,22 @@ def _set(self, key, value): keys, depth = self._split_key(key) if depth == 1: if key in self.single_key_set: - self._namespace_maps["default"][key] = value - self._namespace_maps[key] = value + self._namespace_maps["default"][key] = val + # Uncomment the following line and remove the raise statement + # to enable setting namespaces. + # else: + # if isinstance(val, dict): + # self._namespace_maps[key] = ChainMap({}, val) + # else: + # raise ValueError( + # f"{key} should be set using a dictionary but found " + # f"{type(val)}") + else: + raise KeyError( + f"{key} is not a valid rc parameter (see rcParams.keys() for " + f"a list of valid parameters)") elif depth == 2: - self._namespace_maps[keys[0]][keys[1]] = value + self._namespace_maps[keys[0]][keys[1]] = val def _get(self, key): """ @@ -740,7 +752,7 @@ def _get(self, key): if depth == 1: if key in self.single_key_set: return self._namespace_maps["default"].get(key) - # Comment the following line and remove the raise statement + # Uncomment the following line and remove the raise statement # to enable getting namespace parameters. # return self._namespace_maps[key] else: @@ -807,22 +819,31 @@ def __getitem__(self, key): def _get_backend_or_none(self): """Get the requested backend, if any, without triggering resolution.""" - backend = self._get("default.backend") + backend = self._get("backend") return None if backend is rcsetup._auto_backend_sentinel else backend def __delitem__(self, key): keys, depth = self._split_key(key) - if depth == 1: - del self._namespace_maps[key] - elif depth == 2: - del self._namespace_maps[keys[0]][keys[1]] + try: + if depth == 1: + if key in self.single_key_set: + del self._namespace_maps["default"][key] + else: + raise KeyError + elif depth == 2: + del self._namespace_maps[keys[0]][keys[1]] + except KeyError as err: + raise KeyError( + f"{key} is not a valid rc parameter (see rcParams.keys() for " + f"a list of valid parameters)") from err def __contains__(self, key): keys, depth = self._split_key(key) if depth == 1: if key in self.single_key_set: return key in self._namespace_maps["default"] - return key in self._namespace_maps + else: + return False elif depth == 2: return any(key in mapping for mapping in self._namespace_maps) @@ -865,15 +886,22 @@ def items(self): def pop(self, key): keys, depth = self._split_key(key) if depth == 1: - self._mapping.pop() + if key in self.single_key_set: + return self._namespace_mapping["default"][key] + else: + raise KeyError( + f"{key} is not a valid rc parameter (see rcParams.keys() for " + f"a list of valid parameters)") elif depth == 2: - self._namespace_mapping[keys[0]].pop(keys[1]) + return self._namespace_mapping[keys[0]].pop(keys[1]) def popitem(self): - return self._mapping.popitem() + raise NotImplementedError( + "popitem is not implemented for RcParams.") def clear(self): - self._mapping.clear() + for namespace in self._namespace_maps: + self._namespace_maps[namespace].clear() def setdefault(self, key, default=None): self[key] = default @@ -1005,6 +1033,8 @@ def _rc_params_in_file(fname, transform=lambda x: x, fail_on_error=False): config = RcParams() for key, (val, line, line_no) in rc_temp.items(): + if key in config.single_key_set: + key = f"default.{key}" if key in rcsetup._validators: if fail_on_error: config[key] = val # try to convert to proper type or raise diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index 25e6aaa00d4d..9bd8a622092e 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -82,7 +82,7 @@ ## PS PDF SVG Template ## You can also deploy your own backend outside of Matplotlib by referring to ## the module name (which must be in the PYTHONPATH) as 'module://my_backend'. -##default.backend: Agg +##backend: Agg ## The port to use for the web server in the WebAgg backend. #webagg.port: 8988 @@ -100,12 +100,12 @@ ## If you are running pyplot inside a GUI and your backend choice ## conflicts, we will automatically try to find a compatible one for ## you if backend_fallback is True -#default.backend_fallback: True +#backend_fallback: True -#default.interactive: False +#interactive: False #figure.hooks: # list of dotted.module.name:dotted.callable.name -#default.toolbar: toolbar2 # {None, toolbar2, toolmanager} -#default.timezone: UTC # a pytz timezone string, e.g., US/Central or Europe/Paris +#toolbar: toolbar2 # {None, toolbar2, toolmanager} +#timezone: UTC # a pytz timezone string, e.g., US/Central or Europe/Paris ## *************************************************************************** From 9a0854487040b8827a353c1f0aa00ea72898078a Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Mon, 17 Apr 2023 17:26:17 -0500 Subject: [PATCH 08/22] Update type stubs --- lib/matplotlib/__init__.pyi | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/__init__.pyi b/lib/matplotlib/__init__.pyi index 8ef23a3dc4c2..1bbef561b9e4 100644 --- a/lib/matplotlib/__init__.pyi +++ b/lib/matplotlib/__init__.pyi @@ -65,13 +65,21 @@ def get_cachedir() -> str: ... def get_data_path() -> str: ... def matplotlib_fname() -> str: ... -class RcParams(dict[str, Any]): +class RcParams(MutableMapping): validate: dict[str, Callable] + namespaces: tuple + single_key_set: set def __init__(self, *args, **kwargs) -> None: ... + def _split_key(self, key: str, sep: str = ".") -> tuple[list, int]: ... + def _set(self, key: str, val: Any) -> None: ... + def _get(self, key: str) -> Any: ... def __setitem__(self, key: str, val: Any) -> None: ... def __getitem__(self, key: str) -> Any: ... + def __delitem__(self, key: str) -> None: ... def __iter__(self) -> Generator[str, None, None]: ... def __len__(self) -> int: ... + def keys(self) -> Generator[str, None, None]: ... + def values(self) -> Generator[Any, None, None]: ... def find_all(self, pattern: str) -> RcParams: ... def copy(self) -> RcParams: ... From 9a0ad032f586539d1c4239c46deb5ea1a8756964 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Mon, 24 Apr 2023 21:41:45 -0500 Subject: [PATCH 09/22] Cache splitting keys. --- lib/matplotlib/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 4a4cbd6ca061..18fa9906b4f6 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -685,9 +685,10 @@ def __init__(self, *args, **kwargs): name: mapping.new_child() for name, mapping in self._namespace_maps.items() } - self._mapping = ChainMap(*self._namespace_maps.values()) - def _split_key(self, key, sep="."): + @staticmethod + @functools.lru_cache + def _split_key(key, sep="."): keys = key.split(sep, maxsplit=1) return keys, len(keys) From 7a11e81abe38615f220eaba74a05f2bcc297a996 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Tue, 25 Apr 2023 21:11:29 -0500 Subject: [PATCH 10/22] Return views for .keys(), .values(), .items() --- lib/matplotlib/__init__.py | 25 +++++++------------------ lib/matplotlib/__init__.pyi | 4 +--- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 18fa9906b4f6..2dbacc9d989e 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -135,7 +135,7 @@ import atexit from collections import namedtuple, ChainMap -from collections.abc import MutableMapping, Mapping +from collections.abc import MutableMapping, Mapping, KeysView, ValuesView, ItemsView import contextlib import functools import importlib @@ -850,8 +850,13 @@ def __contains__(self, key): def __iter__(self): """Yield from sorted list of keys""" + keys = ( + ".".join((space, key)) + for space, mapping in self._namespace_maps.items() + for key in mapping.keys() + ) with _api.suppress_matplotlib_deprecation_warning(): - yield from sorted(self.keys()) + yield from sorted(keys) def __len__(self): return sum(len(mapping) for mapping in self._namespace_maps) @@ -868,22 +873,6 @@ def __repr__(self): def __str__(self): return '\n'.join(map('{0[0]}: {0[1]}'.format, sorted(self.items()))) - def keys(self): - keys = ( - ".".join((space, key)) - for space, mapping in self._namespace_maps.items() - for key in mapping.keys() - ) - return keys - - def values(self): - for key in self.keys(): - yield self[key] - - def items(self): - for key, value in zip(self.keys(), self.values()): - yield key, value - def pop(self, key): keys, depth = self._split_key(key) if depth == 1: diff --git a/lib/matplotlib/__init__.pyi b/lib/matplotlib/__init__.pyi index 1bbef561b9e4..fc87f31afc7f 100644 --- a/lib/matplotlib/__init__.pyi +++ b/lib/matplotlib/__init__.pyi @@ -70,7 +70,7 @@ class RcParams(MutableMapping): namespaces: tuple single_key_set: set def __init__(self, *args, **kwargs) -> None: ... - def _split_key(self, key: str, sep: str = ".") -> tuple[list, int]: ... + def _split_key(key: str, sep: str = ...) -> tuple[list, int]: ... def _set(self, key: str, val: Any) -> None: ... def _get(self, key: str) -> Any: ... def __setitem__(self, key: str, val: Any) -> None: ... @@ -78,8 +78,6 @@ class RcParams(MutableMapping): def __delitem__(self, key: str) -> None: ... def __iter__(self) -> Generator[str, None, None]: ... def __len__(self) -> int: ... - def keys(self) -> Generator[str, None, None]: ... - def values(self) -> Generator[Any, None, None]: ... def find_all(self, pattern: str) -> RcParams: ... def copy(self) -> RcParams: ... From 4f9d8b6f5f782d5f152bf6769b6fadcd5bac531c Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Tue, 25 Apr 2023 21:13:20 -0500 Subject: [PATCH 11/22] Add type spec for MutableMapping --- lib/matplotlib/__init__.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/__init__.pyi b/lib/matplotlib/__init__.pyi index fc87f31afc7f..b5966b3be344 100644 --- a/lib/matplotlib/__init__.pyi +++ b/lib/matplotlib/__init__.pyi @@ -65,7 +65,7 @@ def get_cachedir() -> str: ... def get_data_path() -> str: ... def matplotlib_fname() -> str: ... -class RcParams(MutableMapping): +class RcParams(MutableMapping[str, Any]): validate: dict[str, Callable] namespaces: tuple single_key_set: set From 54a1a113616ebe3b9d2b1a80bb7750270ef3c34d Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Wed, 26 Apr 2023 15:10:57 -0500 Subject: [PATCH 12/22] Remove `default.` usage outside the RcParams class This is to make the interface such that the code other than RcParams doesn't have to deal with the `deafult.*` namespace anywhere. This also changes the keys returned by `.keys()` to not have the `default.*` in the key name. --- lib/matplotlib/__init__.py | 35 ++++++++++++--------------- lib/matplotlib/pyplot.py | 12 ++++----- lib/matplotlib/rcsetup.py | 10 ++++---- lib/matplotlib/style/core.py | 6 ++--- lib/matplotlib/tests/test_rcparams.py | 2 +- lib/matplotlib/tests/test_widgets.py | 2 +- 6 files changed, 32 insertions(+), 35 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 2dbacc9d989e..3be422ab3a3c 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -781,12 +781,12 @@ def __setitem__(self, key, val): return elif key == 'backend' or key == "default.backend": if val is rcsetup._auto_backend_sentinel: - if 'backend' in self: + if 'backend' in self or 'default.backend' in self: return - if key in self.single_key_set: - key = f"default.{key}" try: cval = self.validate[key](val) + if key in self.single_key_set: + key = f"default.{key}" except ValueError as ve: raise ValueError(f"Key {key}: {ve}") from None self._set(key, cval) @@ -851,7 +851,7 @@ def __contains__(self, key): def __iter__(self): """Yield from sorted list of keys""" keys = ( - ".".join((space, key)) + ".".join((space, key)) if space != 'default' else key for space, mapping in self._namespace_maps.items() for key in mapping.keys() ) @@ -1023,8 +1023,6 @@ def _rc_params_in_file(fname, transform=lambda x: x, fail_on_error=False): config = RcParams() for key, (val, line, line_no) in rc_temp.items(): - if key in config.single_key_set: - key = f"default.{key}" if key in rcsetup._validators: if fail_on_error: config[key] = val # try to convert to proper type or raise @@ -1101,23 +1099,22 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): # in that case. However, packagers can set a different default backend # (resulting in a normal `#backend: foo` line) in which case we should *not* # fill in _auto_backend_sentinel. -rcParamsDefault.setdefault("default.backend", rcsetup._auto_backend_sentinel) -params_dict = RcParams() -params_dict.update(rcParamsDefault.items()) -params_dict.update(_rc_params_in_file(matplotlib_fname())) -rcParamsOrig = params_dict.copy() +rcParamsDefault.setdefault("backend", rcsetup._auto_backend_sentinel) +rcParams = RcParams() +rcParams.update(rcParamsDefault.items()) +rcParams.update(_rc_params_in_file(matplotlib_fname())) +rcParamsOrig = rcParams.copy() with _api.suppress_matplotlib_deprecation_warning(): # This also checks that all rcParams are indeed listed in the template. # Assigning to rcsetup.defaultParams is left only for backcompat. defaultParams = rcsetup.defaultParams = { # We want to resolve deprecated rcParams, but not backend... - key: [(rcsetup._auto_backend_sentinel if key == "default.backend" else + key: [(rcsetup._auto_backend_sentinel if key == "backend" else rcParamsDefault[key]), validator] for key, validator in rcsetup._validators.items()} -if params_dict['axes.formatter.use_locale']: +if rcParams['axes.formatter.use_locale']: locale.setlocale(locale.LC_ALL, '') -rcParams = RcParams(params_dict) def rc(group, **kwargs): @@ -1393,14 +1390,14 @@ def use(backend, *, force=True): # value which will be respected when the user finally imports # pyplot else: - rcParams['default.backend'] = backend + rcParams['backend'] = backend # if the user has asked for a given backend, do not helpfully # fallback - rcParams['default.backend_fallback'] = False + rcParams['backend_fallback'] = False if os.environ.get('MPLBACKEND'): - rcParams['default.backend'] = os.environ.get('MPLBACKEND') + rcParams['backend'] = os.environ.get('MPLBACKEND') def get_backend(): @@ -1418,7 +1415,7 @@ def interactive(b): """ Set whether to redraw after every plotting command (e.g. `.pyplot.xlabel`). """ - rcParams['default.interactive'] = b + rcParams['interactive'] = b def is_interactive(): @@ -1430,7 +1427,7 @@ def is_interactive(): This function is only intended for use in backends. End users should use `.pyplot.isinteractive` instead. """ - return rcParams['default.interactive'] + return rcParams['interactive'] def _val_or_rc(val, rc_name): diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index a2b5190fff67..945f12cd7137 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -327,16 +327,16 @@ def switch_backend(newbackend: str) -> None: except ImportError: continue else: - rcParamsOrig['default.backend'] = candidate + rcParamsOrig['backend'] = candidate return else: # Switching to Agg should always succeed; if it doesn't, let the # exception propagate out. switch_backend("agg") - rcParamsOrig["default.backend"] = "agg" + rcParamsOrig["backend"] = "agg" return # have to escape the switch on access logic - old_backend = rcParams['default.backend'] + old_backend = rcParams._get('backend') module = importlib.import_module(cbook._backend_module_name(newbackend)) canvas_class = module.FigureCanvas @@ -413,7 +413,7 @@ def draw_if_interactive() -> None: _log.debug("Loaded backend %s version %s.", newbackend, backend_mod.backend_version) - rcParams['default.backend'] = rcParamsDefault['default.backend'] = newbackend + rcParams['backend'] = rcParamsDefault['backend'] = newbackend _backend_mod = backend_mod for func_name in ["new_figure_manager", "draw_if_interactive", "show"]: globals()[func_name].__signature__ = inspect.signature( @@ -2472,11 +2472,11 @@ def polar(*args, **kwargs) -> list[Line2D]: # If rcParams['backend_fallback'] is true, and an interactive backend is # requested, ignore rcParams['backend'] and force selection of a backend that # is compatible with the current running interactive framework. -if (rcParams["default.backend_fallback"] +if (rcParams["backend_fallback"] and rcParams._get_backend_or_none() in ( # type: ignore set(rcsetup.interactive_bk) - {'WebAgg', 'nbAgg'}) and cbook._get_running_interactive_framework()): # type: ignore - rcParams._set("default.backend", rcsetup._auto_backend_sentinel) # type: ignore + rcParams._set("backend", rcsetup._auto_backend_sentinel) # type: ignore # fmt: on diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index bee0204a46b9..38d4606024d3 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -892,12 +892,12 @@ def _convert_validator_spec(key, conv): # The rcParams defaults are defined in lib/matplotlib/mpl-data/matplotlibrc, which # gets copied to matplotlib/mpl-data/matplotlibrc by the setup script. _validators = { - "default.backend": validate_backend, - "default.backend_fallback": validate_bool, + "backend": validate_backend, + "backend_fallback": validate_bool, "figure.hooks": validate_stringlist, - "default.toolbar": _validate_toolbar, - "default.interactive": validate_bool, - "default.timezone": validate_string, + "toolbar": _validate_toolbar, + "interactive": validate_bool, + "timezone": validate_string, "webagg.port": validate_int, "webagg.address": validate_string, diff --git a/lib/matplotlib/style/core.py b/lib/matplotlib/style/core.py index 57ea5fa5df1d..7e9008c56165 100644 --- a/lib/matplotlib/style/core.py +++ b/lib/matplotlib/style/core.py @@ -39,9 +39,9 @@ STYLE_EXTENSION = 'mplstyle' # A list of rcParams that should not be applied from styles STYLE_BLACKLIST = { - 'default.interactive', 'default.backend', 'webagg.port', 'webagg.address', - 'webagg.port_retries', 'webagg.open_in_browser', 'default.backend_fallback', - 'default.toolbar', 'default.timezone', 'figure.max_open_warning', + 'interactive', 'backend', 'webagg.port', 'webagg.address', + 'webagg.port_retries', 'webagg.open_in_browser', 'backend_fallback', + 'toolbar', 'timezone', 'figure.max_open_warning', 'figure.raise_window', 'savefig.directory', 'tk.window_focus', 'docstring.hardcopy', 'date.epoch'} diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 609e80bb4138..65cd823f13a9 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -199,7 +199,7 @@ def test_axes_titlecolor_rcparams(): def test_Issue_1713(tmpdir): rcpath = Path(tmpdir) / 'test_rcparams.rc' - rcpath.write_text('default.timezone: UTC', encoding='utf-8') + rcpath.write_text('timezone: UTC', encoding='utf-8') with mock.patch('locale.getpreferredencoding', return_value='UTF-32-BE'): rc = mpl.rc_params_from_file(rcpath, True, False) assert rc.get('timezone') == 'UTC' diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index b96c5693f6a2..de17bb79bd4b 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -1029,7 +1029,7 @@ def test_CheckButtons(ax): @pytest.mark.parametrize("toolbar", ["none", "toolbar2", "toolmanager"]) def test_TextBox(ax, toolbar): # Avoid "toolmanager is provisional" warning. - plt.rcParams._set("default.toolbar", toolbar) + plt.rcParams._set("toolbar", toolbar) submit_event = mock.Mock(spec=noop, return_value=None) text_change_event = mock.Mock(spec=noop, return_value=None) From c101fec43ab1da11c0ff4c2992dbe5463d0b318f Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Wed, 26 Apr 2023 15:46:13 -0500 Subject: [PATCH 13/22] Update _split_key stub to staticmethod --- lib/matplotlib/__init__.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/__init__.pyi b/lib/matplotlib/__init__.pyi index b5966b3be344..8691ef420166 100644 --- a/lib/matplotlib/__init__.pyi +++ b/lib/matplotlib/__init__.pyi @@ -70,6 +70,7 @@ class RcParams(MutableMapping[str, Any]): namespaces: tuple single_key_set: set def __init__(self, *args, **kwargs) -> None: ... + @staticmethod def _split_key(key: str, sep: str = ...) -> tuple[list, int]: ... def _set(self, key: str, val: Any) -> None: ... def _get(self, key: str) -> Any: ... From 67735b09bc22949b6b831db23da461d1d443e31f Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Wed, 10 May 2023 21:57:43 -0500 Subject: [PATCH 14/22] Add way to access default params --- lib/matplotlib/__init__.py | 52 ++++++++++++++++++++++----- lib/matplotlib/tests/test_rcparams.py | 5 +++ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 3be422ab3a3c..d67d34f968f0 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -681,10 +681,7 @@ class RcParams(MutableMapping): def __init__(self, *args, **kwargs): self._namespace_maps = {name: ChainMap({}) for name in self.namespaces} self.update(*args, **kwargs) - self._namespace_maps = { - name: mapping.new_child() - for name, mapping in self._namespace_maps.items() - } + self._new_child() @staticmethod @functools.lru_cache @@ -692,6 +689,14 @@ def _split_key(key, sep="."): keys = key.split(sep, maxsplit=1) return keys, len(keys) + def _new_child(self): + for space in self._namespace_maps.keys(): + self._namespace_maps[space] = self._namespace_maps[space].new_child() + + def _parents(self): + for space in self._namespace_maps.keys(): + self._namespace_maps[space] = self._namespace_maps[space].parents + def _set(self, key, val): """ Directly write data bypassing deprecation and validation logic. @@ -818,6 +823,36 @@ def __getitem__(self, key): return self._get(key) + def _get_default(self, key): + keys, depth = self._split_key(key) + if depth == 1: + if key in self.single_key_set: + return self._namespace_maps["default"].get(key) + # Uncomment the following line and remove the raise statement + # to enable getting namespace parameters. + # return self._namespace_maps[key] + else: + raise KeyError( + f"{key} is not a valid rc parameter (see rcParams.keys() for " + f"a list of valid parameters)") + elif depth == 2: + return self._namespace_maps[keys[0]].maps[-1].get(keys[1]) + + def getdefault(self, key): + if key in _deprecated_map: + version, alt_key, alt_val, inverse_alt = _deprecated_map[key] + _api.warn_deprecated( + version, name=key, obj_type="rcparam", alternative=alt_key) + return inverse_alt(self._get(alt_key)) + + elif key in _deprecated_ignore_map: + version, alt_key = _deprecated_ignore_map[key] + _api.warn_deprecated( + version, name=key, obj_type="rcparam", alternative=alt_key) + return self._get_default(alt_key) if alt_key else None + + return self._get_default(key) + def _get_backend_or_none(self): """Get the requested backend, if any, without triggering resolution.""" backend = self._get("backend") @@ -1021,6 +1056,7 @@ def _rc_params_in_file(fname, transform=lambda x: x, fail_on_error=False): raise config = RcParams() + config._parents() for key, (val, line, line_no) in rc_temp.items(): if key in rcsetup._validators: @@ -1049,6 +1085,7 @@ def _rc_params_in_file(fname, transform=lambda x: x, fail_on_error=False): or from the matplotlib source distribution""", dict(key=key, fname=fname, line_no=line_no, line=line.rstrip('\n'), version=version)) + config._new_child() return config @@ -1101,6 +1138,7 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): # fill in _auto_backend_sentinel. rcParamsDefault.setdefault("backend", rcsetup._auto_backend_sentinel) rcParams = RcParams() +rcParams._parents() rcParams.update(rcParamsDefault.items()) rcParams.update(_rc_params_in_file(matplotlib_fname())) rcParamsOrig = rcParams.copy() @@ -1115,6 +1153,7 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): for key, validator in rcsetup._validators.items()} if rcParams['axes.formatter.use_locale']: locale.setlocale(locale.LC_ALL, '') +rcParams._new_child() def rc(group, **kwargs): @@ -1318,10 +1357,7 @@ def rc_context(rc=None, fname=None): finally: # Revert to the original rcs. backend = rcParams["backend"] - for space in rcParams._namespace_maps.keys(): - rcParams._namespace_maps[space] = rcParams._namespace_maps[ - space - ].parents + rcParams._parents() rcParams["backend"] = backend diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 65cd823f13a9..a120e08413f2 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -652,3 +652,8 @@ def test_rcparams_path_sketch_from_file(tmpdir, value): rc_path.write(f"path.sketch: {value}") with mpl.rc_context(fname=rc_path): assert mpl.rcParams["path.sketch"] == (1, 2, 3) + + +def test_rcparams_getdefault(): + with mpl.rc_context({"image.lut": 128}): + assert mpl.rcParams.getdefault("image.lut") == 256 From 7e4022ada95a1e68c6451bf4d666574f7e9c0a66 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Wed, 10 May 2023 23:53:51 -0500 Subject: [PATCH 15/22] Remove `rcParamsDefault` --- lib/matplotlib/__init__.py | 28 ++++++++++++++++---------- lib/matplotlib/pyplot.py | 5 +++-- lib/matplotlib/style/core.py | 4 ++-- lib/matplotlib/tests/test_offsetbox.py | 5 +++++ lib/matplotlib/tests/test_rcparams.py | 7 +++++++ 5 files changed, 34 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index d67d34f968f0..69344a420376 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -853,6 +853,12 @@ def getdefault(self, key): return self._get_default(key) + def getdefaults(self): + """Return default values set during initialization.""" + defaults = self.copy() + defaults.clear() + return defaults + def _get_backend_or_none(self): """Get the requested backend, if any, without triggering resolution.""" backend = self._get("backend") @@ -1110,7 +1116,7 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): return config_from_file with _api.suppress_matplotlib_deprecation_warning(): - config = RcParams({**rcParamsDefault, **config_from_file}) + config = RcParams({**rcParams.getdefaults(), **config_from_file}) if "".join(config['text.latex.preamble']): _log.info(""" @@ -1125,21 +1131,22 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): return config -rcParamsDefault = _rc_params_in_file( +rcParams = _rc_params_in_file( cbook._get_data_path("matplotlibrc"), # Strip leading comment. transform=lambda line: line[1:] if line.startswith("#") else line, fail_on_error=True) -rcParamsDefault.update(rcsetup._hardcoded_defaults) +for key in rcsetup._hardcoded_defaults: + space, subkey = key.split(".") + if not rcParams._namespace_maps[space]: + rcParams._namespace_maps[space] = ChainMap({}) +rcParams.update(rcsetup._hardcoded_defaults) # Normally, the default matplotlibrc file contains *no* entry for backend (the # corresponding line starts with ##, not #; we fill on _auto_backend_sentinel # in that case. However, packagers can set a different default backend # (resulting in a normal `#backend: foo` line) in which case we should *not* # fill in _auto_backend_sentinel. -rcParamsDefault.setdefault("backend", rcsetup._auto_backend_sentinel) -rcParams = RcParams() -rcParams._parents() -rcParams.update(rcParamsDefault.items()) +rcParams.setdefault("backend", rcsetup._auto_backend_sentinel) rcParams.update(_rc_params_in_file(matplotlib_fname())) rcParamsOrig = rcParams.copy() with _api.suppress_matplotlib_deprecation_warning(): @@ -1148,12 +1155,11 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): defaultParams = rcsetup.defaultParams = { # We want to resolve deprecated rcParams, but not backend... key: [(rcsetup._auto_backend_sentinel if key == "backend" else - rcParamsDefault[key]), + rcParams.getdefault(key)), validator] for key, validator in rcsetup._validators.items()} if rcParams['axes.formatter.use_locale']: locale.setlocale(locale.LC_ALL, '') -rcParams._new_child() def rc(group, **kwargs): @@ -1253,8 +1259,8 @@ def rcdefaults(): with _api.suppress_matplotlib_deprecation_warning(): from .style.core import STYLE_BLACKLIST rcParams.clear() - rcParams.update({k: v for k, v in rcParamsDefault.items() - if k not in STYLE_BLACKLIST}) + # rcParams.update({k: v for k, v in rcParams.items() + # if k not in STYLE_BLACKLIST}) def rc_file_defaults(): diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 945f12cd7137..8c7b6a873882 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -65,7 +65,8 @@ FigureCanvasBase, FigureManagerBase, MouseButton) from matplotlib.figure import Figure, FigureBase, figaspect from matplotlib.gridspec import GridSpec, SubplotSpec -from matplotlib import rcsetup, rcParamsDefault, rcParamsOrig +from matplotlib import rcParams, get_backend, rcParamsOrig +from matplotlib.rcsetup import interactive_bk as _interactive_bk from matplotlib.artist import Artist from matplotlib.axes import Axes, Subplot # type: ignore from matplotlib.projections import PolarAxes # type: ignore @@ -413,7 +414,7 @@ def draw_if_interactive() -> None: _log.debug("Loaded backend %s version %s.", newbackend, backend_mod.backend_version) - rcParams['backend'] = rcParamsDefault['backend'] = newbackend + rcParams['backend'] = newbackend _backend_mod = backend_mod for func_name in ["new_figure_manager", "draw_if_interactive", "show"]: globals()[func_name].__signature__ = inspect.signature( diff --git a/lib/matplotlib/style/core.py b/lib/matplotlib/style/core.py index 7e9008c56165..392360920f92 100644 --- a/lib/matplotlib/style/core.py +++ b/lib/matplotlib/style/core.py @@ -26,7 +26,7 @@ import importlib_resources import matplotlib as mpl -from matplotlib import _api, _docstring, _rc_params_in_file, rcParamsDefault +from matplotlib import _api, _docstring, _rc_params_in_file _log = logging.getLogger(__name__) @@ -114,7 +114,7 @@ def use(style): # rcParamsDefault, no need to reemit them here. with _api.suppress_matplotlib_deprecation_warning(): # don't trigger RcParams.__getitem__('backend') - style = {k: rcParamsDefault[k] for k in rcParamsDefault + style = {k: mpl.rcParams.getdefault(k) for k in mpl.rcParams if k not in STYLE_BLACKLIST} elif style in library: style = library[style] diff --git a/lib/matplotlib/tests/test_offsetbox.py b/lib/matplotlib/tests/test_offsetbox.py index 49b55e4c9326..edf17542fb17 100644 --- a/lib/matplotlib/tests/test_offsetbox.py +++ b/lib/matplotlib/tests/test_offsetbox.py @@ -257,9 +257,14 @@ def test_anchoredtext_horizontal_alignment(): ax.add_artist(text2) +<<<<<<< HEAD @pytest.mark.parametrize("extent_kind", ["window_extent", "tightbbox"]) def test_annotationbbox_extents(extent_kind): plt.rcParams.update(plt.rcParamsDefault) +======= +def test_annotationbbox_extents(): + plt.rcParams.clear() +>>>>>>> 57d742c95e (Remove `rcParamsDefault`) fig, ax = plt.subplots(figsize=(4, 3), dpi=100) ax.axis([0, 1, 0, 1]) diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index a120e08413f2..50df368ec227 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -657,3 +657,10 @@ def test_rcparams_path_sketch_from_file(tmpdir, value): def test_rcparams_getdefault(): with mpl.rc_context({"image.lut": 128}): assert mpl.rcParams.getdefault("image.lut") == 256 + + +def test_rcparams_getdefaults(): + mpl.rc("image", lut=128) + defaults = mpl.rcParams.getdefaults() + mpl.rcParams.clear() + assert defaults == mpl.rcParams From c23cb2914beba5fb945c22b05b0b80daaaaab90c Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Thu, 11 May 2023 15:18:24 -0500 Subject: [PATCH 16/22] Update `rcdefaults` to use `style.core.use()` --- lib/matplotlib/__init__.py | 11 ++++------- lib/matplotlib/style/core.py | 1 + lib/matplotlib/tests/test_rcparams.py | 14 ++++++++++++++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 69344a420376..8db49cd9fe4f 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1254,13 +1254,10 @@ def rcdefaults(): Use a specific style file. Call ``style.use('default')`` to restore the default style. """ - # Deprecation warnings were already handled when creating rcParamsDefault, - # no need to reemit them here. - with _api.suppress_matplotlib_deprecation_warning(): - from .style.core import STYLE_BLACKLIST - rcParams.clear() - # rcParams.update({k: v for k, v in rcParams.items() - # if k not in STYLE_BLACKLIST}) + # # Deprecation warnings were already handled when creating rcParamsDefault, + # # no need to reemit them here. + from .style import core + core.use('default') def rc_file_defaults(): diff --git a/lib/matplotlib/style/core.py b/lib/matplotlib/style/core.py index 392360920f92..6b2e6467d4a3 100644 --- a/lib/matplotlib/style/core.py +++ b/lib/matplotlib/style/core.py @@ -149,6 +149,7 @@ def use(style): else: filtered[k] = style[k] mpl.rcParams.update(filtered) + mpl.rcParams._new_child() @contextlib.contextmanager diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 50df368ec227..f1a53bfaef9e 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -664,3 +664,17 @@ def test_rcparams_getdefaults(): defaults = mpl.rcParams.getdefaults() mpl.rcParams.clear() assert defaults == mpl.rcParams + + +def test_rcdefaults(): + # webagg.port is a style blacklisted key that shouldn't be + # updated when resetting rcParams to default values. + mpl.rcParams["webagg.port"] = 9000 + # lines.linewidth is not a style blacklisted key and should be + # reset to the default value. + # breakpoint() + lw = mpl.rcParams.getdefault("lines.linewidth") + mpl.rcParams["lines.linewidth"] = lw + 1 + mpl.rcdefaults() + assert mpl.rcParams["webagg.port"] == 9000 + assert mpl.rcParams["lines.linewidth"] == lw From 0374f464a1997a46c73057d801785262b87d7ecd Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Thu, 11 May 2023 21:52:05 -0500 Subject: [PATCH 17/22] Make namespaces private and correct setdefault --- lib/matplotlib/__init__.py | 40 +++++++++++++++++++++--------------- lib/matplotlib/style/core.py | 1 - 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 8db49cd9fe4f..43716ffa00b0 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -667,19 +667,19 @@ class RcParams(MutableMapping): :ref:`customizing-with-matplotlibrc-files` """ validate = rcsetup._validators - namespaces = ("backends", "lines", "patches", "hatches", "boxplot", "font", "text", - "latex", "axes", "date", "xtick", "ytick", "grid", "legend", - "figure", "image", "contour", "errorbar", "hist", "scatter", "agg", - "path", "savefig", "tk", "ps", "pdf", "svg", "pgf", "docstring", - "keymap", "animation", "_internal", "webagg", "markers", "pcolor", - "pcolormesh", "patch", "hatch", "mathtext", "polaraxes", "axes3d", - "xaxis", "yaxis", "default") + _namespaces = ("backends", "lines", "patches", "hatches", "boxplot", "font", "text", + "latex", "axes", "date", "xtick", "ytick", "grid", "legend", + "figure", "image", "contour", "errorbar", "hist", "scatter", "agg", + "path", "savefig", "tk", "ps", "pdf", "svg", "pgf", "docstring", + "keymap", "animation", "_internal", "webagg", "markers", "pcolor", + "pcolormesh", "patch", "hatch", "mathtext", "polaraxes", "axes3d", + "xaxis", "yaxis", "default") - single_key_set = {"backend", "toolbar", "interactive", - "timezone", "backend_fallback"} + _single_key_set = {"backend", "toolbar", "interactive", + "timezone", "backend_fallback"} def __init__(self, *args, **kwargs): - self._namespace_maps = {name: ChainMap({}) for name in self.namespaces} + self._namespace_maps = {name: ChainMap({}) for name in self._namespaces} self.update(*args, **kwargs) self._new_child() @@ -717,7 +717,7 @@ def _set(self, key, val): """ keys, depth = self._split_key(key) if depth == 1: - if key in self.single_key_set: + if key in self._single_key_set: self._namespace_maps["default"][key] = val # Uncomment the following line and remove the raise statement # to enable setting namespaces. @@ -756,7 +756,7 @@ def _get(self, key): """ keys, depth = self._split_key(key) if depth == 1: - if key in self.single_key_set: + if key in self._single_key_set: return self._namespace_maps["default"].get(key) # Uncomment the following line and remove the raise statement # to enable getting namespace parameters. @@ -790,7 +790,7 @@ def __setitem__(self, key, val): return try: cval = self.validate[key](val) - if key in self.single_key_set: + if key in self._single_key_set: key = f"default.{key}" except ValueError as ve: raise ValueError(f"Key {key}: {ve}") from None @@ -826,7 +826,7 @@ def __getitem__(self, key): def _get_default(self, key): keys, depth = self._split_key(key) if depth == 1: - if key in self.single_key_set: + if key in self._single_key_set: return self._namespace_maps["default"].get(key) # Uncomment the following line and remove the raise statement # to enable getting namespace parameters. @@ -868,7 +868,7 @@ def __delitem__(self, key): keys, depth = self._split_key(key) try: if depth == 1: - if key in self.single_key_set: + if key in self._single_key_set: del self._namespace_maps["default"][key] else: raise KeyError @@ -882,7 +882,7 @@ def __delitem__(self, key): def __contains__(self, key): keys, depth = self._split_key(key) if depth == 1: - if key in self.single_key_set: + if key in self._single_key_set: return key in self._namespace_maps["default"] else: return False @@ -917,7 +917,7 @@ def __str__(self): def pop(self, key): keys, depth = self._split_key(key) if depth == 1: - if key in self.single_key_set: + if key in self._single_key_set: return self._namespace_mapping["default"][key] else: raise KeyError( @@ -935,6 +935,12 @@ def clear(self): self._namespace_maps[namespace].clear() def setdefault(self, key, default=None): + """Insert key with a value of default if key is not in the dictionary. + + Return the value for key if key is in the dictionary, else default. + """ + if key in self: + return self[key] self[key] = default return default diff --git a/lib/matplotlib/style/core.py b/lib/matplotlib/style/core.py index 6b2e6467d4a3..392360920f92 100644 --- a/lib/matplotlib/style/core.py +++ b/lib/matplotlib/style/core.py @@ -149,7 +149,6 @@ def use(style): else: filtered[k] = style[k] mpl.rcParams.update(filtered) - mpl.rcParams._new_child() @contextlib.contextmanager From dd99c1d5687fae6f6d5adb2d71956135bea29e96 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Sun, 28 May 2023 14:03:35 -0700 Subject: [PATCH 18/22] Remove namespaces and keep chainmap for settings. As per @timhoffm's suggestion, decreasing scope of this PR to first just remove the dict inheritance and add a ChainMap to maintain settings. This is a fairly straightforward change and doesn't change the interface. Furthermore, the keys are still dotted and don't support namespacing as defining namespaces might take a little more discussion. --- lib/matplotlib/__init__.py | 143 +++++--------------------- lib/matplotlib/style/core.py | 2 +- lib/matplotlib/tests/test_rcparams.py | 12 ++- 3 files changed, 36 insertions(+), 121 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 43716ffa00b0..b850a69d8d3a 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -667,35 +667,11 @@ class RcParams(MutableMapping): :ref:`customizing-with-matplotlibrc-files` """ validate = rcsetup._validators - _namespaces = ("backends", "lines", "patches", "hatches", "boxplot", "font", "text", - "latex", "axes", "date", "xtick", "ytick", "grid", "legend", - "figure", "image", "contour", "errorbar", "hist", "scatter", "agg", - "path", "savefig", "tk", "ps", "pdf", "svg", "pgf", "docstring", - "keymap", "animation", "_internal", "webagg", "markers", "pcolor", - "pcolormesh", "patch", "hatch", "mathtext", "polaraxes", "axes3d", - "xaxis", "yaxis", "default") - - _single_key_set = {"backend", "toolbar", "interactive", - "timezone", "backend_fallback"} def __init__(self, *args, **kwargs): - self._namespace_maps = {name: ChainMap({}) for name in self._namespaces} + self._rcvalues = ChainMap({}) self.update(*args, **kwargs) - self._new_child() - - @staticmethod - @functools.lru_cache - def _split_key(key, sep="."): - keys = key.split(sep, maxsplit=1) - return keys, len(keys) - - def _new_child(self): - for space in self._namespace_maps.keys(): - self._namespace_maps[space] = self._namespace_maps[space].new_child() - - def _parents(self): - for space in self._namespace_maps.keys(): - self._namespace_maps[space] = self._namespace_maps[space].parents + self._rcvalues.new_child() def _set(self, key, val): """ @@ -715,25 +691,7 @@ def _set(self, key, val): :meta public: """ - keys, depth = self._split_key(key) - if depth == 1: - if key in self._single_key_set: - self._namespace_maps["default"][key] = val - # Uncomment the following line and remove the raise statement - # to enable setting namespaces. - # else: - # if isinstance(val, dict): - # self._namespace_maps[key] = ChainMap({}, val) - # else: - # raise ValueError( - # f"{key} should be set using a dictionary but found " - # f"{type(val)}") - else: - raise KeyError( - f"{key} is not a valid rc parameter (see rcParams.keys() for " - f"a list of valid parameters)") - elif depth == 2: - self._namespace_maps[keys[0]][keys[1]] = val + self._rcvalues[key] = val def _get(self, key): """ @@ -754,19 +712,7 @@ def _get(self, key): :meta public: """ - keys, depth = self._split_key(key) - if depth == 1: - if key in self._single_key_set: - return self._namespace_maps["default"].get(key) - # Uncomment the following line and remove the raise statement - # to enable getting namespace parameters. - # return self._namespace_maps[key] - else: - raise KeyError( - f"{key} is not a valid rc parameter (see rcParams.keys() for " - f"a list of valid parameters)") - elif depth == 2: - return self._namespace_maps[keys[0]].get(keys[1]) + return self._rcvalues[key] def __setitem__(self, key, val): try: @@ -790,8 +736,8 @@ def __setitem__(self, key, val): return try: cval = self.validate[key](val) - if key in self._single_key_set: - key = f"default.{key}" + # if key in self._single_key_set: + # key = f"default.{key}" except ValueError as ve: raise ValueError(f"Key {key}: {ve}") from None self._set(key, cval) @@ -824,21 +770,9 @@ def __getitem__(self, key): return self._get(key) def _get_default(self, key): - keys, depth = self._split_key(key) - if depth == 1: - if key in self._single_key_set: - return self._namespace_maps["default"].get(key) - # Uncomment the following line and remove the raise statement - # to enable getting namespace parameters. - # return self._namespace_maps[key] - else: - raise KeyError( - f"{key} is not a valid rc parameter (see rcParams.keys() for " - f"a list of valid parameters)") - elif depth == 2: - return self._namespace_maps[keys[0]].maps[-1].get(keys[1]) + return self._rcvalues.maps[-1][key] - def getdefault(self, key): + def get_default(self, key): if key in _deprecated_map: version, alt_key, alt_val, inverse_alt = _deprecated_map[key] _api.warn_deprecated( @@ -853,7 +787,7 @@ def getdefault(self, key): return self._get_default(key) - def getdefaults(self): + def get_defaults(self): """Return default values set during initialization.""" defaults = self.copy() defaults.clear() @@ -865,54 +799,35 @@ def _get_backend_or_none(self): return None if backend is rcsetup._auto_backend_sentinel else backend def __delitem__(self, key): - keys, depth = self._split_key(key) try: - if depth == 1: - if key in self._single_key_set: - del self._namespace_maps["default"][key] - else: - raise KeyError - elif depth == 2: - del self._namespace_maps[keys[0]][keys[1]] + del self._rcvalues[key] except KeyError as err: raise KeyError( f"{key} is not a valid rc parameter (see rcParams.keys() for " f"a list of valid parameters)") from err def __contains__(self, key): - keys, depth = self._split_key(key) - if depth == 1: - if key in self._single_key_set: - return key in self._namespace_maps["default"] - else: - return False - elif depth == 2: - return any(key in mapping for mapping in self._namespace_maps) + return key in self._rcvalues def __iter__(self): """Yield from sorted list of keys""" - keys = ( - ".".join((space, key)) if space != 'default' else key - for space, mapping in self._namespace_maps.items() - for key in mapping.keys() - ) with _api.suppress_matplotlib_deprecation_warning(): - yield from sorted(keys) + yield from sorted(self._rcvalues.keys()) def __len__(self): - return sum(len(mapping) for mapping in self._namespace_maps) + return len(self._rcvalues) def __repr__(self): class_name = self.__class__.__name__ indent = len(class_name) + 1 with _api.suppress_matplotlib_deprecation_warning(): - repr_split = pprint.pformat(dict(self.items()), indent=1, + repr_split = pprint.pformat(dict(self._rcvalues.items()), indent=1, width=80 - indent).split('\n') repr_indented = ('\n' + ' ' * indent).join(repr_split) return f'{class_name}({repr_indented})' def __str__(self): - return '\n'.join(map('{0[0]}: {0[1]}'.format, sorted(self.items()))) + return '\n'.join(map('{0[0]}: {0[1]}'.format, sorted(self._rcvalues.items()))) def pop(self, key): keys, depth = self._split_key(key) @@ -931,8 +846,7 @@ def popitem(self): "popitem is not implemented for RcParams.") def clear(self): - for namespace in self._namespace_maps: - self._namespace_maps[namespace].clear() + self._rcvalues.clear() def setdefault(self, key, default=None): """Insert key with a value of default if key is not in the dictionary. @@ -1067,8 +981,7 @@ def _rc_params_in_file(fname, transform=lambda x: x, fail_on_error=False): fname) raise - config = RcParams() - config._parents() + config = dict() for key, (val, line, line_no) in rc_temp.items(): if key in rcsetup._validators: @@ -1097,8 +1010,7 @@ def _rc_params_in_file(fname, transform=lambda x: x, fail_on_error=False): or from the matplotlib source distribution""", dict(key=key, fname=fname, line_no=line_no, line=line.rstrip('\n'), version=version)) - config._new_child() - return config + return RcParams(config) def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): @@ -1122,7 +1034,7 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): return config_from_file with _api.suppress_matplotlib_deprecation_warning(): - config = RcParams({**rcParams.getdefaults(), **config_from_file}) + config = RcParams({**rcParams.get_defaults(), **config_from_file}) if "".join(config['text.latex.preamble']): _log.info(""" @@ -1142,10 +1054,10 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): # Strip leading comment. transform=lambda line: line[1:] if line.startswith("#") else line, fail_on_error=True) -for key in rcsetup._hardcoded_defaults: - space, subkey = key.split(".") - if not rcParams._namespace_maps[space]: - rcParams._namespace_maps[space] = ChainMap({}) +# for key in rcsetup._hardcoded_defaults: +# space, subkey = key.split(".") +# if not rcParams._namespace_maps[space]: +# rcParams._namespace_maps[space] = ChainMap({}) rcParams.update(rcsetup._hardcoded_defaults) # Normally, the default matplotlibrc file contains *no* entry for backend (the # corresponding line starts with ##, not #; we fill on _auto_backend_sentinel @@ -1161,7 +1073,7 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): defaultParams = rcsetup.defaultParams = { # We want to resolve deprecated rcParams, but not backend... key: [(rcsetup._auto_backend_sentinel if key == "backend" else - rcParams.getdefault(key)), + rcParams.get_default(key)), validator] for key, validator in rcsetup._validators.items()} if rcParams['axes.formatter.use_locale']: @@ -1354,10 +1266,7 @@ def rc_context(rc=None, fname=None): """ try: - for space in rcParams._namespace_maps.keys(): - rcParams._namespace_maps[space] = rcParams._namespace_maps[ - space - ].new_child() + rcParams._rcvalues = rcParams._rcvalues.new_child() if fname: rc_file(fname) if rc: @@ -1366,7 +1275,7 @@ def rc_context(rc=None, fname=None): finally: # Revert to the original rcs. backend = rcParams["backend"] - rcParams._parents() + rcParams._rcvalues = rcParams._rcvalues.parents rcParams["backend"] = backend diff --git a/lib/matplotlib/style/core.py b/lib/matplotlib/style/core.py index 392360920f92..57b3a60a0a54 100644 --- a/lib/matplotlib/style/core.py +++ b/lib/matplotlib/style/core.py @@ -114,7 +114,7 @@ def use(style): # rcParamsDefault, no need to reemit them here. with _api.suppress_matplotlib_deprecation_warning(): # don't trigger RcParams.__getitem__('backend') - style = {k: mpl.rcParams.getdefault(k) for k in mpl.rcParams + style = {k: mpl.rcParams.get_default(k) for k in mpl.rcParams if k not in STYLE_BLACKLIST} elif style in library: style = library[style] diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index f1a53bfaef9e..8defa1735edf 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -656,12 +656,12 @@ def test_rcparams_path_sketch_from_file(tmpdir, value): def test_rcparams_getdefault(): with mpl.rc_context({"image.lut": 128}): - assert mpl.rcParams.getdefault("image.lut") == 256 + assert mpl.rcParams.get_default("image.lut") == 256 def test_rcparams_getdefaults(): mpl.rc("image", lut=128) - defaults = mpl.rcParams.getdefaults() + defaults = mpl.rcParams.get_defaults() mpl.rcParams.clear() assert defaults == mpl.rcParams @@ -673,8 +673,14 @@ def test_rcdefaults(): # lines.linewidth is not a style blacklisted key and should be # reset to the default value. # breakpoint() - lw = mpl.rcParams.getdefault("lines.linewidth") + lw = mpl.rcParams.get_default("lines.linewidth") mpl.rcParams["lines.linewidth"] = lw + 1 mpl.rcdefaults() assert mpl.rcParams["webagg.port"] == 9000 assert mpl.rcParams["lines.linewidth"] == lw + + +def test_rcparams_clear(): + mpl.rcParams["image.lut"] = 128 + mpl.rcParams.clear() + assert mpl.rcParams["image.lut"] == 256 From aa282d0d3a0dcacc2fc7a59569891cc50930e908 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Wed, 7 Jun 2023 21:10:55 -0700 Subject: [PATCH 19/22] Deprecate clear and other small updates for review --- lib/matplotlib/__init__.py | 57 +++++++-------------------- lib/matplotlib/tests/test_rcparams.py | 12 ++++-- 2 files changed, 23 insertions(+), 46 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index b850a69d8d3a..95f6b4e8e66e 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -671,7 +671,8 @@ class RcParams(MutableMapping): def __init__(self, *args, **kwargs): self._rcvalues = ChainMap({}) self.update(*args, **kwargs) - self._rcvalues.new_child() + self._rcvalues = self._rcvalues.new_child() + self._defaults = self._rcvalues.maps[-1] def _set(self, key, val): """ @@ -730,14 +731,12 @@ def __setitem__(self, key, val): _api.warn_deprecated( version, name=key, obj_type="rcparam", alternative=alt_key) return - elif key == 'backend' or key == "default.backend": + elif key == 'backend': if val is rcsetup._auto_backend_sentinel: - if 'backend' in self or 'default.backend' in self: + if 'backend' in self: return try: cval = self.validate[key](val) - # if key in self._single_key_set: - # key = f"default.{key}" except ValueError as ve: raise ValueError(f"Key {key}: {ve}") from None self._set(key, cval) @@ -769,10 +768,8 @@ def __getitem__(self, key): return self._get(key) - def _get_default(self, key): - return self._rcvalues.maps[-1][key] - def get_default(self, key): + """Return default value for the key set during initialization.""" if key in _deprecated_map: version, alt_key, alt_val, inverse_alt = _deprecated_map[key] _api.warn_deprecated( @@ -783,15 +780,13 @@ def get_default(self, key): version, alt_key = _deprecated_ignore_map[key] _api.warn_deprecated( version, name=key, obj_type="rcparam", alternative=alt_key) - return self._get_default(alt_key) if alt_key else None + return self._defaults[alt_key] if alt_key else None - return self._get_default(key) + return self._defaults[key] def get_defaults(self): """Return default values set during initialization.""" - defaults = self.copy() - defaults.clear() - return defaults + return self._defaults.copy() def _get_backend_or_none(self): """Get the requested backend, if any, without triggering resolution.""" @@ -845,7 +840,11 @@ def popitem(self): raise NotImplementedError( "popitem is not implemented for RcParams.") + @_api.deprecated("3.8") def clear(self): + pass + + def reset(self): self._rcvalues.clear() def setdefault(self, key, default=None): @@ -858,30 +857,6 @@ def setdefault(self, key, default=None): self[key] = default return default - def get(self, key, default=None): - try: - return self[key] - except KeyError as e: - return default - - def update(self, other=(), /, **kwds): - """D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. - If E present and has a .keys() method, does: for k in E: D[k] = E[k] - If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v - In either case, this is followed by: for k, v in F.items(): D[k] = v - """ - if isinstance(other, Mapping): - for key in other: - self[key] = other[key] - elif hasattr(other, "keys"): - for key in other.keys(): - self[key] = other[key] - else: - for key, value in other: - self[key] = value - for key, value in kwds.items(): - self[key] = value - def copy(self): return deepcopy(self) @@ -1054,18 +1029,16 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): # Strip leading comment. transform=lambda line: line[1:] if line.startswith("#") else line, fail_on_error=True) -# for key in rcsetup._hardcoded_defaults: -# space, subkey = key.split(".") -# if not rcParams._namespace_maps[space]: -# rcParams._namespace_maps[space] = ChainMap({}) +rcParams._rcvalues = rcParams._rcvalues.parents rcParams.update(rcsetup._hardcoded_defaults) # Normally, the default matplotlibrc file contains *no* entry for backend (the # corresponding line starts with ##, not #; we fill on _auto_backend_sentinel # in that case. However, packagers can set a different default backend # (resulting in a normal `#backend: foo` line) in which case we should *not* # fill in _auto_backend_sentinel. -rcParams.setdefault("backend", rcsetup._auto_backend_sentinel) rcParams.update(_rc_params_in_file(matplotlib_fname())) +rcParams._rcvalues = rcParams._rcvalues.new_child() +rcParams.setdefault("backend", rcsetup._auto_backend_sentinel) rcParamsOrig = rcParams.copy() with _api.suppress_matplotlib_deprecation_warning(): # This also checks that all rcParams are indeed listed in the template. diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 8defa1735edf..dc4ff1f8cff5 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -662,8 +662,7 @@ def test_rcparams_getdefault(): def test_rcparams_getdefaults(): mpl.rc("image", lut=128) defaults = mpl.rcParams.get_defaults() - mpl.rcParams.clear() - assert defaults == mpl.rcParams + assert defaults == mpl.rcParams._defaults def test_rcdefaults(): @@ -680,7 +679,12 @@ def test_rcdefaults(): assert mpl.rcParams["lines.linewidth"] == lw -def test_rcparams_clear(): +def test_rcparams_reset(): mpl.rcParams["image.lut"] = 128 - mpl.rcParams.clear() + mpl.rcParams.reset() assert mpl.rcParams["image.lut"] == 256 + + +def test_rcparams_clear(): + with pytest.raises(mpl.MatplotlibDeprecationWarning): + mpl.rcParams.clear() From b1754a2e97b71ccfed41243ee3bf1a2425fa51a4 Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Wed, 7 Jun 2023 22:06:31 -0700 Subject: [PATCH 20/22] Update delitem --- lib/matplotlib/__init__.py | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 95f6b4e8e66e..7b8be618af23 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -794,12 +794,16 @@ def _get_backend_or_none(self): return None if backend is rcsetup._auto_backend_sentinel else backend def __delitem__(self, key): + if key not in self.validate: + raise KeyError( + f"{key} is not a valid rc parameter (see rcParams.keys() for " + f"a list of valid parameters)") try: del self._rcvalues[key] except KeyError as err: raise KeyError( - f"{key} is not a valid rc parameter (see rcParams.keys() for " - f"a list of valid parameters)") from err + f"No custom value set for {key}. Cannot delete default value." + ) from err def __contains__(self, key): return key in self._rcvalues @@ -824,22 +828,6 @@ def __repr__(self): def __str__(self): return '\n'.join(map('{0[0]}: {0[1]}'.format, sorted(self._rcvalues.items()))) - def pop(self, key): - keys, depth = self._split_key(key) - if depth == 1: - if key in self._single_key_set: - return self._namespace_mapping["default"][key] - else: - raise KeyError( - f"{key} is not a valid rc parameter (see rcParams.keys() for " - f"a list of valid parameters)") - elif depth == 2: - return self._namespace_mapping[keys[0]].pop(keys[1]) - - def popitem(self): - raise NotImplementedError( - "popitem is not implemented for RcParams.") - @_api.deprecated("3.8") def clear(self): pass @@ -1037,8 +1025,8 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): # (resulting in a normal `#backend: foo` line) in which case we should *not* # fill in _auto_backend_sentinel. rcParams.update(_rc_params_in_file(matplotlib_fname())) -rcParams._rcvalues = rcParams._rcvalues.new_child() rcParams.setdefault("backend", rcsetup._auto_backend_sentinel) +rcParams._rcvalues = rcParams._rcvalues.new_child() rcParamsOrig = rcParams.copy() with _api.suppress_matplotlib_deprecation_warning(): # This also checks that all rcParams are indeed listed in the template. From faed9bacfd4c6018fa0d7349f272135852c7c3db Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Sat, 14 Oct 2023 10:19:26 -0400 Subject: [PATCH 21/22] Make flake8 happy --- lib/matplotlib/__init__.pyi | 8 ++++---- lib/matplotlib/pyplot.py | 7 ++++--- lib/matplotlib/tests/test_offsetbox.py | 5 ----- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/__init__.pyi b/lib/matplotlib/__init__.pyi index 8691ef420166..d37ac6469770 100644 --- a/lib/matplotlib/__init__.pyi +++ b/lib/matplotlib/__init__.pyi @@ -32,12 +32,12 @@ __all__ = [ import os from pathlib import Path -from collections.abc import Callable, Generator +from collections.abc import Callable, Generator, MutableMapping import contextlib from packaging.version import Version from matplotlib._api import MatplotlibDeprecationWarning -from typing import Any, NamedTuple +from typing import Any, NamedTuple, Self class _VersionInfo(NamedTuple): major: int @@ -79,8 +79,8 @@ class RcParams(MutableMapping[str, Any]): def __delitem__(self, key: str) -> None: ... def __iter__(self) -> Generator[str, None, None]: ... def __len__(self) -> int: ... - def find_all(self, pattern: str) -> RcParams: ... - def copy(self) -> RcParams: ... + def find_all(self, pattern: str) -> Self: ... + def copy(self) -> Self: ... def rc_params(fail_on_error: bool = ...) -> RcParams: ... def rc_params_from_file( diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 8c7b6a873882..5669ee82f2f3 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -67,6 +67,7 @@ from matplotlib.gridspec import GridSpec, SubplotSpec from matplotlib import rcParams, get_backend, rcParamsOrig from matplotlib.rcsetup import interactive_bk as _interactive_bk +from matplotlib.rcsetup import _auto_backend_sentinel from matplotlib.artist import Artist from matplotlib.axes import Axes, Subplot # type: ignore from matplotlib.projections import PolarAxes # type: ignore @@ -302,7 +303,7 @@ def switch_backend(newbackend: str) -> None: # make sure the init is pulled up so we can assign to it later import matplotlib.backends - if newbackend is rcsetup._auto_backend_sentinel: + if newbackend is _auto_backend_sentinel: current_framework = cbook._get_running_interactive_framework() mapping = {'qt': 'qtagg', 'gtk3': 'gtk3agg', @@ -2475,9 +2476,9 @@ def polar(*args, **kwargs) -> list[Line2D]: # is compatible with the current running interactive framework. if (rcParams["backend_fallback"] and rcParams._get_backend_or_none() in ( # type: ignore - set(rcsetup.interactive_bk) - {'WebAgg', 'nbAgg'}) + set(_interactive_bk) - {'WebAgg', 'nbAgg'}) and cbook._get_running_interactive_framework()): # type: ignore - rcParams._set("backend", rcsetup._auto_backend_sentinel) # type: ignore + rcParams._set("backend", _auto_backend_sentinel) # type: ignore # fmt: on diff --git a/lib/matplotlib/tests/test_offsetbox.py b/lib/matplotlib/tests/test_offsetbox.py index edf17542fb17..49b55e4c9326 100644 --- a/lib/matplotlib/tests/test_offsetbox.py +++ b/lib/matplotlib/tests/test_offsetbox.py @@ -257,14 +257,9 @@ def test_anchoredtext_horizontal_alignment(): ax.add_artist(text2) -<<<<<<< HEAD @pytest.mark.parametrize("extent_kind", ["window_extent", "tightbbox"]) def test_annotationbbox_extents(extent_kind): plt.rcParams.update(plt.rcParamsDefault) -======= -def test_annotationbbox_extents(): - plt.rcParams.clear() ->>>>>>> 57d742c95e (Remove `rcParamsDefault`) fig, ax = plt.subplots(figsize=(4, 3), dpi=100) ax.axis([0, 1, 0, 1]) From dfbb73f3f1a544eca2449a0884a308bf8d468c8e Mon Sep 17 00:00:00 2001 From: Chahak Mehta Date: Mon, 23 Oct 2023 21:16:55 -0400 Subject: [PATCH 22/22] Remove unnecessary deprecation warning context --- lib/matplotlib/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 7b8be618af23..623b22852e30 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -810,8 +810,7 @@ def __contains__(self, key): def __iter__(self): """Yield from sorted list of keys""" - with _api.suppress_matplotlib_deprecation_warning(): - yield from sorted(self._rcvalues.keys()) + yield from sorted(self._rcvalues.keys()) def __len__(self): return len(self._rcvalues) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy