Skip to content

Commit 2b16348

Browse files
committed
Merge branch 'master' into python3-bytes-decoding-fix
2 parents f76eb17 + 80fcc59 commit 2b16348

File tree

7 files changed

+296
-98
lines changed

7 files changed

+296
-98
lines changed

plotly/grid_objs/grid_objs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def __init__(self, data, name):
6767

6868
def __str__(self):
6969
max_chars = 10
70-
jdata = json.dumps(self.data, cls=utils._plotlyJSONEncoder)
70+
jdata = json.dumps(self.data, cls=utils.PlotlyJSONEncoder)
7171
if len(jdata) > max_chars:
7272
data_string = jdata[:max_chars] + "...]"
7373
else:

plotly/plotly/plotly.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ def write(self, trace, layout=None, validate=True,
536536
stream_object.update(dict(layout=layout))
537537

538538
# TODO: allow string version of this?
539-
jdata = json.dumps(stream_object, cls=utils._plotlyJSONEncoder)
539+
jdata = json.dumps(stream_object, cls=utils.PlotlyJSONEncoder)
540540
jdata += "\n"
541541

542542
try:
@@ -602,7 +602,7 @@ def get(figure_or_data, format='png', width=None, height=None):
602602

603603
url = get_config()['plotly_domain'] + "/apigenimage/"
604604
res = requests.post(
605-
url, data=json.dumps(payload, cls=utils._plotlyJSONEncoder),
605+
url, data=json.dumps(payload, cls=utils.PlotlyJSONEncoder),
606606
headers=headers, verify=get_config()['plotly_ssl_verification']
607607
)
608608

@@ -824,7 +824,7 @@ def upload(cls, grid, filename,
824824

825825
payload = {
826826
'filename': filename,
827-
'data': json.dumps(grid_json, cls=utils._plotlyJSONEncoder),
827+
'data': json.dumps(grid_json, cls=utils.PlotlyJSONEncoder),
828828
'world_readable': world_readable
829829
}
830830

@@ -905,7 +905,7 @@ def append_columns(cls, columns, grid=None, grid_url=None):
905905
raise exceptions.InputError(err)
906906

907907
payload = {
908-
'cols': json.dumps(columns, cls=utils._plotlyJSONEncoder)
908+
'cols': json.dumps(columns, cls=utils.PlotlyJSONEncoder)
909909
}
910910

911911
api_url = _api_v2.api_url('grids')+'/{grid_id}/col'.format(grid_id=grid_id)
@@ -980,7 +980,7 @@ def append_rows(cls, rows, grid=None, grid_url=None):
980980
'column' if n_columns == 1 else 'columns'))
981981

982982
payload = {
983-
'rows': json.dumps(rows, cls=utils._plotlyJSONEncoder)
983+
'rows': json.dumps(rows, cls=utils.PlotlyJSONEncoder)
984984
}
985985

986986
api_url = (_api_v2.api_url('grids')+
@@ -1098,7 +1098,7 @@ def upload(cls, meta, grid=None, grid_url=None):
10981098
grid_id = _api_v2.parse_grid_id_args(grid, grid_url)
10991099

11001100
payload = {
1101-
'metadata': json.dumps(meta, cls=utils._plotlyJSONEncoder)
1101+
'metadata': json.dumps(meta, cls=utils.PlotlyJSONEncoder)
11021102
}
11031103

11041104
api_url = _api_v2.api_url('grids')+'/{grid_id}'.format(grid_id=grid_id)
@@ -1206,14 +1206,14 @@ def _send_to_plotly(figure, **plot_options):
12061206
"""
12071207
fig = tools._replace_newline(figure) # does not mutate figure
12081208
data = json.dumps(fig['data'] if 'data' in fig else [],
1209-
cls=utils._plotlyJSONEncoder)
1209+
cls=utils.PlotlyJSONEncoder)
12101210
username, api_key = _get_session_username_and_key()
12111211
kwargs = json.dumps(dict(filename=plot_options['filename'],
12121212
fileopt=plot_options['fileopt'],
12131213
world_readable=plot_options['world_readable'],
12141214
layout=fig['layout'] if 'layout' in fig
12151215
else {}),
1216-
cls=utils._plotlyJSONEncoder)
1216+
cls=utils.PlotlyJSONEncoder)
12171217

12181218

12191219
payload = dict(platform='python', # TODO: It'd be cool to expose the platform for RaspPi and others
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def setup_package():
2+
import warnings
3+
warnings.filterwarnings('ignore')

plotly/tests/test_optional/test_utils/test_utils.py

Lines changed: 147 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
from nose import with_setup
33
from nose.plugins.attrib import attr
44

5+
from unittest import TestCase
6+
7+
import math
8+
import pytz
59
from datetime import datetime as dt
610
import datetime
711
import numpy as np
@@ -16,6 +20,130 @@
1620
from plotly.matplotlylib import Exporter, PlotlyRenderer
1721
from plotly.plotly import plot
1822

23+
24+
class TestJSONEncoder(TestCase):
25+
26+
def test_encode_as_plotly(self):
27+
28+
# should *fail* when object doesn't have `to_plotly_json` attribute
29+
objs_without_attr = [
30+
1, 'one', {'a', 'set'}, {'a': 'dict'}, ['a', 'list']
31+
]
32+
for obj in objs_without_attr:
33+
self.assertRaises(utils.NotEncodable,
34+
utils.PlotlyJSONEncoder.encode_as_plotly, obj)
35+
36+
# should return without exception when obj has `to_plotly_josn` attr
37+
expected_res = 'wedidit'
38+
39+
class ObjWithAttr(object):
40+
41+
def to_plotly_json(self):
42+
return expected_res
43+
44+
res = utils.PlotlyJSONEncoder.encode_as_plotly(ObjWithAttr())
45+
self.assertEqual(res, expected_res)
46+
47+
def test_encode_as_list(self):
48+
49+
# should *fail* when object doesn't have `tolist` method
50+
objs_without_attr = [
51+
1, 'one', {'a', 'set'}, {'a': 'dict'}, ['a', 'list']
52+
]
53+
for obj in objs_without_attr:
54+
self.assertRaises(utils.NotEncodable,
55+
utils.PlotlyJSONEncoder.encode_as_list, obj)
56+
57+
# should return without exception when obj has `tolist` attr
58+
expected_res = ['some', 'list']
59+
60+
class ObjWithAttr(object):
61+
62+
def tolist(self):
63+
return expected_res
64+
65+
res = utils.PlotlyJSONEncoder.encode_as_list(ObjWithAttr())
66+
self.assertEqual(res, expected_res)
67+
68+
def test_encode_as_pandas(self):
69+
70+
# should *fail* on things that are not specific pandas objects
71+
not_pandas = ['giraffe', 6, float('nan'), ['a', 'list']]
72+
for obj in not_pandas:
73+
self.assertRaises(utils.NotEncodable,
74+
utils.PlotlyJSONEncoder.encode_as_pandas, obj)
75+
76+
# should succeed when we've got specific pandas thingies
77+
res = utils.PlotlyJSONEncoder.encode_as_pandas(pd.NaT)
78+
self.assertIs(res, None)
79+
80+
def test_encode_as_numpy(self):
81+
82+
# should *fail* on non-numpy-y things
83+
not_numpy = ['hippo', 8, float('nan'), {'a': 'dict'}]
84+
for obj in not_numpy:
85+
self.assertRaises(utils.NotEncodable,
86+
utils.PlotlyJSONEncoder.encode_as_numpy, obj)
87+
88+
# should succeed with numpy-y-thingies
89+
res = utils.PlotlyJSONEncoder.encode_as_numpy(np.ma.core.masked)
90+
self.assertTrue(math.isnan(res))
91+
92+
def test_encode_as_datetime(self):
93+
94+
# should *fail* without 'utcoffset' and 'isoformat' and '__sub__' attrs
95+
non_datetimes = [datetime.date(2013, 10, 1), 'noon', 56, '00:00:00']
96+
for obj in non_datetimes:
97+
self.assertRaises(utils.NotEncodable,
98+
utils.PlotlyJSONEncoder.encode_as_datetime, obj)
99+
100+
# should succeed with 'utcoffset', 'isoformat' and '__sub__' attrs
101+
res = utils.PlotlyJSONEncoder.encode_as_datetime(
102+
datetime.datetime(2013, 10, 1)
103+
)
104+
self.assertEqual(res, '2013-10-01')
105+
106+
# should not include extraneous microsecond info if DNE
107+
res = utils.PlotlyJSONEncoder.encode_as_datetime(
108+
datetime.datetime(2013, 10, 1, microsecond=0)
109+
)
110+
self.assertEqual(res, '2013-10-01')
111+
112+
# should include microsecond info if present
113+
res = utils.PlotlyJSONEncoder.encode_as_datetime(
114+
datetime.datetime(2013, 10, 1, microsecond=10)
115+
)
116+
self.assertEqual(res, '2013-10-01 00:00:00.000010')
117+
118+
# should convert tzinfo to utc. Note that in october, we're in EDT!
119+
# therefore the 4 hour difference is correct.
120+
naive_datetime = datetime.datetime(2013, 10, 1)
121+
aware_datetime = pytz.timezone('US/Eastern').localize(naive_datetime)
122+
123+
res = utils.PlotlyJSONEncoder.encode_as_datetime(aware_datetime)
124+
self.assertEqual(res, '2013-10-01 04:00:00')
125+
126+
def test_encode_as_date(self):
127+
128+
# should *fail* without 'utcoffset' and 'isoformat' and '__sub__' attrs
129+
non_datetimes = ['noon', 56, '00:00:00']
130+
for obj in non_datetimes:
131+
self.assertRaises(utils.NotEncodable,
132+
utils.PlotlyJSONEncoder.encode_as_date, obj)
133+
134+
# should work with a date
135+
a_date = datetime.date(2013, 10, 01)
136+
res = utils.PlotlyJSONEncoder.encode_as_date(a_date)
137+
self.assertEqual(res, '2013-10-01')
138+
139+
# should also work with a date time without a utc offset!
140+
# TODO: is this OK? We could raise errors after checking isinstance...
141+
res = utils.PlotlyJSONEncoder.encode_as_date(
142+
datetime.datetime(2013, 10, 1, microsecond=10)
143+
)
144+
self.assertEqual(res, '2013-10-01 00:00:00.000010')
145+
146+
19147
## JSON encoding
20148
numeric_list = [1, 2, 3]
21149
np_list = np.array([1, 2, 3, np.NaN, np.NAN, np.Inf, dt(2014, 1, 5)])
@@ -38,7 +166,7 @@ def test_column_json_encoding():
38166
Column(np_list, 'col 3')
39167
]
40168
json_columns = json.dumps(
41-
columns, cls=utils._plotlyJSONEncoder, sort_keys=True
169+
columns, cls=utils.PlotlyJSONEncoder, sort_keys=True
42170
)
43171
assert('[{"data": [1, 2, 3], "name": "col 1"}, '
44172
'{"data": [1, "A", "2014-01-05", '
@@ -56,8 +184,8 @@ def test_figure_json_encoding():
56184
data = Data([s1, s2])
57185
figure = Figure(data=data)
58186

59-
js1 = json.dumps(s1, cls=utils._plotlyJSONEncoder, sort_keys=True)
60-
js2 = json.dumps(s2, cls=utils._plotlyJSONEncoder, sort_keys=True)
187+
js1 = json.dumps(s1, cls=utils.PlotlyJSONEncoder, sort_keys=True)
188+
js2 = json.dumps(s2, cls=utils.PlotlyJSONEncoder, sort_keys=True)
61189

62190
assert(js1 == '{"type": "scatter3d", "x": [1, 2, 3], '
63191
'"y": [1, 2, 3, NaN, NaN, Infinity, "2014-01-05"], '
@@ -66,8 +194,8 @@ def test_figure_json_encoding():
66194
assert(js2 == '{"type": "scatter", "x": [1, 2, 3]}')
67195

68196
# Test JSON encoding works
69-
json.dumps(data, cls=utils._plotlyJSONEncoder, sort_keys=True)
70-
json.dumps(figure, cls=utils._plotlyJSONEncoder, sort_keys=True)
197+
json.dumps(data, cls=utils.PlotlyJSONEncoder, sort_keys=True)
198+
json.dumps(figure, cls=utils.PlotlyJSONEncoder, sort_keys=True)
71199

72200
# Test data wasn't mutated
73201
assert(bool(np.asarray(np_list ==
@@ -79,47 +207,48 @@ def test_figure_json_encoding():
79207

80208

81209
def test_datetime_json_encoding():
82-
j1 = json.dumps(dt_list, cls=utils._plotlyJSONEncoder)
210+
j1 = json.dumps(dt_list, cls=utils.PlotlyJSONEncoder)
83211
assert(j1 == '["2014-01-05", '
84212
'"2014-01-05 01:01:01", '
85213
'"2014-01-05 01:01:01.000001"]')
86-
j2 = json.dumps({"x": dt_list}, cls=utils._plotlyJSONEncoder)
214+
j2 = json.dumps({"x": dt_list}, cls=utils.PlotlyJSONEncoder)
87215
assert(j2 == '{"x": ["2014-01-05", '
88216
'"2014-01-05 01:01:01", '
89217
'"2014-01-05 01:01:01.000001"]}')
90218

91219

92220
def test_pandas_json_encoding():
93-
j1 = json.dumps(df['col 1'], cls=utils._plotlyJSONEncoder)
221+
j1 = json.dumps(df['col 1'], cls=utils.PlotlyJSONEncoder)
94222
assert(j1 == '[1, 2, 3, "2014-01-05", null, NaN, Infinity]')
95223

96224
# Test that data wasn't mutated
97225
assert_series_equal(df['col 1'],
98226
pd.Series([1, 2, 3, dt(2014, 1, 5),
99227
pd.NaT, np.NaN, np.Inf]))
100228

101-
j2 = json.dumps(df.index, cls=utils._plotlyJSONEncoder)
229+
j2 = json.dumps(df.index, cls=utils.PlotlyJSONEncoder)
102230
assert(j2 == '[0, 1, 2, 3, 4, 5, 6]')
103231

104232
nat = [pd.NaT]
105-
j3 = json.dumps(nat, cls=utils._plotlyJSONEncoder)
233+
j3 = json.dumps(nat, cls=utils.PlotlyJSONEncoder)
106234
assert(j3 == '[null]')
107235
assert(nat[0] is pd.NaT)
108236

109-
j4 = json.dumps(rng, cls=utils._plotlyJSONEncoder)
237+
j4 = json.dumps(rng, cls=utils.PlotlyJSONEncoder)
110238
assert(j4 == '["2011-01-01", "2011-01-01 01:00:00"]')
111239

112-
j5 = json.dumps(ts, cls=utils._plotlyJSONEncoder)
240+
j5 = json.dumps(ts, cls=utils.PlotlyJSONEncoder)
113241
assert(j5 == '[1.5, 2.5]')
114242
assert_series_equal(ts, pd.Series([1.5, 2.5], index=rng))
115243

116-
j6 = json.dumps(ts.index, cls=utils._plotlyJSONEncoder)
244+
j6 = json.dumps(ts.index, cls=utils.PlotlyJSONEncoder)
117245
assert(j6 == '["2011-01-01", "2011-01-01 01:00:00"]')
118246

119247

120248
def test_numpy_masked_json_encoding():
121249
l = [1, 2, np.ma.core.masked]
122-
j1 = json.dumps(l, cls=utils._plotlyJSONEncoder)
250+
j1 = json.dumps(l, cls=utils.PlotlyJSONEncoder)
251+
print j1
123252
assert(j1 == '[1, 2, NaN]')
124253
assert(set(l) == set([1, 2, np.ma.core.masked]))
125254

@@ -142,23 +271,23 @@ def test_masked_constants_example():
142271
renderer = PlotlyRenderer()
143272
Exporter(renderer).run(fig)
144273

145-
json.dumps(renderer.plotly_fig, cls=utils._plotlyJSONEncoder)
274+
json.dumps(renderer.plotly_fig, cls=utils.PlotlyJSONEncoder)
146275

147276
jy = json.dumps(renderer.plotly_fig['data'][1]['y'],
148-
cls=utils._plotlyJSONEncoder)
277+
cls=utils.PlotlyJSONEncoder)
149278
assert(jy == '[-398.11793026999999, -398.11792966000002, '
150279
'-398.11786308000001, NaN]')
151280

152281

153282
def test_numpy_dates():
154283
a = np.arange(np.datetime64('2011-07-11'), np.datetime64('2011-07-18'))
155-
j1 = json.dumps(a, cls=utils._plotlyJSONEncoder)
284+
j1 = json.dumps(a, cls=utils.PlotlyJSONEncoder)
156285
assert(j1 == '["2011-07-11", "2011-07-12", "2011-07-13", '
157286
'"2011-07-14", "2011-07-15", "2011-07-16", '
158287
'"2011-07-17"]')
159288

160289

161290
def test_datetime_dot_date():
162291
a = [datetime.date(2014, 1, 1), datetime.date(2014, 1, 2)]
163-
j1 = json.dumps(a, cls=utils._plotlyJSONEncoder)
292+
j1 = json.dumps(a, cls=utils.PlotlyJSONEncoder)
164293
assert(j1 == '["2014-01-01", "2014-01-02"]')

0 commit comments

Comments
 (0)