Skip to content

Commit 1990218

Browse files
committed
Added new leading arg parameter to all graph obj constructors.
This arg accepts a dict or an instance of the same class. This adds backwards compatibility for things like: ``` go.Scatter({'y': [3, 2, 4], 'marker': {'color': 'green'}}) ```
1 parent dbbffe3 commit 1990218

File tree

3 files changed

+120
-14
lines changed

3 files changed

+120
-14
lines changed

codegen/datatypes.py

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,37 @@ def _prop_descriptions(self):
185185
buffer.write(f"""
186186
def __init__(self""")
187187

188-
add_constructor_params(buffer, subtype_nodes)
189-
header = f"Construct a new {datatype_class} object"
190-
add_docstring(buffer, node, header=header)
188+
add_constructor_params(buffer,
189+
subtype_nodes,
190+
prepend_extras=['arg'])
191+
192+
# ### Constructor Docstring ###
193+
header = f'Construct a new {datatype_class} object'
194+
class_name = (f'plotly.graph_objs'
195+
f'{node.parent_dotpath_str}.'
196+
f'{node.name_datatype_class}')
197+
198+
extras = [(f'arg',
199+
f'dict of properties compatible with this constructor '
200+
f'or an instance of {class_name}')]
201+
202+
add_docstring(buffer, node, header=header, prepend_extras=extras)
191203

192204
buffer.write(f"""
193205
super({datatype_class}, self).__init__('{node.name_property}')
194206
207+
# Validate arg
208+
# ------------
209+
if arg is None:
210+
arg = {{}}
211+
elif isinstance(arg, self.__class__):
212+
arg = arg.to_plotly_json()
213+
elif not isinstance(arg, dict):
214+
raise ValueError(\"\"\"\\
215+
The first argument to the {class_name}
216+
constructor must be a dict or
217+
an instance of {class_name}\"\"\")
218+
195219
# Import validators
196220
# -----------------
197221
from plotly.validators{node.parent_dotpath_str} import (
@@ -210,8 +234,10 @@ def __init__(self""")
210234
# Populate data dict with properties
211235
# ----------------------------------""")
212236
for subtype_node in subtype_nodes:
237+
name_prop = subtype_node.name_property
213238
buffer.write(f"""
214-
self.{subtype_node.name_property} = {subtype_node.name_property}""")
239+
v = arg.pop('{name_prop}', None)
240+
self.{name_prop} = {name_prop} or v""")
215241

216242
# ### Literals ###
217243
if literal_nodes:
@@ -233,7 +259,7 @@ def __init__(self""")
233259
234260
# Process unknown kwargs
235261
# ----------------------
236-
self._process_kwargs(**kwargs)
262+
self._process_kwargs(**dict(arg, **kwargs))
237263
""")
238264

239265
# Return source string
@@ -266,7 +292,10 @@ def reindent_validator_description(validator, extra_indent):
266292
validator.description().strip().split('\n'))
267293

268294

269-
def add_constructor_params(buffer, subtype_nodes, extras=()):
295+
def add_constructor_params(buffer,
296+
subtype_nodes,
297+
prepend_extras=(),
298+
append_extras=()):
270299
"""
271300
Write datatype constructor params to a buffer
272301
@@ -276,17 +305,23 @@ def add_constructor_params(buffer, subtype_nodes, extras=()):
276305
Buffer to write to
277306
subtype_nodes : list of PlotlyNode
278307
List of datatype nodes to be written as constructor params
279-
extras : list[str]
308+
prepend_extras : list[str]
309+
List of extra parameters to include at the beginning of the params
310+
append_extras : list[str]
280311
List of extra parameters to include at the end of the params
281312
Returns
282313
-------
283314
None
284315
"""
316+
for extra in prepend_extras:
317+
buffer.write(f""",
318+
{extra}=None""")
319+
285320
for i, subtype_node in enumerate(subtype_nodes):
286321
buffer.write(f""",
287322
{subtype_node.name_property}=None""")
288323

289-
for extra in extras:
324+
for extra in append_extras:
290325
buffer.write(f""",
291326
{extra}=None""")
292327

@@ -296,7 +331,7 @@ def add_constructor_params(buffer, subtype_nodes, extras=()):
296331
):""")
297332

298333

299-
def add_docstring(buffer, node, header, extras=()):
334+
def add_docstring(buffer, node, header, prepend_extras=(), append_extras=()):
300335
"""
301336
Write docstring for a compound datatype node
302337
@@ -309,6 +344,12 @@ def add_docstring(buffer, node, header, extras=()):
309344
header :
310345
Top-level header for docstring that will preceded the input node's
311346
own description. Header should be < 71 characters long
347+
prepend_extras :
348+
List or tuple of propery name / description pairs that should be
349+
included at the beginning of the docstring
350+
append_extras :
351+
List or tuple of propery name / description pairs that should be
352+
included at the end of the docstring
312353
Returns
313354
-------
314355
@@ -340,11 +381,23 @@ def add_docstring(buffer, node, header, extras=()):
340381

341382
# Write parameter descriptions
342383
# ----------------------------
384+
# Write any prepend extras
385+
for p, v in prepend_extras:
386+
v_wrapped = '\n'.join(textwrap.wrap(
387+
v,
388+
width=79 - 12,
389+
initial_indent=' ' * 12,
390+
subsequent_indent=' ' * 12))
391+
buffer.write(f"""
392+
{p}
393+
{v_wrapped}""")
394+
395+
# Write core docstring
343396
buffer.write(node.get_constructor_params_docstring(
344397
indent=8))
345398

346-
# Write any extras
347-
for p, v in extras:
399+
# Write any append extras
400+
for p, v in append_extras:
348401
v_wrapped = '\n'.join(textwrap.wrap(
349402
v,
350403
width=79-12,

codegen/figure.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,9 @@ def __init__(self, data=None, layout=None, frames=None):
9191
def add_{trace_node.plotly_name}(self""")
9292

9393
# #### Function params####
94-
add_constructor_params(buffer, trace_node.child_datatypes,
95-
['row', 'col'])
94+
add_constructor_params(buffer,
95+
trace_node.child_datatypes,
96+
append_extras=['row', 'col'])
9697

9798
# #### Docstring ####
9899
header = f"Add a new {trace_node.name_datatype_class} trace"
@@ -106,7 +107,7 @@ def add_{trace_node.plotly_name}(self""")
106107
'added. Only valid if figure was created using '
107108
'`plotly.tools.make_subplots`'))
108109

109-
add_docstring(buffer, trace_node, header, extras=extras)
110+
add_docstring(buffer, trace_node, header, append_extras=extras)
110111

111112
# #### Function body ####
112113
buffer.write(f"""
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from unittest import TestCase
2+
import plotly.graph_objs as go
3+
from nose.tools import raises
4+
5+
6+
class TestGraphObjConstructor(TestCase):
7+
8+
def test_kwarg(self):
9+
m = go.scatter.Marker(color='green')
10+
self.assertEqual(m.to_plotly_json(),
11+
{'color': 'green'})
12+
13+
def test_valid_arg_dict(self):
14+
m = go.scatter.Marker(dict(color='green'))
15+
self.assertEqual(m.to_plotly_json(),
16+
{'color': 'green'})
17+
18+
def test_valid_arg_obj(self):
19+
m = go.scatter.Marker(
20+
go.scatter.Marker(color='green'))
21+
22+
self.assertEqual(m.to_plotly_json(),
23+
{'color': 'green'})
24+
25+
def test_kwarg_takes_precedence(self):
26+
m = go.scatter.Marker(
27+
dict(color='green',
28+
size=12),
29+
color='blue',
30+
opacity=0.6
31+
)
32+
33+
self.assertEqual(m.to_plotly_json(),
34+
{'color': 'blue',
35+
'size': 12,
36+
'opacity': 0.6})
37+
38+
@raises(ValueError)
39+
def test_invalid_kwarg(self):
40+
go.scatter.Marker(bogus=[1, 2, 3])
41+
42+
@raises(ValueError)
43+
def test_invalid_arg(self):
44+
go.scatter.Marker([1, 2, 3])
45+
46+
@raises(ValueError)
47+
def test_valid_arg_with_invalid_key_name(self):
48+
go.scatter.Marker({'bogus': 12})
49+
50+
@raises(ValueError)
51+
def test_valid_arg_with_invalid_key_value(self):
52+
go.scatter.Marker({'color': 'bogus'})

0 commit comments

Comments
 (0)