Skip to content

Commit f11e538

Browse files
authored
Merge pull request #15827 from anntzer/linestyle-validation
Fix validation of linestyle in rcparams and cycler.
2 parents 8a1cc04 + 4899580 commit f11e538

File tree

2 files changed

+56
-42
lines changed

2 files changed

+56
-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) # Parsing matplotlibrc.
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

+7-4
Original file line numberDiff line numberDiff line change
@@ -370,18 +370,21 @@ 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
385+
(("a", [1, 2]), ValueError), # wrong explicit offset
386+
((1, [1, 2, 3]), ValueError), # odd length sequence
387+
(([1, 2], 1), ValueError), # inverted offset/onoff
385388
)
386389
},
387390
)

0 commit comments

Comments
 (0)