Skip to content

Commit 2b5b24e

Browse files
committed
Fix validation of linestyle in rcparams and cycler.
Correcly use _validate_linestyle instead of validate_stringlist in the cycler linestyle validator. For compat, support both the (on, off, on, off, ...) and (offset, (on, off, on, off, ...)) forms. Don't support passing on, off, etc. as *individual* separate strings anymore as that's not a realistic use case (unlike passing the whole thing as a single string, which is supported for parsing the rc file).
1 parent 08008d5 commit 2b5b24e

File tree

2 files changed

+53
-42
lines changed

2 files changed

+53
-42
lines changed

lib/matplotlib/rcsetup.py

+49-38
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@
1414
:file:`matplotlibrc.template` in matplotlib's root source directory.
1515
"""
1616

17+
import ast
1718
from collections.abc import Iterable, Mapping
1819
from functools import partial, reduce
1920
import logging
21+
from numbers import Number
2022
import operator
2123
import os
2224
import re
2325

26+
import numpy as np
27+
2428
import matplotlib as mpl
2529
from matplotlib import animation, cbook
2630
from matplotlib.cbook import ls_mapper
@@ -532,6 +536,50 @@ def validate_ps_distiller(s):
532536
'ghostscript or xpdf')
533537

534538

539+
# A validator dedicated to the named line styles, based on the items in
540+
# ls_mapper, and a list of possible strings read from Line2D.set_linestyle
541+
_validate_named_linestyle = ValidateInStrings(
542+
'linestyle',
543+
[*ls_mapper.keys(), *ls_mapper.values(), 'None', 'none', ' ', ''],
544+
ignorecase=True)
545+
546+
547+
def _validate_linestyle(ls):
548+
"""
549+
A validator for all possible line styles, the named ones *and*
550+
the on-off ink sequences.
551+
"""
552+
if isinstance(ls, str):
553+
try: # Look first for a valid named line style, like '--' or 'solid'.
554+
return _validate_named_linestyle(ls)
555+
except ValueError:
556+
pass
557+
try:
558+
ls = ast.literal_eval(ls)
559+
except (SyntaxError, ValueError):
560+
pass # Will error with the ValueError at the end.
561+
562+
def _is_iterable_not_string_like(x):
563+
# Explicitly exclude bytes/bytearrays so that they are not
564+
# nonsensically interpreted as sequences of numbers (codepoints).
565+
return np.iterable(x) and not isinstance(x, (str, bytes, bytearray))
566+
567+
# (offset, (on, off, on, off, ...))
568+
if (_is_iterable_not_string_like(ls)
569+
and len(ls) == 2
570+
and isinstance(ls[0], (type(None), Number))
571+
and _is_iterable_not_string_like(ls[1])
572+
and len(ls[1]) % 2 == 0
573+
and all(isinstance(elem, Number) for elem in ls[1])):
574+
return ls
575+
# For backcompat: (on, off, on, off, ...); the offset is implicitly None.
576+
if (_is_iterable_not_string_like(ls)
577+
and len(ls) % 2 == 0
578+
and all(isinstance(elem, Number) for elem in ls)):
579+
return (None, ls)
580+
raise ValueError(f"linestyle {ls!r} is not a valid on-off ink sequence.")
581+
582+
535583
validate_joinstyle = ValidateInStrings('joinstyle',
536584
['miter', 'round', 'bevel'],
537585
ignorecase=True)
@@ -748,7 +796,7 @@ def validate_hatch(s):
748796
'color': _listify_validator(validate_color_for_prop_cycle,
749797
allow_stringlist=True),
750798
'linewidth': validate_floatlist,
751-
'linestyle': validate_stringlist,
799+
'linestyle': _listify_validator(_validate_linestyle),
752800
'facecolor': validate_colorlist,
753801
'edgecolor': validate_colorlist,
754802
'joinstyle': validate_joinstylelist,
@@ -970,43 +1018,6 @@ def validate_webagg_address(s):
9701018
raise ValueError("'webagg.address' is not a valid IP address")
9711019

9721020

973-
# A validator dedicated to the named line styles, based on the items in
974-
# ls_mapper, and a list of possible strings read from Line2D.set_linestyle
975-
_validate_named_linestyle = ValidateInStrings(
976-
'linestyle',
977-
[*ls_mapper.keys(), *ls_mapper.values(), 'None', 'none', ' ', ''],
978-
ignorecase=True)
979-
980-
981-
def _validate_linestyle(ls):
982-
"""
983-
A validator for all possible line styles, the named ones *and*
984-
the on-off ink sequences.
985-
"""
986-
# Look first for a valid named line style, like '--' or 'solid' Also
987-
# includes bytes(-arrays) here (they all fail _validate_named_linestyle);
988-
# otherwise, if *ls* is of even-length, it will be passed to the instance
989-
# of validate_nseq_float, which will return an absurd on-off ink
990-
# sequence...
991-
if isinstance(ls, (str, bytes, bytearray)):
992-
return _validate_named_linestyle(ls)
993-
994-
# Look for an on-off ink sequence (in points) *of even length*.
995-
# Offset is set to None.
996-
try:
997-
if len(ls) % 2 != 0:
998-
raise ValueError("the linestyle sequence {!r} is not of even "
999-
"length.".format(ls))
1000-
1001-
return (None, validate_nseq_float()(ls))
1002-
1003-
except (ValueError, TypeError):
1004-
# TypeError can be raised inside the instance of validate_nseq_float,
1005-
# by wrong types passed to float(), like NoneType.
1006-
raise ValueError("linestyle {!r} is not a valid on-off ink "
1007-
"sequence.".format(ls))
1008-
1009-
10101021
validate_axes_titlelocation = ValidateInStrings('axes.titlelocation', ['left', 'center', 'right'])
10111022

10121023
# a map from key -> value, converter

lib/matplotlib/tests/test_rcparams.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -370,16 +370,16 @@ def generate_validator_testcases(valid):
370370
('', ''), (' ', ' '),
371371
('None', 'none'), ('none', 'none'),
372372
('DoTtEd', 'dotted'), # case-insensitive
373-
(['1.23', '4.56'], (None, [1.23, 4.56])),
373+
('1, 3', (None, (1, 3))),
374374
([1.23, 456], (None, [1.23, 456.0])),
375375
([1, 2, 3, 4], (None, [1.0, 2.0, 3.0, 4.0])),
376+
((None, [1, 2]), (None, [1, 2])),
377+
((0, [1, 2]), (0, [1, 2])),
378+
((-1, [1, 2]), (-1, [1, 2])),
376379
),
377380
'fail': (('aardvark', ValueError), # not a valid string
378381
(b'dotted', ValueError),
379382
('dotted'.encode('utf-16'), ValueError),
380-
((None, [1, 2]), ValueError), # (offset, dashes) != OK
381-
((0, [1, 2]), ValueError), # idem
382-
((-1, [1, 2]), ValueError), # idem
383383
([1, 2, 3], ValueError), # sequence with odd length
384384
(1.23, ValueError), # not a sequence
385385
)

0 commit comments

Comments
 (0)