Repository: sphinx-gallery/sphinx-gallery

Issue:
matplotlib inline vs notebook
Currently sphinx-gallery puts ["% matplotlib inline"](https://github.com/sphinx-gallery/sphinx-gallery/blob/830a11c6ba5a80a4cbbaff7e2932dd37e81321a5/sphinx_gallery/notebook.py#L113) at the top of all notebooks.  Would it be possible to add an option to use "% matplotlib notebook" instead?  Interactive elements in figures seem to work better with notebook.


Discussion:
choldgraf: @Titan-C I think I got those comments addressed, LMK what you think!
choldgraf: ping :-)
choldgraf: ok, latest push fixes the basestring suggestion from @larsoner and I think that was the last comment...RTM from my end.

Patch:
diff --git a/doc/advanced_configuration.rst b/doc/advanced_configuration.rst
index 94809a5..775c0ce 100644
--- a/doc/advanced_configuration.rst
+++ b/doc/advanced_configuration.rst
@@ -33,6 +33,7 @@ file:
 - ``expected_failing_examples`` (:ref:`dont_fail_exit`)
 - ``min_reported_time`` (:ref:`min_reported_time`)
 - ``binder`` (:ref:`binder_links`)
+- ``first_notebook_cell`` (:ref:`first_notebook_cell`)
 
 Some options can also be set or overridden on a file-by-file basis:
 
@@ -392,6 +393,36 @@ Note that for Sphinx < 1.3, the line numbers will not be consistent with the
 original file.
 
 
+.. _first_notebook_cell:
+
+Add your own first notebook cell
+================================
+
+Sphinx-Gallery adds an extra cell to the beginning of every generated notebook.
+This is often for adding code that is required to run properly in the notebook,
+but not in a ``.py`` file. By default, this text is
+
+.. code-block:: python
+
+   %matplotlib inline
+
+You can choose whatever text you like by modifying the ``first_notebook_cell``
+configuration parameter. For example, the gallery of this documentation
+displays a comment along-side each the code shown above.
+
+.. code-block:: python
+
+  # This cell is added by sphinx-gallery
+  # It can be customized to whatever you like
+  %matplotlib inline
+
+Which is achieved by the following configuration::
+
+  'first_notebook_cell': ("# This cell is added by sphinx-gallery\n"
+                          "# It can be customized to whatever you like\n"
+                          "%matplotlib inline")
+
+
 .. _disable_all_scripts_download:
 
 Disabling download button of all scripts
diff --git a/doc/conf.py b/doc/conf.py
index 0ab0800..9b34dad 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -348,5 +348,5 @@ sphinx_gallery_conf = {
                'dependencies': './binder/requirements.txt',
                'notebooks_dir': 'notebooks',
                'use_jupyter_lab': True,
-               }
+               },
 }
diff --git a/sphinx_gallery/gen_gallery.py b/sphinx_gallery/gen_gallery.py
index 9e4910d..3a3fd6d 100644
--- a/sphinx_gallery/gen_gallery.py
+++ b/sphinx_gallery/gen_gallery.py
@@ -62,6 +62,7 @@ DEFAULT_GALLERY_CONF = {
     'binder': {},
     'image_scrapers': ('matplotlib',),
     'reset_modules': ('matplotlib', 'seaborn'),
+    'first_notebook_cell': '%matplotlib inline'
 }
 
 logger = sphinx_compatibility.getLogger('sphinx-gallery')
@@ -103,6 +104,7 @@ def parse_config(app):
     gallery_conf = _complete_gallery_conf(
         app.config.sphinx_gallery_conf, src_dir, plot_gallery,
         abort_on_example_error)
+
     # this assures I can call the config in other places
     app.config.sphinx_gallery_conf = gallery_conf
     app.config.html_static_path.append(glr_path_static())
@@ -170,6 +172,12 @@ def _complete_gallery_conf(sphinx_gallery_conf, src_dir, plot_gallery,
     gallery_conf['reset_modules'] = tuple(resetters)
     del resetters
 
+    # Ensure the first cell text is a string if we have it
+    first_cell = gallery_conf.get("first_notebook_cell")
+    if not isinstance(first_cell, basestring):
+        raise ValueError("The 'first_notebook_cell' parameter must be type str"
+                         "found type %s" % type(first_cell))
+    gallery_conf['first_notebook_cell'] = first_cell
     return gallery_conf
 
 
diff --git a/sphinx_gallery/gen_rst.py b/sphinx_gallery/gen_rst.py
index c7489ee..c4e187c 100644
--- a/sphinx_gallery/gen_rst.py
+++ b/sphinx_gallery/gen_rst.py
@@ -579,7 +579,7 @@ def generate_file_rst(fname, target_dir, src_dir, gallery_conf):
 
     save_thumbnail(image_path_template, src_file, file_conf, gallery_conf)
 
-    example_nb = jupyter_notebook(script_blocks)
+    example_nb = jupyter_notebook(script_blocks, gallery_conf)
     save_notebook(example_nb, replace_py_ipynb(target_file))
 
     return intro, time_elapsed
diff --git a/sphinx_gallery/notebook.py b/sphinx_gallery/notebook.py
index 19d7250..79472b2 100644
--- a/sphinx_gallery/notebook.py
+++ b/sphinx_gallery/notebook.py
@@ -15,6 +15,7 @@ import argparse
 import json
 import re
 import sys
+import copy
 
 from .py_source_parser import split_code_and_text_blocks
 from .utils import replace_py_ipynb
@@ -100,17 +101,19 @@ def rst2md(text):
     return text
 
 
-def jupyter_notebook(script_blocks):
+def jupyter_notebook(script_blocks, gallery_conf):
     """Generate a Jupyter notebook file cell-by-cell
 
     Parameters
     ----------
-    script_blocks: list
-        script execution cells
+    script_blocks : list
+        Script execution cells.
+    gallery_conf : dict
+        The sphinx-gallery configuration dictionary.
     """
-
+    first_cell = gallery_conf.get("first_notebook_cell", "%matplotlib inline")
     work_notebook = jupyter_notebook_skeleton()
-    add_code_cell(work_notebook, "%matplotlib inline")
+    add_code_cell(work_notebook, first_cell)
     fill_notebook(work_notebook, script_blocks)
 
     return work_notebook
@@ -180,6 +183,7 @@ def python_to_jupyter_cli(args=None, namespace=None):
 
     Takes the same arguments as ArgumentParser.parse_args
     """
+    from . import gen_gallery  # To avoid circular import
     parser = argparse.ArgumentParser(
         description='Sphinx-Gallery Notebook converter')
     parser.add_argument('python_src_file', nargs='+',
@@ -191,5 +195,6 @@ def python_to_jupyter_cli(args=None, namespace=None):
     for src_file in args.python_src_file:
         file_conf, blocks = split_code_and_text_blocks(src_file)
         print('Converting {0}'.format(src_file))
-        example_nb = jupyter_notebook(blocks)
+        gallery_conf = copy.deepcopy(gen_gallery.DEFAULT_GALLERY_CONF)
+        example_nb = jupyter_notebook(blocks, gallery_conf)
         save_notebook(example_nb, replace_py_ipynb(src_file))


Test patch:
diff --git a/sphinx_gallery/tests/test_gen_gallery.py b/sphinx_gallery/tests/test_gen_gallery.py
index d83259d..221d5dc 100644
--- a/sphinx_gallery/tests/test_gen_gallery.py
+++ b/sphinx_gallery/tests/test_gen_gallery.py
@@ -360,3 +360,14 @@ def test_examples_not_expected_to_pass(sphinx_app_wrapper):
     with pytest.raises(ValueError) as excinfo:
         sphinx_app_wrapper.build_sphinx_app()
     assert "expected to fail, but not failing" in str(excinfo.value)
+
+
+@pytest.mark.conf_file(content="""
+sphinx_gallery_conf = {
+    'first_notebook_cell': 2,
+}""")
+def test_first_notebook_cell_config(sphinx_app_wrapper):
+    from sphinx_gallery.gen_gallery import parse_config
+    # First cell must be str
+    with pytest.raises(ValueError):
+        parse_config(sphinx_app_wrapper.create_sphinx_app())
diff --git a/sphinx_gallery/tests/test_notebook.py b/sphinx_gallery/tests/test_notebook.py
index 5f7760e..bd7a95d 100644
--- a/sphinx_gallery/tests/test_notebook.py
+++ b/sphinx_gallery/tests/test_notebook.py
@@ -14,6 +14,8 @@ import pytest
 import sphinx_gallery.gen_rst as sg
 from sphinx_gallery.notebook import (rst2md, jupyter_notebook, save_notebook,
                                      python_to_jupyter_cli)
+from sphinx_gallery.tests.test_gen_rst import gallery_conf
+
 try:
     FileNotFoundError
 except NameError:
@@ -78,10 +80,10 @@ For more details on interpolation see the page `channel_interpolation`.
     assert rst2md(rst) == markdown
 
 
-def test_jupyter_notebook():
+def test_jupyter_notebook(gallery_conf):
     """Test that written ipython notebook file corresponds to python object"""
     file_conf, blocks = sg.split_code_and_text_blocks('tutorials/plot_parse.py')
-    example_nb = jupyter_notebook(blocks)
+    example_nb = jupyter_notebook(blocks, gallery_conf)
 
     with tempfile.NamedTemporaryFile('w', delete=False) as f:
         save_notebook(example_nb, f.name)
@@ -90,6 +92,13 @@ def test_jupyter_notebook():
             assert json.load(fname) == example_nb
     finally:
         os.remove(f.name)
+    assert example_nb.get('cells')[0]['source'][0] == "%matplotlib inline"
+
+    # Test custom first cell text
+    test_text = '# testing\n%matplotlib notebook'
+    gallery_conf['first_notebook_cell'] = test_text
+    example_nb = jupyter_notebook(blocks, gallery_conf)
+    assert example_nb.get('cells')[0]['source'][0] == test_text
 
 ###############################################################################
 # Notebook shell utility
diff --git a/sphinx_gallery/tests/test_utils.py b/sphinx_gallery/tests/test_utils.py
index 2d4f6db..2db502e 100644
--- a/sphinx_gallery/tests/test_utils.py
+++ b/sphinx_gallery/tests/test_utils.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 r"""
-Test utility functions 
+Test utility functions
 ==================
 
 
@@ -13,7 +13,6 @@ import sphinx_gallery.utils as utils
 import pytest
 
 def test_replace_py_ipynb():
-    
     # Test behavior of function with expected input:
     for file_name in ['some/file/name', '/corner.pycase']:
         assert utils.replace_py_ipynb(file_name+'.py') == file_name+'.ipynb'