maker

Create one or more modules from selected notebook cells

Helpers

Variable helpers

These functions let us find and modify the definitions of variables in Python modules.


find_var

 find_var (lines, varname)

Find the line numbers where varname is defined in lines

t = '''a_=(1,
  2,
  3)

b_=3'''
test_eq(find_var(t.splitlines(), 'a_'), (0,3))
test_eq(find_var(t.splitlines(), 'b_'), (4,5))

read_var

 read_var (code, varname)

Eval and return the value of varname defined in code

test_eq(read_var(t, 'a_'), (1,2,3))
test_eq(read_var(t, 'b_'), 3)

update_var

 update_var (varname, func, fn=None, code=None)

Update the definition of varname in file fn, by calling func with the current definition

g = exec_new(t)
test_eq((g['a_'],g['b_']), ((1,2,3),3))
t2 = update_var('a_', lambda o:0, code=t)
exec(t2, g)
test_eq((g['a_'],g['b_']), (0,3))
t3 = update_var('b_', lambda o:0, code=t)
exec(t3, g)
test_eq((g['a_'],g['b_']), ((1,2,3),0))

ModuleMaker

 ModuleMaker (dest, name, nb_path, is_new=True, parse=True)

Helper class to create exported library from notebook source cells

In order to export a notebook, we need an way to create a Python file. ModuleMaker fills that role. Pass in the directory where you want to module created, the name of the module, the path of the notebook source, and set is_new to True if this is a new file being created (rather than an existing file being added to). The location of the saved module will be in fname. Finally, if the source in the notebooks should not be parsed by Python (such as partial class declarations in cells), parse should be set to False.

Note: If doing so, then the __all__ generation will be turned off as well.

mm = ModuleMaker(dest='tmp', name='test.testing', nb_path=Path.cwd()/'01_export.ipynb', is_new=True)
mm.fname
Path('tmp/test/testing.py')

decor_id

 decor_id (d)

id attr of decorator, regardless of whether called as function or bare


retr_exports

 retr_exports (trees)

ModuleMaker.make_all

 ModuleMaker.make_all (cells)

Create __all__ with all exports in cells


make_code_cells

 make_code_cells (*ss)

We want to add an __all__ to the top of the exported module. This methods autogenerates it from all code in cells.

nb = make_code_cells("from __future__ import print_function", "def a():...", "def b():...",
                      "c=d=1", "_f=1", "_g=1", "_all_=['_g']", "@patch\ndef h(self:ca):...")
test_eq(set(mm.make_all(nb)), set(['a','b','c','d', '_g']))

relative_import

 relative_import (name, fname, level=0)

Convert a module name to a name relative to fname

test_eq(relative_import('nbdev.core', "xyz"), 'nbdev.core')
test_eq(relative_import('nbdev.core', 'nbdev'), '.core')
_p = Path('fastai')
test_eq(relative_import('fastai.core', _p/'vision'), '..core')
test_eq(relative_import('fastai.core', _p/'vision/transform'), '...core')
test_eq(relative_import('fastai.vision.transform', _p/'vision'), '.transform')
test_eq(relative_import('fastai.notebook.core', _p/'data'), '..notebook.core')
test_eq(relative_import('fastai.vision', _p/'vision'), '.')
test_eq(relative_import('fastai', _p), '.')
test_eq(relative_import('fastai', _p/'vision'), '..')
test_eq(relative_import('fastai', _p/'vision/transform'), '...')

NbCell.import2relative

 NbCell.import2relative (cell:execnb.nbio.NbCell, libname)

update_import

 update_import (source, tree, libname, f=<functionrelative_import>)
ss = "from nbdev.export import *\nfrom nbdev.a.b import *"
cell = make_code_cells([ss])[0]
cell.import2relative('nbdev')
test_eq(cell.source, 'from .export import *\nfrom .a.b import *')

cell = make_code_cells([ss])[0]
cell.import2relative('nbdev/a')
test_eq(cell.source, 'from ..export import *\nfrom .b import *')

ModuleMaker.make

 ModuleMaker.make (cells, all_cells=None, lib_path=None)

Write module containing cells with __all__ generated from all_cells

cells = make_code_cells("from __future__ import print_function", "#|export\ndef a(): ...", "def b(): ...")
mm.make(cells, L([cells[1]]))
show_src(Path('tmp/test/testing.py').read_text())
# AUTOGENERATED! DO NOT EDIT! File to edit: ../01_export.ipynb.

# %% ../01_export.ipynb 0
from __future__ import print_function

# %% auto 0
__all__ = ['a']

# %% ../01_export.ipynb 2
#|export
def a(): ...

# %% ../01_export.ipynb 3
def b(): ...

Pass all_cells=[] or parse=False if you don’t want any __all__ added.

Passing parse=False is also handy for when writing broken up functions or classes that ast.parse might not like but still want it to be exported, such as having once cell with the contents of:

#|export
class A:

Note that by doing so we cannot properly generate a __all__, so we assume that it is unwanted.

am = ModuleMaker(dest='tmp', name='test.testing_noall', nb_path=Path.cwd()/'01_export.ipynb', is_new=True, parse=False)
am.fname
Path('tmp/test/testing_noall.py')
cells = make_code_cells("from __future__ import print_function", "#|export\ndef a(): ...", "#|export\nclass A:")
am.make(cells)
show_src(Path('tmp/test/testing_noall.py').read_text())
# AUTOGENERATED! DO NOT EDIT! File to edit: ../01_export.ipynb.

# %% ../01_export.ipynb 0
from __future__ import print_function

# %% ../01_export.ipynb 1
#|export
def a(): ...

# %% ../01_export.ipynb 2
#|export
class A:

If is_new=False then the additional definitions are added to the bottom, and any existing __all__ is updated with the newly-added symbols.

c2 = make_code_cells("def c(): ...", "def d(): ...")
mm = ModuleMaker(dest='tmp', name='test.testing', nb_path=Path.cwd()/'01_export.ipynb', is_new=False)
mm.make(c2, c2)
show_src(Path('tmp/test/testing.py').read_text())
# AUTOGENERATED! DO NOT EDIT! File to edit: ../01_export.ipynb.

# %% ../01_export.ipynb 0
from __future__ import print_function

# %% auto 0
__all__ = ['a', 'c', 'd']

# %% ../01_export.ipynb 2
#|export
def a(): ...

# %% ../01_export.ipynb 3
def b(): ...

# %% ../01_export.ipynb 0
def c(): ...

# %% ../01_export.ipynb 1
def d(): ...
g = exec_import('.tmp.test.testing', '*')
for s in "a c d".split(): assert s in g, s
assert 'b' not in g
assert g['a']() is None

basic_export_nb2

 basic_export_nb2 (fname, name, dest=None)

A basic exporter to bootstrap nbdev using ModuleMaker

path = Path('../nbdev')
(path/'read.py').unlink(missing_ok=True)
(path/'maker.py').unlink(missing_ok=True)

add_init(path)
cfg = get_config()

basic_export_nb2('01_read.ipynb', 'read')
basic_export_nb2('02_maker.ipynb', 'maker')

g = exec_import('nbdev', 'maker')
assert g['maker'].ModuleMaker
assert 'ModuleMaker' in g['maker'].__all__