2626import six
2727import six .moves
2828
29+ from requests .auth import HTTPBasicAuth
30+
2931from plotly import exceptions , tools , utils , version
3032from plotly .plotly import chunked_requests
3133from plotly .session import (sign_in , update_session_plot_options ,
3941 'fileopt' : "new" ,
4042 'world_readable' : True ,
4143 'auto_open' : True ,
42- 'validate' : True
44+ 'validate' : True ,
45+ 'sharing' : "public"
4346}
4447
4548# test file permissions and make sure nothing is corrupted
@@ -105,6 +108,39 @@ def _plot_option_logic(plot_options):
105108 'fileopt' not in session_plot_options and
106109 'fileopt' not in plot_options ):
107110 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+
108144 return current_plot_options
109145
110146
@@ -119,21 +155,51 @@ def iplot(figure_or_data, **plot_options):
119155 'extend': add additional numbers (data) to existing traces
120156 'append': add additional traces to existing data lists
121157 world_readable (default=True) -- make this figure private/public
122-
158+ sharing ('public' | 'private' | 'sharing') -- 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.
123175 """
124176 if 'auto_open' not in plot_options :
125177 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 = {}
129184
130185 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'
135194
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 )
137203
138204
139205def plot (figure_or_data , validate = True , ** plot_options ):
@@ -150,6 +216,23 @@ def plot(figure_or_data, validate=True, **plot_options):
150216 auto_open (default=True) -- Toggle browser options
151217 True: open this plot in a new browser tab
152218 False: do not open plot in the browser, but do return the unique url
219+ sharing ('public' | 'private' | 'sharing') -- 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.
153236
154237 """
155238 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):
174257 warnings .warn (msg )
175258 except TypeError :
176259 pass
260+
177261 plot_options = _plot_option_logic (plot_options )
178262 res = _send_to_plotly (figure , ** plot_options )
179263 if res ['error' ] == '' :
@@ -185,7 +269,8 @@ def plot(figure_or_data, validate=True, **plot_options):
185269 raise exceptions .PlotlyAccountError (res ['error' ])
186270
187271
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 ):
189274 """Replot a matplotlib figure with plotly in IPython.
190275
191276 This function:
@@ -212,7 +297,8 @@ def iplot_mpl(fig, resize=True, strip_style=False, update=None, **plot_options):
212297 fig .update (update )
213298 fig .validate ()
214299 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." )
216302 err .prepare ()
217303 raise
218304 elif update is not None :
@@ -250,7 +336,8 @@ def plot_mpl(fig, resize=True, strip_style=False, update=None, **plot_options):
250336 fig .update (update )
251337 fig .validate ()
252338 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." )
254341 err .prepare ()
255342 raise
256343 elif update is not None :
@@ -274,8 +361,8 @@ def get_figure(file_owner_or_url, file_id=None, raw=False):
274361
275362 Note, if you're using a file_owner string as the first argument, you MUST
276363 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.
279366
280367 Positional arguments:
281368 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,
451538 layout (default=None) - A valid Layout object
452539 Run help(plotly.graph_objs.Layout)
453540 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.
455543
456544 Some valid keys for trace dictionaries:
457545 'x', 'y', 'text', 'z', 'marker', 'line'
@@ -556,8 +644,9 @@ def get(figure_or_data, format='png', width=None, height=None, scale=None):
556644 - format: 'png', 'svg', 'jpeg', 'pdf'
557645 - width: output width
558646 - 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.
561650
562651 example:
563652 ```
@@ -930,7 +1019,8 @@ def append_columns(cls, columns, grid=None, grid_url=None):
9301019 py.grid_ops.append_columns([column_2], grid=grid)
9311020 ```
9321021
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
9341024 ```
9351025 from plotly.grid_objs import Grid, Column
9361026 import plotly.plotly as py
@@ -1033,7 +1123,7 @@ def append_rows(cls, rows, grid=None, grid_url=None):
10331123 'rows' : json .dumps (rows , cls = utils .PlotlyJSONEncoder )
10341124 }
10351125
1036- api_url = (_api_v2 .api_url ('grids' )+
1126+ api_url = (_api_v2 .api_url ('grids' ) +
10371127 '/{grid_id}/row' .format (grid_id = grid_id ))
10381128 res = requests .post (api_url , data = payload , headers = _api_v2 .headers (),
10391129 verify = get_config ()['plotly_ssl_verification' ])
@@ -1086,7 +1176,7 @@ def delete(cls, grid=None, grid_url=None):
10861176
10871177 """
10881178 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
10901180 res = requests .delete (api_url , headers = _api_v2 .headers (),
10911181 verify = get_config ()['plotly_ssl_verification' ])
10921182 _api_v2 .response_handler (res )
@@ -1153,7 +1243,7 @@ def upload(cls, meta, grid=None, grid_url=None):
11531243 'metadata' : json .dumps (meta , cls = utils .PlotlyJSONEncoder )
11541244 }
11551245
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 )
11571247
11581248 res = requests .patch (api_url , data = payload , headers = _api_v2 .headers (),
11591249 verify = get_config ()['plotly_ssl_verification' ])
@@ -1209,14 +1299,22 @@ def parse_grid_id_args(cls, grid, grid_url):
12091299
12101300 @classmethod
12111301 def response_handler (cls , response ):
1212-
12131302 try :
12141303 response .raise_for_status ()
12151304 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
12201318
12211319 if ('content-type' in response .headers and
12221320 'json' in response .headers ['content-type' ] and
@@ -1271,6 +1369,34 @@ def validate_credentials(credentials):
12711369 raise exceptions .PlotlyLocalCredentialsError ()
12721370
12731371
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+
12741400def _send_to_plotly (figure , ** plot_options ):
12751401 """
12761402
@@ -1285,6 +1411,7 @@ def _send_to_plotly(figure, **plot_options):
12851411 kwargs = json .dumps (dict (filename = plot_options ['filename' ],
12861412 fileopt = plot_options ['fileopt' ],
12871413 world_readable = plot_options ['world_readable' ],
1414+ sharing = plot_options ['sharing' ],
12881415 layout = fig ['layout' ] if 'layout' in fig
12891416 else {}),
12901417 cls = utils .PlotlyJSONEncoder )
@@ -1304,6 +1431,17 @@ def _send_to_plotly(figure, **plot_options):
13041431 verify = get_config ()['plotly_ssl_verification' ])
13051432 r .raise_for_status ()
13061433 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+
13071445 if 'error' in r and r ['error' ] != '' :
13081446 print (r ['error' ])
13091447 if 'warning' in r and r ['warning' ] != '' :
0 commit comments