Skip to content

Fix validation of linestyle in rcparams and cycler. #15827

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

Merged
merged 1 commit into from
Dec 8, 2019
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
87 changes: 49 additions & 38 deletions lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@
:file:`matplotlibrc.template` in matplotlib's root source directory.
"""

import ast
from collections.abc import Iterable, Mapping
from functools import partial, reduce
import logging
from numbers import Number
import operator
import os
import re

import numpy as np

import matplotlib as mpl
from matplotlib import animation, cbook
from matplotlib.cbook import ls_mapper
Expand Down Expand Up @@ -532,6 +536,50 @@ def validate_ps_distiller(s):
'ghostscript or xpdf')


# A validator dedicated to the named line styles, based on the items in
# ls_mapper, and a list of possible strings read from Line2D.set_linestyle
_validate_named_linestyle = ValidateInStrings(
'linestyle',
[*ls_mapper.keys(), *ls_mapper.values(), 'None', 'none', ' ', ''],
ignorecase=True)


def _validate_linestyle(ls):
"""
A validator for all possible line styles, the named ones *and*
the on-off ink sequences.
"""
if isinstance(ls, str):
try: # Look first for a valid named line style, like '--' or 'solid'.
return _validate_named_linestyle(ls)
except ValueError:
pass
try:
ls = ast.literal_eval(ls) # Parsing matplotlibrc.
except (SyntaxError, ValueError):
pass # Will error with the ValueError at the end.

def _is_iterable_not_string_like(x):
# Explicitly exclude bytes/bytearrays so that they are not
# nonsensically interpreted as sequences of numbers (codepoints).
return np.iterable(x) and not isinstance(x, (str, bytes, bytearray))

# (offset, (on, off, on, off, ...))
if (_is_iterable_not_string_like(ls)
and len(ls) == 2
and isinstance(ls[0], (type(None), Number))
and _is_iterable_not_string_like(ls[1])
and len(ls[1]) % 2 == 0
and all(isinstance(elem, Number) for elem in ls[1])):
return ls
# For backcompat: (on, off, on, off, ...); the offset is implicitly None.
if (_is_iterable_not_string_like(ls)
and len(ls) % 2 == 0
and all(isinstance(elem, Number) for elem in ls)):
return (None, ls)
raise ValueError(f"linestyle {ls!r} is not a valid on-off ink sequence.")


validate_joinstyle = ValidateInStrings('joinstyle',
['miter', 'round', 'bevel'],
ignorecase=True)
Expand Down Expand Up @@ -748,7 +796,7 @@ def validate_hatch(s):
'color': _listify_validator(validate_color_for_prop_cycle,
allow_stringlist=True),
'linewidth': validate_floatlist,
'linestyle': validate_stringlist,
'linestyle': _listify_validator(_validate_linestyle),
'facecolor': validate_colorlist,
'edgecolor': validate_colorlist,
'joinstyle': validate_joinstylelist,
Expand Down Expand Up @@ -970,43 +1018,6 @@ def validate_webagg_address(s):
raise ValueError("'webagg.address' is not a valid IP address")


# A validator dedicated to the named line styles, based on the items in
# ls_mapper, and a list of possible strings read from Line2D.set_linestyle
_validate_named_linestyle = ValidateInStrings(
'linestyle',
[*ls_mapper.keys(), *ls_mapper.values(), 'None', 'none', ' ', ''],
ignorecase=True)


def _validate_linestyle(ls):
"""
A validator for all possible line styles, the named ones *and*
the on-off ink sequences.
"""
# Look first for a valid named line style, like '--' or 'solid' Also
# includes bytes(-arrays) here (they all fail _validate_named_linestyle);
# otherwise, if *ls* is of even-length, it will be passed to the instance
# of validate_nseq_float, which will return an absurd on-off ink
# sequence...
if isinstance(ls, (str, bytes, bytearray)):
return _validate_named_linestyle(ls)

# Look for an on-off ink sequence (in points) *of even length*.
# Offset is set to None.
try:
if len(ls) % 2 != 0:
raise ValueError("the linestyle sequence {!r} is not of even "
"length.".format(ls))

return (None, validate_nseq_float()(ls))

except (ValueError, TypeError):
# TypeError can be raised inside the instance of validate_nseq_float,
# by wrong types passed to float(), like NoneType.
raise ValueError("linestyle {!r} is not a valid on-off ink "
"sequence.".format(ls))


validate_axes_titlelocation = ValidateInStrings('axes.titlelocation', ['left', 'center', 'right'])

# a map from key -> value, converter
Expand Down
11 changes: 7 additions & 4 deletions lib/matplotlib/tests/test_rcparams.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,18 +370,21 @@ def generate_validator_testcases(valid):
('', ''), (' ', ' '),
('None', 'none'), ('none', 'none'),
('DoTtEd', 'dotted'), # case-insensitive
(['1.23', '4.56'], (None, [1.23, 4.56])),
('1, 3', (None, (1, 3))),
([1.23, 456], (None, [1.23, 456.0])),
([1, 2, 3, 4], (None, [1.0, 2.0, 3.0, 4.0])),
((None, [1, 2]), (None, [1, 2])),
((0, [1, 2]), (0, [1, 2])),
((-1, [1, 2]), (-1, [1, 2])),
),
'fail': (('aardvark', ValueError), # not a valid string
(b'dotted', ValueError),
('dotted'.encode('utf-16'), ValueError),
((None, [1, 2]), ValueError), # (offset, dashes) != OK
((0, [1, 2]), ValueError), # idem
((-1, [1, 2]), ValueError), # idem
([1, 2, 3], ValueError), # sequence with odd length
(1.23, ValueError), # not a sequence
(("a", [1, 2]), ValueError), # wrong explicit offset
((1, [1, 2, 3]), ValueError), # odd length sequence
(([1, 2], 1), ValueError), # inverted offset/onoff
)
},
)
Expand Down