diff --git a/_plotly_utils/basevalidators.py b/_plotly_utils/basevalidators.py index 6942e6cb69b..388732ec159 100644 --- a/_plotly_utils/basevalidators.py +++ b/_plotly_utils/basevalidators.py @@ -874,6 +874,17 @@ def __init__(self, self.array_ok = array_ok self.values = values + @staticmethod + def to_str_or_unicode_or_none(v): + """ + Convert a value to a string if it's not None, a string, + or a unicode (on Python 2). + """ + if v is None or isinstance(v, string_types): + return v + else: + return str(v) + def description(self): desc = ("""\ The '{plotly_name}' property is a string and must be specified as:""" @@ -938,10 +949,8 @@ def validate_coerce(self, v): elif is_simple_array(v): if not self.strict: - # Convert all elements other than None to strings - # Leave None as is, Plotly.js will decide how to handle - # these null values. - v = [str(e) if e is not None else None for e in v] + v = [StringValidator.to_str_or_unicode_or_none(e) + for e in v] # Check no_blank if self.no_blank: @@ -962,12 +971,14 @@ def validate_coerce(self, v): if not isinstance(v, string_types): self.raise_invalid_val(v) else: - if not isinstance(v, string_types + (int, float)): + if isinstance(v, string_types): + pass + elif isinstance(v, (int, float)): + # Convert value to a string + v = str(v) + else: self.raise_invalid_val(v) - # Convert value to a string - v = str(v) - if self.no_blank and len(v) == 0: self.raise_invalid_val(v) diff --git a/_plotly_utils/tests/validators/test_string_validator.py b/_plotly_utils/tests/validators/test_string_validator.py index 2c21567cf3e..5a9ae008526 100644 --- a/_plotly_utils/tests/validators/test_string_validator.py +++ b/_plotly_utils/tests/validators/test_string_validator.py @@ -1,4 +1,6 @@ import pytest +from six import string_types + from _plotly_utils.basevalidators import StringValidator import numpy as np @@ -50,9 +52,10 @@ def validator_no_blanks_aok(): # ### Acceptance ### @pytest.mark.parametrize('val', ['bar', 234, np.nan, - 'HELLO!!!', 'world!@#$%^&*()', '']) + 'HELLO!!!', 'world!@#$%^&*()', '', u'\u03BC']) def test_acceptance(val, validator): - assert validator.validate_coerce(val) == str(val) + expected = str(val) if not isinstance(val, string_types) else val + assert validator.validate_coerce(val) == expected # ### Rejection by value ### @@ -85,7 +88,7 @@ def test_rejection_values(val, validator_values): # ### No blanks ### @pytest.mark.parametrize('val', - ['bar', 'HELLO!!!', 'world!@#$%^&*()']) + ['bar', 'HELLO!!!', 'world!@#$%^&*()', u'\u03BC']) def test_acceptance_no_blanks(val, validator_no_blanks): assert validator_no_blanks.validate_coerce(val) == val @@ -103,7 +106,7 @@ def test_rejection_no_blanks(val, validator_no_blanks): # ------ # ### Acceptance ### @pytest.mark.parametrize('val', - ['bar', 'HELLO!!!', 'world!@#$%^&*()', '']) + ['bar', 'HELLO!!!', 'world!@#$%^&*()', '', u'\u03BC']) def test_acceptance_strict(val, validator_strict): assert validator_strict.validate_coerce(val) == val @@ -122,7 +125,7 @@ def test_rejection_strict(val, validator_strict): # -------- # ### Acceptance ### @pytest.mark.parametrize('val', - ['foo', 'BAR', '', 'baz']) + ['foo', 'BAR', '', 'baz', u'\u03BC']) def test_acceptance_aok_scalars(val, validator_aok): assert validator_aok.validate_coerce(val) == val @@ -130,9 +133,9 @@ def test_acceptance_aok_scalars(val, validator_aok): @pytest.mark.parametrize('val', ['foo', ['foo'], - np.array(['BAR', ''], dtype='object'), + np.array(['BAR', '', u'\u03BC'], dtype='object'), ['baz', 'baz', 'baz'], - ['foo', None, 'bar']]) + ['foo', None, 'bar', u'\u03BC']]) def test_acceptance_aok_list(val, validator_aok): coerce_val = validator_aok.validate_coerce(val) if isinstance(val, np.ndarray): @@ -173,7 +176,7 @@ def test_rejection_aok_values(val, validator_aok_values): ['123', ['bar', 'HELLO!!!'], np.array(['bar', 'HELLO!!!'], dtype='object'), - ['world!@#$%^&*()']]) + ['world!@#$%^&*()', u'\u03BC']]) def test_acceptance_no_blanks_aok(val, validator_no_blanks_aok): coerce_val = validator_no_blanks_aok.validate_coerce(val) if isinstance(val, np.ndarray):