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

Skip to content

Commit ecf156c

Browse files
timhoffmoscargusjklymakQuLogicstory645
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. Co-authored-by: Oscar Gustafsson <[email protected]> Co-authored-by: Jody Klymak <[email protected]> Co-authored-by: Elliott Sales de Andrade <[email protected]> Co-authored-by: story645 <[email protected]>
1 parent 07af522 commit ecf156c

File tree

6 files changed

+82
-17
lines changed

6 files changed

+82
-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+
The `.RcParams` class does validation checking on calls to
9+
``.RcParams.__getitem__`` and ``.RcParams.__setitem__``. However, there are rare
10+
cases where we want to circumvent the validation logic and directly access the
11+
underlying data values. Previously, this could be accomplished via a call to
12+
the parent methods ``dict.__getitem__(rcParams, key)`` and
13+
``dict.__setitem__(rcParams, key, val)``.
14+
15+
Matplotlib 3.7 introduces ``rcParams._set(key, val)`` and
16+
``rcParams._get(key)`` as a replacement to calling the parent methods. They are
17+
intentionally marked private to discourage external use; However, if direct
18+
`.RcParams` data access is needed, please switch from the dict functions to the
19+
new ``_get()`` and ``_set()``. Even though marked private, we guarantee API
20+
stability for these methods and they are subject to Matplotlib's API and
21+
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: 49 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 key-value store for config parameters, including validation.
609609
610610
Validating functions are defined and associated with rc parameters in
611611
:mod:`matplotlib.rcsetup`.
@@ -625,6 +625,47 @@ 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+
Notes
633+
-----
634+
As end user or downstream library you almost always should use
635+
``rcParams[key] = val`` and not ``_set()``.
636+
637+
There are only very few special cases that need direct data access.
638+
These cases previously used ``dict.__setitem__(rcParams, key, val)``,
639+
which is now deprecated and replaced by ``rcParams._set(key, val)``.
640+
641+
Even though private, we guarantee API stability for ``rcParams._set``,
642+
i.e. it is subject to Matplotlib's API and deprecation policy.
643+
644+
:meta public:
645+
"""
646+
dict.__setitem__(self, key, val)
647+
648+
def _get(self, key):
649+
"""
650+
Directly read data bypassing deprecation, backend and validation
651+
logic.
652+
653+
Notes
654+
-----
655+
As end user or downstream library you almost always should use
656+
``val = rcParams[key]`` and not ``_get()``.
657+
658+
There are only very few special cases that need direct data access.
659+
These cases previously used ``dict.__getitem__(rcParams, key, val)``,
660+
which is now deprecated and replaced by ``rcParams._get(key)``.
661+
662+
Even though private, we guarantee API stability for ``rcParams._get``,
663+
i.e. it is subject to Matplotlib's API and deprecation policy.
664+
665+
:meta public:
666+
"""
667+
return dict.__getitem__(self, key)
668+
628669
def __setitem__(self, key, val):
629670
try:
630671
if key in _deprecated_map:
@@ -649,7 +690,7 @@ def __setitem__(self, key, val):
649690
cval = self.validate[key](val)
650691
except ValueError as ve:
651692
raise ValueError(f"Key {key}: {ve}") from None
652-
dict.__setitem__(self, key, cval)
693+
self._set(key, cval)
653694
except KeyError as err:
654695
raise KeyError(
655696
f"{key} is not a valid rc parameter (see rcParams.keys() for "
@@ -660,27 +701,27 @@ def __getitem__(self, key):
660701
version, alt_key, alt_val, inverse_alt = _deprecated_map[key]
661702
_api.warn_deprecated(
662703
version, name=key, obj_type="rcparam", alternative=alt_key)
663-
return inverse_alt(dict.__getitem__(self, alt_key))
704+
return inverse_alt(self._get(alt_key))
664705

665706
elif key in _deprecated_ignore_map:
666707
version, alt_key = _deprecated_ignore_map[key]
667708
_api.warn_deprecated(
668709
version, name=key, obj_type="rcparam", alternative=alt_key)
669-
return dict.__getitem__(self, alt_key) if alt_key else None
710+
return self._get(alt_key) if alt_key else None
670711

671712
# In theory, this should only ever be used after the global rcParams
672713
# has been set up, but better be safe e.g. in presence of breakpoints.
673714
elif key == "backend" and self is globals().get("rcParams"):
674-
val = dict.__getitem__(self, key)
715+
val = self._get(key)
675716
if val is rcsetup._auto_backend_sentinel:
676717
from matplotlib import pyplot as plt
677718
plt.switch_backend(rcsetup._auto_backend_sentinel)
678719

679-
return dict.__getitem__(self, key)
720+
return self._get(key)
680721

681722
def _get_backend_or_none(self):
682723
"""Get the requested backend, if any, without triggering resolution."""
683-
backend = dict.__getitem__(self, "backend")
724+
backend = self._get("backend")
684725
return None if backend is rcsetup._auto_backend_sentinel else backend
685726

686727
def __repr__(self):
@@ -722,7 +763,7 @@ def find_all(self, pattern):
722763
def copy(self):
723764
rccopy = RcParams()
724765
for k in self: # Skip deprecations and revalidation.
725-
dict.__setitem__(rccopy, k, dict.__getitem__(self, k))
766+
rccopy._set(k, self._get(k))
726767
return rccopy
727768

728769

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)