Skip to content

Commit ecb635a

Browse files
authoredMay 7, 2020
Fix FigureWidget attribute error on wildcard import with ipywidgets not installed (#2445)
* Fix `from plotly.graph_objs import *` when ipywidgets is not installed Introduces a dummy `plotly.missing_ipywidgets.FigureWidget` class that is imported as plotly.graph_obj(ect)?s when ipywidgets is not installed with a supported version. This class is a BaseFigure subclass that raises an ImportError in the constructor. * Add changelog entry
1 parent 4021682 commit ecb635a

File tree

7 files changed

+109
-30
lines changed

7 files changed

+109
-30
lines changed
 

‎CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## [4.7.1] - ???
6+
7+
### Fixed
8+
9+
- 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)).
10+
11+
512
## [4.7.0] - 2020-05-06
613

714
### Updated

‎packages/python/plotly/codegen/__init__.py

+16-11
Original file line numberDiff line numberDiff line change
@@ -269,14 +269,14 @@ def perform_codegen():
269269
optional_figure_widget_import = f"""
270270
if sys.version_info < (3, 7):
271271
try:
272-
import ipywidgets
273-
from distutils.version import LooseVersion
274-
if LooseVersion(ipywidgets.__version__) >= LooseVersion('7.0.0'):
272+
import ipywidgets as _ipywidgets
273+
from distutils.version import LooseVersion as _LooseVersion
274+
if _LooseVersion(_ipywidgets.__version__) >= _LooseVersion("7.0.0"):
275275
from ..graph_objs._figurewidget import FigureWidget
276-
del LooseVersion
277-
del ipywidgets
278-
except ImportError:
279-
pass
276+
else:
277+
raise ImportError()
278+
except Exception:
279+
from ..missing_ipywidgets import FigureWidget
280280
else:
281281
__all__.append("FigureWidget")
282282
orig_getattr = __getattr__
@@ -285,12 +285,17 @@ def __getattr__(import_name):
285285
try:
286286
import ipywidgets
287287
from distutils.version import LooseVersion
288-
if LooseVersion(ipywidgets.__version__) >= LooseVersion('7.0.0'):
288+
289+
if LooseVersion(ipywidgets.__version__) >= LooseVersion("7.0.0"):
289290
from ..graph_objs._figurewidget import FigureWidget
291+
290292
return FigureWidget
291-
except ImportError:
292-
pass
293-
293+
else:
294+
raise ImportError()
295+
except Exception:
296+
from ..missing_ipywidgets import FigureWidget
297+
return FigureWidget
298+
294299
return orig_getattr(import_name)
295300
"""
296301
# ### __all__ ###

‎packages/python/plotly/plotly/graph_objects/__init__.py

+13-9
Original file line numberDiff line numberDiff line change
@@ -261,15 +261,15 @@
261261

262262
if sys.version_info < (3, 7):
263263
try:
264-
import ipywidgets
265-
from distutils.version import LooseVersion
264+
import ipywidgets as _ipywidgets
265+
from distutils.version import LooseVersion as _LooseVersion
266266

267-
if LooseVersion(ipywidgets.__version__) >= LooseVersion("7.0.0"):
267+
if _LooseVersion(_ipywidgets.__version__) >= _LooseVersion("7.0.0"):
268268
from ..graph_objs._figurewidget import FigureWidget
269-
del LooseVersion
270-
del ipywidgets
271-
except ImportError:
272-
pass
269+
else:
270+
raise ImportError()
271+
except Exception:
272+
from ..missing_ipywidgets import FigureWidget
273273
else:
274274
__all__.append("FigureWidget")
275275
orig_getattr = __getattr__
@@ -284,7 +284,11 @@ def __getattr__(import_name):
284284
from ..graph_objs._figurewidget import FigureWidget
285285

286286
return FigureWidget
287-
except ImportError:
288-
pass
287+
else:
288+
raise ImportError()
289+
except Exception:
290+
from ..missing_ipywidgets import FigureWidget
291+
292+
return FigureWidget
289293

290294
return orig_getattr(import_name)

‎packages/python/plotly/plotly/graph_objs/__init__.py

+13-9
Original file line numberDiff line numberDiff line change
@@ -261,15 +261,15 @@
261261

262262
if sys.version_info < (3, 7):
263263
try:
264-
import ipywidgets
265-
from distutils.version import LooseVersion
264+
import ipywidgets as _ipywidgets
265+
from distutils.version import LooseVersion as _LooseVersion
266266

267-
if LooseVersion(ipywidgets.__version__) >= LooseVersion("7.0.0"):
267+
if _LooseVersion(_ipywidgets.__version__) >= _LooseVersion("7.0.0"):
268268
from ..graph_objs._figurewidget import FigureWidget
269-
del LooseVersion
270-
del ipywidgets
271-
except ImportError:
272-
pass
269+
else:
270+
raise ImportError()
271+
except Exception:
272+
from ..missing_ipywidgets import FigureWidget
273273
else:
274274
__all__.append("FigureWidget")
275275
orig_getattr = __getattr__
@@ -284,7 +284,11 @@ def __getattr__(import_name):
284284
from ..graph_objs._figurewidget import FigureWidget
285285

286286
return FigureWidget
287-
except ImportError:
288-
pass
287+
else:
288+
raise ImportError()
289+
except Exception:
290+
from ..missing_ipywidgets import FigureWidget
291+
292+
return FigureWidget
289293

290294
return orig_getattr(import_name)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from .basedatatypes import BaseFigure
2+
3+
4+
class FigureWidget(BaseFigure):
5+
"""
6+
FigureWidget stand-in for use when ipywidgets is not installed. The only purpose
7+
of this class is to provide something to import as
8+
`plotly.graph_objs.FigureWidget` when ipywidgets is not installed. This class
9+
simply raises an informative error message when the constructor is called
10+
"""
11+
12+
def __init__(self, *args, **kwargs):
13+
raise ImportError(
14+
"Please install ipywidgets>=7.0.0 to use the FigureWidget class"
15+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import pytest
2+
3+
# Use wildcard import to make sure FigureWidget is always included
4+
from plotly.graph_objects import *
5+
from plotly.missing_ipywidgets import FigureWidget as FigureWidgetMissingIPywidgets
6+
7+
try:
8+
import ipywidgets as _ipywidgets
9+
from distutils.version import LooseVersion as _LooseVersion
10+
11+
if _LooseVersion(_ipywidgets.__version__) >= _LooseVersion("7.0.0"):
12+
missing_ipywidgets = False
13+
else:
14+
raise ImportError()
15+
except Exception:
16+
missing_ipywidgets = True
17+
18+
19+
if missing_ipywidgets:
20+
21+
def test_import_figurewidget_without_ipywidgets():
22+
assert FigureWidget is FigureWidgetMissingIPywidgets
23+
24+
with pytest.raises(ImportError):
25+
# ipywidgets import error raised on construction, not import
26+
FigureWidget()
27+
28+
29+
else:
30+
31+
def test_import_figurewidget_with_ipywidgets():
32+
from plotly.graph_objs._figurewidget import (
33+
FigureWidget as FigureWidgetWithIPywidgets,
34+
)
35+
36+
assert FigureWidget is FigureWidgetWithIPywidgets
37+
fig = FigureWidget()
38+
assert isinstance(fig, FigureWidgetWithIPywidgets)

‎packages/python/plotly/plotly/tests/test_core/test_figure_widget_backend/test_validate_no_frames.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22
import plotly.graph_objs as go
33
import pytest
44

5+
try:
6+
go.FigureWidget()
7+
figure_widget_available = True
8+
except ImportError:
9+
figure_widget_available = False
10+
511

612
class TestNoFrames(TestCase):
7-
if "FigureWidget" in go.__dict__.keys():
13+
if figure_widget_available:
814

915
def test_no_frames_in_constructor_kwarg(self):
1016
with pytest.raises(ValueError):

0 commit comments

Comments
 (0)
Please sign in to comment.