From 5c9ccf09d4b341c4be647d773372dac93bb1f47f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 24 Sep 2014 23:37:34 -0400 Subject: [PATCH 01/19] MNT : make sure to cast to unicode --- lib/matplotlib/rcsetup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index e0bfb8b96395..54d79e234d72 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -263,7 +263,7 @@ def validate_colorlist(s): def validate_stringlist(s): 'return a list' if isinstance(s, six.string_types): - return [v.strip() for v in s.split(',')] + return [six.text_type(v.strip()) for v in s.split(',')] else: assert type(s) in [list, tuple] return [six.text_type(v) for v in s] From ec7276c1c1ae778653d9d801686c25a47956fe56 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 24 Sep 2014 23:37:54 -0400 Subject: [PATCH 02/19] BUG : make rcParams.update validates inputs Close #3470 Over-ride `update` so that inputs are validated. --- lib/matplotlib/__init__.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 8a20272636f0..de1ef9384241 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -821,7 +821,7 @@ class RcParams(dict): def __setitem__(self, key, val): try: if key in _deprecated_map: - alt_key, alt_val = _deprecated_map[key] + alt_key, alt_val = _deprecated_map[key] warnings.warn(self.msg_depr % (key, alt_key)) key = alt_key val = alt_val(val) @@ -840,7 +840,7 @@ def __setitem__(self, key, val): def __getitem__(self, key): if key in _deprecated_map: - alt_key, alt_val = _deprecated_map[key] + alt_key, alt_val = _deprecated_map[key] warnings.warn(self.msg_depr % (key, alt_key)) key = alt_key elif key in _deprecated_ignore_map: @@ -849,6 +849,16 @@ def __getitem__(self, key): key = alt return dict.__getitem__(self, key) + # http://stackoverflow.com/questions/2390827/how-to-properly-subclass-dict-and-override-get-set + # the default dict `update` does not use __setitem__ + # so rcParams.update(...) (such as in seaborn) side-steps + # all of the validation over-ride update to force + # through __setitem__ + def update(self, *args, **kwargs): + + for k, v in six.iteritems(dict(*args, **kwargs)): + self[k] = v + def __repr__(self): import pprint class_name = self.__class__.__name__ From a298ab2859561796f1f16efe440d3722502797b6 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 24 Sep 2014 23:44:38 -0400 Subject: [PATCH 03/19] MNT : simplify and relax validation on nseq Simplify and relax the validation for sequences of floats and ints. - unified logic - input is not restricted to coma-separated lists, list, and tuples. any length 2 sequence (like an array) will now work. --- lib/matplotlib/rcsetup.py | 56 +++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 54d79e234d72..5777db1f8260 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -172,50 +172,54 @@ def validate_maskedarray(v): ' please delete it from your matplotlibrc file') -class validate_nseq_float: +_seq_err_msg = ('You must supply exactly {n:d} values, you provided ' + '{num:d} values: {s}') + +_str_err_msg = ('You must supply exactly {n:d} comma-separated values, ' + 'you provided ' + '{num:d} comma-separated values: {s}') + + +class validate_nseq_float(object): def __init__(self, n): self.n = n def __call__(self, s): """return a seq of n floats or raise""" if isinstance(s, six.string_types): - ss = s.split(',') - if len(ss) != self.n: - raise ValueError( - 'You must supply exactly %d comma separated values' % - self.n) - try: - return [float(val) for val in ss] - except ValueError: - raise ValueError('Could not convert all entries to floats') + s = s.split(',') + err_msg = _str_err_msg else: - assert type(s) in (list, tuple) - if len(s) != self.n: - raise ValueError('You must supply exactly %d values' % self.n) + err_msg = _seq_err_msg + + if len(s) != self.n: + raise ValueError(err_msg.format(n=self.n, num=len(s), s=s)) + + try: return [float(val) for val in s] + except ValueError: + raise ValueError('Could not convert all entries to floats') -class validate_nseq_int: +class validate_nseq_int(object): def __init__(self, n): self.n = n def __call__(self, s): """return a seq of n ints or raise""" if isinstance(s, six.string_types): - ss = s.split(',') - if len(ss) != self.n: - raise ValueError( - 'You must supply exactly %d comma separated values' % - self.n) - try: - return [int(val) for val in ss] - except ValueError: - raise ValueError('Could not convert all entries to ints') + s = s.split(',') + err_msg = _str_err_msg else: - assert type(s) in (list, tuple) - if len(s) != self.n: - raise ValueError('You must supply exactly %d values' % self.n) + err_msg = _seq_err_msg + + if len(s) != self.n: + raise ValueError(err_msg.format(n=self.n, num=len(s), s=s)) + + try: return [int(val) for val in s] + except ValueError: + raise ValueError('Could not convert all entries to ints') def validate_color(s): From 86029129361f0487d1d4b2ab0e35889b81e36ae5 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 1 Oct 2014 00:51:59 -0400 Subject: [PATCH 04/19] BUG : validate_stringlist should drop empty strings This is a minor api change, but I can't think of why one would want to carry around empty strings. --- lib/matplotlib/rcsetup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 5777db1f8260..8f756f8e4e57 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -267,10 +267,10 @@ def validate_colorlist(s): def validate_stringlist(s): 'return a list' if isinstance(s, six.string_types): - return [six.text_type(v.strip()) for v in s.split(',')] + return [six.text_type(v.strip()) for v in s.split(',') if v.strip()] else: assert type(s) in [list, tuple] - return [six.text_type(v) for v in s] + return [six.text_type(v) for v in s if v] validate_orientation = ValidateInStrings( From 31fe5e0d4573e62f6498f77f7debbb82b51c429a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 1 Oct 2014 00:53:10 -0400 Subject: [PATCH 05/19] BUG : give animation args valid default values --- lib/matplotlib/rcsetup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 8f756f8e4e57..aab149aa17ad 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -801,21 +801,21 @@ def __call__(self, s): # Path to FFMPEG binary. If just binary name, subprocess uses $PATH. 'animation.ffmpeg_path': ['ffmpeg', six.text_type], - ## Additional arguments for ffmpeg movie writer (using pipes) - 'animation.ffmpeg_args': ['', validate_stringlist], + # Additional arguments for ffmpeg movie writer (using pipes) + 'animation.ffmpeg_args': [[], validate_stringlist], # Path to AVConv binary. If just binary name, subprocess uses $PATH. 'animation.avconv_path': ['avconv', six.text_type], # Additional arguments for avconv movie writer (using pipes) - 'animation.avconv_args': ['', validate_stringlist], + 'animation.avconv_args': [[], validate_stringlist], # Path to MENCODER binary. If just binary name, subprocess uses $PATH. 'animation.mencoder_path': ['mencoder', six.text_type], # Additional arguments for mencoder movie writer (using pipes) - 'animation.mencoder_args': ['', validate_stringlist], + 'animation.mencoder_args': [[], validate_stringlist], # Path to convert binary. If just binary name, subprocess uses $PATH 'animation.convert_path': ['convert', six.text_type], # Additional arguments for mencoder movie writer (using pipes) - 'animation.convert_args': ['', validate_stringlist]} + 'animation.convert_args': [[], validate_stringlist]} if __name__ == '__main__': From 30eaa0be51682c33e7065e36c372aeb2bc48ce57 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 1 Oct 2014 00:56:37 -0400 Subject: [PATCH 06/19] TST : minor tweaks to animation smoketests --- lib/matplotlib/tests/test_animation.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_animation.py b/lib/matplotlib/tests/test_animation.py index c1fd1d63a9bd..97ffe5327c60 100644 --- a/lib/matplotlib/tests/test_animation.py +++ b/lib/matplotlib/tests/test_animation.py @@ -28,7 +28,7 @@ def test_save_animation_smoketest(): yield check_save_animation, writer, extension -@with_setup(CleanupTest.setup_class, CleanupTest.teardown_class) +@cleanup def check_save_animation(writer, extension='mp4'): if not animation.writers.is_available(writer): raise KnownFailureTest("writer '%s' not available on this system" @@ -39,6 +39,9 @@ def check_save_animation(writer, extension='mp4'): fig, ax = plt.subplots() line, = ax.plot([], []) + ax.set_xlim(0, 10) + ax.set_ylim(-1, 1) + def init(): line.set_data([], []) return line, From 1c2bc58e231914be3f68a5a25bb65727c0c13637 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 1 Oct 2014 10:31:24 -0400 Subject: [PATCH 07/19] TST : added tests for some validation functions --- lib/matplotlib/tests/test_rcparams.py | 76 +++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 928393ecd5c7..863ee7d6342a 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -9,8 +9,15 @@ import matplotlib as mpl from matplotlib.tests import assert_str_equal -from nose.tools import assert_true, assert_raises +from nose.tools import assert_true, assert_raises, assert_equal import nose +from itertools import chain +import numpy as np +from matplotlib.rcsetup import (validate_bool_maybe_none, + validate_stringlist, + validate_bool, + validate_nseq_int, + validate_nseq_float) mpl.rc('text', usetex=False) @@ -18,8 +25,8 @@ fname = os.path.join(os.path.dirname(__file__), 'test_rcparams.rc') -def test_rcparams(): +def test_rcparams(): usetex = mpl.rcParams['text.usetex'] linewidth = mpl.rcParams['lines.linewidth'] @@ -55,7 +62,6 @@ def test_RcParams_class(): 'font.weight': 'normal', 'font.size': 12}) - if six.PY3: expected_repr = """ RcParams({'font.cursive': ['Apple Chancery', @@ -96,6 +102,7 @@ def test_RcParams_class(): assert ['font.cursive', 'font.size'] == sorted(rc.find_all('i[vz]').keys()) assert ['font.family'] == list(six.iterkeys(rc.find_all('family'))) + def test_Bug_2543(): # Test that it possible to add all values to itself / deepcopy # This was not possible because validate_bool_maybe_none did not @@ -116,7 +123,6 @@ def test_Bug_2543(): with mpl.rc_context(): from copy import deepcopy _deep_copy = deepcopy(mpl.rcParams) - from matplotlib.rcsetup import validate_bool_maybe_none, validate_bool # real test is that this does not raise assert_true(validate_bool_maybe_none(None) is None) assert_true(validate_bool_maybe_none("none") is None) @@ -126,6 +132,7 @@ def test_Bug_2543(): mpl.rcParams['svg.embed_char_paths'] = False assert_true(mpl.rcParams['svg.fonttype'] == "none") + def test_Bug_2543_newer_python(): # only split from above because of the usage of assert_raises # as a context manager, which only works in 2.7 and above @@ -141,5 +148,64 @@ def test_Bug_2543_newer_python(): mpl.rcParams['svg.fonttype'] = True if __name__ == '__main__': - import nose nose.runmodule(argv=['-s', '--with-doctest'], exit=False) + + +def _validation_test_helper(validator, arg, target): + res = validator(arg) + assert_equal(res, target) + + +def _validation_fail_helper(validator, arg, exception_type): + with assert_raises(exception_type): + validator(arg) + + +def test_validators(): + validation_tests = ( + {'validator': validate_bool, + 'success': chain(((_, True) for _ in + ('t', 'y', 'yes', 'on', 'true', '1', 1, True)), + ((_, False) for _ in + ('f', 'n', 'no', 'off', 'false', '0', 0, False))), + 'fail': ((_, ValueError) + for _ in ('aardvark', 2, -1, [], ))}, + {'validator': validate_stringlist, + 'success': (('', []), + ('a,b', ['a', 'b']), + ('aardvark', ['aardvark']), + ('aardvark, ', ['aardvark']), + ('aardvark, ,', ['aardvark']), + (['a', 'b'], ['a', 'b']), + (('a', 'b'), ['a', 'b']), + ((1, 2), ['1', '2'])), + 'fail': ((dict(), AssertionError), + (1, AssertionError),) + }, + {'validator': validate_nseq_int(2), + 'success': ((_, [1, 2]) + for _ in ('1, 2', [1.5, 2.5], [1, 2], + (1, 2), np.array((1, 2)))), + 'fail': ((_, ValueError) + for _ in ('aardvark', ('a', 1), + (1, 2, 3) + )) + }, + {'validator': validate_nseq_float(2), + 'success': ((_, [1.5, 2.5]) + for _ in ('1.5, 2.5', [1.5, 2.5], [1.5, 2.5], + (1.5, 2.5), np.array((1.5, 2.5)))), + 'fail': ((_, ValueError) + for _ in ('aardvark', ('a', 1), + (1, 2, 3) + )) + } + + ) + + for validator_dict in validation_tests: + validator = validator_dict['validator'] + for arg, target in validator_dict['success']: + yield _validation_test_helper, validator, arg, target + for arg, error_type in validator_dict['fail']: + yield _validation_fail_helper, validator, arg, error_type From 1f063134b31c3b73dd8fbc93ec82237c688e16d1 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 1 Oct 2014 10:43:31 -0400 Subject: [PATCH 08/19] BUG : validate input on the way into rcparams Same as `update`, the default behavior of `dict.__init__` does not call `__setitem__` so we have not been validating values passed at creation time. Update the tests to account for the validation. --- lib/matplotlib/__init__.py | 5 +++++ lib/matplotlib/tests/test_rcparams.py | 16 ++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index de1ef9384241..eea5e552d4bb 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -818,6 +818,11 @@ class RcParams(dict): msg_depr = "%s is deprecated and replaced with %s; please use the latter." msg_depr_ignore = "%s is deprecated and ignored. Use %s" + # validate values on the way in + def __init__(self, *args, **kwargs): + for k, v in six.iteritems(dict(*args, **kwargs)): + self[k] = v + def __setitem__(self, key, val): try: if key in _deprecated_map: diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 863ee7d6342a..ee3ad41ea18e 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -68,8 +68,8 @@ def test_RcParams_class(): 'Textile', 'Zapf Chancery', 'cursive'], - 'font.family': 'sans-serif', - 'font.size': 12, + 'font.family': ['sans-serif'], + 'font.size': 12.0, 'font.weight': 'normal'})""".lstrip() else: expected_repr = """ @@ -77,8 +77,8 @@ def test_RcParams_class(): u'Textile', u'Zapf Chancery', u'cursive'], - u'font.family': u'sans-serif', - u'font.size': 12, + u'font.family': [u'sans-serif'], + u'font.size': 12.0, u'font.weight': u'normal'})""".lstrip() assert_str_equal(expected_repr, repr(rc)) @@ -86,14 +86,14 @@ def test_RcParams_class(): if six.PY3: expected_str = """ font.cursive: ['Apple Chancery', 'Textile', 'Zapf Chancery', 'cursive'] -font.family: sans-serif -font.size: 12 +font.family: ['sans-serif'] +font.size: 12.0 font.weight: normal""".lstrip() else: expected_str = """ font.cursive: [u'Apple Chancery', u'Textile', u'Zapf Chancery', u'cursive'] -font.family: sans-serif -font.size: 12 +font.family: [u'sans-serif'] +font.size: 12.0 font.weight: normal""".lstrip() assert_str_equal(expected_str, str(rc)) From a88f5dd77d8fb5e716f363085d623e81d0c93e6d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 1 Oct 2014 11:12:53 -0400 Subject: [PATCH 09/19] TST : tests to make sure rcparams get validated - assert raise on invalid update - assert raise on invalid init --- lib/matplotlib/tests/test_rcparams.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index ee3ad41ea18e..0e982e2f579f 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -103,6 +103,19 @@ def test_RcParams_class(): assert ['font.family'] == list(six.iterkeys(rc.find_all('family'))) +def test_rcparams_update(): + rc = mpl.RcParams({'figure.figsize': (3.5, 42)}) + bad_dict = {'figure.figsize': (3.5, 42, 1)} + # make sure validation happens on input + with assert_raises(ValueError): + rc.update(bad_dict) + + +def test_rcparams_init(): + with assert_raises(ValueError): + mpl.RcParams({'figure.figsize': (3.5, 42, 1)}) + + def test_Bug_2543(): # Test that it possible to add all values to itself / deepcopy # This was not possible because validate_bool_maybe_none did not From 0aa2fcd6282267da0d3de8e6f30334604d88f75a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 1 Oct 2014 14:14:05 -0400 Subject: [PATCH 10/19] TST : skip the fail tests on 2.6 --- lib/matplotlib/tests/test_rcparams.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 0e982e2f579f..2e1bdadef11c 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -170,6 +170,9 @@ def _validation_test_helper(validator, arg, target): def _validation_fail_helper(validator, arg, exception_type): + if sys.version_info[:2] < (2, 7): + raise nose.SkipTest("assert_raises as context manager not " + "supported with Python < 2.7") with assert_raises(exception_type): validator(arg) From d9ab665ac98bb51520fad9d63b60379d092b9578 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 1 Oct 2014 14:16:35 -0400 Subject: [PATCH 11/19] MNT : no deprecated rcparams in RcParamDefault suppresses possibly scary warnings --- lib/matplotlib/__init__.py | 23 +++++++++++++++-------- lib/matplotlib/rcsetup.py | 4 +++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index eea5e552d4bb..a140e004c054 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -191,7 +191,7 @@ def _forward_ilshift(self, other): from matplotlib.rcsetup import (defaultParams, - validate_backend) + validate_backend, obsolete_rcparams) major, minor1, minor2, s, tmp = sys.version_info _python24 = (major == 2 and minor1 >= 4) or major >= 3 @@ -814,7 +814,8 @@ class RcParams(dict): """ validate = dict((key, converter) for key, (default, converter) in - six.iteritems(defaultParams)) + six.iteritems(defaultParams) + if key not in obsolete_rcparams) msg_depr = "%s is deprecated and replaced with %s; please use the latter." msg_depr_ignore = "%s is deprecated and ignored. Use %s" @@ -917,8 +918,9 @@ def rc_params(fail_on_error=False): if not os.path.exists(fname): # this should never happen, default in mpl-data should always be found message = 'could not find rc file; returning defaults' - ret = RcParams([(key, default) for key, (default, _) in \ - six.iteritems(defaultParams)]) + ret = RcParams([(key, default) for key, (default, _) in + six.iteritems(defaultParams) + if key not in obsolete_rcparams]) warnings.warn(message) return ret @@ -1040,7 +1042,8 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): return config_from_file iter_params = six.iteritems(defaultParams) - config = RcParams([(key, default) for key, (default, _) in iter_params]) + config = RcParams([(key, default) for key, (default, _) in iter_params + if key not in obsolete_rcparams]) config.update(config_from_file) verbose.set_level(config['verbose.level']) @@ -1082,16 +1085,20 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): rcParamsOrig = rcParams.copy() -rcParamsDefault = RcParams([(key, default) for key, (default, converter) in \ - six.iteritems(defaultParams)]) +rcParamsDefault = RcParams([(key, default) for key, (default, converter) in + six.iteritems(defaultParams) + if key not in obsolete_rcparams]) -rcParams['ps.usedistiller'] = checkdep_ps_distiller(rcParams['ps.usedistiller']) + +rcParams['ps.usedistiller'] = checkdep_ps_distiller( + rcParams['ps.usedistiller']) rcParams['text.usetex'] = checkdep_usetex(rcParams['text.usetex']) if rcParams['axes.formatter.use_locale']: import locale locale.setlocale(locale.LC_ALL, '') + def rc(group, **kwargs): """ Set the current rc params. Group is the grouping for the rc, e.g., diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index aab149aa17ad..3e7ff7522c7c 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -817,7 +817,9 @@ def __call__(self, s): 'animation.convert_args': [[], validate_stringlist]} - +obsolete_rcparams = ('tk.pythoninspect', 'savefig.extension', + 'svg.embed_char_paths', + 'svg.embed_char_paths') if __name__ == '__main__': rc = defaultParams rc['datapath'][0] = '/' From 99e3be389297bcb0b5f0096aee740be4a22c34f4 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 1 Oct 2014 14:20:01 -0400 Subject: [PATCH 12/19] TST : add cleanup decorators to some tests Mostly paranoia --- lib/matplotlib/tests/test_rcparams.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 2e1bdadef11c..497009677ef6 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -9,6 +9,7 @@ import matplotlib as mpl from matplotlib.tests import assert_str_equal +from matplotlib.testing.decorators import cleanup from nose.tools import assert_true, assert_raises, assert_equal import nose from itertools import chain @@ -116,6 +117,7 @@ def test_rcparams_init(): mpl.RcParams({'figure.figsize': (3.5, 42, 1)}) +@cleanup def test_Bug_2543(): # Test that it possible to add all values to itself / deepcopy # This was not possible because validate_bool_maybe_none did not @@ -146,6 +148,7 @@ def test_Bug_2543(): assert_true(mpl.rcParams['svg.fonttype'] == "none") +@cleanup def test_Bug_2543_newer_python(): # only split from above because of the usage of assert_raises # as a context manager, which only works in 2.7 and above From c93041c49793145e32f3cb10f58d9cd2eff81b9c Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 1 Oct 2014 14:29:41 -0400 Subject: [PATCH 13/19] MNT : re-work obsolete_rcparams re-use the existing frame work for this --- lib/matplotlib/__init__.py | 15 ++++++++++----- lib/matplotlib/rcsetup.py | 4 +--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index a140e004c054..f785623a8c98 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -105,6 +105,7 @@ import six import sys import distutils.version +from itertools import chain __version__ = '1.4.x' __version__numpy__ = '1.6' # minimum required numpy version @@ -191,7 +192,7 @@ def _forward_ilshift(self, other): from matplotlib.rcsetup import (defaultParams, - validate_backend, obsolete_rcparams) + validate_backend) major, minor1, minor2, s, tmp = sys.version_info _python24 = (major == 2 and minor1 >= 4) or major >= 3 @@ -803,6 +804,10 @@ def matplotlib_fname(): _deprecated_ignore_map = { } +_obsolete_set = set(['tk.pythoninspect', ]) +_all_deprecated = set(chain(_deprecated_ignore_map, + _deprecated_map, _obsolete_set)) + class RcParams(dict): @@ -815,7 +820,7 @@ class RcParams(dict): validate = dict((key, converter) for key, (default, converter) in six.iteritems(defaultParams) - if key not in obsolete_rcparams) + if key not in _all_deprecated) msg_depr = "%s is deprecated and replaced with %s; please use the latter." msg_depr_ignore = "%s is deprecated and ignored. Use %s" @@ -920,7 +925,7 @@ def rc_params(fail_on_error=False): message = 'could not find rc file; returning defaults' ret = RcParams([(key, default) for key, (default, _) in six.iteritems(defaultParams) - if key not in obsolete_rcparams]) + if key not in _all_deprecated]) warnings.warn(message) return ret @@ -1043,7 +1048,7 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): iter_params = six.iteritems(defaultParams) config = RcParams([(key, default) for key, (default, _) in iter_params - if key not in obsolete_rcparams]) + if key not in _all_deprecated]) config.update(config_from_file) verbose.set_level(config['verbose.level']) @@ -1087,7 +1092,7 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): rcParamsDefault = RcParams([(key, default) for key, (default, converter) in six.iteritems(defaultParams) - if key not in obsolete_rcparams]) + if key not in _all_deprecated]) rcParams['ps.usedistiller'] = checkdep_ps_distiller( diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 3e7ff7522c7c..aab149aa17ad 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -817,9 +817,7 @@ def __call__(self, s): 'animation.convert_args': [[], validate_stringlist]} -obsolete_rcparams = ('tk.pythoninspect', 'savefig.extension', - 'svg.embed_char_paths', - 'svg.embed_char_paths') + if __name__ == '__main__': rc = defaultParams rc['datapath'][0] = '/' From 962793b1ec9a05bd1fd5cd5e0abffa694e5d4246 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 1 Oct 2014 14:32:26 -0400 Subject: [PATCH 14/19] TST : known fail more tests on 2.6 --- lib/matplotlib/tests/test_rcparams.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 497009677ef6..3b7fe600960e 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -105,6 +105,9 @@ def test_RcParams_class(): def test_rcparams_update(): + if sys.version_info[:2] < (2, 7): + raise nose.SkipTest("assert_raises as context manager " + "not supported with Python < 2.7") rc = mpl.RcParams({'figure.figsize': (3.5, 42)}) bad_dict = {'figure.figsize': (3.5, 42, 1)} # make sure validation happens on input @@ -113,6 +116,9 @@ def test_rcparams_update(): def test_rcparams_init(): + if sys.version_info[:2] < (2, 7): + raise nose.SkipTest("assert_raises as context manager " + "not supported with Python < 2.7") with assert_raises(ValueError): mpl.RcParams({'figure.figsize': (3.5, 42, 1)}) From b7e0719720a6716c6ffb33c07cff131f0fb4b5e8 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 1 Oct 2014 17:06:40 -0400 Subject: [PATCH 15/19] TST : special case pathexists for None There seems to be a bit more magic involved in setting up datapath --- lib/matplotlib/rcsetup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index aab149aa17ad..2db436349c5b 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -66,6 +66,8 @@ def validate_any(s): def validate_path_exists(s): """If s is a path, return s, else False""" + if s is None: + return None if os.path.exists(s): return s else: From 4cc899c9eaf6b760a162a5759244f88e6f1189cb Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 4 Oct 2014 22:12:10 -0400 Subject: [PATCH 16/19] BUG : fixed string list default values The other fixes on this branch make this un-needed (as the default values of are now properly validated and a single word string (as separated by commas) are correctly converted into length 1 list) but out of principle make the default value of things labeled an 'string lists' should have default values which are lists of strings. --- lib/matplotlib/rcsetup.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 2db436349c5b..d2650ee33bf4 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -523,7 +523,7 @@ def __call__(self, s): ## font props - 'font.family': ['sans-serif', validate_stringlist], # used by text object + 'font.family': [['sans-serif'], validate_stringlist], # used by text object 'font.style': ['normal', six.text_type], 'font.variant': ['normal', six.text_type], 'font.stretch': ['normal', six.text_type], @@ -782,14 +782,14 @@ def __call__(self, s): 'keymap.home': [['h', 'r', 'home'], validate_stringlist], 'keymap.back': [['left', 'c', 'backspace'], validate_stringlist], 'keymap.forward': [['right', 'v'], validate_stringlist], - 'keymap.pan': ['p', validate_stringlist], - 'keymap.zoom': ['o', validate_stringlist], - 'keymap.save': [('s', 'ctrl+s'), validate_stringlist], - 'keymap.quit': [('ctrl+w', 'cmd+w'), validate_stringlist], - 'keymap.grid': ['g', validate_stringlist], - 'keymap.yscale': ['l', validate_stringlist], + 'keymap.pan': [['p'], validate_stringlist], + 'keymap.zoom': [['o'], validate_stringlist], + 'keymap.save': [['s', 'ctrl+s'], validate_stringlist], + 'keymap.quit': [['ctrl+w', 'cmd+w'], validate_stringlist], + 'keymap.grid': [['g'], validate_stringlist], + 'keymap.yscale': [['l'], validate_stringlist], 'keymap.xscale': [['k', 'L'], validate_stringlist], - 'keymap.all_axes': ['a', validate_stringlist], + 'keymap.all_axes': [['a'], validate_stringlist], # sample data 'examples.directory': ['', six.text_type], From f1f5795ba0d6844cb50bf3d71f5bd405ac7cb6e0 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 10 Oct 2014 12:09:57 -0400 Subject: [PATCH 17/19] ENH : change failed validations into warnings Catch exceptions raised due to failed validations, force setting the (un validated!) value, and print a warning. --- lib/matplotlib/__init__.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index f785623a8c98..37ad33f39dbf 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -245,6 +245,7 @@ def _is_writable_dir(p): return True + class Verbose: """ A class to handle reporting. Set the fileo attribute to any file @@ -808,6 +809,14 @@ def matplotlib_fname(): _all_deprecated = set(chain(_deprecated_ignore_map, _deprecated_map, _obsolete_set)) +_rcparam_warn_str = ("Trying to set {key} to {value} via the {func} " + "method of RcParams which does not validate cleanly. " + "This warning will turn into an Exception in 1.5. " + "If you think {value} should validate correctly for " + "rcParams[{key}] " + "please create an issue on github." + ) + class RcParams(dict): @@ -827,7 +836,14 @@ class RcParams(dict): # validate values on the way in def __init__(self, *args, **kwargs): for k, v in six.iteritems(dict(*args, **kwargs)): - self[k] = v + try: + self[k] = v + except (ValueError, RuntimeError): + # force the issue + warnings.warn(_rcparam_warn_str.format(key=repr(k), + value=repr(v), + func='__init__')) + dict.__setitem__(self, k, v) def __setitem__(self, key, val): try: @@ -866,9 +882,15 @@ def __getitem__(self, key): # all of the validation over-ride update to force # through __setitem__ def update(self, *args, **kwargs): - for k, v in six.iteritems(dict(*args, **kwargs)): - self[k] = v + try: + self[k] = v + except (ValueError, RuntimeError): + # force the issue + warnings.warn(_rcparam_warn_str.format(key=repr(k), + value=repr(v), + func='update')) + dict.__setitem__(self, k, v) def __repr__(self): import pprint From d8fb74576ffb8b938e3626aa188ca31ee34c5fdc Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 10 Oct 2014 15:16:33 -0400 Subject: [PATCH 18/19] TST : known-fail rcparam exceptions tests Added known-fail to tests that expect rcparam __init__ and update to raise exceptions on invalid input. --- lib/matplotlib/tests/test_rcparams.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 3b7fe600960e..c36bab30c24e 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -9,7 +9,7 @@ import matplotlib as mpl from matplotlib.tests import assert_str_equal -from matplotlib.testing.decorators import cleanup +from matplotlib.testing.decorators import cleanup, knownfailureif from nose.tools import assert_true, assert_raises, assert_equal import nose from itertools import chain @@ -104,6 +104,8 @@ def test_RcParams_class(): assert ['font.family'] == list(six.iterkeys(rc.find_all('family'))) +# remove know failure + warnings after merging to master +@knownfailureif(not (sys.version_info[:2] < (2, 7))) def test_rcparams_update(): if sys.version_info[:2] < (2, 7): raise nose.SkipTest("assert_raises as context manager " @@ -112,15 +114,26 @@ def test_rcparams_update(): bad_dict = {'figure.figsize': (3.5, 42, 1)} # make sure validation happens on input with assert_raises(ValueError): - rc.update(bad_dict) + + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', + message='.*(validate)', + category=UserWarning) + rc.update(bad_dict) +# remove know failure + warnings after merging to master +@knownfailureif(not (sys.version_info[:2] < (2, 7))) def test_rcparams_init(): if sys.version_info[:2] < (2, 7): raise nose.SkipTest("assert_raises as context manager " "not supported with Python < 2.7") with assert_raises(ValueError): - mpl.RcParams({'figure.figsize': (3.5, 42, 1)}) + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', + message='.*(validate)', + category=UserWarning) + mpl.RcParams({'figure.figsize': (3.5, 42, 1)}) @cleanup From 726495efb5b0e682e3739e4b39bb3bab3089187a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 11 Oct 2014 16:42:02 -0400 Subject: [PATCH 19/19] TST : added test that keymap values are all lists Closes #497 --- lib/matplotlib/tests/test_rcparams.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index c36bab30c24e..f2016aed8289 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -247,3 +247,9 @@ def test_validators(): yield _validation_test_helper, validator, arg, target for arg, error_type in validator_dict['fail']: yield _validation_fail_helper, validator, arg, error_type + + +def test_keymaps(): + key_list = [k for k in mpl.rcParams if 'keymap' in k] + for k in key_list: + assert(isinstance(mpl.rcParams[k], list))