-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract chart studio functionality, optimized imports (#1476)
## Overview This PR is an important step towards the [version 4 goal](#1420) of removing all of the chart studio (i.e. cloud-service related) functionality from plotly.py, and putting it in a separate optional package. ## chart studio extraction For the time being, I've done this by creating a new top-level `chart_studio` package next to the top-level `plotly` package. I've moved all of the cloud-related functionality to the `chart_studio` package, following the same structure as in the current plotly package. For example, the `plotly.plotly` module was moved to `chart_studio.plotly`. This PR takes advantage of the `_future_plotly_` system introduced in #1474 to make this refactor backward compatible. - By default all of the old entry points are still usable and they are aliased to the `chart_studio` package. - If the `extract_chart_studio` future flag is set, then deprecation warnings are raised whenever the `chart_studio` modules/functions are used from their legacy locations under the `plotly` package. - If the `remove_deprecations` future flag is set then the chart studio functions are fully removed from the plotly package and are accessible only under `chart_studio`. When `remove_deprecations` is set, `plotly` has no dependency on the `chart_studio` package. ## Usage To remove the chart_studio functionality from the main `plotly` module, use the ```python from _plotly_future_ import remove_deprecations ``` This will further speed up imports, and will allow for testing code to make sure it will be compatible with the package structure of plotly.py version 4. ## Import optimization This PR also makes a relatively minor change to the code generation logic for `graph_objs` and `validator` that yields an import time reduction of ~10-20% . Rather that creating a single file for each datatype and validator class, all of the classes in a `graph_obj` or `validator` module are specified directly in the `__init__.py` file. This reduces the number of files significantly, which seems to yield a modest but consistent speedup while being 100% backward compatible.
- Loading branch information
Showing
8,169 changed files
with
496,021 additions
and
484,974 deletions.
The diff you're trying to view is too large. We only load the first 3000 changed files.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Oops, something went wrong.