Skip to content

Commit 1d665ba

Browse files
committed
Add array_ok to SubplotidValidator, used in pie.legend
1 parent 4b902be commit 1d665ba

File tree

2 files changed

+81
-16
lines changed

2 files changed

+81
-16
lines changed

_plotly_utils/basevalidators.py

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,7 +1740,7 @@ class SubplotidValidator(BaseValidator):
17401740
}
17411741
"""
17421742

1743-
def __init__(self, plotly_name, parent_name, dflt=None, regex=None, **kwargs):
1743+
def __init__(self, plotly_name, parent_name, dflt=None, regex=None, array_ok=False, **kwargs):
17441744
if dflt is None and regex is None:
17451745
raise ValueError("One or both of regex and deflt must be specified")
17461746

@@ -1755,6 +1755,7 @@ def __init__(self, plotly_name, parent_name, dflt=None, regex=None, **kwargs):
17551755
self.base = re.match(r"/\^(\w+)", regex).group(1)
17561756

17571757
self.regex = self.base + r"(\d*)"
1758+
self.array_ok = array_ok
17581759

17591760
def description(self):
17601761
desc = """\
@@ -1763,31 +1764,49 @@ def description(self):
17631764
optionally followed by an integer >= 1
17641765
(e.g. '{base}', '{base}1', '{base}2', '{base}3', etc.)
17651766
""".format(plotly_name=self.plotly_name, base=self.base)
1767+
1768+
desc = """\
1769+
The '{plotly_name}' property is an identifier of a particular
1770+
subplot, of type '{base}', that may be specified as:
1771+
- the string '{base}'
1772+
optionally followed by an integer >= 1
1773+
(e.g. '{base}', '{base}1', '{base}2', '{base}3', etc.)""".format(plotly_name=self.plotly_name, base=self.base)
1774+
if self.array_ok:
1775+
desc += """
1776+
- A tuple or list of the above"""
17661777
return desc
17671778

17681779
def validate_coerce(self, v):
1769-
if v is None:
1770-
pass
1771-
elif not isinstance(v, str):
1772-
self.raise_invalid_val(v)
1773-
else:
1774-
# match = re.fullmatch(self.regex, v)
1775-
match = fullmatch(self.regex, v)
1780+
def coerce(value, invalid_els):
1781+
if not isinstance(value, str):
1782+
invalid_els.append(value)
1783+
return value
1784+
match = fullmatch(self.regex, value)
17761785
if not match:
1777-
is_valid = False
1786+
invalid_els.append(value)
1787+
return value
17781788
else:
17791789
digit_str = match.group(1)
17801790
if len(digit_str) > 0 and int(digit_str) == 0:
1781-
is_valid = False
1791+
invalid_els.append(value)
1792+
return value
17821793
elif len(digit_str) > 0 and int(digit_str) == 1:
1783-
# Remove 1 suffix (e.g. x1 -> x)
1784-
v = self.base
1785-
is_valid = True
1794+
return self.base
17861795
else:
1787-
is_valid = True
1796+
return value
17881797

1789-
if not is_valid:
1790-
self.raise_invalid_val(v)
1798+
if v is None:
1799+
pass
1800+
elif self.array_ok and is_simple_array(v):
1801+
invalid_els = []
1802+
v = [e for e in v if coerce(e, invalid_els)]
1803+
if invalid_els:
1804+
self.raise_invalid_elements(invalid_els[:10])
1805+
else:
1806+
invalid_els = []
1807+
v = coerce(v, invalid_els)
1808+
if invalid_els:
1809+
self.raise_invalid_val(self.base)
17911810
return v
17921811

17931812

tests/test_plotly_utils/validators/test_subplotid_validator.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@
1010
def validator():
1111
return SubplotidValidator("prop", "parent", dflt="geo")
1212

13+
@pytest.fixture()
14+
def validator_aok():
15+
return SubplotidValidator("prop", "parent", dflt="legend", array_ok=True)
1316

1417
# Tests
1518

19+
# Array not ok (default)
1620

1721
# Acceptance
22+
1823
@pytest.mark.parametrize("val", ["geo"] + ["geo%d" % i for i in range(2, 10)])
1924
def test_acceptance(val, validator):
2025
assert validator.validate_coerce(val) == val
@@ -43,3 +48,44 @@ def test_rejection_value(val, validator):
4348
validator.validate_coerce(val)
4449

4550
assert "Invalid value" in str(validation_failure.value)
51+
52+
# Array ok
53+
54+
# Acceptance
55+
56+
@pytest.mark.parametrize("val", ["legend2", ["legend", "legend2"], ("legend1", "legend2")])
57+
def test_acceptance(val, validator_aok):
58+
v = validator_aok.validate_coerce(val)
59+
if isinstance(val, tuple):
60+
assert val == tuple(v)
61+
else:
62+
assert val == v
63+
64+
65+
# Rejection by type
66+
@pytest.mark.parametrize("val", [23, [2, 3], {}, set(), np_inf(), np_nan()])
67+
def test_rejection_type(val, validator_aok):
68+
with pytest.raises(ValueError) as validation_failure:
69+
validator_aok.validate_coerce(val)
70+
71+
failure_msg = str(validation_failure.value)
72+
assert "Invalid value" in failure_msg or "Invalid elements" in failure_msg
73+
74+
75+
# Rejection by value
76+
@pytest.mark.parametrize(
77+
"val",
78+
[
79+
"", # Cannot be empty
80+
"bogus", # Must begin with 'geo'
81+
"legend0", # If followed by a number the number must be > 1,
82+
["", "legend"],
83+
("bogus", "legend2")
84+
],
85+
)
86+
def test_rejection_value(val, validator_aok):
87+
with pytest.raises(ValueError) as validation_failure:
88+
validator_aok.validate_coerce(val)
89+
90+
failure_msg = str(validation_failure.value)
91+
assert "Invalid value" in failure_msg or "Invalid elements" in failure_msg

0 commit comments

Comments
 (0)