Repository: plotly/plotly.py (python)

Issue:
No plotly.js found if init_notebook_mode run twice.
Hello,

I have been running into issues when using the offline notebook mode of plotly with jupyter.  Specifically, if I run init_notebook_mode twice, and then save and close the notebook with a plotted figure, upon re-opening the figure no longer shows and I can no longer get any plots to show.


Below is a notebook that reproduces the error.  To break it, run all cells, then run cell 2 again, save, and close.  Upon reload, it is not possible to get a plotly plot to show.  Interestingly, if you run cell 1 and 2 again, save, and close, the issue is fixed upon next re-open.

The issue does not show up at all if you do not include a plot in your saved notebook.

Cell 1:
```
from plotly.offline import init_notebook_mode
```
Cell 2:
```
init_notebook_mode()
```
Cell 3:
```
import plotly as py
import plotly.graph_objs as go

trace = go.Scatter3d(
    x=[1,0],
    y=[1,0],
    z=[1,0]
)
fig = go.Figure(data=[trace])
py.offline.iplot(fig)
```

My jupyter logs show the following:

> [W 17:32:55.779 NotebookApp] 404 GET /static/plotly.js?v=20160512163326 (127.0.0.1) 4.02ms referer=http://localhost:8895/notebooks/experiments/Test.ipynb

I am running version 1.9.10 as installed from pip on python 2.7.

Pull request:
V1.10.0 - Inject plotly.js into the output cell on every `init_notebook_mode` call

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a25f836d4..7eaa68f6c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,22 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ## [Unreleased]
 
+## [1.10.0] - 2016-05-19
+### Fixed
+Version 1.9.13 fixed an issue in offline mode where if you ran `init_notebook_mode`
+more than once the function would skip importing (because it saw that it had
+already imported the library) but then accidentally clear plotly.js from the DOM.
+This meant that if you ran `init_notebook_mode` more than once, your graphs would
+not appear when you refreshed the page.
+Version 1.9.13 solved this issue by injecting plotly.js with every iplot call.
+While this works, it also injects the library excessively, causing notebooks
+to have multiple versions of plotly.js inline in the DOM, potentially making
+notebooks with many `iplot` calls very large.
+Version 1.10.0 brings back the requirement to call `init_notebook_mode` before
+making an `iplot` call. It makes `init_notebook_mode` idempotent: you can call
+it multiple times without worrying about losing your plots on refresh.
+
+
 ## [1.9.13] - 2016-05-19
 ### Fixed
 - Fixed issue in offline mode related to the inability to reload plotly.js on page refresh and extra init_notebook_mode calls.
diff --git a/plotly/offline/offline.py b/plotly/offline/offline.py
index f51507136..170bf8f3c 100644
--- a/plotly/offline/offline.py
+++ b/plotly/offline/offline.py
@@ -14,6 +14,7 @@ import webbrowser
 
 import plotly
 from plotly import tools, utils
+from plotly.exceptions import PlotlyError
 
 try:
     import IPython
@@ -28,6 +29,7 @@ try:
 except ImportError:
     _matplotlib_imported = False
 
+__PLOTLY_OFFLINE_INITIALIZED = False
 
 def download_plotlyjs(download_url):
     warnings.warn('''
@@ -50,16 +52,11 @@ def init_notebook_mode():
     yet. This is an idempotent method and can and should be called from any
     offline methods that require plotly.js to be loaded into the notebook dom.
     """
-    warnings.warn('''
-        `init_notebook_mode` is deprecated and will be removed in the
-        next release. Notebook mode is now automatically initialized when
-        notebook methods are invoked, so it is no
-        longer necessary to manually initialize.
-    ''', DeprecationWarning)
-
     if not _ipython_imported:
         raise ImportError('`iplot` can only run inside an IPython Notebook.')
 
+    global __PLOTLY_OFFLINE_INITIALIZED
+    # Inject plotly.js into the output cell
     script_inject = (
         ''
         '<script type=\'text/javascript\'>'
@@ -68,7 +65,6 @@ def init_notebook_mode():
         '{script}'
         '}});'
         'require([\'plotly\'], function(Plotly) {{'
-        'console.log(Plotly);'
         'window.Plotly = Plotly;'
         '}});'
         '}}'
@@ -76,6 +72,7 @@ def init_notebook_mode():
         '').format(script=get_plotlyjs())
 
     display(HTML(script_inject))
+    __PLOTLY_OFFLINE_INITIALIZED = True
 
 
 def _plot_html(figure_or_data, show_link, link_text,
@@ -177,13 +174,22 @@ def iplot(figure_or_data, show_link=True, link_text='Export to plot.ly',
 
     Example:
     ```
-    from plotly.offline import iplot
-
+    from plotly.offline import init_notebook_mode, iplot
+    init_notebook_mode()
     iplot([{'x': [1, 2, 3], 'y': [5, 2, 7]}])
     ```
     """
-
-    init_notebook_mode()
+    if not __PLOTLY_OFFLINE_INITIALIZED:
+        raise PlotlyError('\n'.join([
+            'Plotly Offline mode has not been initialized in this notebook. '
+            'Run: ',
+            '',
+            'import plotly',
+            'plotly.offline.init_notebook_mode() '
+            '# run at the start of every ipython notebook',
+        ]))
+    if not tools._ipython_imported:
+        raise ImportError('`iplot` can only run inside an IPython Notebook.')
 
     plot_html, plotdivid, width, height = _plot_html(
         figure_or_data, show_link, link_text, validate,
@@ -415,7 +421,7 @@ def iplot_mpl(mpl_fig, resize=False, strip_style=False,
 
     Example:
     ```
-    from plotly.offline import iplot_mpl
+    from plotly.offline import init_notebook_mode, iplot_mpl
     import matplotlib.pyplot as plt
 
     fig = plt.figure()
@@ -423,11 +429,10 @@ def iplot_mpl(mpl_fig, resize=False, strip_style=False,
     y = [100, 250, 200, 150, 300]
     plt.plot(x, y, "o")
 
+    init_notebook_mode()
     iplot_mpl(fig)
     ```
     """
-    init_notebook_mode()
-
     plotly_plot = tools.mpl_to_plotly(mpl_fig, resize, strip_style, verbose)
     return iplot(plotly_plot, show_link, link_text, validate)
 
diff --git a/plotly/version.py b/plotly/version.py
index e9eb8afa5..52af183e5 100644
--- a/plotly/version.py
+++ b/plotly/version.py
@@ -1,1 +1,1 @@
-__version__ = '1.9.13'
+__version__ = '1.10.0'


Test patch:
diff --git a/plotly/tests/test_optional/test_offline/test_offline.py b/plotly/tests/test_optional/test_offline/test_offline.py
index 2311a70d7..93d2c4c37 100644
--- a/plotly/tests/test_optional/test_offline/test_offline.py
+++ b/plotly/tests/test_optional/test_offline/test_offline.py
@@ -29,7 +29,8 @@ class PlotlyOfflineTestCase(TestCase):
     def setUp(self):
         pass
 
-    def test_iplot_works_wihout_calling_init_notebook_mode(self):
+    @raises(plotly.exceptions.PlotlyError)
+    def test_iplot_doesnt_work_before_you_call_init_notebook_mode(self):
         plotly.offline.iplot([{}])
 
     def test_iplot_works_after_you_call_init_notebook_mode(self):
@@ -86,4 +87,3 @@ class PlotlyOfflineMPLTestCase(TestCase):
         self.assertTrue(PLOTLYJS in html)         # and the source code
         # and it's an <html> doc
         self.assertTrue(html.startswith('<html>') and html.endswith('</html>'))
-