Skip to content

Commit 8547239

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

File tree

6 files changed

+44
-16
lines changed

6 files changed

+44
-16
lines changed

lib/matplotlib/__init__.py

Lines changed: 4 additions & 5 deletions
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

Lines changed: 19 additions & 4 deletions
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,21 @@ 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+
_error_cls :
191+
Class of error to raise.
192+
183193
Examples
184194
--------
185195
>>> _api.check_getitem({"foo": "bar"}, arg=arg)
@@ -190,9 +200,14 @@ def check_getitem(mapping, /, **kwargs):
190200
try:
191201
return mapping[v]
192202
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
203+
if _suggest_close_matches:
204+
if len(best := difflib.get_close_matches(v, mapping.keys(), cutoff=0.5)):
205+
suggestion = f"Did you mean one of {best}?"
206+
else:
207+
suggestion = ""
208+
else:
209+
suggestion = f"Supported values are {', '.join(map(repr, mapping))}"
210+
raise _error_cls(f"{v!r} is not a valid value for {k}. {suggestion}") from None
196211

197212

198213
def caching_module_getattr(cls):

lib/matplotlib/_api/__init__.pyi

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from collections.abc import Callable, Generator, Iterable, Mapping, Sequence
2-
from typing import Any, TypeVar, overload
2+
from typing import Any, Type, TypeVar, overload
33
from typing_extensions import Self # < Py 3.11
44

55
from numpy.typing import NDArray
@@ -42,7 +42,9 @@ def check_in_list(
4242
values: Sequence[Any], /, *, _print_supported_values: bool = ..., **kwargs: Any
4343
) -> None: ...
4444
def check_shape(shape: tuple[int | None, ...], /, **kwargs: NDArray) -> None: ...
45-
def check_getitem(mapping: Mapping[Any, Any], /, **kwargs: Any) -> Any: ...
45+
def check_getitem(
46+
mapping: Mapping[Any, Any], /, _suggest_close_matches: bool, _error_cls: Type[Exception], **kwargs: Any
47+
) -> Any: ...
4648
def caching_module_getattr(cls: type) -> Callable[[str], Any]: ...
4749
@overload
4850
def define_aliases(

lib/matplotlib/cm.py

Lines changed: 4 additions & 4 deletions
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

Lines changed: 10 additions & 0 deletions
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

Lines changed: 3 additions & 1 deletion
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)
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