Skip to content

Commit f727c46

Browse files
authored
Add to/from/read/write json functions to the plotly.io module (#1188)
* Added to/from/read/write json functions to plotly.io module * pin matplotlib to version 2.2.3 as version 3.0.0 breaks some matplotlylib tests
1 parent f0915ee commit f727c46

File tree

7 files changed

+459
-13
lines changed

7 files changed

+459
-13
lines changed

Diff for: optional-requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ coverage==4.3.1
1616
mock==2.0.0
1717
nose==1.3.3
1818
pytest==3.5.1
19+
backports.tempfile==1.0
1920

2021
## orca ##
2122
psutil

Diff for: plotly/io/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
from ._orca import to_image, write_image
22
from . import orca
3+
4+
from ._json import to_json, from_json, read_json, write_json

Diff for: plotly/io/_json.py

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
from six import string_types
2+
import json
3+
4+
from plotly.utils import PlotlyJSONEncoder
5+
from plotly.io._utils import (validate_coerce_fig_to_dict,
6+
validate_coerce_output_type)
7+
8+
9+
def to_json(fig,
10+
validate=True,
11+
pretty=False,
12+
remove_uids=True):
13+
"""
14+
Convert a figure to a JSON string representation
15+
16+
Parameters
17+
----------
18+
fig:
19+
Figure object or dict representing a figure
20+
21+
validate: bool (default True)
22+
True if the figure should be validated before being converted to
23+
JSON, False otherwise.
24+
25+
pretty: bool (default False)
26+
True if JSON representation should be pretty-printed, False if
27+
representation should be as compact as possible.
28+
29+
remove_uids: bool (default True)
30+
True if trace UIDs should be omitted from the JSON representation
31+
32+
Returns
33+
-------
34+
str
35+
Representation of figure as a JSON string
36+
"""
37+
# Validate figure
38+
# ---------------
39+
fig_dict = validate_coerce_fig_to_dict(fig, validate)
40+
41+
# Remove trace uid
42+
# ----------------
43+
if remove_uids:
44+
for trace in fig_dict.get('data', []):
45+
trace.pop('uid')
46+
47+
# Dump to a JSON string and return
48+
# --------------------------------
49+
opts = {'sort_keys': True}
50+
if pretty:
51+
opts['indent'] = 2
52+
else:
53+
# Remove all whitespace
54+
opts['separators'] = (',', ':')
55+
56+
return json.dumps(fig_dict, cls=PlotlyJSONEncoder, **opts)
57+
58+
59+
def write_json(fig, file, validate=True, pretty=False, remove_uids=True):
60+
"""
61+
Convert a figure to JSON and write it to a file or writeable
62+
object
63+
64+
Parameters
65+
----------
66+
fig:
67+
Figure object or dict representing a figure
68+
69+
file: str or writeable
70+
A string representing a local file path or a writeable object
71+
(e.g. an open file descriptor)
72+
73+
pretty: bool (default False)
74+
True if JSON representation should be pretty-printed, False if
75+
representation should be as compact as possible.
76+
77+
remove_uids: bool (default True)
78+
True if trace UIDs should be omitted from the JSON representation
79+
80+
Returns
81+
-------
82+
None
83+
"""
84+
85+
# Get JSON string
86+
# ---------------
87+
# Pass through validate argument and let to_json handle validation logic
88+
json_str = to_json(
89+
fig, validate=validate, pretty=pretty, remove_uids=remove_uids)
90+
91+
# Check if file is a string
92+
# -------------------------
93+
file_is_str = isinstance(file, string_types)
94+
95+
# Open file
96+
# ---------
97+
if file_is_str:
98+
with open(file, 'w') as f:
99+
f.write(json_str)
100+
else:
101+
file.write(json_str)
102+
103+
104+
def from_json(value, output_type='Figure', skip_invalid=False):
105+
"""
106+
Construct a figure from a JSON string
107+
108+
Parameters
109+
----------
110+
value: str
111+
String containing the JSON representation of a figure
112+
113+
output_type: type or str (default 'Figure')
114+
The output figure type or type name.
115+
One of: graph_objs.Figure, 'Figure',
116+
graph_objs.FigureWidget, 'FigureWidget'
117+
118+
skip_invalid: bool (default False)
119+
False if invalid figure properties should result in an exception.
120+
True if invalid figure properties should be silently ignored.
121+
122+
Raises
123+
------
124+
ValueError
125+
if value is not a string, or if skip_invalid=False and value contains
126+
invalid figure properties
127+
128+
Returns
129+
-------
130+
Figure or FigureWidget
131+
"""
132+
133+
# Validate value
134+
# --------------
135+
if not isinstance(value, string_types):
136+
raise ValueError("""
137+
from_json requires a string argument but received value of type {typ}
138+
Received value: {value}""".format(typ=type(value),
139+
value=value))
140+
141+
# Decode JSON
142+
# -----------
143+
fig_dict = json.loads(value)
144+
145+
# Validate coerce output type
146+
# ---------------------------
147+
cls = validate_coerce_output_type(output_type)
148+
149+
# Create and return figure
150+
# ------------------------
151+
fig = cls(fig_dict, skip_invalid=skip_invalid)
152+
return fig
153+
154+
155+
def read_json(file, output_type='Figure', skip_invalid=False):
156+
"""
157+
Construct a figure from the JSON contents of a local file or readable
158+
Python object
159+
160+
Parameters
161+
----------
162+
file: str or readable
163+
A string containing the path to a local file or a read-able Python
164+
object (e.g. an open file descriptor)
165+
166+
output_type: type or str (default 'Figure')
167+
The output figure type or type name.
168+
One of: graph_objs.Figure, 'Figure',
169+
graph_objs.FigureWidget, 'FigureWidget'
170+
171+
skip_invalid: bool (default False)
172+
False if invalid figure properties should result in an exception.
173+
True if invalid figure properties should be silently ignored.
174+
175+
Returns
176+
-------
177+
Figure or FigureWidget
178+
"""
179+
180+
# Check if file is a string
181+
# -------------------------
182+
# If it's a string we assume it's a local file path. If it's not a string
183+
# then we assume it's a read-able Python object
184+
file_is_str = isinstance(file, string_types)
185+
186+
# Read file contents into JSON string
187+
# -----------------------------------
188+
if file_is_str:
189+
with open(file, 'r') as f:
190+
json_str = f.read()
191+
else:
192+
json_str = file.read()
193+
194+
# Construct and return figure
195+
# ---------------------------
196+
return from_json(json_str,
197+
skip_invalid=skip_invalid,
198+
output_type=output_type)

Diff for: plotly/io/_orca.py

+2-13
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
from six import string_types
1414

1515
import plotly
16-
from plotly.basedatatypes import BaseFigure
1716
from plotly.files import PLOTLY_DIR
17+
from plotly.io._utils import validate_coerce_fig_to_dict
1818
from plotly.optional_imports import get_module
1919

2020
psutil = get_module('psutil')
@@ -1281,18 +1281,7 @@ def to_image(fig,
12811281

12821282
# Validate figure
12831283
# ---------------
1284-
if isinstance(fig, BaseFigure):
1285-
fig_dict = fig.to_plotly_json()
1286-
elif isinstance(fig, dict):
1287-
if validate:
1288-
# This will raise an exception if fig is not a valid plotly figure
1289-
fig_dict = plotly.graph_objs.Figure(fig).to_plotly_json()
1290-
else:
1291-
fig_dict = fig
1292-
else:
1293-
raise ValueError("""
1294-
The fig parameter must be a dict or Figure.
1295-
Received value of type {typ}: {v}""".format(typ=type(fig), v=fig))
1284+
fig_dict = validate_coerce_fig_to_dict(fig, validate)
12961285

12971286
# Request image from server
12981287
# -------------------------

Diff for: plotly/io/_utils.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import plotly
2+
from plotly.basedatatypes import BaseFigure
3+
import plotly.graph_objs as go
4+
5+
6+
def validate_coerce_fig_to_dict(fig, validate):
7+
if isinstance(fig, BaseFigure):
8+
fig_dict = fig.to_dict()
9+
elif isinstance(fig, dict):
10+
if validate:
11+
# This will raise an exception if fig is not a valid plotly figure
12+
fig_dict = plotly.graph_objs.Figure(fig).to_plotly_json()
13+
else:
14+
fig_dict = fig
15+
else:
16+
raise ValueError("""
17+
The fig parameter must be a dict or Figure.
18+
Received value of type {typ}: {v}""".format(typ=type(fig), v=fig))
19+
return fig_dict
20+
21+
22+
def validate_coerce_output_type(output_type):
23+
if output_type == 'Figure' or output_type == go.Figure:
24+
cls = go.Figure
25+
elif (output_type == 'FigureWidget' or
26+
(hasattr(go, 'FigureWidget') and output_type == go.FigureWidget)):
27+
cls = go.FigureWidget
28+
else:
29+
raise ValueError("""
30+
Invalid output type: {output_type}
31+
Must be one of: 'Figure', 'FigureWidget'""")
32+
return cls

0 commit comments

Comments
 (0)