Skip to content

Commit e1a7d76

Browse files
committed
Add skip_invalid kwarg to graph_objs constructors.
When set to True (Default is False) all invalid properties (invalid by name or by value) will be skipped an no exception will be raised due to schema violations. The option applies recursively to all child objects, but it only applies within the scope of the constructor. Still TODO: add the skip_invalid option to the figure constructor. propagate this state to trace, layout, and frames validators during figure construction.
1 parent 5814699 commit e1a7d76

File tree

580 files changed

+4754
-23
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

580 files changed

+4754
-23
lines changed

_plotly_utils/basevalidators.py

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,18 +1869,21 @@ def description(self):
18691869

18701870
return desc
18711871

1872-
def validate_coerce(self, v):
1872+
def validate_coerce(self, v, skip_invalid=False):
18731873
if v is None:
18741874
v = self.data_class()
18751875

18761876
elif isinstance(v, dict):
1877-
v = self.data_class(**v)
1877+
v = self.data_class(skip_invalid=skip_invalid, **v)
18781878

18791879
elif isinstance(v, self.data_class):
18801880
# Copy object
1881-
v = self.data_class(**v.to_plotly_json())
1881+
v = self.data_class(v)
18821882
else:
1883-
self.raise_invalid_val(v)
1883+
if skip_invalid:
1884+
v = self.data_class()
1885+
else:
1886+
self.raise_invalid_val(v)
18841887

18851888
v._plotly_name = self.plotly_name
18861889
return v
@@ -1927,7 +1930,7 @@ def data_class(self):
19271930

19281931
return self._data_class
19291932

1930-
def validate_coerce(self, v):
1933+
def validate_coerce(self, v, skip_invalid=False):
19311934

19321935
if v is None:
19331936
v = []
@@ -1937,19 +1940,26 @@ def validate_coerce(self, v):
19371940
invalid_els = []
19381941
for v_el in v:
19391942
if isinstance(v_el, self.data_class):
1940-
res.append(v_el)
1943+
res.append(self.data_class(v_el))
19411944
elif isinstance(v_el, dict):
1942-
res.append(self.data_class(**v_el))
1945+
res.append(self.data_class(skip_invalid=skip_invalid,
1946+
**v_el))
19431947
else:
1944-
res.append(None)
1945-
invalid_els.append(v_el)
1948+
if skip_invalid:
1949+
res.append(self.data_class())
1950+
else:
1951+
res.append(None)
1952+
invalid_els.append(v_el)
19461953

19471954
if invalid_els:
19481955
self.raise_invalid_elements(invalid_els)
19491956

19501957
v = to_scalar_or_list(res)
19511958
else:
1952-
self.raise_invalid_val(v)
1959+
if skip_invalid:
1960+
v = []
1961+
else:
1962+
self.raise_invalid_val(v)
19531963

19541964
return v
19551965

@@ -2011,7 +2021,7 @@ def class_map(self):
20112021

20122022
return self._class_map
20132023

2014-
def validate_coerce(self, v):
2024+
def validate_coerce(self, v, skip_invalid=False):
20152025

20162026
# Import Histogram2dcontour, this is the deprecated name of the
20172027
# Histogram2dContour trace.
@@ -2041,14 +2051,26 @@ def validate_coerce(self, v):
20412051
trace_type = 'scatter'
20422052

20432053
if trace_type not in self.class_map:
2044-
res.append(None)
2045-
invalid_els.append(v_el)
2054+
if skip_invalid:
2055+
# Treat as scatter trace
2056+
trace = self.class_map['scatter'](
2057+
skip_invalid=skip_invalid, **v_copy)
2058+
res.append(trace)
2059+
else:
2060+
res.append(None)
2061+
invalid_els.append(v_el)
20462062
else:
2047-
trace = self.class_map[trace_type](**v_copy)
2063+
trace = self.class_map[trace_type](
2064+
skip_invalid=skip_invalid, **v_copy)
20482065
res.append(trace)
20492066
else:
2050-
res.append(None)
2051-
invalid_els.append(v_el)
2067+
if skip_invalid:
2068+
# Add empty scatter trace
2069+
trace = self.class_map['scatter']()
2070+
res.append(trace)
2071+
else:
2072+
res.append(None)
2073+
invalid_els.append(v_el)
20522074

20532075
if invalid_els:
20542076
self.raise_invalid_elements(invalid_els)
@@ -2061,6 +2083,9 @@ def validate_coerce(self, v):
20612083
trace.uid = str(uuid.uuid1())
20622084

20632085
else:
2064-
self.raise_invalid_val(v)
2086+
if skip_invalid:
2087+
v = []
2088+
else:
2089+
self.raise_invalid_val(v)
20652090

20662091
return v

_plotly_utils/tests/validators/test_basetraces_validator.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,27 @@
22
from _plotly_utils.basevalidators import BaseDataValidator
33
from plotly.graph_objs import Scatter, Bar, Box
44

5+
56
# Fixtures
67
# --------
78
@pytest.fixture()
89
def validator():
910
return BaseDataValidator(class_strs_map={'scatter': 'Scatter',
10-
'bar': 'Bar',
11-
'box': 'Box'},
11+
'bar': 'Bar',
12+
'box': 'Box'},
1213
plotly_name='prop',
1314
parent_name='parent',
1415
set_uid=True)
1516

17+
@pytest.fixture()
18+
def validator_nouid():
19+
return BaseDataValidator(class_strs_map={'scatter': 'Scatter',
20+
'bar': 'Bar',
21+
'box': 'Box'},
22+
plotly_name='prop',
23+
parent_name='parent',
24+
set_uid=False)
25+
1626

1727
# Tests
1828
# -----
@@ -104,3 +114,30 @@ def test_rejection_element_tracetype(validator):
104114
validator.validate_coerce(val)
105115

106116
assert "Invalid element(s)" in str(validation_failure.value)
117+
118+
119+
def test_skip_invalid(validator_nouid):
120+
val = (dict(type='scatter',
121+
mode='lines',
122+
marker={'color': 'green',
123+
'bogus': 23},
124+
line='bad_value'),
125+
dict(type='box',
126+
fillcolor='yellow',
127+
bogus=111),
128+
dict(type='bogus',
129+
mode='lines+markers',
130+
x=[2, 1, 3]))
131+
132+
expected = [dict(type='scatter',
133+
mode='lines',
134+
marker={'color': 'green'}),
135+
dict(type='box',
136+
fillcolor='yellow'),
137+
dict(type='scatter',
138+
mode='lines+markers',
139+
x=[2, 1, 3])]
140+
141+
res = validator_nouid.validate_coerce(val, skip_invalid=True)
142+
143+
assert [el.to_plotly_json() for el in res] == expected

_plotly_utils/tests/validators/test_compound_validator.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,43 @@ def test_rejection_value(validator):
5959
assert ("Invalid property specified for object of type "
6060
"plotly.graph_objs.scatter.Marker: 'bogus'" in
6161
str(validation_failure.value))
62+
63+
64+
def test_skip_invalid(validator):
65+
val = dict(
66+
color='green',
67+
size=10,
68+
bogus=99, # Bad property name
69+
colorbar={'bgcolor': 'blue',
70+
'bogus_inner': 23 # Bad nested property name
71+
},
72+
opacity='bogus value' # Bad value for valid property
73+
)
74+
75+
expected = dict(
76+
color='green',
77+
size=10,
78+
colorbar={'bgcolor': 'blue'})
79+
80+
res = validator.validate_coerce(val, skip_invalid=True)
81+
assert res.to_plotly_json() == expected
82+
83+
84+
def test_skip_invalid_empty_object(validator):
85+
val = dict(
86+
color='green',
87+
size=10,
88+
colorbar={'bgcolor': 'bad_color', # Bad value for valid property
89+
'bogus_inner': 23 # Bad nested property name
90+
},
91+
opacity=0.5 # Bad value for valid property
92+
)
93+
94+
# The colorbar property should be absent, not None or {}
95+
expected = dict(
96+
color='green',
97+
size=10,
98+
opacity=0.5)
99+
100+
res = validator.validate_coerce(val, skip_invalid=True)
101+
assert res.to_plotly_json() == expected

_plotly_utils/tests/validators/test_compoundarray_validator.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,20 @@ def test_rejection_value(validator):
8989
assert ("Invalid property specified for object of type "
9090
"plotly.graph_objs.layout.Image" in
9191
str(validation_failure.value))
92+
93+
94+
def test_skip_invalid(validator):
95+
val = [dict(opacity='bad_opacity',
96+
x=23,
97+
sizex=120),
98+
dict(x=99,
99+
bogus={'a': 23},
100+
sizey=300)]
101+
102+
expected = [dict(x=23,
103+
sizex=120),
104+
dict(x=99,
105+
sizey=300)]
106+
107+
res = validator.validate_coerce(val, skip_invalid=True)
108+
assert [el.to_plotly_json() for el in res] == expected

codegen/datatypes.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,10 @@ def __init__(self""")
221221
constructor must be a dict or
222222
an instance of {class_name}\"\"\")
223223
224+
# Handle skip_invalid
225+
# -------------------
226+
self._skip_invalid = kwargs.pop('skip_invalid', False)
227+
224228
# Import validators
225229
# -----------------
226230
from plotly.validators{node.parent_dotpath_str} import (
@@ -266,6 +270,10 @@ def __init__(self""")
266270
# Process unknown kwargs
267271
# ----------------------
268272
self._process_kwargs(**dict(arg, **kwargs))
273+
274+
# Reset skip_invalid
275+
# ------------------
276+
self._skip_invalid = False
269277
""")
270278

271279
# Return source string

plotly/basedatatypes.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2202,6 +2202,11 @@ def __init__(self, plotly_name, **kwargs):
22022202
kwargs : dict
22032203
Invalid props/values to raise on
22042204
"""
2205+
# ### _skip_invalid ##
2206+
# If True, then invalid properties should be skipped, if False then
2207+
# invalid properties will result in an exception
2208+
self._skip_invalid = False
2209+
22052210
# Validate inputs
22062211
# ---------------
22072212
self._process_kwargs(**kwargs)
@@ -2252,7 +2257,8 @@ def _process_kwargs(self, **kwargs):
22522257
"""
22532258
Process any extra kwargs that are not predefined as constructor params
22542259
"""
2255-
self._raise_on_invalid_property_error(*kwargs.keys())
2260+
if not self._skip_invalid:
2261+
self._raise_on_invalid_property_error(*kwargs.keys())
22562262

22572263
@property
22582264
def plotly_name(self):
@@ -2903,7 +2909,13 @@ def _set_prop(self, prop, val):
29032909
# Import value
29042910
# ------------
29052911
validator = self._validators.get(prop)
2906-
val = validator.validate_coerce(val)
2912+
try:
2913+
val = validator.validate_coerce(val)
2914+
except ValueError as err:
2915+
if self._skip_invalid:
2916+
return
2917+
else:
2918+
raise err
29072919

29082920
# val is None
29092921
# -----------
@@ -2962,7 +2974,7 @@ def _set_compound_prop(self, prop, val):
29622974
# ------------
29632975
validator = self._validators.get(prop)
29642976
# type: BasePlotlyType
2965-
val = validator.validate_coerce(val)
2977+
val = validator.validate_coerce(val, skip_invalid=self._skip_invalid)
29662978

29672979
# Save deep copies of current and new states
29682980
# ------------------------------------------
@@ -3036,7 +3048,7 @@ def _set_array_prop(self, prop, val):
30363048
# ------------
30373049
validator = self._validators.get(prop)
30383050
# type: Tuple[BasePlotlyType]
3039-
val = validator.validate_coerce(val)
3051+
val = validator.validate_coerce(val, skip_invalid=self._skip_invalid)
30403052

30413053
# Save deep copies of current and new states
30423054
# ------------------------------------------

plotly/graph_objs/_area.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,10 @@ def __init__(
710710
an instance of plotly.graph_objs.Area"""
711711
)
712712

713+
# Handle skip_invalid
714+
# -------------------
715+
self._skip_invalid = kwargs.pop('skip_invalid', False)
716+
713717
# Import validators
714718
# -----------------
715719
from plotly.validators import (area as v_area)
@@ -791,3 +795,7 @@ def __init__(
791795
# Process unknown kwargs
792796
# ----------------------
793797
self._process_kwargs(**dict(arg, **kwargs))
798+
799+
# Reset skip_invalid
800+
# ------------------
801+
self._skip_invalid = False

plotly/graph_objs/_bar.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,6 +1995,10 @@ def __init__(
19951995
an instance of plotly.graph_objs.Bar"""
19961996
)
19971997

1998+
# Handle skip_invalid
1999+
# -------------------
2000+
self._skip_invalid = kwargs.pop('skip_invalid', False)
2001+
19982002
# Import validators
19992003
# -----------------
20002004
from plotly.validators import (bar as v_bar)
@@ -2178,3 +2182,7 @@ def __init__(
21782182
# Process unknown kwargs
21792183
# ----------------------
21802184
self._process_kwargs(**dict(arg, **kwargs))
2185+
2186+
# Reset skip_invalid
2187+
# ------------------
2188+
self._skip_invalid = False

plotly/graph_objs/_box.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,10 @@ def __init__(
14361436
an instance of plotly.graph_objs.Box"""
14371437
)
14381438

1439+
# Handle skip_invalid
1440+
# -------------------
1441+
self._skip_invalid = kwargs.pop('skip_invalid', False)
1442+
14391443
# Import validators
14401444
# -----------------
14411445
from plotly.validators import (box as v_box)
@@ -1580,3 +1584,7 @@ def __init__(
15801584
# Process unknown kwargs
15811585
# ----------------------
15821586
self._process_kwargs(**dict(arg, **kwargs))
1587+
1588+
# Reset skip_invalid
1589+
# ------------------
1590+
self._skip_invalid = False

plotly/graph_objs/_candlestick.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,10 @@ def __init__(
11131113
an instance of plotly.graph_objs.Candlestick"""
11141114
)
11151115

1116+
# Handle skip_invalid
1117+
# -------------------
1118+
self._skip_invalid = kwargs.pop('skip_invalid', False)
1119+
11161120
# Import validators
11171121
# -----------------
11181122
from plotly.validators import (candlestick as v_candlestick)
@@ -1240,3 +1244,7 @@ def __init__(
12401244
# Process unknown kwargs
12411245
# ----------------------
12421246
self._process_kwargs(**dict(arg, **kwargs))
1247+
1248+
# Reset skip_invalid
1249+
# ------------------
1250+
self._skip_invalid = False

0 commit comments

Comments
 (0)