Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract chart studio functionality, optimized imports #1476

Merged
merged 89 commits into from
Apr 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
54c96fa
Initial plotly.io.renderers implementation
jonmmease Mar 16, 2019
8342680
rename 'default' to 'plotly_mimetype'
jonmmease Mar 16, 2019
2cc4f89
Added _repr_mimebundle_
jonmmease Mar 16, 2019
28a669c
Add initial browser renderer to show figure in browser tab
jonmmease Mar 16, 2019
8bf793f
Expose renderer base classes
jonmmease Mar 17, 2019
3dfb24a
Documentation / cleanup of _renderers
jonmmease Mar 17, 2019
2dd92c0
Auto-detect VSCode environment and default to renderer combination of
jonmmease Mar 18, 2019
953bae8
Documentation / cleanup of _base_renderers
jonmmease Mar 18, 2019
da39c34
Update default renderers explanation
jonmmease Mar 18, 2019
c2a6471
Add renderer docstring to repr for easy discoverability
jonmmease Mar 18, 2019
e6b9e45
Make ipython an optional dependency
jonmmease Mar 18, 2019
54ac011
Python 2.7 support
jonmmease Mar 18, 2019
7dc1029
Add parent <html></html. tags for HtmlRenderer with fullhtml=True
jonmmease Mar 18, 2019
c23f76e
Added initial renderers test suite
jonmmease Mar 18, 2019
a1d9d9e
Added IFrameRenderer
jonmmease Mar 19, 2019
9d082f0
Proper HTML tags for fullhtml
jonmmease Mar 19, 2019
b499b35
Added initial to_html functions to plotly.io
jonmmease Mar 19, 2019
63424a5
Reimplement plotly.offline.plot using plotly.io.write_html/to_div.
jonmmease Mar 19, 2019
28463c2
Reimplement plotly.offline.iplot and init_notebook_mode pio.show
jonmmease Mar 19, 2019
e4421bb
Add responsive=True config when figure width/height aren't set
jonmmease Mar 19, 2019
3b11783
Add MathJax configuration when initializing HTML renderer in notebook
jonmmease Mar 19, 2019
2580797
Reimplement HTML Renderers using the plotly.io html functions
jonmmease Mar 19, 2019
8a787fd
Restore plot/iplot image export by adding support for custom JS snippets
jonmmease Mar 20, 2019
c4cb4a7
Remove default renderer and make rendering figure on display an option
jonmmease Mar 20, 2019
a109f86
Update renderer tests with plotly.io.renderers.render_on_display
jonmmease Mar 20, 2019
f7f7cb7
Add download image plotly.offline.plot test
jonmmease Mar 20, 2019
f073a14
add set_v3_defaults and set_v4_defaults renderer methods to make
jonmmease Mar 20, 2019
1058683
Added future flag system under _plotly_future_ to control
jonmmease Mar 20, 2019
df3515e
Roll to_div function into to_html with full_html argument
jonmmease Mar 20, 2019
86eb69a
Rename SideEffectRenderer -> ExternalRenderer
jonmmease Mar 21, 2019
049e304
WIP delaying import in io module.
jonmmease Mar 21, 2019
1d3ac88
Initial chart_studio refactor
jonmmease Mar 21, 2019
2649557
Fix up chart_studio imports. Everything import cleanly now
jonmmease Mar 21, 2019
562a60a
Mapping to deprecated modules
jonmmease Mar 21, 2019
81221de
Per function deprecation of chart_studio tools functions
jonmmease Mar 22, 2019
a51730b
Add extract_chart_studio to v4 future group
jonmmease Mar 22, 2019
b2542c1
whitespace
jonmmease Mar 22, 2019
75562fa
WIP checkpoint on import optimization
jonmmease Mar 22, 2019
7765578
Optimize import time a bit by reducing the number of files create
jonmmease Mar 23, 2019
68f039f
Delay optional imports of pandas/numpy/matplotlib
jonmmease Mar 23, 2019
9aae2c7
Restore proper versioneer _version.py file
jonmmease Mar 23, 2019
c209a18
Fix optional test suite
jonmmease Mar 23, 2019
084ac36
offline fixes
jonmmease Mar 23, 2019
19de7b9
Use correct config property to grab platform url from saved config
jonmmease Mar 23, 2019
ecf4867
Remove legacy unused _plot_html function
jonmmease Mar 23, 2019
b87ebed
Move static image renderer tests to orca test suite
jonmmease Mar 23, 2019
3907d61
Merge enh_renderers
jonmmease Mar 23, 2019
81c4ced
Move optional_imports to plotly_utils so that it can be used in
jonmmease Mar 23, 2019
e11d74f
Move combination renderers test to orca test suite
jonmmease Mar 23, 2019
e8d2f1b
Remove remaining image renderer references from test_io
jonmmease Mar 23, 2019
de80ffc
Add ipython to orca environment for renderer tests
jonmmease Mar 23, 2019
7cba367
Remove pdf image from renderer tests since it seems to be non-determi…
jonmmease Mar 23, 2019
3451340
Merge branch 'enh_renderers' into imports_v4
jonmmease Mar 23, 2019
f19f150
Added get_config_plotly_server_url to retrieve `plotly_domain`
jonmmease Mar 23, 2019
b24bc7e
Fix jupyter connected test
jonmmease Mar 23, 2019
cb699f4
delete tmp files
jonmmease Mar 23, 2019
a58d68c
Fixed optional tests
jonmmease Mar 23, 2019
fc2a716
chart_studio test fixes for new import structure
jonmmease Mar 23, 2019
73752d7
Added chromium browser renderer
jonmmease Mar 26, 2019
8a0f6cc
Fix plotly mimetype renderer when used with numpy arrays/pandas Series
jonmmease Mar 26, 2019
504e342
Lazily initialize default renderers.
jonmmease Mar 26, 2019
1115608
Use 'browser' default renderer if ipython isn't available
jonmmease Mar 26, 2019
1bbbe83
Merge branch 'enh_renderers' into imports_v4
jonmmease Mar 26, 2019
6071473
Remove non_recursive_imports flag for this PR
jonmmease Mar 26, 2019
d8fb7c8
Update setup.py `packages`
jonmmease Mar 26, 2019
3af116a
Add plotly.widgets deprecation package
jonmmease Mar 26, 2019
0b8cfb6
Remove moved packages from setup.py
jonmmease Mar 26, 2019
a49c937
Update tox to run tests from chart_studio package
jonmmease Mar 26, 2019
ea74d44
Add the chart_studio package itself to setup.py
jonmmease Mar 26, 2019
43c99ce
Test fixes
jonmmease Mar 27, 2019
9e5504a
Test fixes
jonmmease Mar 27, 2019
12c58bb
Reorganize exceptions to work around circular import errors
jonmmease Mar 27, 2019
49ecfbf
Move plotly.file to _plotly_utils to avoid circular import errors
jonmmease Mar 27, 2019
574b1b9
Update version constraints on pandas/numpy
jonmmease Mar 27, 2019
db01dd4
Move PlotlyJSONEncoder ot _plotly_utils
jonmmease Mar 27, 2019
6921bf1
Python 2 absolute import
jonmmease Mar 27, 2019
f7456d2
plotly.api -> chart_studio.api within chart_studio package
jonmmease Mar 27, 2019
1945368
Merge branch 'master'
jonmmease Apr 10, 2019
b1c3f34
Fix plotly.tools import
jonmmease Apr 10, 2019
ef9ac99
chart_studio recursive imports
jonmmease Apr 10, 2019
cdf2c40
plotly -> chart_studio import
jonmmease Apr 11, 2019
0b4cd7a
chart_studio -> plotly in templategen
jonmmease Apr 11, 2019
5e4aca9
typo
jonmmease Apr 11, 2019
609d1f7
typo
jonmmease Apr 11, 2019
fe64acf
Code review updates
jonmmease Apr 11, 2019
922458d
Restore all of the original tests to test our backward compatibility
jonmmease Apr 12, 2019
860e078
test fixes
jonmmease Apr 12, 2019
54c33f2
Fix mock modules
jonmmease Apr 12, 2019
4b69d6a
session test fix
jonmmease Apr 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
49 changes: 49 additions & 0 deletions _plotly_future_/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import warnings
import functools

_future_flags = set()


Expand All @@ -6,3 +9,49 @@ def _assert_plotly_not_imported():
if 'plotly' in sys.modules:
raise ImportError("""\
The _plotly_future_ module must be imported before the plotly module""")


warnings.filterwarnings(
'default',
'.*?is deprecated, please use chart_studio*',
DeprecationWarning
)


def _chart_studio_warning(submodule):
if 'extract_chart_studio' in _future_flags:
warnings.warn(
'The plotly.{submodule} module is deprecated, '
'please use chart_studio.{submodule} instead'
.format(submodule=submodule),
DeprecationWarning,
stacklevel=2)


def _chart_studio_deprecation(fn):

fn_name = fn.__name__
fn_module = fn.__module__
plotly_name = '.'.join(
['plotly'] + fn_module.split('.')[1:] + [fn_name])
chart_studio_name = '.'.join(
['chart_studio'] + fn_module.split('.')[1:] + [fn_name])

msg = """\
{plotly_name} is deprecated, please use {chart_studio_name}\
""".format(plotly_name=plotly_name, chart_studio_name=chart_studio_name)

@functools.wraps(fn)
def wrapper(*args, **kwargs):
if 'extract_chart_studio' in _future_flags:
warnings.warn(
msg,
DeprecationWarning,
stacklevel=2)

return fn(*args, **kwargs)

return wrapper


__all__ = ['_future_flags', '_chart_studio_warning']
5 changes: 5 additions & 0 deletions _plotly_future_/extract_chart_studio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from __future__ import absolute_import
from _plotly_future_ import _future_flags, _assert_plotly_not_imported

_assert_plotly_not_imported()
_future_flags.add('extract_chart_studio')
5 changes: 5 additions & 0 deletions _plotly_future_/remove_deprecations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from __future__ import absolute_import
from _plotly_future_ import _future_flags, _assert_plotly_not_imported

_assert_plotly_not_imported()
_future_flags.add('remove_deprecations')
4 changes: 3 additions & 1 deletion _plotly_future_/v4.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
from __future__ import absolute_import
from _plotly_future_ import renderer_defaults, template_defaults
from _plotly_future_ import (
renderer_defaults, template_defaults, extract_chart_studio,
remove_deprecations)
38 changes: 15 additions & 23 deletions _plotly_utils/basevalidators.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,18 @@
from __future__ import absolute_import

import base64
import numbers
import textwrap
import uuid
from importlib import import_module
import copy

import io
from copy import deepcopy

import re

# Optional imports
# ----------------
import sys
from six import string_types

np = None
pd = None

try:
np = import_module('numpy')

try:
pd = import_module('pandas')
except ImportError:
pass

except ImportError:
pass
from _plotly_utils.optional_imports import get_module


# back-port of fullmatch from Py3.4+
Expand All @@ -50,6 +35,8 @@ def to_scalar_or_list(v):
# Python native scalar type ('float' in the example above).
# We explicitly check if is has the 'item' method, which conventionally
# converts these types to native scalars.
np = get_module('numpy')
pd = get_module('pandas')
if np and np.isscalar(v) and hasattr(v, 'item'):
return v.item()
if isinstance(v, (list, tuple)):
Expand Down Expand Up @@ -86,7 +73,8 @@ def copy_to_readonly_numpy_array(v, kind=None, force_numeric=False):
np.ndarray
Numpy array with the 'WRITEABLE' flag set to False
"""

np = get_module('numpy')
pd = get_module('pandas')
assert np is not None

# ### Process kind ###
Expand Down Expand Up @@ -175,7 +163,9 @@ def is_numpy_convertable(v):
def is_homogeneous_array(v):
"""
Return whether a value is considered to be a homogeneous array
"""
"""
np = get_module('numpy')
pd = get_module('pandas')
if ((np and isinstance(v, np.ndarray) or
(pd and isinstance(v, (pd.Series, pd.Index))))):
return True
Expand Down Expand Up @@ -616,7 +606,7 @@ def description(self):
as a plotly.grid_objs.Column object""".format(plotly_name=self.plotly_name))

def validate_coerce(self, v):
from plotly.grid_objs import Column
from chart_studio.grid_objs import Column
if v is None:
# Pass None through
pass
Expand Down Expand Up @@ -704,7 +694,7 @@ def validate_coerce(self, v):
# Pass None through
pass
elif self.array_ok and is_homogeneous_array(v):

np = get_module('numpy')
try:
v_array = copy_to_readonly_numpy_array(v, force_numeric=True)
except (ValueError, TypeError, OverflowError):
Expand Down Expand Up @@ -825,7 +815,7 @@ def validate_coerce(self, v):
# Pass None through
pass
elif self.array_ok and is_homogeneous_array(v):

np = get_module('numpy')
v_array = copy_to_readonly_numpy_array(v,
kind=('i', 'u'),
force_numeric=True)
Expand Down Expand Up @@ -964,6 +954,8 @@ def validate_coerce(self, v):
self.raise_invalid_elements(invalid_els)

if is_homogeneous_array(v):
np = get_module('numpy')

# If not strict, let numpy cast elements to strings
v = copy_to_readonly_numpy_array(v, kind='U')

Expand Down
82 changes: 82 additions & 0 deletions _plotly_utils/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
class PlotlyError(Exception):
pass


class PlotlyEmptyDataError(PlotlyError):
pass


class PlotlyGraphObjectError(PlotlyError):
def __init__(self, message='', path=(), notes=()):
"""
General graph object error for validation failures.

:param (str|unicode) message: The error message.
:param (iterable) path: A path pointing to the error.
:param notes: Add additional notes, but keep default exception message.

"""
self.message = message
self.plain_message = message # for backwards compat
self.path = list(path)
self.notes = notes
super(PlotlyGraphObjectError, self).__init__(message)

def __str__(self):
"""This is called by Python to present the error message."""
format_dict = {
'message': self.message,
'path': '[' + ']['.join(repr(k) for k in self.path) + ']',
'notes': '\n'.join(self.notes)
}
return ('{message}\n\nPath To Error: {path}\n\n{notes}'
.format(**format_dict))


class PlotlyDictKeyError(PlotlyGraphObjectError):
def __init__(self, obj, path, notes=()):
"""See PlotlyGraphObjectError.__init__ for param docs."""
format_dict = {'attribute': path[-1], 'object_name': obj._name}
message = ("'{attribute}' is not allowed in '{object_name}'"
.format(**format_dict))
notes = [obj.help(return_help=True)] + list(notes)
super(PlotlyDictKeyError, self).__init__(
message=message, path=path, notes=notes
)


class PlotlyDictValueError(PlotlyGraphObjectError):
def __init__(self, obj, path, notes=()):
"""See PlotlyGraphObjectError.__init__ for param docs."""
format_dict = {'attribute': path[-1], 'object_name': obj._name}
message = ("'{attribute}' has invalid value inside '{object_name}'"
.format(**format_dict))
notes = [obj.help(path[-1], return_help=True)] + list(notes)
super(PlotlyDictValueError, self).__init__(
message=message, notes=notes, path=path
)


class PlotlyListEntryError(PlotlyGraphObjectError):
def __init__(self, obj, path, notes=()):
"""See PlotlyGraphObjectError.__init__ for param docs."""
format_dict = {'index': path[-1], 'object_name': obj._name}
message = ("Invalid entry found in '{object_name}' at index, '{index}'"
.format(**format_dict))
notes = [obj.help(return_help=True)] + list(notes)
super(PlotlyListEntryError, self).__init__(
message=message, path=path, notes=notes
)


class PlotlyDataTypeError(PlotlyGraphObjectError):
def __init__(self, obj, path, notes=()):
"""See PlotlyGraphObjectError.__init__ for param docs."""
format_dict = {'index': path[-1], 'object_name': obj._name}
message = ("Invalid entry found in '{object_name}' at index, '{index}'"
.format(**format_dict))
note = "It's invalid because it doesn't contain a valid 'type' value."
notes = [note] + list(notes)
super(PlotlyDataTypeError, self).__init__(
message=message, path=path, notes=notes
)
36 changes: 36 additions & 0 deletions _plotly_utils/files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import os

PLOTLY_DIR = os.environ.get("PLOTLY_DIR",
os.path.join(os.path.expanduser("~"), ".plotly"))
TEST_FILE = os.path.join(PLOTLY_DIR, ".permission_test")


def _permissions():
try:
if not os.path.exists(PLOTLY_DIR):
try:
os.mkdir(PLOTLY_DIR)
except Exception:
# in case of race
if not os.path.isdir(PLOTLY_DIR):
raise
with open(TEST_FILE, 'w') as f:
f.write('testing\n')
try:
os.remove(TEST_FILE)
except Exception:
pass
return True
except Exception: # Do not trap KeyboardInterrupt.
return False


_file_permissions = None


def ensure_writable_plotly_dir():
# Cache permissions status
global _file_permissions
if _file_permissions is None:
_file_permissions = _permissions()
return _file_permissions
31 changes: 31 additions & 0 deletions _plotly_utils/optional_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
Stand-alone module to provide information about whether optional deps exist.

"""
from __future__ import absolute_import

from importlib import import_module
import logging

logger = logging.getLogger(__name__)
_not_importable = set()


def get_module(name):
"""
Return module or None. Absolute import is required.

:param (str) name: Dot-separated module path. E.g., 'scipy.stats'.
:raise: (ImportError) Only when exc_msg is defined.
:return: (module|None) If import succeeds, the module will be returned.

"""
if name not in _not_importable:
try:
return import_module(name)
except ImportError:
_not_importable.add(name)
except Exception as e:
_not_importable.add(name)
msg = "Error importing optional module {}".format(name)
logger.exception(msg)
Loading