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

Skip to content
Merged
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
48 changes: 33 additions & 15 deletions lib/matplotlib/_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,37 @@ def type_name(tp):
type_name(type(v))))


def check_in_list(values, /, *, _print_supported_values=True, **kwargs):
def list_suggestion_error_msg(name, potential, values):
"""
Generate an error message that a potential setting is not an acceptable value.

If the acceptable values are all strings, and sufficiently large, then add just a
few suggestions to the end of the message. Otherwise list the supported values.

Parameters
----------
name : str
The name of the setting, keyword argument, etc. to generate the message for.
potential
The potential value from the user that is not a valid choice.
values : iterable
Sequence of values to check on.
"""
if len(values) > 5 and all(isinstance(v, str) for v in [potential, *values]):
best = difflib.get_close_matches(potential, values, cutoff=0.5)
match len(best):
case 0:
suggestion = ""
case 1:
suggestion = f" Did you mean: {best[0]!r}?"
case _:
suggestion = f" Did you mean one of: {', '.join(map(repr, best))}?"
else:
suggestion = f" Supported values are {', '.join(map(repr, values))}"
return f"{potential!r} is not a valid value for {name}.{suggestion}"


def check_in_list(values, /, **kwargs):
"""
For each *key, value* pair in *kwargs*, check that *value* is in *values*;
if not, raise an appropriate ValueError.
Expand All @@ -119,8 +149,6 @@ def check_in_list(values, /, *, _print_supported_values=True, **kwargs):

Note: All values must support == comparisons.
This means in particular the entries must not be numpy arrays.
_print_supported_values : bool, default: True
Whether to print *values* when raising ValueError.
**kwargs : dict
*key, value* pairs as keyword arguments to find in *values*.

Expand Down Expand Up @@ -148,10 +176,7 @@ def check_in_list(values, /, *, _print_supported_values=True, **kwargs):
# the individual `val == values[i]` ValueError surface.
exists = False
if not exists:
msg = f"{val!r} is not a valid value for {key}"
if _print_supported_values:
msg += f"; supported values are {', '.join(map(repr, values))}"
raise ValueError(msg)
raise ValueError(list_suggestion_error_msg(key, val, values))


def check_shape(shape, /, **kwargs):
Expand Down Expand Up @@ -210,14 +235,7 @@ def getitem_checked(mapping, /, _error_cls=ValueError, **kwargs):
try:
return mapping[v]
except KeyError:
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
raise _error_cls(list_suggestion_error_msg(k, v, mapping.keys())) from None


def caching_module_getattr(cls):
Expand Down
5 changes: 2 additions & 3 deletions lib/matplotlib/_api/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@ class classproperty(Any):
def check_isinstance(
types: type | tuple[type | None, ...], /, **kwargs: Any
) -> None: ...
def check_in_list(
values: Sequence[Any], /, *, _print_supported_values: bool = ..., **kwargs: Any
) -> None: ...
def list_suggestion_error_msg(name: str, potential: Any, values: Sequence[Any]) -> str: ...
def check_in_list(values: Sequence[Any], /, **kwargs: Any) -> None: ...
def check_shape(shape: tuple[int | None, ...], /, **kwargs: NDArray) -> None: ...
def getitem_checked(
mapping: Mapping[Any, _T], /, _error_cls: type[Exception], **kwargs: Any
Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ def list(self):

def __getitem__(self, name):
"""Get an available writer class from its name."""
_api.check_in_list(self._registered, writer=name)
if self.is_available(name):
return self._registered[name]
raise RuntimeError(f"Requested MovieWriter ({name}) not available")
Expand Down
6 changes: 2 additions & 4 deletions lib/matplotlib/colorizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -826,10 +826,8 @@ def _ensure_cmap(cmap, accept_multivariate=False):
# this error message is a variant of _api.check_in_list but gives
# additional hints as to how to access multivariate colormaps

raise ValueError(f"{cmap!r} is not a valid value for cmap"
"; supported values for scalar colormaps are "
f"{', '.join(map(repr, sorted(mpl.colormaps)))}\n"
"See `matplotlib.bivar_colormaps()` and"
raise ValueError(_api.list_suggestion_error_msg('cmap', cmap, mpl.colormaps) +
"\nSee `matplotlib.bivar_colormaps()` and"
" `matplotlib.multivar_colormaps()` for"
" bivariate and multivariate colormaps")

Expand Down
6 changes: 2 additions & 4 deletions lib/matplotlib/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,8 @@ def __init__(self):
self._color_sequences = {**self._BUILTIN_COLOR_SEQUENCES}

def __getitem__(self, item):
try:
return list(self._color_sequences[item])
except KeyError:
raise KeyError(f"{item!r} is not a known color sequence name")
return list(_api.getitem_checked(self._color_sequences, _error_cls=KeyError,
sequence_name=item))

def __iter__(self):
return iter(self._color_sequences)
Expand Down
12 changes: 5 additions & 7 deletions lib/matplotlib/projections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
`matplotlib.projections.polar` may also be of interest.
"""

from .. import axes, _docstring
from .. import _api, axes, _docstring
from .geo import AitoffAxes, HammerAxes, LambertAxes, MollweideAxes
from .polar import PolarAxes

Expand All @@ -78,9 +78,10 @@ def register(self, *projections):
name = projection.name
self._all_projection_types[name] = projection

def get_projection_class(self, name):
def get_projection_class(self, name, _error_cls=KeyError):
"""Get a projection class from its *name*."""
return self._all_projection_types[name]
return _api.getitem_checked(self._all_projection_types, _error_cls=_error_cls,
projection=name)

def get_projection_names(self):
"""Return the names of all projections currently registered."""
Expand Down Expand Up @@ -116,10 +117,7 @@ def get_projection_class(projection=None):
if projection is None:
projection = 'rectilinear'

try:
return projection_registry.get_projection_class(projection)
except KeyError as err:
raise ValueError("Unknown projection %r" % projection) from err
return projection_registry.get_projection_class(projection, _error_cls=ValueError)


get_projection_names = projection_registry.get_projection_names
Expand Down
8 changes: 8 additions & 0 deletions lib/matplotlib/tests/test_animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ def animate(i):
return klass(fig=fig, func=animate, init_func=init, **kwargs)


def test_invalid_writer():
# Note, this triggers for Animation.save as well, but this is a lighter test.
with pytest.raises(ValueError,
match=r"'pllow' is not a valid value for writer\. "
r"Did you mean: 'pillow'\?"):
animation.writers['pllow']


class NullMovieWriter(animation.AbstractMovieWriter):
"""
A minimal MovieWriter. It doesn't actually write anything.
Expand Down
7 changes: 7 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3338,6 +3338,13 @@ def test_scatter_c_facecolor_warning_integration(c, facecolor):
ax.scatter(x, y, c=c, facecolor=facecolor)


def test_invalid_projection():
with pytest.raises(ValueError,
match=r"'aitof' is not a valid value for projection\. "
r"Did you mean: 'aitoff'\?"):
plt.subplots(subplot_kw={'projection': 'aitof'})


def test_as_mpl_axes_api():
# tests the _as_mpl_axes api
class Polar:
Expand Down
11 changes: 8 additions & 3 deletions lib/matplotlib/tests/test_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -1882,10 +1882,15 @@ 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']?"
)):
r"'grays' is not a valid value for colormap\. "
r"Did you mean one of: 'gray', 'Grays', 'gray_r'\?")):
matplotlib.colormaps["grays"]
with pytest.raises(
KeyError,
match=(
"'set' is not a valid value for sequence_name. "
"Did you mean one of: 'Set3', 'Set2', 'Set1'?")):
matplotlib.color_sequences["set"]


def test_multi_norm_creation():
Expand Down
5 changes: 2 additions & 3 deletions lib/matplotlib/tests/test_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,8 @@ 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, match="\'badparam\' is not a valid value for rcParam. "
):
with pytest.raises(KeyError,
match=r"'badparam' is not a valid value for rcParam\."):
with x:
pass
assert mpl.rcParams[PARAM] == other_value
Expand Down
4 changes: 2 additions & 2 deletions lib/matplotlib/tests/test_transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1072,8 +1072,8 @@ def test_scale_swapping(fig_test, fig_ref):

def test_offset_copy_errors():
with pytest.raises(ValueError,
match="'fontsize' is not a valid value for units;"
" supported values are 'dots', 'points', 'inches'"):
match="'fontsize' is not a valid value for units. "
"Supported values are 'dots', 'points', 'inches'"):
mtransforms.offset_copy(None, units='fontsize')

with pytest.raises(ValueError,
Expand Down
Loading