Skip to content

Commit 5e8c0d7

Browse files
committed
resolve merge conflicts and bump version #
2 parents 18ad118 + cff2ab5 commit 5e8c0d7

File tree

13 files changed

+600
-391
lines changed

13 files changed

+600
-391
lines changed

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Change Log
2+
All notable changes to this project will be documented in this file.
3+
This project adheres to [Semantic Versioning](http://semver.org/).
4+
5+
## [Unreleased]
6+
7+
## [1.8.3] - 2015-08-14
8+
### Fixed
9+
- Fixed typos in `plot` and `iplot` documentations
10+
11+
## [1.8.2] - 2015-08-11
12+
### Added
13+
- CHANGELOG
14+
- `sharing` keyword argument for `plotly.plotly.plot` and `plotly.plotly.iplot` with options `'public' | 'private' | 'secret'` to control the privacy of the charts. Depreciates `world_readable`
15+
16+
### Changed
17+
- If the response from `plot` or `iplot` contains an error message, raise an exception
18+
19+
### Removed
20+
- `height` and `width` are no longer accepted in `iplot`. Just stick them into your figure's layout instead, it'll be more consistent when you view it outside of the IPython notebook environment. So, instead of this:
21+
22+
```
23+
py.iplot([{'x': [1, 2, 3], 'y': [3, 1, 5]}], height=800)
24+
```
25+
26+
do this:
27+
28+
```
29+
py.iplot({
30+
'data': [{'x': [1, 2, 3], 'y': [3, 1, 5]}],
31+
'layout': {'height': 800}
32+
})
33+
```
34+
35+
### Fixed
36+
- The height of the graph in `iplot` respects the figure's height in layout

plotly/plotly/plotly.py

Lines changed: 165 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import six
2727
import six.moves
2828

29+
from requests.auth import HTTPBasicAuth
30+
2931
from plotly import exceptions, tools, utils, version
3032
from plotly.plotly import chunked_requests
3133
from plotly.session import (sign_in, update_session_plot_options,
@@ -39,7 +41,8 @@
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' | '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.
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

139205
def 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' | '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.
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.\nQuestions? "
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+
12741400
def _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

Comments
 (0)