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

Skip to content

Commit 44f74e3

Browse files
committed
Finalized the validating cycler design.
1 parent 55bf053 commit 44f74e3

File tree

3 files changed

+90
-19
lines changed

3 files changed

+90
-19
lines changed

lib/matplotlib/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,13 @@
116116
import contextlib
117117
import distutils.sysconfig
118118

119-
from cycler import cycler
120-
121119
# cbook must import matplotlib only within function
122120
# definitions, so it is safe to import from it here.
123121
from matplotlib.cbook import is_string_like, mplDeprecation
124122
from matplotlib.compat import subprocess
125123
from matplotlib.rcsetup import (defaultParams,
126-
validate_backend)
124+
validate_backend,
125+
cycler)
127126

128127
import numpy
129128
from matplotlib.externals.six.moves.urllib.request import urlopen

lib/matplotlib/rcsetup.py

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818

1919
from matplotlib.externals import six
2020

21+
import operator
2122
import os
2223
import warnings
2324
from matplotlib.fontconfig_pattern import parse_fontconfig_pattern
2425
from matplotlib.colors import is_color_like
2526

26-
from cycler import cycler, Cycler
27+
# Don't let the original cycler collide with our validating cycler
28+
from cycler import Cycler, cycler as ccycler
2729

2830
#interactive_bk = ['gtk', 'gtkagg', 'gtkcairo', 'qt4agg',
2931
# 'tkagg', 'wx', 'wxagg', 'cocoaagg', 'webagg']
@@ -579,6 +581,7 @@ def __call__(self, s):
579581

580582
validate_grid_axis = ValidateInStrings('axes.grid.axis', ['x', 'y', 'both'])
581583

584+
582585
_prop_validators = {
583586
'color': validate_colorlist,
584587
'linewidth': validate_floatlist,
@@ -607,24 +610,86 @@ def __call__(self, s):
607610
'ms': 'markersize',
608611
}
609612

610-
def _cycler_wrap(prop, vals):
613+
def cycler(*args, **kwargs):
611614
"""
612-
This is an internal wrapper around :func:`cycler`
613-
that will be used in :func:`validate_cycler` to normalize
614-
and validate property inputs.
615+
Creates a :class:`cycler.Cycler` object much like :func:`cycler.cycler`,
616+
but includes input validation.
617+
618+
cyl(arg)
619+
cyl(label, itr)
620+
cyl(label1=itr1[, label2=itr2[, ...]])
621+
622+
Form 1 simply copies a given `Cycler` object.
623+
624+
Form 2 composes a `Cycler` as an inner product of the
625+
pairs of keyword arguments. In other words, all of the
626+
iterables are cycled simultaneously, as if through zip().
627+
628+
Form 3 creates a `Cycler` from a label and an iterable.
629+
This is useful for when the label cannot be a keyword argument
630+
(e.g., an integer or a name that has a space in it).
631+
632+
Parameters
633+
----------
634+
arg : Cycler
635+
Copy constructor for Cycler.
636+
637+
label : name
638+
The property key. In the 2-arg form of the function,
639+
the label can be any hashable object. In the keyword argument
640+
form of the function, it must be a valid python identifier.
641+
642+
itr : iterable
643+
Finite length iterable of the property values.
644+
645+
Returns
646+
-------
647+
cycler : Cycler
648+
New :class:`cycler.Cycler` for the given properties
615649
616650
"""
617-
norm_prop = _prop_aliases.get(prop, prop)
618-
validator = _prop_validators.get(norm_prop, None)
619-
if validator is None:
620-
raise TypeError("Unknown artist property: %s" % prop)
621-
vals = validator(vals)
622-
# We will normalize the property names as well to reduce
623-
# the amount of alias handling code elsewhere.
624-
return cycler(norm_prop, vals)
651+
if args and kwargs:
652+
raise TypeError("cyl() can only accept positional OR keyword "
653+
"arguments -- not both.")
654+
elif not args and not kwargs:
655+
raise TypeError("cyl() must have positional OR keyword arguments")
656+
657+
if len(args) == 1:
658+
if not isinstance(args[0], Cycler):
659+
raise TypeError("If only one positional argument given, it must "
660+
" be a Cycler instance.")
661+
662+
c = args[0]
663+
unknowns = c.keys - (set(_prop_validators.keys()) |
664+
set(_prop_aliases.keys()))
665+
if unknowns:
666+
# This is about as much validation I can do
667+
raise TypeError("Unknown artist properties: %s" % unknowns)
668+
else:
669+
return Cycler(c)
670+
elif len(args) == 2:
671+
pairs = [(args[0], args[1])]
672+
elif len(args) > 2:
673+
raise TypeError("No more than 2 positional arguments allowed")
674+
else:
675+
pairs = six.iteritems(kwargs)
676+
677+
validated = []
678+
for prop, vals in pairs:
679+
norm_prop = _prop_aliases.get(prop, prop)
680+
validator = _prop_validators.get(norm_prop, None)
681+
if validator is None:
682+
raise TypeError("Unknown artist property: %s" % prop)
683+
vals = validator(vals)
684+
# We will normalize the property names as well to reduce
685+
# the amount of alias handling code elsewhere.
686+
validated.append((norm_prop, vals))
687+
688+
return reduce(operator.add, (ccycler(k, v) for k, v in validated))
689+
625690

626691
def validate_cycler(s):
627-
'return a cycler object from a string repr or the object itself'
692+
'return a Cycler object from a string repr or the object itself'
628693
if isinstance(s, six.string_types):
629694
try:
630695
# TODO: We might want to rethink this...
@@ -645,7 +710,7 @@ def validate_cycler(s):
645710
# someone managed to hack the cycler module. But, if
646711
# someone does that, this wouldn't make anything
647712
# worse because we have to import the module anyway.
648-
s = eval(s, {'cycler': _cycler_wrap})
713+
s = eval(s, {'cycler': cycler})
649714
except BaseException as e:
650715
raise ValueError("'%s' is not a valid cycler construction: %s" %
651716
(s, e))
@@ -851,7 +916,7 @@ def validate_cycler(s):
851916
# This entry can be either a cycler object or a
852917
# string repr of a cycler-object, which gets eval()'ed
853918
# to create the object.
854-
'axes.prop_cycle': [cycler('color', 'bgrcmyk'),
919+
'axes.prop_cycle': [ccycler('color', 'bgrcmyk'),
855920
validate_cycler],
856921
'axes.xmargin': [0, ValidateInterval(0, 1,
857922
closedmin=True,

lib/matplotlib/tests/test_rcparams.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,11 @@ def test_validators():
307307
cycler("mew", [2, 3, 5]))""",
308308
(cycler("color", 'rgb') +
309309
cycler("markeredgewidth", [2, 3, 5]))),
310+
("cycler(c='rgb', lw=[1, 2, 3])",
311+
cycler('color', 'rgb') + cycler('linewidth', [1, 2, 3])),
312+
("cycler('c', 'rgb') * cycler('linestyle', ['-', '--'])",
313+
(cycler('color', 'rgb') *
314+
cycler('linestyle', ['-', '--']))),
310315
),
311316
# This is *so* incredibly important: validate_cycler() eval's
312317
# an arbitrary string! I think I have it locked down enough,
@@ -326,6 +331,8 @@ def test_validators():
326331
ValueError), # Should not be able to define anything
327332
# even if it does return a cycler
328333
('cycler("waka", [1, 2, 3])', ValueError), # not a property
334+
('cycler(c=[1, 2, 3])', ValueError), # invalid values
335+
("cycler(lw=['a', 'b', 'c'])", ValueError), # invalid values
329336
)
330337
},
331338
)

0 commit comments

Comments
 (0)