8000 Include close matches in error message when key not found by dstansby · Pull Request #30001 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Include close matches in error message when key not found #30001

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,12 +740,11 @@ def __setitem__(self, key, val):
and val is rcsetup._auto_backend_sentinel
and "backend" in self):
return
valid_key = _api.check_getitem(
self.validate, rcParam=key, _error_cls=KeyError
)
try:
cval = self.validate[key](val)
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
cval = valid_key(val)
except ValueError as ve:
raise ValueError(f"Key {key}: {ve}") from None
self._set(key, cval)
Expand Down
21 changes: 17 additions & 4 deletions lib/matplotlib/_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

"""

import difflib
import functools
import itertools
import pathlib
Expand Down Expand Up @@ -174,12 +175,19 @@ def check_shape(shape, /, **kwargs):
)


def check_getitem(mapping, /, **kwargs):
def check_getitem(
mapping, /, _error_cls=ValueError, **kwargs
):
"""
*kwargs* must consist of a single *key, value* pair. If *key* is in
*mapping*, return ``mapping[value]``; else, raise an appropriate
ValueError.

Parameters
----------
_error_cls :
Class of error to raise.

Examples
--------
>>> _api.check_getitem({"foo": "bar"}, arg=arg)
Expand All @@ -190,9 +198,14 @@ def check_getitem(mapping, /, **kwargs):
try:
return mapping[v]
except KeyError:
raise ValueError(
f"{v!r} is not a valid value for {k}; supported values are "
f"{', '.join(map(repr, mapping))}") from None
if len(mapping) > 5:
if len(best := difflib.get_close_matches(v, mapping.keys(), cutoff=0.5)):
suggestion = f"Did you mean one of {best}?"
else:
suggestion = ""
else:
suggestion = f"Supported values are {', '.join(map(repr, mapping))}"
raise _error_cls(f"{v!r} is not a valid value for {k}. {suggestion}") from None


def caching_module_getattr(cls):
Expand Down
6 changes: 4 additions & 2 deletions lib/matplotlib/_api/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from collections.abc import Callable, Generator, Iterable, Mapping, Sequence
from typing import Any, TypeVar, overload
from typing import Any, Type, TypeVar, overload
from typing_extensions import Self # < Py 3.11

from numpy.typing import NDArray
Expand Down Expand Up @@ -42,7 +42,9 @@ def check_in_list(
values: Sequence[Any], /, *, _print_supported_values: bool = ..., **kwargs: Any
) -> None: ...
def check_shape(shape: tuple[int | None, ...], /, **kwargs: NDArray) -> None: ...
def check_getitem(mapping: Mapping[Any, Any], /, **kwargs: Any) -> Any: ...
def check_getitem(
mapping: Mapping[Any, _T], /, _error_cls: Type[Exception], **kwargs: Any
) -> _T: ...
def caching_module_getattr(cls: type) -> Callable[[str], Any]: ...
@overload
def define_aliases(
Expand Down
8 changes: 4 additions & 4 deletions lib/matplotlib/cm.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ def __init__(self, cmaps):
self._builtin_cmaps = tuple(cmaps)

def __getitem__(self, item):
try:
return self._cmaps[item].copy()
except KeyError:
raise KeyError(f"{item!r} is not a known colormap name") from None
cmap = _api.check_getitem(
self._cmaps, colormap=item, _error_cls=KeyError
)
return cmap.copy()

def __iter__(self):
return iter(self._cmaps)
Expand Down
10 changes: 10 additions & 0 deletions lib/matplotlib/tests/test_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -1829,3 +1829,13 @@ def test_LinearSegmentedColormap_from_list_value_color_tuple():
cmap([value for value, _ in value_color_tuples]),
to_rgba_array([color for _, color in value_color_tuples]),
)


def test_close_error_name():
with pytest.raises(
KeyError,
match=(
"'grays' is not a valid value for colormap. "
"Did you mean one of ['gray', 'Grays', 'gray_r']?"
)):
matplotlib.colormaps["grays"]
4 changes: 3 additions & 1 deletion lib/matplotlib/tests/test_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ def test_context_with_badparam():
with style.context({PARAM: other_value}):
assert mpl.rcParams[PARAM] == other_value
x = style.context({PARAM: original_value, 'badparam': None})
with pytest.raises(KeyError):
with pytest.raises(
KeyError, match="\'badparam\' is not a valid value for rcParam. "
):
with x:
pass
assert mpl.rcParams[PARAM] == other_value
Expand Down
Loading
0