Skip to content

Commit 3f98b36

Browse files
committed
Include close matches when key not found
1 parent abda9dd commit 3f98b36

File tree

5 files changed

+38
-14
lines changed

5 files changed

+38
-14
lines changed

lib/matplotlib/__init__.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -740,12 +740,11 @@ def __setitem__(self, key, val):
740740
and val is rcsetup._auto_backend_sentinel
741741
and "backend" in self):
742742
return
743+
valid_key = _api.check_getitem(
744+
self.validate, rcParam=key, _suggest_close_matches=True, _error_cls=KeyError
745+
)
743746
try:
744-
cval = self.validate[key](val)
745-
except KeyError as err:
746-
raise KeyError(
747-
f"{key} is not a valid rc parameter (see rcParams.keys() for "
748-
f"a list of valid parameters)") from err
747+
cval = valid_key(val)
749748
except ValueError as ve:
750749
raise ValueError(f"Key {key}: {ve}") from None
751750
self._set(key, cval)

lib/matplotlib/_api/__init__.py

+17-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
1111
"""
1212

13+
import difflib
1314
import functools
1415
import itertools
1516
import pathlib
@@ -174,12 +175,19 @@ def check_shape(shape, /, **kwargs):
174175
)
175176

176177

177-
def check_getitem(mapping, /, **kwargs):
178+
def check_getitem(
179+
mapping, /, _suggest_close_matches=False, _error_cls=ValueError, **kwargs
180+
):
178181
"""
179182
*kwargs* must consist of a single *key, value* pair. If *key* is in
180183
*mapping*, return ``mapping[value]``; else, raise an appropriate
181184
ValueError.
182185
186+
Parameters
187+
----------
188+
_suggest_close_matches
189+
If True, suggest only close matches instead of all valid values.
190+
183191
Examples
184192
--------
185193
>>> _api.check_getitem({"foo": "bar"}, arg=arg)
@@ -190,9 +198,14 @@ def check_getitem(mapping, /, **kwargs):
190198
try:
191199
return mapping[v]
192200
except KeyError:
193-
raise ValueError(
194-
f"{v!r} is not a valid value for {k}; supported values are "
195-
f"{', '.join(map(repr, mapping))}") from None
201+
if _suggest_close_matches:
202+
if len(best := difflib.get_close_matches(v, mapping.keys(), cutoff=0.5)):
203+
suggestion = f"Did you mean one of {best}?"
204+
else:
205+
suggestion = ""
206+
else:
207+
suggestion = f"Supported values are {', '.join(map(repr, mapping))}"
208+
raise _error_cls(f"{v!r} is not a valid value for {k}. {suggestion}") from None
196209

197210

198211
def caching_module_getattr(cls):

lib/matplotlib/cm.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@ def __init__(self, cmaps):
9292
self._builtin_cmaps = tuple(cmaps)
9393

9494
def __getitem__(self, item):
95-
try:
96-
return self._cmaps[item].copy()
97-
except KeyError:
98-
raise KeyError(f"{item!r} is not a known colormap name") from None
95+
cmap = _api.check_getitem(
96+
self._cmaps, colormap=item, _suggest_close_matches=True, _error_cls=KeyError
97+
)
98+
return cmap.copy()
9999

100100
def __iter__(self):
101101
return iter(self._cmaps)

lib/matplotlib/tests/test_colors.py

+10
Original file line numberDiff line numberDiff line change
@@ -1828,3 +1828,13 @@ def test_LinearSegmentedColormap_from_list_value_color_tuple():
18281828
cmap([value for value, _ in value_color_tuples]),
18291829
to_rgba_array([color for _, color in value_color_tuples]),
18301830
)
1831+
1832+
1833+
def test_close_error_name():
1834+
with pytest.raises(
1835+
KeyError,
1836+
match=(
1837+
"'grays' is not a valid value for colormap. "
1838+
"Did you mean one of ['gray', 'Grays', 'gray_r']?"
1839+
)):
1840+
matplotlib.colormaps["grays"]

lib/matplotlib/tests/test_style.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,9 @@ def test_context_with_badparam():
140140
with style.context({PARAM: other_value}):
141141
assert mpl.rcParams[PARAM] == other_value
142142
x = style.context({PARAM: original_value, 'badparam': None})
143-
with pytest.raises(KeyError):
143+
with pytest.raises(
144+
KeyError, match="\'badparam\' is not a valid value for rcParam. "
145+
):
144146
with x:
145147
pass
146148
assert mpl.rcParams[PARAM] == other_value

0 commit comments

Comments
 (0)