forked from grst/nbimporter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
nbimporter.py
145 lines (115 loc) · 4.98 KB
/
nbimporter.py
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
"""
Allow for importing of IPython Notebooks as modules from Jupyter v4.
Updated from module collated here:
https://github.com/adrn/ipython/blob/master/examples/Notebook/Importing%20Notebooks.ipynb
Importing from a notebook is different from a module: because one
typically keeps many computations and tests besides exportable defs,
here we only run code which either defines a function or a class, or
imports code from other modules and notebooks. This behaviour can be
disabled by setting nbimporter.options['only_defs'] = False.
Furthermore, in order to provide per-notebook initialisation, if a
special function __nbinit__() is defined in the notebook, it will be
executed the first time an import statement is. This behaviour can be
disabled by setting nbimporter.options['run_nbinit'] = False.
Finally, you can set the encoding of the notebooks with
nbimporter.options['encoding']. The default is 'utf-8'.
"""
import io, os, sys, types, ast
import nbformat
from IPython import get_ipython
from IPython.core.interactiveshell import InteractiveShell
options = {'only_defs': True, 'run_nbinit': True, 'encoding': 'utf-8'}
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
class CellDeleter(ast.NodeTransformer):
""" Removes all nodes from an AST which are not suitable
for exporting out of a notebook. """
def visit(self, node):
""" Visit a node. """
if node.__class__.__name__ in ['Module', 'FunctionDef', 'ClassDef',
'Import', 'ImportFrom']:
return node
return None
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)
# load the notebook object
nb_version = nbformat.current_nbformat
with io.open(path, 'r', encoding=options['encoding']) as f:
nb = nbformat.read(f, nb_version)
# 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
# Only do something if it's a python notebook
if 'python' not in nb.metadata.kernelspec.language:
print("Ignoring '%s': not a python notebook." % path)
return mod
print("Importing Jupyter notebook from %s" % path)
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:
deleter = CellDeleter()
for cell in filter(lambda c: c.cell_type == 'code', nb.cells):
# transform the input into executable Python
code = self.shell.input_transformer_manager.transform_cell(cell.source)
if options['only_defs']:
# Remove anything that isn't a def or a class
tree = deleter.generic_visit(ast.parse(code))
else:
tree = ast.parse(code)
# run the code in the module
codeobj = compile(tree, filename=path, mode='exec')
exec(codeobj, mod.__dict__)
finally:
self.shell.user_ns = save_user_ns
# Run any initialisation if available, but only once
if options['run_nbinit'] and '__nbinit_done__' not in mod.__dict__:
try:
mod.__nbinit__()
mod.__nbinit_done__ = True
except (KeyError, AttributeError) as _:
pass
return mod
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]
sys.meta_path.append(NotebookFinder())