File: Importing%20Notebooks.rst

package info (click to toggle)
jupyter-notebook 4.2.3-4~bpo8%2B1
  • links: PTS, VCS
  • area: main
  • in suites: jessie-backports
  • size: 7,804 kB
  • sloc: python: 8,698; makefile: 240; sh: 74
file content (286 lines) | stat: -rw-r--r-- 8,500 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286

`View the original notebook on nbviewer <http://nbviewer.jupyter.org/github/jupyter/notebook/blob/master/docs/source/examples/Notebook/Importing%20Notebooks.ipynb>`__

Importing Jupyter Notebooks as Modules
======================================

It is a common problem that people want to import code from Jupyter
Notebooks. This is made difficult by the fact that Notebooks are not
plain Python files, and thus cannot be imported by the regular Python
machinery.

Fortunately, Python provides some fairly sophisticated
`hooks <http://www.python.org/dev/peps/pep-0302/>`__ into the import
machinery, so we can actually make Jupyter notebooks importable without
much difficulty, and only using public APIs.

.. code:: python

    import io, os, sys, types

.. code:: python

    from IPython import get_ipython
    from IPython.nbformat import current
    from IPython.core.interactiveshell import InteractiveShell

Import hooks typically take the form of two objects:

1. a Module **Loader**, which takes a module name (e.g.
   ``'IPython.display'``), and returns a Module
2. a Module **Finder**, which figures out whether a module might exist,
   and tells Python what **Loader** to use

.. code:: python

    def find_notebook(fullname, path=None):
        """find a notebook, given its fully qualified name and an optional path
        
        This turns "foo.bar" into "foo/bar.ipynb"
        and tries turning "Foo_Bar" into "Foo Bar" if Foo_Bar
        does not exist.
        """
        name = fullname.rsplit('.', 1)[-1]
        if not path:
            path = ['']
        for d in path:
            nb_path = os.path.join(d, name + ".ipynb")
            if os.path.isfile(nb_path):
                return nb_path
            # let import Notebook_Name find "Notebook Name.ipynb"
            nb_path = nb_path.replace("_", " ")
            if os.path.isfile(nb_path):
                return nb_path
                

Notebook Loader
---------------

Here we have our Notebook Loader. It's actually quite simple - once we
figure out the filename of the module, all it does is:

1. load the notebook document into memory
2. create an empty Module
3. execute every cell in the Module namespace

Since IPython cells can have extended syntax, the IPython transform is
applied to turn each of these cells into their pure-Python counterparts
before executing them. If all of your notebook cells are pure-Python,
this step is unnecessary.

.. code:: python

    class NotebookLoader(object):
        """Module Loader for Jupyter Notebooks"""
        def __init__(self, path=None):
            self.shell = InteractiveShell.instance()
            self.path = path
        
        def load_module(self, fullname):
            """import a notebook as a module"""
            path = find_notebook(fullname, self.path)
            
            print ("importing Jupyter notebook from %s" % path)
                                           
            # load the notebook object
            with io.open(path, 'r', encoding='utf-8') as f:
                nb = current.read(f, 'json')
            
            
            # create the module and add it to sys.modules
            # if name in sys.modules:
            #    return sys.modules[name]
            mod = types.ModuleType(fullname)
            mod.__file__ = path
            mod.__loader__ = self
            mod.__dict__['get_ipython'] = get_ipython
            sys.modules[fullname] = mod
            
            # extra work to ensure that magics that would affect the user_ns
            # actually affect the notebook module's ns
            save_user_ns = self.shell.user_ns
            self.shell.user_ns = mod.__dict__
            
            try:
              for cell in nb.worksheets[0].cells:
                if cell.cell_type == 'code' and cell.language == 'python':
                    # transform the input to executable Python
                    code = self.shell.input_transformer_manager.transform_cell(cell.input)
                    # run the code in themodule
                    exec(code, mod.__dict__)
            finally:
                self.shell.user_ns = save_user_ns
            return mod


The Module Finder
-----------------

The finder is a simple object that tells you whether a name can be
imported, and returns the appropriate loader. All this one does is
check, when you do:

.. code:: python

    import mynotebook

it checks whether ``mynotebook.ipynb`` exists. If a notebook is found,
then it returns a NotebookLoader.

Any extra logic is just for resolving paths within packages.

.. code:: python

    class NotebookFinder(object):
        """Module finder that locates Jupyter Notebooks"""
        def __init__(self):
            self.loaders = {}
        
        def find_module(self, fullname, path=None):
            nb_path = find_notebook(fullname, path)
            if not nb_path:
                return
            
            key = path
            if path:
                # lists aren't hashable
                key = os.path.sep.join(path)
            
            if key not in self.loaders:
                self.loaders[key] = NotebookLoader(path)
            return self.loaders[key]


Register the hook
-----------------

Now we register the ``NotebookFinder`` with ``sys.meta_path``

.. code:: python

    sys.meta_path.append(NotebookFinder())

After this point, my notebooks should be importable.

Let's look at what we have in the CWD:

.. code:: python

    ls nbpackage

So I should be able to ``import nbimp.mynotebook``.

Aside: displaying notebooks
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Here is some simple code to display the contents of a notebook with
syntax highlighting, etc.

.. code:: python

    from pygments import highlight
    from pygments.lexers import PythonLexer
    from pygments.formatters import HtmlFormatter
    
    from IPython.display import display, HTML
    
    formatter = HtmlFormatter()
    lexer = PythonLexer()
    
    # publish the CSS for pygments highlighting
    display(HTML("""
    <style type='text/css'>
    %s
    </style>
    """ % formatter.get_style_defs()
    ))

.. code:: python

    def show_notebook(fname):
        """display a short summary of the cells of a notebook"""
        with io.open(fname, 'r', encoding='utf-8') as f:
            nb = current.read(f, 'json')
        html = []
        for cell in nb.worksheets[0].cells:
            html.append("<h4>%s cell</h4>" % cell.cell_type)
            if cell.cell_type == 'code':
                html.append(highlight(cell.input, lexer, formatter))
            else:
                html.append("<pre>%s</pre>" % cell.source)
        display(HTML('\n'.join(html)))
    
    show_notebook(os.path.join("nbpackage", "mynotebook.ipynb"))

So my notebook has a heading cell and some code cells, one of which
contains some IPython syntax.

Let's see what happens when we import it

.. code:: python

    from nbpackage import mynotebook

Hooray, it imported! Does it work?

.. code:: python

    mynotebook.foo()

Hooray again!

Even the function that contains IPython syntax works:

.. code:: python

    mynotebook.has_ip_syntax()

Notebooks in packages
---------------------

We also have a notebook inside the ``nb`` package, so let's make sure
that works as well.

.. code:: python

    ls nbpackage/nbs

Note that the ``__init__.py`` is necessary for ``nb`` to be considered a
package, just like usual.

.. code:: python

    show_notebook(os.path.join("nbpackage", "nbs", "other.ipynb"))

.. code:: python

    from nbpackage.nbs import other
    other.bar(5)

So now we have importable notebooks, from both the local directory and
inside packages.

I can even put a notebook inside IPython, to further demonstrate that
this is working properly:

.. code:: python

    import shutil
    from IPython.utils.path import get_ipython_package_dir
    
    utils = os.path.join(get_ipython_package_dir(), 'utils')
    shutil.copy(os.path.join("nbpackage", "mynotebook.ipynb"),
                os.path.join(utils, "inside_ipython.ipynb")
    )

and import the notebook from ``IPython.utils``

.. code:: python

    from IPython.utils import inside_ipython
    inside_ipython.whatsmyname()

This approach can even import functions and classes that are defined in
a notebook using the ``%%cython`` magic.

`View the original notebook on nbviewer <http://nbviewer.jupyter.org/github/jupyter/notebook/blob/master/docs/source/examples/Notebook/Importing%20Notebooks.ipynb>`__