26
26
import six
27
27
import six .moves
28
28
29
+ from requests .auth import HTTPBasicAuth
30
+
29
31
from plotly import exceptions , tools , utils , version
30
32
from plotly .plotly import chunked_requests
31
33
from plotly .session import (sign_in , update_session_plot_options ,
39
41
'fileopt' : "new" ,
40
42
'world_readable' : True ,
41
43
'auto_open' : True ,
42
- 'validate' : True
44
+ 'validate' : True ,
45
+ 'sharing' : "public"
43
46
}
44
47
45
48
# test file permissions and make sure nothing is corrupted
@@ -105,6 +108,39 @@ def _plot_option_logic(plot_options):
105
108
'fileopt' not in session_plot_options and
106
109
'fileopt' not in plot_options ):
107
110
current_plot_options ['fileopt' ] = 'overwrite'
111
+
112
+ # Check for any conflicts between 'sharing' and 'world_readable'
113
+ if 'sharing' in plot_options :
114
+ if plot_options ['sharing' ] in ['public' , 'private' , 'secret' ]:
115
+
116
+ if 'world_readable' not in plot_options :
117
+ if plot_options ['sharing' ] != 'public' :
118
+ current_plot_options ['world_readable' ] = False
119
+ else :
120
+ current_plot_options ['world_readable' ] = True
121
+ elif (plot_options ['world_readable' ] and
122
+ plot_options ['sharing' ] != 'public' ):
123
+ raise exceptions .PlotlyError (
124
+ "Looks like you are setting your plot privacy to both "
125
+ "public and private.\n If you set world_readable as True, "
126
+ "sharing can only be set to 'public'" )
127
+ elif (not plot_options ['world_readable' ] and
128
+ plot_options ['sharing' ] == 'public' ):
129
+ raise exceptions .PlotlyError (
130
+ "Looks like you are setting your plot privacy to both "
131
+ "public and private.\n If you set world_readable as "
132
+ "False, sharing can only be set to 'private' or 'secret'" )
133
+ else :
134
+ raise exceptions .PlotlyError (
135
+ "The 'sharing' argument only accepts one of the following "
136
+ "strings:\n 'public' -- for public plots\n "
137
+ "'private' -- for private plots\n "
138
+ "'secret' -- for private plots that can be shared with a "
139
+ "secret url"
140
+ )
141
+ else :
142
+ current_plot_options ['sharing' ] = None
143
+
108
144
return current_plot_options
109
145
110
146
@@ -119,21 +155,51 @@ def iplot(figure_or_data, **plot_options):
119
155
'extend': add additional numbers (data) to existing traces
120
156
'append': add additional traces to existing data lists
121
157
world_readable (default=True) -- make this figure private/public
122
-
158
+ sharing ('public' | 'private' | 'secret') -- Toggle who can view this
159
+ graph
160
+ - 'public': Anyone can view this graph. It will appear in your profile
161
+ and can appear in search engines. You do not need to be
162
+ logged in to Plotly to view this chart.
163
+ - 'private': Only you can view this plot. It will not appear in the
164
+ Plotly feed, your profile, or search engines. You must be
165
+ logged in to Plotly to view this graph. You can privately
166
+ share this graph with other Plotly users in your online
167
+ Plotly account and they will need to be logged in to
168
+ view this plot.
169
+ - 'secret': Anyone with this secret link can view this chart. It will
170
+ not appear in the Plotly feed, your profile, or search
171
+ engines. If it is embedded inside a webpage or an IPython
172
+ notebook, anybody who is viewing that page will be able to
173
+ view the graph. You do not need to be logged in to view
174
+ this plot.
123
175
"""
124
176
if 'auto_open' not in plot_options :
125
177
plot_options ['auto_open' ] = False
126
- res = plot (figure_or_data , ** plot_options )
127
- urlsplit = res .split ('/' )
128
- username , plot_id = urlsplit [- 2 ][1 :], urlsplit [- 1 ] # TODO: HACKY!
178
+ url = plot (figure_or_data , ** plot_options )
179
+
180
+ if isinstance (figure_or_data , dict ):
181
+ layout = figure_or_data .get ('layout' , {})
182
+ else :
183
+ layout = {}
129
184
130
185
embed_options = dict ()
131
- if 'width' in plot_options :
132
- embed_options ['width' ] = plot_options ['width' ]
133
- if 'height' in plot_options :
134
- embed_options ['height' ] = plot_options ['height' ]
186
+ embed_options ['width' ] = layout .get ('width' , '100%' )
187
+ embed_options ['height' ] = layout .get ('height' , 525 )
188
+ try :
189
+ float (embed_options ['width' ])
190
+ except (ValueError , TypeError ):
191
+ pass
192
+ else :
193
+ embed_options ['width' ] = str (embed_options ['width' ]) + 'px'
135
194
136
- return tools .embed (username , plot_id , ** embed_options )
195
+ try :
196
+ float (embed_options ['height' ])
197
+ except (ValueError , TypeError ):
198
+ pass
199
+ else :
200
+ embed_options ['height' ] = str (embed_options ['height' ]) + 'px'
201
+
202
+ return tools .embed (url , ** embed_options )
137
203
138
204
139
205
def plot (figure_or_data , validate = True , ** plot_options ):
@@ -150,6 +216,23 @@ def plot(figure_or_data, validate=True, **plot_options):
150
216
auto_open (default=True) -- Toggle browser options
151
217
True: open this plot in a new browser tab
152
218
False: do not open plot in the browser, but do return the unique url
219
+ sharing ('public' | 'private' | 'secret') -- Toggle who can view this
220
+ graph
221
+ - 'public': Anyone can view this graph. It will appear in your profile
222
+ and can appear in search engines. You do not need to be
223
+ logged in to Plotly to view this chart.
224
+ - 'private': Only you can view this plot. It will not appear in the
225
+ Plotly feed, your profile, or search engines. You must be
226
+ logged in to Plotly to view this graph. You can privately
227
+ share this graph with other Plotly users in your online
228
+ Plotly account and they will need to be logged in to
229
+ view this plot.
230
+ - 'secret': Anyone with this secret link can view this chart. It will
231
+ not appear in the Plotly feed, your profile, or search
232
+ engines. If it is embedded inside a webpage or an IPython
233
+ notebook, anybody who is viewing that page will be able to
234
+ view the graph. You do not need to be logged in to view
235
+ this plot.
153
236
154
237
"""
155
238
figure = tools .return_figure_from_figure_or_data (figure_or_data , validate )
@@ -174,6 +257,7 @@ def plot(figure_or_data, validate=True, **plot_options):
174
257
warnings .warn (msg )
175
258
except TypeError :
176
259
pass
260
+
177
261
plot_options = _plot_option_logic (plot_options )
178
262
res = _send_to_plotly (figure , ** plot_options )
179
263
if res ['error' ] == '' :
@@ -185,7 +269,8 @@ def plot(figure_or_data, validate=True, **plot_options):
185
269
raise exceptions .PlotlyAccountError (res ['error' ])
186
270
187
271
188
- def iplot_mpl (fig , resize = True , strip_style = False , update = None , ** plot_options ):
272
+ def iplot_mpl (fig , resize = True , strip_style = False , update = None ,
273
+ ** plot_options ):
189
274
"""Replot a matplotlib figure with plotly in IPython.
190
275
191
276
This function:
@@ -212,7 +297,8 @@ def iplot_mpl(fig, resize=True, strip_style=False, update=None, **plot_options):
212
297
fig .update (update )
213
298
fig .validate ()
214
299
except exceptions .PlotlyGraphObjectError as err :
215
- err .add_note ("Your updated figure could not be properly validated." )
300
+ err .add_note ("Your updated figure could not be properly "
301
+ "validated." )
216
302
err .prepare ()
217
303
raise
218
304
elif update is not None :
@@ -250,7 +336,8 @@ def plot_mpl(fig, resize=True, strip_style=False, update=None, **plot_options):
250
336
fig .update (update )
251
337
fig .validate ()
252
338
except exceptions .PlotlyGraphObjectError as err :
253
- err .add_note ("Your updated figure could not be properly validated." )
339
+ err .add_note ("Your updated figure could not be properly "
340
+ "validated." )
254
341
err .prepare ()
255
342
raise
256
343
elif update is not None :
@@ -274,8 +361,8 @@ def get_figure(file_owner_or_url, file_id=None, raw=False):
274
361
275
362
Note, if you're using a file_owner string as the first argument, you MUST
276
363
specify a `file_id` keyword argument. Else, if you're using a url string
277
- as the first argument, you MUST NOT specify a `file_id` keyword argument, or
278
- file_id must be set to Python's None value.
364
+ as the first argument, you MUST NOT specify a `file_id` keyword argument,
365
+ or file_id must be set to Python's None value.
279
366
280
367
Positional arguments:
281
368
file_owner_or_url (string) -- a valid plotly username OR a valid plotly url
@@ -451,7 +538,8 @@ def write(self, trace, layout=None, validate=True,
451
538
layout (default=None) - A valid Layout object
452
539
Run help(plotly.graph_objs.Layout)
453
540
validate (default = True) - Validate this stream before sending?
454
- This will catch local errors if set to True.
541
+ This will catch local errors if set to
542
+ True.
455
543
456
544
Some valid keys for trace dictionaries:
457
545
'x', 'y', 'text', 'z', 'marker', 'line'
@@ -556,8 +644,9 @@ def get(figure_or_data, format='png', width=None, height=None, scale=None):
556
644
- format: 'png', 'svg', 'jpeg', 'pdf'
557
645
- width: output width
558
646
- height: output height
559
- - scale: Increase the resolution of the image by `scale` amount (e.g. `3`)
560
- Only valid for PNG and JPEG images.
647
+ - scale: Increase the resolution of the image by `scale`
648
+ amount (e.g. `3`)
649
+ Only valid for PNG and JPEG images.
561
650
562
651
example:
563
652
```
@@ -930,7 +1019,8 @@ def append_columns(cls, columns, grid=None, grid_url=None):
930
1019
py.grid_ops.append_columns([column_2], grid=grid)
931
1020
```
932
1021
933
- Usage example 2: Append a column to a grid that already exists on Plotly
1022
+ Usage example 2: Append a column to a grid that already exists on
1023
+ Plotly
934
1024
```
935
1025
from plotly.grid_objs import Grid, Column
936
1026
import plotly.plotly as py
@@ -1033,7 +1123,7 @@ def append_rows(cls, rows, grid=None, grid_url=None):
1033
1123
'rows' : json .dumps (rows , cls = utils .PlotlyJSONEncoder )
1034
1124
}
1035
1125
1036
- api_url = (_api_v2 .api_url ('grids' )+
1126
+ api_url = (_api_v2 .api_url ('grids' ) +
1037
1127
'/{grid_id}/row' .format (grid_id = grid_id ))
1038
1128
res = requests .post (api_url , data = payload , headers = _api_v2 .headers (),
1039
1129
verify = get_config ()['plotly_ssl_verification' ])
@@ -1086,7 +1176,7 @@ def delete(cls, grid=None, grid_url=None):
1086
1176
1087
1177
"""
1088
1178
grid_id = _api_v2 .parse_grid_id_args (grid , grid_url )
1089
- api_url = _api_v2 .api_url ('grids' )+ '/' + grid_id
1179
+ api_url = _api_v2 .api_url ('grids' ) + '/' + grid_id
1090
1180
res = requests .delete (api_url , headers = _api_v2 .headers (),
1091
1181
verify = get_config ()['plotly_ssl_verification' ])
1092
1182
_api_v2 .response_handler (res )
@@ -1153,7 +1243,7 @@ def upload(cls, meta, grid=None, grid_url=None):
1153
1243
'metadata' : json .dumps (meta , cls = utils .PlotlyJSONEncoder )
1154
1244
}
1155
1245
1156
- api_url = _api_v2 .api_url ('grids' )+ '/{grid_id}' .format (grid_id = grid_id )
1246
+ api_url = _api_v2 .api_url ('grids' ) + '/{grid_id}' .format (grid_id = grid_id )
1157
1247
1158
1248
res = requests .patch (api_url , data = payload , headers = _api_v2 .headers (),
1159
1249
verify = get_config ()['plotly_ssl_verification' ])
@@ -1209,14 +1299,22 @@ def parse_grid_id_args(cls, grid, grid_url):
1209
1299
1210
1300
@classmethod
1211
1301
def response_handler (cls , response ):
1212
-
1213
1302
try :
1214
1303
response .raise_for_status ()
1215
1304
except requests .exceptions .HTTPError as requests_exception :
1216
- plotly_exception = exceptions .PlotlyRequestError (
1217
- requests_exception
1218
- )
1219
- raise (plotly_exception )
1305
+ if (response .status_code == 404 and
1306
+ get_config ()['plotly_api_domain' ]
1307
+ != tools .get_config_defaults ()['plotly_api_domain' ]):
1308
+ raise exceptions .PlotlyError (
1309
+ "This endpoint is unavailable at {url}. If you are using "
1310
+ "Plotly Enterprise, you may need to upgrade your Plotly "
1311
+ "Enterprise server to request against this endpoint or "
1312
+ "this endpoint may not be available yet.\n Questions? "
1313
+ "[email protected] or your plotly administrator."
1314
+ .format (url = get_config ()['plotly_api_domain' ])
1315
+ )
1316
+ else :
1317
+ raise requests_exception
1220
1318
1221
1319
if ('content-type' in response .headers and
1222
1320
'json' in response .headers ['content-type' ] and
@@ -1271,6 +1369,34 @@ def validate_credentials(credentials):
1271
1369
raise exceptions .PlotlyLocalCredentialsError ()
1272
1370
1273
1371
1372
+ def add_share_key_to_url (plot_url ):
1373
+ """
1374
+ Update plot's url to include the secret key
1375
+
1376
+ """
1377
+ urlsplit = six .moves .urllib .parse .urlparse (plot_url )
1378
+ file_owner = urlsplit .path .split ('/' )[1 ].split ('~' )[1 ]
1379
+ file_id = urlsplit .path .split ('/' )[2 ]
1380
+
1381
+ url = _api_v2 .api_url ("files/" ) + file_owner + ":" + file_id
1382
+ new_response = requests .patch (url ,
1383
+ headers = _api_v2 .headers (),
1384
+ data = {"share_key_enabled" :
1385
+ "True" ,
1386
+ "world_readable" :
1387
+ "False" })
1388
+
1389
+ _api_v2 .response_handler (new_response )
1390
+
1391
+ # decode bytes for python 3.3: https://bugs.python.org/issue10976
1392
+ str_content = new_response .content .decode ('utf-8' )
1393
+
1394
+ new_response_data = json .loads (str_content )
1395
+ plot_url += '?share_key=' + new_response_data ['share_key' ]
1396
+
1397
+ return plot_url
1398
+
1399
+
1274
1400
def _send_to_plotly (figure , ** plot_options ):
1275
1401
"""
1276
1402
@@ -1285,6 +1411,7 @@ def _send_to_plotly(figure, **plot_options):
1285
1411
kwargs = json .dumps (dict (filename = plot_options ['filename' ],
1286
1412
fileopt = plot_options ['fileopt' ],
1287
1413
world_readable = plot_options ['world_readable' ],
1414
+ sharing = plot_options ['sharing' ],
1288
1415
layout = fig ['layout' ] if 'layout' in fig
1289
1416
else {}),
1290
1417
cls = utils .PlotlyJSONEncoder )
@@ -1304,6 +1431,17 @@ def _send_to_plotly(figure, **plot_options):
1304
1431
verify = get_config ()['plotly_ssl_verification' ])
1305
1432
r .raise_for_status ()
1306
1433
r = json .loads (r .text )
1434
+
1435
+ if 'error' in r and r ['error' ] != '' :
1436
+ raise exceptions .PlotlyError (r ['error' ])
1437
+
1438
+ # Check if the url needs a secret key
1439
+ if (plot_options ['sharing' ] == 'secret' and
1440
+ 'share_key=' not in r ['url' ]):
1441
+
1442
+ # add_share_key_to_url updates the url to include the share_key
1443
+ r ['url' ] = add_share_key_to_url (r ['url' ])
1444
+
1307
1445
if 'error' in r and r ['error' ] != '' :
1308
1446
print (r ['error' ])
1309
1447
if 'warning' in r and r ['warning' ] != '' :
0 commit comments