diff --git a/packages/python/chart-studio/CHANGELOG.md b/packages/python/chart-studio/CHANGELOG.md
index e69de29bb2d..a37cbbc8b2f 100644
--- a/packages/python/chart-studio/CHANGELOG.md
+++ b/packages/python/chart-studio/CHANGELOG.md
@@ -0,0 +1,18 @@
+# Change Log
+All notable changes to this project will be documented in this file.
+This project adheres to [Semantic Versioning](http://semver.org/).
+
+## [1.0.0] - ???
+
+The initial release of the stand-alone `chart-studio` package. This package contains utilities for interfacing with Plotly's Chart Studio service (both Chart Studio cloud and Chart Studio On-Prem). Prior to plotly.py version 4, This functionality was included in the `plotly` package under the `plotly.plotly` module. As part of plotly.py version 4, the Chart Studio functionality was removed from the `plotly` package and released in this `chart-studio` package.
+
+
+### Updated
+ - The `chart_studio.plotly.plot`/`iplot` functions have been ported to the Chart Studio [v2 API](https://api.plot.ly/v2/).
+ - The `chart_studio.plotly.plot`/`iplot` functions now support uploading figures that contain frames. This makes the legacy `chart_studio.plotly.create_animations`/`icreate_animations` functions unnecessary, though they are still included for backward compatibility.
+
+### Fixed
+ - Fixed iframe warning resulting from `chart_studio.plotly.iplot`
+
+### Removed
+ - The `fileopt` argument to `chart_studio.plotly.plot`/`iplot` was deprecated in plotly.py version 3.9.0 and has been removed in this initial release of the `chart-studio` package.
diff --git a/packages/python/chart-studio/README.md b/packages/python/chart-studio/README.md
index a9860520407..a24b0e42fd6 100644
--- a/packages/python/chart-studio/README.md
+++ b/packages/python/chart-studio/README.md
@@ -1 +1,2 @@
-Package for interfacing with the plotly's Chart Studio
+# chart-studio
+This package contains utilities for interfacing with Plotly's Chart Studio service (both Chart Studio cloud and Chart Studio On-Prem). Prior to plotly.py version 4, This functionality was included in the `plotly` package under the `plotly.plotly` module. As part of plotly.py version 4, the Chart Studio functionality was removed from the `plotly` package and released in this `chart-studio` package.
diff --git a/packages/python/chart-studio/chart_studio/api/v1/__init__.py b/packages/python/chart-studio/chart_studio/api/v1/__init__.py
deleted file mode 100644
index 05fbba4143a..00000000000
--- a/packages/python/chart-studio/chart_studio/api/v1/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from __future__ import absolute_import
-
-from chart_studio.api.v1.clientresp import clientresp
diff --git a/packages/python/chart-studio/chart_studio/api/v1/clientresp.py b/packages/python/chart-studio/chart_studio/api/v1/clientresp.py
deleted file mode 100644
index 1f7707e1d97..00000000000
--- a/packages/python/chart-studio/chart_studio/api/v1/clientresp.py
+++ /dev/null
@@ -1,48 +0,0 @@
-"""Interface to deprecated /clientresp API. Subject to deletion."""
-from __future__ import absolute_import
-
-import warnings
-
-import json as _json
-
-
-from _plotly_utils.utils import PlotlyJSONEncoder
-from chart_studio import config, utils
-from chart_studio.api.v1.utils import request
-
-
-def clientresp(data, **kwargs):
- """
- Deprecated endpoint, still used because it can parse data out of a plot.
-
- When we get around to forcing users to create grids and then create plots,
- we can finally get rid of this.
-
- :param (list) data: The data array from a figure.
-
- """
- from plotly import version
-
- creds = config.get_credentials()
- cfg = config.get_config()
-
- dumps_kwargs = {'sort_keys': True, 'cls': PlotlyJSONEncoder}
-
- payload = {
- 'platform': 'python', 'version': version.stable_semver(),
- 'args': _json.dumps(data, **dumps_kwargs),
- 'un': creds['username'], 'key': creds['api_key'], 'origin': 'plot',
- 'kwargs': _json.dumps(kwargs, **dumps_kwargs)
- }
-
- url = '{plotly_domain}/clientresp'.format(**cfg)
- response = request('post', url, data=payload)
-
- # Old functionality, just keeping it around.
- parsed_content = response.json()
- if parsed_content.get('warning'):
- warnings.warn(parsed_content['warning'])
- if parsed_content.get('message'):
- print(parsed_content['message'])
-
- return response
diff --git a/packages/python/chart-studio/chart_studio/api/v1/utils.py b/packages/python/chart-studio/chart_studio/api/v1/utils.py
deleted file mode 100644
index d0c40263a17..00000000000
--- a/packages/python/chart-studio/chart_studio/api/v1/utils.py
+++ /dev/null
@@ -1,93 +0,0 @@
-from __future__ import absolute_import
-
-import requests
-from requests.exceptions import RequestException
-from retrying import retry
-
-import _plotly_utils.exceptions
-from chart_studio import config, exceptions
-from chart_studio.api.utils import basic_auth
-from chart_studio.api.v2.utils import should_retry
-
-
-def validate_response(response):
- """
- Raise a helpful PlotlyRequestError for failed requests.
-
- :param (requests.Response) response: A Response object from an api request.
- :raises: (PlotlyRequestError) If the request failed for any reason.
- :returns: (None)
-
- """
- content = response.content
- status_code = response.status_code
- try:
- parsed_content = response.json()
- except ValueError:
- message = content if content else 'No Content'
- raise exceptions.PlotlyRequestError(message, status_code, content)
-
- message = ''
- if isinstance(parsed_content, dict):
- error = parsed_content.get('error')
- if error:
- message = error
- else:
- if response.ok:
- return
- if not message:
- message = content if content else 'No Content'
-
- raise exceptions.PlotlyRequestError(message, status_code, content)
-
-
-def get_headers():
- """
- Using session credentials/config, get headers for a v1 API request.
-
- Users may have their own proxy layer and so we free up the `authorization`
- header for this purpose (instead adding the user authorization in a new
- `plotly-authorization` header). See pull #239.
-
- :returns: (dict) Headers to add to a requests.request call.
-
- """
- headers = {}
- creds = config.get_credentials()
- proxy_auth = basic_auth(creds['proxy_username'], creds['proxy_password'])
-
- if config.get_config()['plotly_proxy_authorization']:
- headers['authorization'] = proxy_auth
-
- return headers
-
-
-@retry(wait_exponential_multiplier=1000, wait_exponential_max=16000,
- stop_max_delay=180000, retry_on_exception=should_retry)
-def request(method, url, **kwargs):
- """
- Central place to make any v1 api request.
-
- :param (str) method: The request method ('get', 'put', 'delete', ...).
- :param (str) url: The full api url to make the request to.
- :param kwargs: These are passed along to requests.
- :return: (requests.Response) The response directly from requests.
-
- """
- if kwargs.get('json', None) is not None:
- # See chart_studio.api.v2.utils.request for examples on how to do this.
- raise _plotly_utils.exceptions.PlotlyError(
- 'V1 API does not handle arbitrary json.')
- kwargs['headers'] = dict(kwargs.get('headers', {}), **get_headers())
- kwargs['verify'] = config.get_config()['plotly_ssl_verification']
- try:
- response = requests.request(method, url, **kwargs)
- except RequestException as e:
- # The message can be an exception. E.g., MaxRetryError.
- message = str(getattr(e, 'message', 'No message'))
- response = getattr(e, 'response', None)
- status_code = response.status_code if response else None
- content = response.content if response else 'No content'
- raise exceptions.PlotlyRequestError(message, status_code, content)
- validate_response(response)
- return response
diff --git a/packages/python/chart-studio/chart_studio/package_data/graphWidget.js b/packages/python/chart-studio/chart_studio/package_data/graphWidget.js
deleted file mode 100644
index 151ceff2445..00000000000
--- a/packages/python/chart-studio/chart_studio/package_data/graphWidget.js
+++ /dev/null
@@ -1,159 +0,0 @@
-window.genUID = function() {
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
- var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
- return v.toString(16);
- });
-};
-
-
-define('graphWidget', ["@jupyter-widgets/base"], function (widget) {
-
- var GraphView = widget.DOMWidgetView.extend({
- render: function(){
- var that = this;
-
- var graphId = window.genUID();
- var loadingId = 'loading-'+graphId;
-
-
- var _graph_url = that.model.get('_graph_url');
-
- // variable plotlyDomain in the case of enterprise
- var url_parts = _graph_url.split('/');
- var plotlyDomain = url_parts[0] + '//' + url_parts[2];
-
- if(!('plotlyDomains' in window)){
- window.plotlyDomains = {};
- }
- window.plotlyDomains[graphId] = plotlyDomain;
-
- // Place IFrame in output cell div `$el`
- that.$el.css('width', '100%');
- that.$graph = $([''].join(' '));
- that.$graph.appendTo(that.$el);
-
- that.$loading = $('
Initializing...
')
- .appendTo(that.$el);
-
- // for some reason the 'width' is being changed in IPython 3.0.0
- // for the containing `div` element. There's a flicker here, but
- // I was unable to fix it otherwise.
- setTimeout(function () {
- if (IPYTHON_VERSION === '3') {
- $('#' + graphId)[0].parentElement.style.width = '100%';
- }
- }, 500);
-
- // initialize communication with the iframe
- if(!('pingers' in window)){
- window.pingers = {};
- }
-
- window.pingers[graphId] = setInterval(function() {
- that.graphContentWindow = $('#'+graphId)[0].contentWindow;
- that.graphContentWindow.postMessage({task: 'ping'}, plotlyDomain);
- }, 200);
-
- // Assign a message listener to the 'message' events
- // from iframe's postMessage protocol.
- // Filter the messages by iframe src so that the right message
- // gets passed to the right widget
- if(!('messageListeners' in window)){
- window.messageListeners = {};
- }
-
- window.messageListeners[graphId] = function(e) {
- if(_graph_url.indexOf(e.origin)>-1) {
- var frame = document.getElementById(graphId);
-
- if(frame === null){
- // frame doesn't exist in the dom anymore, clean up it's old event listener
- window.removeEventListener('message', window.messageListeners[graphId]);
- clearInterval(window.pingers[graphId]);
- } else if(frame.contentWindow === e.source) {
- // TODO: Stop event propagation, so each frame doesn't listen and filter
- var frameContentWindow = $('#'+graphId)[0].contentWindow;
- var message = e.data;
-
- if('pong' in message && message.pong) {
- $('#loading-'+graphId).hide();
- clearInterval(window.pingers[graphId]);
- that.send({event: 'pong', graphId: graphId});
- } else if (message.type==='hover' ||
- message.type==='zoom' ||
- message.type==='click' ||
- message.type==='unhover') {
-
- // click and hover events contain all of the data in the traces,
- // which can be a very large object and may take a ton of time
- // to pass to the python backend. Strip out the data, and require
- // the user to call get_figure if they need trace information
- if(message.type !== 'zoom') {
- for(var i in message.points) {
- delete message.points[i].data;
- delete message.points[i].fullData;
- }
- }
- that.send({event: message.type, message: message, graphId: graphId});
- } else if (message.task === 'getAttributes') {
- that.send({event: 'getAttributes', response: message.response});
- }
- }
- }
- };
-
- window.removeEventListener('message', window.messageListeners[graphId]);
- window.addEventListener('message', window.messageListeners[graphId]);
-
- },
-
- update: function() {
- // Listen for messages from the graph widget in python
- var jmessage = this.model.get('_message');
- var message = JSON.parse(jmessage);
-
- // check for duplicate messages
- if(!('messageIds' in window)){
- window.messageIds = {};
- }
-
- if(!(message.uid in window.messageIds)){
- // message hasn't been received yet, do stuff
- window.messageIds[message.uid] = true;
-
- if (message.fadeTo) {
- this.fadeTo(message);
- } else {
- var plot = $('#' + message.graphId)[0].contentWindow;
- plot.postMessage(message, window.plotlyDomains[message.graphId]);
- }
- }
-
- return GraphView.__super__.update.apply(this);
- },
-
- /**
- * Wrapper for jquery's `fadeTo` function.
- *
- * @param message Contains the id we need to find the element.
- */
- fadeTo: function (message) {
- var plot = $('#' + message.graphId);
- plot.fadeTo(message.duration, message.opacity);
- }
- });
-
- // Register the GraphView with the widget manager.
- return {
- GraphView: GraphView
- }
-
-});
-
-//@ sourceURL=graphWidget.js
diff --git a/packages/python/chart-studio/chart_studio/plotly/plotly.py b/packages/python/chart-studio/chart_studio/plotly/plotly.py
index 7bfb50c2af8..476b4081700 100644
--- a/packages/python/chart-studio/chart_studio/plotly/plotly.py
+++ b/packages/python/chart-studio/chart_studio/plotly/plotly.py
@@ -34,7 +34,7 @@
from _plotly_utils.utils import PlotlyJSONEncoder
from chart_studio import files, session, tools, utils, exceptions
-from chart_studio.api import v1, v2
+from chart_studio.api import v2
from chart_studio.plotly import chunked_requests
from chart_studio.grid_objs import Grid
from chart_studio.dashboard_objs import dashboard_objs as dashboard
@@ -46,17 +46,12 @@
DEFAULT_PLOT_OPTIONS = {
'filename': "plot from API",
- 'fileopt': "new",
'world_readable': files.FILE_CONTENT[files.CONFIG_FILE]['world_readable'],
'auto_open': files.FILE_CONTENT[files.CONFIG_FILE]['auto_open'],
'validate': True,
'sharing': files.FILE_CONTENT[files.CONFIG_FILE]['sharing']
}
-warnings.filterwarnings(
- 'default', r'The fileopt parameter is deprecated .*', DeprecationWarning
-)
-
SHARING_ERROR_MSG = (
"Whoops, sharing can only be set to either 'public', 'private', or "
"'secret'."
@@ -91,28 +86,12 @@ def _plot_option_logic(plot_options_from_args):
session_options = session.get_session_plot_options()
plot_options_from_args = copy.deepcopy(plot_options_from_args)
- # fileopt deprecation warnings
- fileopt_warning = ('The fileopt parameter is deprecated '
- 'and will be removed in plotly.py version 4')
- if ('filename' in plot_options_from_args and
- plot_options_from_args.get('fileopt', 'overwrite') != 'overwrite'):
- warnings.warn(fileopt_warning, DeprecationWarning)
-
- if ('filename' not in plot_options_from_args and
- plot_options_from_args.get('fileopt', 'new') != 'new'):
- warnings.warn(fileopt_warning, DeprecationWarning)
-
# Validate options and fill in defaults w world_readable and sharing
for option_set in [plot_options_from_args,
session_options, file_options]:
utils.validate_world_readable_and_sharing_settings(option_set)
utils.set_sharing_and_world_readable(option_set)
- # dynamic defaults
- if ('filename' in option_set and
- 'fileopt' not in option_set):
- option_set['fileopt'] = 'overwrite'
-
user_plot_options = {}
user_plot_options.update(default_plot_options)
user_plot_options.update(file_options)
@@ -129,11 +108,6 @@ def iplot(figure_or_data, **plot_options):
plot_options keyword arguments:
filename (string) -- the name that will be associated with this figure
- fileopt ('new' | 'overwrite' | 'extend' | 'append')
- - 'new': create a new, unique url for this plot
- - 'overwrite': overwrite the file associated with `filename` with this
- - 'extend': add additional numbers (data) to existing traces
- - 'append': add additional traces to existing data lists
sharing ('public' | 'private' | 'secret') -- Toggle who can view this graph
- 'public': Anyone can view this graph. It will appear in your profile
and can appear in search engines. You do not need to be
@@ -192,11 +166,6 @@ def plot(figure_or_data, validate=True, **plot_options):
plot_options keyword arguments:
filename (string) -- the name that will be associated with this figure
- fileopt ('new' | 'overwrite' | 'extend' | 'append') -- 'new' creates a
- 'new': create a new, unique url for this plot
- 'overwrite': overwrite the file associated with `filename` with this
- 'extend': add additional numbers (data) to existing traces
- 'append': add additional traces to existing data lists
auto_open (default=True) -- Toggle browser options
True: open this plot in a new browser tab
False: do not open plot in the browser, but do return the unique url
@@ -251,22 +220,83 @@ def plot(figure_or_data, validate=True, **plot_options):
plot_options = _plot_option_logic(plot_options)
- fig = plotly.tools._replace_newline(figure) # does not mutate figure
- data = fig.get('data', [])
- plot_options['layout'] = fig.get('layout', {})
- response = v1.clientresp(data, **plot_options)
+ # Initialize API payload
+ payload = {
+ 'figure': figure,
+ 'world_readable': True
+ }
- # Check if the url needs a secret key
- url = response.json()['url']
- if plot_options['sharing'] == 'secret':
- if 'share_key=' not in url:
- # add_share_key_to_url updates the url to include the share_key
- url = add_share_key_to_url(url)
+ # Process filename
+ filename = plot_options.get('filename', None)
+ if filename:
+ # Strip trailing slash
+ if filename[-1] == '/':
+ filename = filename[0:-1]
- if plot_options['auto_open']:
- _open_url(url)
+ # split off any parent directory
+ paths = filename.split('/')
+ parent_path = '/'.join(paths[0:-1])
+ filename = paths[-1]
- return url
+ # Create parent directory
+ if parent_path != '':
+ file_ops.ensure_dirs(parent_path)
+ payload['parent_path'] = parent_path
+
+ payload['filename'] = filename
+ else:
+ parent_path = ''
+
+ # Process sharing
+ sharing = plot_options.get('sharing', None)
+ if sharing == 'public':
+ payload['world_readable'] = True
+ elif sharing == 'private':
+ payload['world_readable'] = False
+ elif sharing == 'secret':
+ payload['world_readable'] = False
+ payload['share_key_enabled'] = True
+ else:
+ raise _plotly_utils.exceptions.PlotlyError(
+ SHARING_ERROR_MSG
+ )
+
+ # Extract grid
+ figure, grid = _extract_grid_from_fig_like(figure)
+
+ # Upload grid if anything was extracted
+ if len(grid) > 0:
+ if not filename:
+ grid_filename = None
+ elif parent_path:
+ grid_filename = parent_path + '/' + filename + '_grid'
+ else:
+ grid_filename = filename + '_grid'
+
+ grid_ops.upload(grid=grid,
+ filename=grid_filename,
+ world_readable=payload['world_readable'],
+ auto_open=False)
+
+ _set_grid_column_references(figure, grid)
+ payload['figure'] = figure
+
+ file_info = _create_or_update(payload, 'plot')
+
+ # Compute viewing URL
+ if sharing == 'secret':
+ web_url = (file_info['web_url'][:-1] +
+ '?share_key=' + file_info['share_key'])
+ else:
+ web_url = file_info['web_url']
+
+ # Handle auto_open
+ auto_open = plot_options.get('auto_open', None)
+ if auto_open:
+ _open_url(web_url)
+
+ # Return URL
+ return web_url
def iplot_mpl(fig, resize=True, strip_style=False, update=None,
@@ -1384,24 +1414,6 @@ def add_share_key_to_url(plot_url, attempt=0):
return url_share_key
-def _send_to_plotly(figure, **plot_options):
- import plotly.tools
- fig = plotly.tools._replace_newline(figure) # does not mutate figure
- data = fig.get('data', [])
- response = v1.clientresp(data, **plot_options)
-
- parsed_content = response.json()
-
- # Check if the url needs a secret key
- if plot_options['sharing'] == 'secret':
- url = parsed_content['url']
- if 'share_key=' not in url:
- # add_share_key_to_url updates the url to include the share_key
- parsed_content['url'] = add_share_key_to_url(url)
-
- return parsed_content
-
-
def get_grid(grid_url, raw=False):
"""
Returns the specified grid as a Grid instance or in JSON/dict form.
@@ -1447,7 +1459,12 @@ def _create_or_update(data, filetype):
if filename:
try:
lookup_res = v2.files.lookup(filename)
- matching_file = json.loads(lookup_res.content)
+ if isinstance(lookup_res.content, bytes):
+ content = lookup_res.content.decode('utf-8')
+ else:
+ content = lookup_res.content
+
+ matching_file = json.loads(content)
if matching_file['filetype'] == filetype:
fid = matching_file['fid']
@@ -1984,74 +2001,14 @@ def create_animations(figure, filename=None, sharing='public', auto_open=True):
py.create_animations(figure, 'growing_circles')
```
"""
- payload = {
- 'figure': figure,
- 'world_readable': True
- }
-
- # set filename if specified
- if filename:
- # Strip trailing slash
- if filename[-1] == '/':
- filename = filename[0:-1]
-
- # split off any parent directory
- paths = filename.split('/')
- parent_path = '/'.join(paths[0:-1])
- filename = paths[-1]
-
- # Create parent directory
- if parent_path != '':
- file_ops.ensure_dirs(parent_path)
- payload['parent_path'] = parent_path
-
- payload['filename'] = filename
- else:
- parent_path = ''
-
- # set sharing
- if sharing == 'public':
- payload['world_readable'] = True
- elif sharing == 'private':
- payload['world_readable'] = False
- elif sharing == 'secret':
- payload['world_readable'] = False
- payload['share_key_enabled'] = True
- else:
- raise _plotly_utils.exceptions.PlotlyError(
- SHARING_ERROR_MSG
- )
-
- # Extract grid
- figure, grid = _extract_grid_from_fig_like(figure)
-
- if len(grid) > 0:
- if not filename:
- grid_filename = None
- elif parent_path:
- grid_filename = parent_path + '/' + filename + '_grid'
- else:
- grid_filename = filename + '_grid'
-
- grid_ops.upload(grid=grid,
- filename=grid_filename,
- world_readable=payload['world_readable'],
- auto_open=False)
- _set_grid_column_references(figure, grid)
- payload['figure'] = figure
-
- file_info = _create_or_update(payload, 'plot')
-
- if sharing == 'secret':
- web_url = (file_info['web_url'][:-1] +
- '?share_key=' + file_info['share_key'])
- else:
- web_url = file_info['web_url']
-
- if auto_open:
- _open_url(web_url)
-
- return web_url
+ # This function is no longer needed since plot now supports figures with
+ # frames. Delegate to this implementation for compatibility
+ return plot(
+ figure,
+ filename=filename,
+ sharing=sharing,
+ auto_open=auto_open,
+ )
def icreate_animations(figure, filename=None, sharing='public', auto_open=False):
diff --git a/packages/python/chart-studio/chart_studio/tests/test_optional/test_ipython/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_optional/test_ipython/__init__.py
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/packages/python/chart-studio/chart_studio/tests/test_optional/test_ipython/test_widgets.py b/packages/python/chart-studio/chart_studio/tests/test_optional/test_ipython/test_widgets.py
deleted file mode 100644
index 8cd365d1e49..00000000000
--- a/packages/python/chart-studio/chart_studio/tests/test_optional/test_ipython/test_widgets.py
+++ /dev/null
@@ -1,14 +0,0 @@
-"""
-Module for testing IPython widgets
-
-"""
-from __future__ import absolute_import
-
-from unittest import TestCase
-
-from chart_studio.widgets import GraphWidget
-
-class TestWidgets(TestCase):
-
- def test_instantiate_graph_widget(self):
- widget = GraphWidget
diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v1/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v1/__init__.py
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v1/test_clientresp.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v1/test_clientresp.py
deleted file mode 100644
index 2ce3fe66df2..00000000000
--- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v1/test_clientresp.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from __future__ import absolute_import
-
-from plotly import version
-from chart_studio.api.v1 import clientresp
-from chart_studio.tests.test_plot_ly.test_api import PlotlyApiTestCase
-
-
-class Duck(object):
- def to_plotly_json(self):
- return 'what else floats?'
-
-
-class ClientrespTest(PlotlyApiTestCase):
-
- def setUp(self):
- super(ClientrespTest, self).setUp()
-
- # Mock the actual api call, we don't want to do network tests here.
- self.request_mock = self.mock('chart_studio.api.v1.utils.requests.request')
- self.request_mock.return_value = self.get_response(b'{}', 200)
-
- # Mock the validation function since we can test that elsewhere.
- self.mock('chart_studio.api.v1.utils.validate_response')
-
- def test_data_only(self):
- data = [{'y': [3, 5], 'name': Duck()}]
- clientresp(data)
- assert self.request_mock.call_count == 1
-
- args, kwargs = self.request_mock.call_args
- method, url = args
- self.assertEqual(method, 'post')
- self.assertEqual(url, '{}/clientresp'.format(self.plotly_domain))
- expected_data = ({
- 'origin': 'plot',
- 'args': '[{"name": "what else floats?", "y": [3, 5]}]',
- 'platform': 'python', 'version': version.stable_semver(), 'key': 'bar',
- 'kwargs': '{}', 'un': 'foo'
- })
- self.assertEqual(kwargs['data'], expected_data)
- self.assertTrue(kwargs['verify'])
- self.assertEqual(kwargs['headers'], {})
-
- def test_data_and_kwargs(self):
- data = [{'y': [3, 5], 'name': Duck()}]
- clientresp_kwargs = {'layout': {'title': 'mah plot'}, 'filename': 'ok'}
- clientresp(data, **clientresp_kwargs)
- assert self.request_mock.call_count == 1
- args, kwargs = self.request_mock.call_args
- method, url = args
- self.assertEqual(method, 'post')
- self.assertEqual(url, '{}/clientresp'.format(self.plotly_domain))
- expected_data = ({
- 'origin': 'plot',
- 'args': '[{"name": "what else floats?", "y": [3, 5]}]',
- 'platform': 'python', 'version': version.stable_semver(), 'key': 'bar',
- 'kwargs': '{"filename": "ok", "layout": {"title": "mah plot"}}',
- 'un': 'foo'
- })
- self.assertEqual(kwargs['data'], expected_data)
- self.assertTrue(kwargs['verify'])
- self.assertEqual(kwargs['headers'], {})
diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v1/test_utils.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v1/test_utils.py
deleted file mode 100644
index 8fb761de550..00000000000
--- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v1/test_utils.py
+++ /dev/null
@@ -1,181 +0,0 @@
-from __future__ import absolute_import
-
-from requests import Response
-import json as _json
-from requests.exceptions import ConnectionError
-
-from chart_studio.api.utils import to_native_utf8_string
-from chart_studio.api.v1 import utils
-from chart_studio.exceptions import PlotlyRequestError
-from _plotly_utils.exceptions import PlotlyError
-from chart_studio.session import sign_in
-from chart_studio.tests.test_plot_ly.test_api import PlotlyApiTestCase
-from chart_studio.tests.utils import PlotlyTestCase
-
-import sys
-
-# import from mock, MagicMock
-if sys.version_info.major == 3 and sys.version_info.minor >= 3:
- from unittest.mock import MagicMock, patch
-else:
- from mock import patch, MagicMock
-
-
-class ValidateResponseTest(PlotlyApiTestCase):
-
- def test_validate_ok(self):
- try:
- utils.validate_response(self.get_response(content=b'{}'))
- except PlotlyRequestError:
- self.fail('Expected this to pass!')
-
- def test_validate_not_ok(self):
- bad_status_codes = (400, 404, 500)
- for bad_status_code in bad_status_codes:
- response = self.get_response(content=b'{}',
- status_code=bad_status_code)
- self.assertRaises(PlotlyRequestError, utils.validate_response,
- response)
-
- def test_validate_no_content(self):
-
- # We shouldn't flake if the response has no content.
-
- response = self.get_response(content=b'', status_code=200)
- try:
- utils.validate_response(response)
- except PlotlyRequestError as e:
- self.assertEqual(e.message, 'No Content')
- self.assertEqual(e.status_code, 200)
- self.assertEqual(e.content, b'')
- else:
- self.fail('Expected this to raise!')
-
- def test_validate_non_json_content(self):
- response = self.get_response(content=b'foobar', status_code=200)
- try:
- utils.validate_response(response)
- except PlotlyRequestError as e:
- self.assertEqual(e.message, 'foobar')
- self.assertEqual(e.status_code, 200)
- self.assertEqual(e.content, b'foobar')
- else:
- self.fail('Expected this to raise!')
-
- def test_validate_json_content_array(self):
- content = self.to_bytes(_json.dumps([1, 2, 3]))
- response = self.get_response(content=content, status_code=200)
- try:
- utils.validate_response(response)
- except PlotlyRequestError as e:
- self.assertEqual(e.message, to_native_utf8_string(content))
- self.assertEqual(e.status_code, 200)
- self.assertEqual(e.content, content)
- else:
- self.fail('Expected this to raise!')
-
- def test_validate_json_content_dict_no_error(self):
- content = self.to_bytes(_json.dumps({'foo': 'bar'}))
- response = self.get_response(content=content, status_code=400)
- try:
- utils.validate_response(response)
- except PlotlyRequestError as e:
- self.assertEqual(e.message, to_native_utf8_string(content))
- self.assertEqual(e.status_code, 400)
- self.assertEqual(e.content, content)
- else:
- self.fail('Expected this to raise!')
-
- def test_validate_json_content_dict_error_empty(self):
- content = self.to_bytes(_json.dumps({'error': ''}))
- response = self.get_response(content=content, status_code=200)
- try:
- utils.validate_response(response)
- except PlotlyRequestError:
- self.fail('Expected this not to raise!')
-
- def test_validate_json_content_dict_one_error_ok(self):
- content = self.to_bytes(_json.dumps({'error': 'not ok!'}))
- response = self.get_response(content=content, status_code=200)
- try:
- utils.validate_response(response)
- except PlotlyRequestError as e:
- self.assertEqual(e.message, 'not ok!')
- self.assertEqual(e.status_code, 200)
- self.assertEqual(e.content, content)
- else:
- self.fail('Expected this to raise!')
-
-
-class GetHeadersTest(PlotlyTestCase):
-
- def setUp(self):
- super(GetHeadersTest, self).setUp()
- self.domain = 'https://foo.bar'
- self.username = 'hodor'
- self.api_key = 'secret'
- sign_in(self.username, self.api_key, proxy_username='kleen-kanteen',
- proxy_password='hydrated', plotly_proxy_authorization=False)
-
- def test_normal_auth(self):
- headers = utils.get_headers()
- expected_headers = {}
- self.assertEqual(headers, expected_headers)
-
- def test_proxy_auth(self):
- sign_in(self.username, self.api_key, plotly_proxy_authorization=True)
- headers = utils.get_headers()
- expected_headers = {
- 'authorization': 'Basic a2xlZW4ta2FudGVlbjpoeWRyYXRlZA=='
- }
- self.assertEqual(headers, expected_headers)
-
-
-class RequestTest(PlotlyTestCase):
-
- def setUp(self):
- super(RequestTest, self).setUp()
- self.domain = 'https://foo.bar'
- self.username = 'hodor'
- self.api_key = 'secret'
- sign_in(self.username, self.api_key, proxy_username='kleen-kanteen',
- proxy_password='hydrated', plotly_proxy_authorization=False)
-
- # Mock the actual api call, we don't want to do network tests here.
- patcher = patch('chart_studio.api.v1.utils.requests.request')
- self.request_mock = patcher.start()
- self.addCleanup(patcher.stop)
- self.request_mock.return_value = MagicMock(Response)
-
- # Mock the validation function since we test that elsewhere.
- patcher = patch('chart_studio.api.v1.utils.validate_response')
- self.validate_response_mock = patcher.start()
- self.addCleanup(patcher.stop)
-
- self.method = 'get'
- self.url = 'https://foo.bar.does.not.exist.anywhere'
-
- def test_request_with_json(self):
-
- # You can pass along non-native objects in the `json` kwarg for a
- # requests.request, however, V1 packs up json objects a little
- # differently, so we don't allow such requests.
-
- self.assertRaises(PlotlyError, utils.request, self.method,
- self.url, json={})
-
- def test_request_with_ConnectionError(self):
-
- # requests can flake out and not return a response object, we want to
- # make sure we remain consistent with our errors.
-
- self.request_mock.side_effect = ConnectionError()
- self.assertRaises(PlotlyRequestError, utils.request, self.method,
- self.url)
-
- def test_request_validate_response(self):
-
- # Finally, we check details elsewhere, but make sure we do validate.
-
- utils.request(self.method, self.url)
- assert self.validate_response_mock.call_count == 1
diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_plotly/test_plot.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_plotly/test_plot.py
index 6c0bd8934c3..dd8fa3cb980 100644
--- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_plotly/test_plot.py
+++ b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_plotly/test_plot.py
@@ -129,7 +129,6 @@ def test_plot_option_logic_only_world_readable_given(self):
kwargs = {'filename': 'test',
'auto_open': True,
- 'fileopt': 'overwrite',
'validate': True,
'world_readable': False}
@@ -137,7 +136,6 @@ def test_plot_option_logic_only_world_readable_given(self):
expected_plot_option_logic = {'filename': 'test',
'auto_open': True,
- 'fileopt': 'overwrite',
'validate': True,
'world_readable': False,
'sharing': 'private'}
@@ -150,7 +148,6 @@ def test_plot_option_logic_only_sharing_given(self):
kwargs = {'filename': 'test',
'auto_open': True,
- 'fileopt': 'overwrite',
'validate': True,
'sharing': 'private'}
@@ -158,51 +155,6 @@ def test_plot_option_logic_only_sharing_given(self):
expected_plot_option_logic = {'filename': 'test',
'auto_open': True,
- 'fileopt': 'overwrite',
- 'validate': True,
- 'world_readable': False,
- 'sharing': 'private'}
- self.assertEqual(plot_option_logic, expected_plot_option_logic)
-
- def test_plot_option_fileopt_deprecations(self):
-
- # Make sure DeprecationWarnings aren't filtered out by nose
- warnings.filterwarnings('default', category=DeprecationWarning)
-
- # If filename is not given and fileopt is not 'new',
- # raise a deprecation warning
- kwargs = {'auto_open': True,
- 'fileopt': 'overwrite',
- 'validate': True,
- 'sharing': 'private'}
-
- with warnings.catch_warnings(record=True) as w:
- plot_option_logic = py._plot_option_logic(kwargs)
- assert w[0].category == DeprecationWarning
-
- expected_plot_option_logic = {'filename': 'plot from API',
- 'auto_open': True,
- 'fileopt': 'overwrite',
- 'validate': True,
- 'world_readable': False,
- 'sharing': 'private'}
- self.assertEqual(plot_option_logic, expected_plot_option_logic)
-
- # If filename is given and fileopt is not 'overwrite',
- # raise a depreacation warning
- kwargs = {'filename': 'test',
- 'auto_open': True,
- 'fileopt': 'append',
- 'validate': True,
- 'sharing': 'private'}
-
- with warnings.catch_warnings(record=True) as w:
- plot_option_logic = py._plot_option_logic(kwargs)
- assert w[0].category == DeprecationWarning
-
- expected_plot_option_logic = {'filename': 'test',
- 'auto_open': True,
- 'fileopt': 'append',
'validate': True,
'world_readable': False,
'sharing': 'private'}
@@ -218,11 +170,10 @@ def test_plot_url_given_sharing_key(self):
fig = plotly.tools.return_figure_from_figure_or_data(self.simple_figure,
validate)
kwargs = {'filename': 'is_share_key_included',
- 'fileopt': 'overwrite',
'world_readable': False,
+ 'auto_open': False,
'sharing': 'secret'}
- response = py._send_to_plotly(fig, **kwargs)
- plot_url = response['url']
+ plot_url = py.plot(fig, **kwargs)
self.assertTrue('share_key=' in plot_url)
@@ -233,7 +184,6 @@ def test_plot_url_response_given_sharing_key(self):
# be 200
kwargs = {'filename': 'is_share_key_included',
- 'fileopt': 'overwrite',
'auto_open': False,
'world_readable': False,
'sharing': 'secret'}
@@ -253,12 +203,12 @@ def test_private_plot_response_with_and_without_share_key(self):
# share_key is added it should be 200
kwargs = {'filename': 'is_share_key_included',
- 'fileopt': 'overwrite',
'world_readable': False,
+ 'auto_open': False,
'sharing': 'private'}
- private_plot_url = py._send_to_plotly(self.simple_figure,
- **kwargs)['url']
+ private_plot_url = py.plot(self.simple_figure,
+ **kwargs)
private_plot_response = requests.get(private_plot_url + ".json")
# The json file of the private plot should be 404
@@ -299,7 +249,7 @@ def test_default_options(self):
options = py._plot_option_logic({})
config_options = tls.get_config_file()
for key in options:
- if key != 'fileopt' and key in config_options:
+ if key in config_options:
self.assertEqual(options[key], config_options[key])
def test_conflicting_plot_options_in_plot_option_logic(self):
diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_stream/test_stream.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_stream/test_stream.py
index 7d7aac7670a..1e544b81185 100644
--- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_stream/test_stream.py
+++ b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_stream/test_stream.py
@@ -35,7 +35,7 @@ def test_initialize_stream_plot(self):
auto_open=False,
world_readable=True,
filename='stream-test')
- assert url == 'https://plot.ly/~PythonAPI/461'
+ self.assertEqual('https://plot.ly/~PythonAPI/461/', url)
time.sleep(.5)
@attr('slow')
diff --git a/packages/python/chart-studio/chart_studio/tools.py b/packages/python/chart-studio/chart_studio/tools.py
index 37acf6ec19e..bdcd66e9432 100644
--- a/packages/python/chart-studio/chart_studio/tools.py
+++ b/packages/python/chart-studio/chart_studio/tools.py
@@ -22,6 +22,8 @@
from chart_studio.files import CONFIG_FILE, CREDENTIALS_FILE, FILE_CONTENT
ipython_core_display = optional_imports.get_module('IPython.core.display')
+ipython_display = optional_imports.get_module('IPython.display')
+
sage_salvus = optional_imports.get_module('sage_salvus')
@@ -231,30 +233,7 @@ def reset_config_file():
### embed tools ###
-
-def get_embed(file_owner_or_url, file_id=None, width="100%", height=525):
- """Returns HTML code to embed figure on a webpage as an