Skip to content

Commit b8103f3

Browse files
committed
Add skip_invalid kwarg to Figure and FigureWidget.
Added docstring documentatin of skip_invalid, and added new tests. 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.
1 parent e1a7d76 commit b8103f3

File tree

6 files changed

+159
-20
lines changed

6 files changed

+159
-20
lines changed

codegen/figure.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,35 @@ class {fig_classname}({base_classname}):\n""")
6767
frames_description = reindent_validator_description(frame_validator, 8)
6868

6969
buffer.write(f"""
70-
def __init__(self, data=None, layout=None, frames=None):
70+
def __init__(self, data=None, layout=None,
71+
frames=None, skip_invalid=False):
7172
\"\"\"
7273
Create a new {fig_classname} instance
7374
7475
Parameters
7576
----------
7677
data
7778
{data_description}
79+
7880
layout
7981
{layout_description}
82+
8083
frames
8184
{frames_description}
85+
86+
skip_invalid: bool
87+
If True, invalid properties in the figure specification will be
88+
skipped silently. If False (default) invalid properties in the
89+
figure specification will result in a ValueError
90+
91+
Raises
92+
------
93+
ValueError
94+
if a property in the specification of data, layout, or frames
95+
is invalid AND skip_invalid is False
8296
\"\"\"
83-
super({fig_classname} ,self).__init__(data, layout, frames)
97+
super({fig_classname} ,self).__init__(data, layout,
98+
frames, skip_invalid)
8499
""")
85100

86101
# ### add_trace methods for each trace type ###

plotly/basedatatypes.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ class BaseFigure(object):
3939

4040
# Constructor
4141
# -----------
42-
def __init__(self, data=None, layout_plotly=None, frames=None):
42+
def __init__(self,
43+
data=None,
44+
layout_plotly=None,
45+
frames=None,
46+
skip_invalid=False):
4347
"""
4448
Construct a BaseFigure object
4549
@@ -72,6 +76,17 @@ class is a subclass of both BaseFigure and widgets.DOMWidget.
7276
7377
If the `data` property is a BaseFigure instance, or a dict that
7478
contains a 'frames' key, then this property is ignored.
79+
80+
skip_invalid: bool
81+
If True, invalid properties in the figure specification will be
82+
skipped silently. If False (default) invalid properties in the
83+
figure specification will result in a ValueError
84+
85+
Raises
86+
------
87+
ValueError
88+
if a property in the specification of data, layout, or frames
89+
is invalid AND skip_invalid is False
7590
"""
7691
super(BaseFigure, self).__init__()
7792

@@ -113,7 +128,8 @@ class is a subclass of both BaseFigure and widgets.DOMWidget.
113128
self._data_validator = DataValidator(set_uid=True)
114129

115130
# ### Import traces ###
116-
data = self._data_validator.validate_coerce(data)
131+
data = self._data_validator.validate_coerce(data,
132+
skip_invalid=skip_invalid)
117133

118134
# ### Save tuple of trace objects ###
119135
self._data_objs = data
@@ -154,7 +170,8 @@ class is a subclass of both BaseFigure and widgets.DOMWidget.
154170
self._layout_validator = LayoutValidator()
155171

156172
# ### Import Layout ###
157-
self._layout_obj = self._layout_validator.validate_coerce(layout)
173+
self._layout_obj = self._layout_validator.validate_coerce(
174+
layout, skip_invalid=skip_invalid)
158175

159176
# ### Import clone of layout properties ###
160177
self._layout = deepcopy(self._layout_obj._props)
@@ -175,7 +192,8 @@ class is a subclass of both BaseFigure and widgets.DOMWidget.
175192
self._frames_validator = FramesValidator()
176193

177194
# ### Import frames ###
178-
self._frame_objs = self._frames_validator.validate_coerce(frames)
195+
self._frame_objs = self._frames_validator.validate_coerce(
196+
frames, skip_invalid=skip_invalid)
179197

180198
# Note: Because frames are not currently supported in the widget
181199
# context, we don't need to follow the pattern above and create

plotly/basewidget.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ class BaseFigureWidget(BaseFigure, widgets.DOMWidget):
116116
def __init__(self,
117117
data=None,
118118
layout=None,
119-
frames=None):
119+
frames=None,
120+
skip_invalid=False):
120121

121122
# Call superclass constructors
122123
# ----------------------------
@@ -125,7 +126,8 @@ def __init__(self,
125126
# ipywidgets class
126127
super(BaseFigureWidget, self).__init__(data=data,
127128
layout_plotly=layout,
128-
frames=frames)
129+
frames=frames,
130+
skip_invalid=skip_invalid)
129131

130132
# Validate Frames
131133
# ---------------

plotly/graph_objs/_figure.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

1212
class Figure(BaseFigure):
1313

14-
def __init__(self, data=None, layout=None, frames=None):
14+
def __init__(
15+
self, data=None, layout=None, frames=None, skip_invalid=False
16+
):
1517
"""
1618
Create a new Figure instance
1719
@@ -40,6 +42,7 @@ def __init__(self, data=None, layout=None, frames=None):
4042
the specified trace type
4143
4244
(e.g. [{'type': 'scatter', ...}, {'type': 'bar, ...}])
45+
4346
layout
4447
The 'layout' property is an instance of Layout
4548
that may be specified as:
@@ -300,6 +303,7 @@ def __init__(self, data=None, layout=None, frames=None):
300303
yaxis
301304
plotly.graph_objs.layout.YAxis instance or dict
302305
with compatible properties
306+
303307
frames
304308
The 'frames' property is a tuple of instances of
305309
Frame that may be specified as:
@@ -332,8 +336,19 @@ def __init__(self, data=None, layout=None, frames=None):
332336
traces
333337
A list of trace indices that identify the
334338
respective traces in the data attribute
339+
340+
skip_invalid: bool
341+
If True, invalid properties in the figure specification will be
342+
skipped silently. If False (default) invalid properties in the
343+
figure specification will result in a ValueError
344+
345+
Raises
346+
------
347+
ValueError
348+
if a property in the specification of data, layout, or frames
349+
is invalid AND skip_invalid is False
335350
"""
336-
super(Figure, self).__init__(data, layout, frames)
351+
super(Figure, self).__init__(data, layout, frames, skip_invalid)
337352

338353
def add_area(
339354
self,

plotly/graph_objs/_figurewidget.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

1212
class FigureWidget(BaseFigureWidget):
1313

14-
def __init__(self, data=None, layout=None, frames=None):
14+
def __init__(
15+
self, data=None, layout=None, frames=None, skip_invalid=False
16+
):
1517
"""
1618
Create a new FigureWidget instance
1719
@@ -40,6 +42,7 @@ def __init__(self, data=None, layout=None, frames=None):
4042
the specified trace type
4143
4244
(e.g. [{'type': 'scatter', ...}, {'type': 'bar, ...}])
45+
4346
layout
4447
The 'layout' property is an instance of Layout
4548
that may be specified as:
@@ -300,6 +303,7 @@ def __init__(self, data=None, layout=None, frames=None):
300303
yaxis
301304
plotly.graph_objs.layout.YAxis instance or dict
302305
with compatible properties
306+
303307
frames
304308
The 'frames' property is a tuple of instances of
305309
Frame that may be specified as:
@@ -332,8 +336,19 @@ def __init__(self, data=None, layout=None, frames=None):
332336
traces
333337
A list of trace indices that identify the
334338
respective traces in the data attribute
339+
340+
skip_invalid: bool
341+
If True, invalid properties in the figure specification will be
342+
skipped silently. If False (default) invalid properties in the
343+
figure specification will result in a ValueError
344+
345+
Raises
346+
------
347+
ValueError
348+
if a property in the specification of data, layout, or frames
349+
is invalid AND skip_invalid is False
335350
"""
336-
super(FigureWidget, self).__init__(data, layout, frames)
351+
super(FigureWidget, self).__init__(data, layout, frames, skip_invalid)
337352

338353
def add_area(
339354
self,

plotly/tests/test_core/test_graph_objs/test_figure.py

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from unittest import TestCase
44

5-
from plotly.graph_objs import Figure
5+
import plotly.graph_objs as go
66

77

88
class FigureTest(TestCase):
@@ -15,24 +15,98 @@ def test_instantiation(self):
1515
'frames': []
1616
}
1717

18-
Figure(native_figure)
19-
Figure()
18+
go.Figure(native_figure)
19+
go.Figure()
2020

2121
def test_access_top_level(self):
2222

2323
# Figure is special, we define top-level objects that always exist.
2424

25-
self.assertEqual(Figure().data, ())
26-
self.assertEqual(Figure().layout.to_plotly_json(), {})
27-
self.assertEqual(Figure().frames, ())
25+
self.assertEqual(go.Figure().data, ())
26+
self.assertEqual(go.Figure().layout.to_plotly_json(), {})
27+
self.assertEqual(go.Figure().frames, ())
2828

2929
def test_nested_frames(self):
3030
with self.assertRaisesRegexp(ValueError, 'frames'):
31-
Figure({'frames': [{'frames': []}]})
31+
go.Figure({'frames': [{'frames': []}]})
3232

33-
figure = Figure()
33+
figure = go.Figure()
3434
figure.frames = [{}]
3535

3636
with self.assertRaisesRegexp(ValueError, 'frames'):
3737
figure.to_plotly_json()['frames'][0]['frames'] = []
3838
figure.frames[0].frames = []
39+
40+
def test_raises_invalid_property_name(self):
41+
with self.assertRaises(ValueError):
42+
go.Figure(
43+
data=[{'type': 'bar', 'bogus': 123}],
44+
layout={'bogus': 23, 'title': 'Figure title'},
45+
frames=[{
46+
'data': [{'type': 'bar', 'bogus': 123}],
47+
'layout': {'bogus': 23, 'title': 'Figure title'},
48+
}])
49+
50+
def test_skip_invalid_property_name(self):
51+
fig = go.Figure(
52+
data=[{'type': 'bar', 'bogus': 123}],
53+
layout={'bogus': 23, 'title': 'Figure title'},
54+
frames=[{
55+
'data': [{'type': 'bar', 'bogus': 123}],
56+
'layout': {'bogus': 23, 'title': 'Figure title'},
57+
}],
58+
skip_invalid=True)
59+
60+
fig_dict = fig.to_dict()
61+
62+
# Remove trace uid property
63+
for trace in fig_dict['data']:
64+
trace.pop('uid', None)
65+
66+
self.assertEqual(fig_dict['data'],
67+
[{'type': 'bar'}])
68+
self.assertEqual(fig_dict['layout'],
69+
{'title': 'Figure title'})
70+
self.assertEqual(fig_dict['frames'],
71+
[{
72+
'data': [{'type': 'bar'}],
73+
'layout': {'title': 'Figure title'}
74+
}])
75+
76+
def test_raises_invalid_property_value(self):
77+
with self.assertRaises(ValueError):
78+
go.Figure(
79+
data=[{'type': 'bar', 'showlegend': 'bad_value'}],
80+
layout={'paper_bgcolor': 'bogus_color',
81+
'title': 'Figure title'},
82+
frames=[{
83+
'data': [{'type': 'bar', 'showlegend': 'bad_value'}],
84+
'layout': {'bgcolor': 'bad_color',
85+
'title': 'Figure title'},
86+
}])
87+
88+
def test_skip_invalid_property_value(self):
89+
fig = go.Figure(
90+
data=[{'type': 'bar', 'showlegend': 'bad_value'}],
91+
layout={'paper_bgcolor': 'bogus_color', 'title': 'Figure title'},
92+
frames=[{
93+
'data': [{'type': 'bar', 'showlegend': 'bad_value'}],
94+
'layout': {'bgcolor': 'bad_color', 'title': 'Figure title'},
95+
}],
96+
skip_invalid=True)
97+
98+
fig_dict = fig.to_dict()
99+
100+
# Remove trace uid property
101+
for trace in fig_dict['data']:
102+
trace.pop('uid', None)
103+
104+
self.assertEqual(fig_dict['data'],
105+
[{'type': 'bar'}])
106+
self.assertEqual(fig_dict['layout'],
107+
{'title': 'Figure title'})
108+
self.assertEqual(fig_dict['frames'],
109+
[{
110+
'data': [{'type': 'bar'}],
111+
'layout': {'title': 'Figure title'}
112+
}])

0 commit comments

Comments
 (0)