Skip to content

Instantly share code, notes, and snippets.

@bryan-lunt
Last active July 26, 2026 18:51
Show Gist options
  • Select an option

  • Save bryan-lunt/681a2467d5fff9a87748f34fde031bdc to your computer and use it in GitHub Desktop.

Select an option

Save bryan-lunt/681a2467d5fff9a87748f34fde031bdc to your computer and use it in GitHub Desktop.
Jupyter / IPython notebook loader for Python >= 3.4 (PEP 451)
#http://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Importing%20Notebooks.html
import io, os, sys, types
from typing import override
import importlib, importlib.abc, importlib.util
from IPython import get_ipython
from nbformat import read
from IPython.core.interactiveshell import InteractiveShell
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 NotebookLoader(importlib.abc.Loader):
"""Module Loader for Jupyter Notebooks"""
def __init__(self, fullname, path=None):
self.shell = InteractiveShell.instance()
self.fullname = fullname
self.path = path
def create_module(self, spec):
mod = types.ModuleType(spec.name)
mod.__file__ = spec.loader.path
mod.__loader__ = self
mod.__dict__['get_ipython'] = get_ipython
return mod
def exec_module(self, module):
"""import a notebook as a module"""
path = find_notebook(self.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 = read(f, 4)
# 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 = module.__dict__
try:
for cell in nb.cells:
if cell.cell_type == 'code':
# transform the input to executable Python
code = self.shell.input_transformer_manager.transform_cell(cell.source)
# run the code in themodule
exec(code, module.__dict__)
finally:
self.shell.user_ns = save_user_ns
#
class NotebookFinder(importlib.abc.MetaPathFinder):
"""Module finder that locates Jupyter Notebooks"""
def __init__(self):
self.loaders = {}
def find_spec(self, fullname, path=None, target=None):
nb_path = find_notebook(fullname, path)
if not nb_path:
return None
key = path
if path:
# lists aren't hashable
key = os.path.sep.join(path)
else:
key = fullname
if key not in self.loaders:
self.loaders[key] = importlib.util.spec_from_loader(fullname, NotebookLoader(fullname, path))
return self.loaders[key]
#
#happens when this is imported
#Still not a proper jupyter extension though
sys.meta_path.append(NotebookFinder())
@bryan-lunt

Copy link
Copy Markdown
Author

The code that creates the key is not completely trustworthy yet.

Eventually it needs to be submit back to the Jupyter project.

The code at https://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Importing%20Notebooks.html no longer works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment