= '''a_=(1,
t 2,
3)
b_=3'''
'a_'), (0,3))
test_eq(find_var(t.splitlines(), 'b_'), (4,5)) test_eq(find_var(t.splitlines(),
maker
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
read_var
read_var (code, varname)
Eval and return the value of varname
defined in code
'a_'), (1,2,3))
test_eq(read_var(t, 'b_'), 3) test_eq(read_var(t,
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
= exec_new(t)
g 'a_'],g['b_']), ((1,2,3),3))
test_eq((g[= update_var('a_', lambda o:0, code=t)
t2 exec(t2, g)
'a_'],g['b_']), (0,3))
test_eq((g[= update_var('b_', lambda o:0, code=t)
t3 exec(t3, g)
'a_'],g['b_']), ((1,2,3),0)) test_eq((g[
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.
= ModuleMaker(dest='tmp', name='test.testing', nb_path=Path.cwd()/'04_export.ipynb', is_new=True)
mm mm.fname
Path('tmp/test/testing.py')
decor_id
decor_id (d)
id
attr of decorator, regardless of whether called as function or bare
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
.
= make_code_cells("from __future__ import print_function", "def a():...", "def b():...",
nb "c=d=1", "_f=1", "_g=1", "_h=1", "_all_=['_g', _h]", "@patch\ndef h(self:ca):...")
set(mm.make_all(nb)), set(['a','b','c','d', '_g', '_h'])) test_eq(
relative_import
relative_import (name, fname, level=0)
Convert a module name
to a name relative to fname
'nbdev.core', "xyz"), 'nbdev.core')
test_eq(relative_import('nbdev.core', 'nbdev'), '.core')
test_eq(relative_import(= Path('fastai')
_p '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'), '...') test_eq(relative_import(
NbCell.import2relative
NbCell.import2relative (cell:execnb.nbio.NbCell, libname)
update_import
update_import (source, tree, libname, f=<function relative_import>)
= "from nbdev.export import *\nfrom nbdev.a.b import *"
ss = make_code_cells([ss])[0]
cell 'nbdev')
cell.import2relative('from .export import *\nfrom .a.b import *')
test_eq(cell.source,
= make_code_cells([ss])[0]
cell 'nbdev/a')
cell.import2relative('from ..export import *\nfrom .b import *') test_eq(cell.source,
ModuleMaker.make
ModuleMaker.make (cells, all_cells=None, lib_path=None)
Write module containing cells
with __all__
generated from all_cells
= make_code_cells("from __future__ import print_function",
cells "#|export\ndef a(): ...", "def b(): ...")
2]]))
mm.make(cells, L([cells['tmp/test/testing.py').read_text(encoding='utf-8')) show_src(Path(
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../04_export.ipynb.
# %% ../../04_export.ipynb 0
from __future__ import print_function
# %% auto 0
= ['b']
__all__
# %% ../../04_export.ipynb
#|export
def a(): ...
# %% ../../04_export.ipynb
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 a cell with:
#|export
class A:
Note that by doing so we cannot properly generate a __all__
, so we assume that it is unwanted.
= ModuleMaker(dest='tmp', name='test.testing_noall', nb_path=Path.cwd()/'01_export.ipynb', is_new=True, parse=False)
am am.fname
Path('tmp/test/testing_noall.py')
= make_code_cells("from __future__ import print_function", "#|export\ndef a(): ...", "#|export\nclass A:")
cells
am.make(cells)'tmp/test/testing_noall.py').read_text(encoding='utf-8')) show_src(Path(
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../01_export.ipynb.
# %% ../../01_export.ipynb
from __future__ import print_function
# %% ../../01_export.ipynb
#|export
def a(): ...
# %% ../../01_export.ipynb
#|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.
= make_code_cells("def c(): ...", "def d(): ...")
c2 = ModuleMaker(dest='tmp', name='test.testing', nb_path=Path.cwd()/'04_export.ipynb', is_new=False)
mm mm.make(c2, c2)
'tmp/test/testing.py').read_text(encoding='utf-8')) show_src(Path(
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../04_export.ipynb.
# %% ../../04_export.ipynb 0
from __future__ import print_function
# %% auto 0
= ['b', 'c', 'd']
__all__
# %% ../../04_export.ipynb
#|export
def a(): ...
# %% ../../04_export.ipynb
def b(): ...
# %% ../../04_export.ipynb 0
def c(): ...
# %% ../../04_export.ipynb 1
def d(): ...
try:
= exec_import('tmp.test.testing', '*')
g for s in "b c d".split(): assert s in g, s
assert 'a' not in g
assert g['b']() is None
finally: shutil.rmtree('tmp')