Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 888811c

Browse files
committed
Data access API for rcParams
This provides a defined API for accessing rcParams via `rcParams._data[key]` while circumventing any validation logic happening in `rcParams[key]`. Before, direct data access was realized through `dict.__getitem(rcParams, key)` / `dict.__setitem(rcParams, key, val)`, which depends on the implementation detail of `rcParams` being a dict subclass. The new data access API gets rid of this dependence and thus opens up a way to later move away from dict subclassing. We want to move away from dict subclassing and only guarantee the `MutableMapping` interface for `rcParams` in the future. This allows other future restructings like introducing a new configuration management and changing `rcParams` into a backward-compatible adapter.
1 parent 07af522 commit 888811c

File tree

6 files changed

+78
-17
lines changed

6 files changed

+78
-17
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
rcParams type
2+
~~~~~~~~~~~~~
3+
Relying on ``rcParams`` being a ``dict`` subclass is deprecated.
4+
5+
Nothing will change for regular users because ``rcParams`` will continue to
6+
be dict-like (technically fulfill the ``MutableMapping`` interface).
7+
8+
However, there are some rare known cases that use
9+
``dict.__getitem__(rcParams, key)`` and
10+
``dict.__setitem__(rcParams, key, val)`` for direct data access that
11+
circumvents any validation logic. While this is technically public, it
12+
depends on the implementation detail that ``rcParams`` is currently a ``dict``
13+
subclass.
14+
15+
Matplotlib 3.7 introduces ``rcParams._set(key, val)`` and
16+
``rcParams._get(key)`` as a replacements for these cases. They are
17+
intentionally marked private because there is very little need for external
18+
users to use them. However, if you have a compelling reason for direct data
19+
access, please switch from the dict functions to the new ``_get()`` and
20+
``_set()``. Even though marked private, we guarantee API stability for these
21+
methods, i.e. they are subject to Matplotlib's API and deprecation policy.
22+
23+
Please notify the Matplotlib developers if you rely on ``rcParams`` being a
24+
dict subclass in any other way,for which there is no migration path yet.

lib/matplotlib/__init__.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ def gen_candidates():
605605
)
606606
class RcParams(MutableMapping, dict):
607607
"""
608-
A dictionary object including validation.
608+
A dict-like configuration parameter container including validation.
609609
610610
Validating functions are defined and associated with rc parameters in
611611
:mod:`matplotlib.rcsetup`.
@@ -625,6 +625,43 @@ class RcParams(MutableMapping, dict):
625625
def __init__(self, *args, **kwargs):
626626
self.update(*args, **kwargs)
627627

628+
def _set(self, key, val):
629+
"""
630+
Directly write data bypassing deprecation and validation logic.
631+
632+
As end user or downstream library you almost always should use
633+
``rcParams[key] = val`` and not ``_set()``.
634+
635+
There are only very few special cases that need direct data access.
636+
These cases previously used ``dict.__setitem__(rcParams, key, val)``,
637+
which is now deprecated and replaced by ``rcParams._set(key, val)``.
638+
639+
Even though private, we guarantee API stability for ``rcParams._set``,
640+
i.e. it is subject to Matplotlib's API and deprecation policy.
641+
642+
:meta public:
643+
"""
644+
dict.__setitem__(self, key, val)
645+
646+
def _get(self, key):
647+
"""
648+
Directly read data bypassing deprecation, backend and validation
649+
logic.
650+
651+
As end user or downstream library you almost always should use
652+
``val = rcParams[key]`` and not ``_get()``.
653+
654+
There are only very few special cases that need direct data access.
655+
These cases previously used ``dict.__getitem__(rcParams, key, val)``,
656+
which is now deprecated and replaced by ``rcParams._get(key)``.
657+
658+
Even though private, we guarantee API stability for ``rcParams._get``,
659+
i.e. it is subject to Matplotlib's API and deprecation policy.
660+
661+
:meta public:
662+
"""
663+
return dict.__getitem__(self, key)
664+
628665
def __setitem__(self, key, val):
629666
try:
630667
if key in _deprecated_map:
@@ -649,7 +686,7 @@ def __setitem__(self, key, val):
649686
cval = self.validate[key](val)
650687
except ValueError as ve:
651688
raise ValueError(f"Key {key}: {ve}") from None
652-
dict.__setitem__(self, key, cval)
689+
self._set(key, cval)
653690
except KeyError as err:
654691
raise KeyError(
655692
f"{key} is not a valid rc parameter (see rcParams.keys() for "
@@ -660,27 +697,27 @@ def __getitem__(self, key):
660697
version, alt_key, alt_val, inverse_alt = _deprecated_map[key]
661698
_api.warn_deprecated(
662699
version, name=key, obj_type="rcparam", alternative=alt_key)
663-
return inverse_alt(dict.__getitem__(self, alt_key))
700+
return inverse_alt(self._get(alt_key))
664701

665702
elif key in _deprecated_ignore_map:
666703
version, alt_key = _deprecated_ignore_map[key]
667704
_api.warn_deprecated(
668705
version, name=key, obj_type="rcparam", alternative=alt_key)
669-
return dict.__getitem__(self, alt_key) if alt_key else None
706+
return self._get(alt_key) if alt_key else None
670707

671708
# In theory, this should only ever be used after the global rcParams
672709
# has been set up, but better be safe e.g. in presence of breakpoints.
673710
elif key == "backend" and self is globals().get("rcParams"):
674-
val = dict.__getitem__(self, key)
711+
val = self._get(key)
675712
if val is rcsetup._auto_backend_sentinel:
676713
from matplotlib import pyplot as plt
677714
plt.switch_backend(rcsetup._auto_backend_sentinel)
678715

679-
return dict.__getitem__(self, key)
716+
return self._get(key)
680717

681718
def _get_backend_or_none(self):
682719
"""Get the requested backend, if any, without triggering resolution."""
683-
backend = dict.__getitem__(self, "backend")
720+
backend = self._get("backend")
684721
return None if backend is rcsetup._auto_backend_sentinel else backend
685722

686723
def __repr__(self):
@@ -722,7 +759,7 @@ def find_all(self, pattern):
722759
def copy(self):
723760
rccopy = RcParams()
724761
for k in self: # Skip deprecations and revalidation.
725-
dict.__setitem__(rccopy, k, dict.__getitem__(self, k))
762+
rccopy._set(k, self._get(k))
726763
return rccopy
727764

728765

lib/matplotlib/pyplot.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,10 @@ def _get_backend_mod():
200200
This is currently private, but may be made public in the future.
201201
"""
202202
if _backend_mod is None:
203-
# Use __getitem__ here to avoid going through the fallback logic (which
204-
# will (re)import pyplot and then call switch_backend if we need to
205-
# resolve the auto sentinel)
206-
switch_backend(dict.__getitem__(rcParams, "backend"))
203+
# Use rcParams.get("backend") to avoid going through the fallback
204+
# logic (which will (re)import pyplot and then call switch_backend if
205+
# we need to resolve the auto sentinel)
206+
switch_backend(rcParams._get("backend"))
207207
return _backend_mod
208208

209209

@@ -2197,7 +2197,7 @@ def polar(*args, **kwargs):
21972197
and rcParams._get_backend_or_none() in (
21982198
set(_interactive_bk) - {'WebAgg', 'nbAgg'})
21992199
and cbook._get_running_interactive_framework()):
2200-
dict.__setitem__(rcParams, "backend", rcsetup._auto_backend_sentinel)
2200+
rcParams._set("backend", rcsetup._auto_backend_sentinel)
22012201

22022202

22032203
################# REMAINING CONTENT GENERATED BY boilerplate.py ##############

lib/matplotlib/tests/test_backends_interactive.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,13 +247,13 @@ def _impl_test_lazy_auto_backend_selection():
247247
import matplotlib
248248
import matplotlib.pyplot as plt
249249
# just importing pyplot should not be enough to trigger resolution
250-
bk = dict.__getitem__(matplotlib.rcParams, 'backend')
250+
bk = matplotlib.rcParams._get('backend')
251251
assert not isinstance(bk, str)
252252
assert plt._backend_mod is None
253253
# but actually plotting should
254254
plt.plot(5)
255255
assert plt._backend_mod is not None
256-
bk = dict.__getitem__(matplotlib.rcParams, 'backend')
256+
bk = matplotlib.rcParams._get('backend')
257257
assert isinstance(bk, str)
258258

259259

lib/matplotlib/tests/test_rcparams.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ def test_backend_fallback_headful(tmpdir):
543543
"sentinel = mpl.rcsetup._auto_backend_sentinel; "
544544
# Check that access on another instance does not resolve the sentinel.
545545
"assert mpl.RcParams({'backend': sentinel})['backend'] == sentinel; "
546-
"assert dict.__getitem__(mpl.rcParams, 'backend') == sentinel; "
546+
"assert mpl.rcParams._get('backend') == sentinel; "
547547
"import matplotlib.pyplot; "
548548
"print(matplotlib.get_backend())"],
549549
env=env, universal_newlines=True)

lib/matplotlib/tests/test_widgets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,7 @@ def test_CheckButtons(ax):
965965
@pytest.mark.parametrize("toolbar", ["none", "toolbar2", "toolmanager"])
966966
def test_TextBox(ax, toolbar):
967967
# Avoid "toolmanager is provisional" warning.
968-
dict.__setitem__(plt.rcParams, "toolbar", toolbar)
968+
plt.rcParams._set("toolbar", toolbar)
969969

970970
submit_event = mock.Mock(spec=noop, return_value=None)
971971
text_change_event = mock.Mock(spec=noop, return_value=None)

0 commit comments

Comments
 (0)