diff --git a/CHANGELOG.md b/CHANGELOG.md index cd428c03688..9b423962209 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [4.7.1] - ??? + +### Fixed + + - Fix `AttributeError: module 'plotly.graph_objs' has no attribute 'FigureWidget'` exception on `from plotly.graph_objs import *` when `ipywidgets` is not installed. Error also occurred when importing `plotly.figure_factor`. It is now possible to import `plotly.graph_objs.FigureWidget` when `ipywidgets` is not installed, and an informative `ImportError` exception will be raised in the `FigureWidget` constructor ([#2443](https://github.com/plotly/plotly.py/issues/2443), [#1111](https://github.com/plotly/plotly.py/issues/1111)). + + ## [4.7.0] - 2020-05-06 ### Updated diff --git a/packages/python/plotly/codegen/__init__.py b/packages/python/plotly/codegen/__init__.py index 82f3b94abd9..3f61adbe5dd 100644 --- a/packages/python/plotly/codegen/__init__.py +++ b/packages/python/plotly/codegen/__init__.py @@ -269,14 +269,14 @@ def perform_codegen(): optional_figure_widget_import = f""" if sys.version_info < (3, 7): try: - import ipywidgets - from distutils.version import LooseVersion - if LooseVersion(ipywidgets.__version__) >= LooseVersion('7.0.0'): + import ipywidgets as _ipywidgets + from distutils.version import LooseVersion as _LooseVersion + if _LooseVersion(_ipywidgets.__version__) >= _LooseVersion("7.0.0"): from ..graph_objs._figurewidget import FigureWidget - del LooseVersion - del ipywidgets - except ImportError: - pass + else: + raise ImportError() + except Exception: + from ..missing_ipywidgets import FigureWidget else: __all__.append("FigureWidget") orig_getattr = __getattr__ @@ -285,12 +285,17 @@ def __getattr__(import_name): try: import ipywidgets from distutils.version import LooseVersion - if LooseVersion(ipywidgets.__version__) >= LooseVersion('7.0.0'): + + if LooseVersion(ipywidgets.__version__) >= LooseVersion("7.0.0"): from ..graph_objs._figurewidget import FigureWidget + return FigureWidget - except ImportError: - pass - + else: + raise ImportError() + except Exception: + from ..missing_ipywidgets import FigureWidget + return FigureWidget + return orig_getattr(import_name) """ # ### __all__ ### diff --git a/packages/python/plotly/plotly/graph_objects/__init__.py b/packages/python/plotly/plotly/graph_objects/__init__.py index d63d8ff757b..cb52fcecac9 100644 --- a/packages/python/plotly/plotly/graph_objects/__init__.py +++ b/packages/python/plotly/plotly/graph_objects/__init__.py @@ -261,15 +261,15 @@ if sys.version_info < (3, 7): try: - import ipywidgets - from distutils.version import LooseVersion + import ipywidgets as _ipywidgets + from distutils.version import LooseVersion as _LooseVersion - if LooseVersion(ipywidgets.__version__) >= LooseVersion("7.0.0"): + if _LooseVersion(_ipywidgets.__version__) >= _LooseVersion("7.0.0"): from ..graph_objs._figurewidget import FigureWidget - del LooseVersion - del ipywidgets - except ImportError: - pass + else: + raise ImportError() + except Exception: + from ..missing_ipywidgets import FigureWidget else: __all__.append("FigureWidget") orig_getattr = __getattr__ @@ -284,7 +284,11 @@ def __getattr__(import_name): from ..graph_objs._figurewidget import FigureWidget return FigureWidget - except ImportError: - pass + else: + raise ImportError() + except Exception: + from ..missing_ipywidgets import FigureWidget + + return FigureWidget return orig_getattr(import_name) diff --git a/packages/python/plotly/plotly/graph_objs/__init__.py b/packages/python/plotly/plotly/graph_objs/__init__.py index f39d43dd1e8..e2861522c42 100644 --- a/packages/python/plotly/plotly/graph_objs/__init__.py +++ b/packages/python/plotly/plotly/graph_objs/__init__.py @@ -261,15 +261,15 @@ if sys.version_info < (3, 7): try: - import ipywidgets - from distutils.version import LooseVersion + import ipywidgets as _ipywidgets + from distutils.version import LooseVersion as _LooseVersion - if LooseVersion(ipywidgets.__version__) >= LooseVersion("7.0.0"): + if _LooseVersion(_ipywidgets.__version__) >= _LooseVersion("7.0.0"): from ..graph_objs._figurewidget import FigureWidget - del LooseVersion - del ipywidgets - except ImportError: - pass + else: + raise ImportError() + except Exception: + from ..missing_ipywidgets import FigureWidget else: __all__.append("FigureWidget") orig_getattr = __getattr__ @@ -284,7 +284,11 @@ def __getattr__(import_name): from ..graph_objs._figurewidget import FigureWidget return FigureWidget - except ImportError: - pass + else: + raise ImportError() + except Exception: + from ..missing_ipywidgets import FigureWidget + + return FigureWidget return orig_getattr(import_name) diff --git a/packages/python/plotly/plotly/missing_ipywidgets.py b/packages/python/plotly/plotly/missing_ipywidgets.py new file mode 100644 index 00000000000..9f5d5726087 --- /dev/null +++ b/packages/python/plotly/plotly/missing_ipywidgets.py @@ -0,0 +1,15 @@ +from .basedatatypes import BaseFigure + + +class FigureWidget(BaseFigure): + """ + FigureWidget stand-in for use when ipywidgets is not installed. The only purpose + of this class is to provide something to import as + `plotly.graph_objs.FigureWidget` when ipywidgets is not installed. This class + simply raises an informative error message when the constructor is called + """ + + def __init__(self, *args, **kwargs): + raise ImportError( + "Please install ipywidgets>=7.0.0 to use the FigureWidget class" + ) diff --git a/packages/python/plotly/plotly/tests/test_core/test_figure_widget_backend/test_missing_ipywigets.py b/packages/python/plotly/plotly/tests/test_core/test_figure_widget_backend/test_missing_ipywigets.py new file mode 100644 index 00000000000..8e03e2c59a0 --- /dev/null +++ b/packages/python/plotly/plotly/tests/test_core/test_figure_widget_backend/test_missing_ipywigets.py @@ -0,0 +1,38 @@ +import pytest + +# Use wildcard import to make sure FigureWidget is always included +from plotly.graph_objects import * +from plotly.missing_ipywidgets import FigureWidget as FigureWidgetMissingIPywidgets + +try: + import ipywidgets as _ipywidgets + from distutils.version import LooseVersion as _LooseVersion + + if _LooseVersion(_ipywidgets.__version__) >= _LooseVersion("7.0.0"): + missing_ipywidgets = False + else: + raise ImportError() +except Exception: + missing_ipywidgets = True + + +if missing_ipywidgets: + + def test_import_figurewidget_without_ipywidgets(): + assert FigureWidget is FigureWidgetMissingIPywidgets + + with pytest.raises(ImportError): + # ipywidgets import error raised on construction, not import + FigureWidget() + + +else: + + def test_import_figurewidget_with_ipywidgets(): + from plotly.graph_objs._figurewidget import ( + FigureWidget as FigureWidgetWithIPywidgets, + ) + + assert FigureWidget is FigureWidgetWithIPywidgets + fig = FigureWidget() + assert isinstance(fig, FigureWidgetWithIPywidgets) diff --git a/packages/python/plotly/plotly/tests/test_core/test_figure_widget_backend/test_validate_no_frames.py b/packages/python/plotly/plotly/tests/test_core/test_figure_widget_backend/test_validate_no_frames.py index 81f07496954..ca5a2b5da40 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_figure_widget_backend/test_validate_no_frames.py +++ b/packages/python/plotly/plotly/tests/test_core/test_figure_widget_backend/test_validate_no_frames.py @@ -2,9 +2,15 @@ import plotly.graph_objs as go import pytest +try: + go.FigureWidget() + figure_widget_available = True +except ImportError: + figure_widget_available = False + class TestNoFrames(TestCase): - if "FigureWidget" in go.__dict__.keys(): + if figure_widget_available: def test_no_frames_in_constructor_kwarg(self): with pytest.raises(ValueError):