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 = """
-<!DOCTYPE HTML>
-<html>
-  <head>
-    <style>
-      body {{
-        margin: 0px;
-        padding: 0px;
-      }}
-    </style>
-  </head>
-  <body>
-    <canvas id="myCanvas" width="{width}" height="{height}"></canvas>
-    <script>
-      var canvas = document.getElementById('myCanvas');
-      var context = canvas.getContext('2d');
-      <!-- Dashboard -->
-      context.beginPath();
-      context.rect(0, 0, {width}, {height});
-      context.lineWidth = 2;
-      context.strokeStyle = 'black';
-      context.stroke();
-      </script>
-  </body>
-</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 = """<!-- Draw some lines in -->
-          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("</script>") - 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 = """<!-- Insert box numbers -->
-          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("</script>") - 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 = '<name="{name}", data={data_string}, id={id}>'
-        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 = (
-            '<iframe id="igraph" scrolling="no" '
-            'style="border:none;" seamless="seamless" '
-            'src="{plotly_rest_url}/'
-            "~{file_owner}/{file_id}.embed?"
-            'share_key={share_key}" '
-            'height="{iframe_height}" '
-            'width="{iframe_width}">'
-            "</iframe>"
-        ).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://raw.githubusercontent.com/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://raw.githubusercontent.com/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 <iframe>
-
-    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
-
-    """
-    embed_url = _get_embed_url(file_owner_or_url, file_id)
-
-    return (
-        '<iframe id="igraph" scrolling="no" style="border:none;" '
-        'seamless="seamless" '
-        'src="{embed_url}" '
-        'height="{iframe_height}" width="{iframe_width}">'
-        "</iframe>"
-    ).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'))
-<Column "column 1": [1, 2, 3]>
-```
-
-```python
->> print(Grid(col1, col2))
-<Grid: [<Column "column 1": [1, 2, 3]>, <Column "column 2": ["a", "b", "c"]>]>
-```
-
-```python
->> grid = Grid(col1, col2)
->> print(grid[0])
-<Column "column 1": [1, 2, 3]>
-```
-
-```python
->> grid = Grid(col1, col2)
->> print(grid.get_column('column 1'))
-<Column "column 1": [1, 2, 3]>
-```
-
-### 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.