Skip to content

Commit ca1d792

Browse files
committed
Allow scrubbing magics via nbdev_export --procs
Documentation on the `scrub_magics` processor is included just below its associated code. None of the existing tutorials or explanations notebooks seemed appropriate for this small contribution. The scrub_magics configuration was removed because the processor is no longer always executed. However, the existing behavior of always executing the black_format processor is retained, for compatibility purposes. usage: nbdev_export [-h] [--path PATH] [--symlinks] [--file_glob FILE_GLOB] [--file_re FILE_RE] [--folder_re FOLDER_RE] [--skip_file_glob SKIP_FILE_GLOB] [--skip_file_re SKIP_FILE_RE] [--skip_folder_re SKIP_FOLDER_RE] [--procs PROCS] Export notebooks in `path` to Python modules optional arguments: ... --procs PROCS whitespace delimited tokens of processors to use. (default: black_format) Thank you for the feedback on 9366c7f @jph00. Quick and hopefully unimportant question: You mentioned that this is a "new directive". Should we refer to these as directives instead of as processors? I didn't want to muddy up the diff with the refactoring. The terms do seem similar, but I do worry that calling black_format and scrub_magics export processors might confuse people who can't find them under the `10_processors.ipynb` notebook. It certainly confused me at first.
1 parent 3f7dea8 commit ca1d792

File tree

8 files changed

+156
-30
lines changed

8 files changed

+156
-30
lines changed

nbdev/_modidx.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
'nbdev.export.ExportModuleProc.begin': ('api/export.html#exportmoduleproc.begin', 'nbdev/export.py'),
7878
'nbdev.export.black_format': ('api/export.html#black_format', 'nbdev/export.py'),
7979
'nbdev.export.nb_export': ('api/export.html#nb_export', 'nbdev/export.py'),
80+
'nbdev.export.optional_procs': ('api/export.html#optional_procs', 'nbdev/export.py'),
8081
'nbdev.export.scrub_magics': ('api/export.html#scrub_magics', 'nbdev/export.py')},
8182
'nbdev.extract_attachments': {},
8283
'nbdev.frontmatter': { 'nbdev.frontmatter.FrontmatterProc': ('api/frontmatter.html#frontmatterproc', 'nbdev/frontmatter.py'),

nbdev/config.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ def _apply_defaults(
5959
language='English', # Language PyPI classifier
6060
recursive:bool_arg=True, # Include subfolders in notebook globs?
6161
black_formatting:bool_arg=False, # Format libraries with black?
62-
scrub_magics:bool_arg=False, # Remove magics from exported modules?
6362
readme_nb='index.ipynb', # Notebook to export as repo readme
6463
title='%(lib_name)s', # Quarto website title
6564
allowed_metadata_keys='', # Preserve the list of keys in the main notebook metadata

nbdev/doclinks.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,23 +127,28 @@ def nbglob_cli(
127127
skip_file_glob=skip_file_glob, skip_file_re=skip_file_re, skip_folder_re=skip_folder_re)
128128

129129
# %% ../nbs/api/05_doclinks.ipynb 22
130+
# the import is needed to call getattr on it
131+
import nbdev.export
132+
130133
@call_parse
131134
@delegates(nbglob_cli)
132135
def nbdev_export(
133136
path:str=None, # Path or filename
137+
procs:str="black_format", # whitespace delimited tokens of processors to use.
134138
**kwargs):
135139
"Export notebooks in `path` to Python modules"
136140
if os.environ.get('IN_TEST',0): return
141+
if procs: procs = [getattr(nbdev.export, p) for p in procs.split(" ") if p in optional_procs()]
137142
files = nbglob(path=path, as_path=True, **kwargs).sorted('name')
138-
for f in files: nb_export(f)
143+
for f in files: nb_export(f, procs=procs)
139144
add_init(get_config().lib_path)
140145
_build_modidx()
141146

142-
# %% ../nbs/api/05_doclinks.ipynb 24
147+
# %% ../nbs/api/05_doclinks.ipynb 25
143148
import importlib,ast
144149
from functools import lru_cache
145150

146-
# %% ../nbs/api/05_doclinks.ipynb 25
151+
# %% ../nbs/api/05_doclinks.ipynb 26
147152
def _find_mod(mod):
148153
mp,_,mr = mod.partition('/')
149154
spec = importlib.util.find_spec(mp)
@@ -166,7 +171,7 @@ def _get_exps(mod):
166171

167172
def _lineno(sym, fname): return _get_exps(fname).get(sym, None) if fname else None
168173

169-
# %% ../nbs/api/05_doclinks.ipynb 27
174+
# %% ../nbs/api/05_doclinks.ipynb 28
170175
def _qual_sym(s, settings):
171176
if not isinstance(s,tuple): return s
172177
nb,py = s
@@ -181,10 +186,10 @@ def _qual_syms(entries):
181186
if 'doc_host' not in settings: return entries
182187
return {'syms': {mod:_qual_mod(d, settings) for mod,d in entries['syms'].items()}, 'settings':settings}
183188

184-
# %% ../nbs/api/05_doclinks.ipynb 28
189+
# %% ../nbs/api/05_doclinks.ipynb 29
185190
_re_backticks = re.compile(r'`([^`\s]+)`')
186191

187-
# %% ../nbs/api/05_doclinks.ipynb 29
192+
# %% ../nbs/api/05_doclinks.ipynb 30
188193
@lru_cache(None)
189194
class NbdevLookup:
190195
"Mapping from symbol names to docs and source URLs"

nbdev/export.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/04_export.ipynb.
22

33
# %% auto 0
4-
__all__ = ['ExportModuleProc', 'black_format', 'scrub_magics', 'nb_export']
4+
__all__ = ['ExportModuleProc', 'black_format', 'scrub_magics', 'optional_procs', 'nb_export']
55

66
# %% ../nbs/api/04_export.ipynb 2
77
from .config import *
@@ -26,7 +26,7 @@ def _export_(self, cell, exp_to=None):
2626
self.in_all[ifnone(exp_to, '#')].append(cell)
2727
_exports_=_export_
2828

29-
# %% ../nbs/api/04_export.ipynb 7
29+
# %% ../nbs/api/04_export.ipynb 8
3030
def black_format(cell, # Cell to format
3131
force=False): # Turn black formatting on regardless of settings.ini
3232
"Processor to format code with `black`"
@@ -40,20 +40,27 @@ def black_format(cell, # Cell to format
4040
try: cell.source = _format_str(cell.source).strip()
4141
except: pass
4242

43-
# %% ../nbs/api/04_export.ipynb 9
44-
_magics_pattern = re.compile(r'^\s*(%%|%).*', re.MULTILINE)
43+
# %% ../nbs/api/04_export.ipynb 10
44+
# includes the newline, because calling .strip() would affect all cells.
45+
_magics_pattern = re.compile(r'^\s*(%%|%).*\n?', re.MULTILINE)
4546

4647
def scrub_magics(cell): # Cell to format
4748
"Processor to remove cell magics from exported code"
4849
try: cfg = get_config()
4950
except FileNotFoundError: return
50-
if (not cfg.scrub_magics) or cell.cell_type != 'code': return
51-
try:
52-
if cell.cell_type == 'code': cell.source = _magics_pattern.sub('', cell.source).strip()
51+
if cell.cell_type != 'code': return
52+
try: cell.source = _magics_pattern.sub('', cell.source)
5353
except: pass
5454

55-
# %% ../nbs/api/04_export.ipynb 10
56-
def nb_export(nbname, lib_path=None, procs=[black_format,scrub_magics], debug=False, mod_maker=ModuleMaker, name=None):
55+
# %% ../nbs/api/04_export.ipynb 13
56+
import nbdev.export
57+
def optional_procs():
58+
"An explicit list of processors that could be used by `nb_export`"
59+
return L([p for p in nbdev.export.__all__
60+
if p not in ["nb_export", "ExportModuleProc", "optional_procs"]])
61+
62+
# %% ../nbs/api/04_export.ipynb 16
63+
def nb_export(nbname, lib_path=None, procs=None, debug=False, mod_maker=ModuleMaker, name=None):
5764
"Create module(s) from notebook"
5865
if lib_path is None: lib_path = get_config().lib_path
5966
exp = ExportModuleProc()

nbs/api/01_config.ipynb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@
155155
" language='English', # Language PyPI classifier\n",
156156
" recursive:bool_arg=True, # Include subfolders in notebook globs?\n",
157157
" black_formatting:bool_arg=False, # Format libraries with black?\n",
158-
" scrub_magics:bool_arg=False, # Remove magics from exported modules?\n",
159158
" readme_nb='index.ipynb', # Notebook to export as repo readme\n",
160159
" title='%(lib_name)s', # Quarto website title\n",
161160
" allowed_metadata_keys='', # Preserve the list of keys in the main notebook metadata\n",

nbs/api/04_export.ipynb

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@
9595
"assert 'h_n' in exp.in_all['some.thing'][0].source"
9696
]
9797
},
98+
{
99+
"cell_type": "markdown",
100+
"metadata": {},
101+
"source": [
102+
"### Optional export processors"
103+
]
104+
},
98105
{
99106
"cell_type": "code",
100107
"execution_count": null,
@@ -122,7 +129,7 @@
122129
"metadata": {},
123130
"outputs": [],
124131
"source": [
125-
"_cell = read_nb('../../tests/black.ipynb')['cells'][0]\n",
132+
"_cell = read_nb('../../tests/export_procs.ipynb')['cells'][0]\n",
126133
"black_format(_cell, force=True)\n",
127134
"test_eq(_cell.source, 'j = [1, 2, 3]')"
128135
]
@@ -134,26 +141,99 @@
134141
"outputs": [],
135142
"source": [
136143
"#|export\n",
137-
"_magics_pattern = re.compile(r'^\\s*(%%|%).*', re.MULTILINE)\n",
144+
"# includes the newline, because calling .strip() would affect all cells.\n",
145+
"_magics_pattern = re.compile(r'^\\s*(%%|%).*\\n?', re.MULTILINE)\n",
138146
"\n",
139147
"def scrub_magics(cell): # Cell to format\n",
140148
" \"Processor to remove cell magics from exported code\"\n",
141149
" try: cfg = get_config()\n",
142150
" except FileNotFoundError: return\n",
143-
" if (not cfg.scrub_magics) or cell.cell_type != 'code': return\n",
144-
" try:\n",
145-
" if cell.cell_type == 'code': cell.source = _magics_pattern.sub('', cell.source).strip()\n",
151+
" if cell.cell_type != 'code': return\n",
152+
" try: cell.source = _magics_pattern.sub('', cell.source)\n",
146153
" except: pass"
147154
]
148155
},
156+
{
157+
"cell_type": "markdown",
158+
"metadata": {},
159+
"source": [
160+
"`scrub_magics` is a processor that scrubs the jupyter \"magics\" lines out of exported cells. This can be helpful when using tools like [sparkmagic](https://github.com/jupyter-incubator/sparkmagic) or just [Jupyter's builtin magics](https://ipython.readthedocs.io/en/stable/interactive/magics.html) in an nbdev project.\n",
161+
"\n",
162+
"Usage: \n",
163+
"This behavior can be enabled by passing `scrub_magics` into the `--procs` flag of the `nbdev_export` command.\n",
164+
"- `nbdev_export --procs scrub_magics`\n",
165+
"- `nbdev_export --procs 'scrub_magics black_format'`\n",
166+
"\n",
167+
"Example:\n",
168+
"\n",
169+
"A cell like below could export the line `\"hello nbdev\"` into the `bar` module. And the `%%spark` magic line would be omitted.\n",
170+
"\n",
171+
"```python\n",
172+
"%%spark\n",
173+
"#|export bar\n",
174+
"\"hello nbdev\"\n",
175+
"```\n",
176+
"\n",
177+
"It will export as something similar to this:\n",
178+
"\n",
179+
"```python\n",
180+
"# %% ../path/to/01_bar.ipynb 1\n",
181+
"\"hello nbdev\"\n",
182+
"```\n",
183+
"\n"
184+
]
185+
},
186+
{
187+
"cell_type": "code",
188+
"execution_count": null,
189+
"metadata": {},
190+
"outputs": [],
191+
"source": [
192+
"_cell = read_nb('../../tests/export_procs.ipynb')['cells'][2]\n",
193+
"scrub_magics(_cell)\n",
194+
"test_eq(_cell.source, '''#|export bar\n",
195+
"\"hello nbdev\"''')"
196+
]
197+
},
198+
{
199+
"cell_type": "code",
200+
"execution_count": null,
201+
"metadata": {},
202+
"outputs": [],
203+
"source": [
204+
"#|export\n",
205+
"import nbdev.export\n",
206+
"def optional_procs():\n",
207+
" \"An explicit list of processors that could be used by `nb_export`\"\n",
208+
" return L([p for p in nbdev.export.__all__\n",
209+
" if p not in [\"nb_export\", \"ExportModuleProc\", \"optional_procs\"]])"
210+
]
211+
},
212+
{
213+
"cell_type": "code",
214+
"execution_count": null,
215+
"metadata": {},
216+
"outputs": [],
217+
"source": [
218+
"# every optional processor should be explicitly listed here\n",
219+
"test_eq(optional_procs(), ['black_format', 'scrub_magics'])"
220+
]
221+
},
222+
{
223+
"cell_type": "markdown",
224+
"metadata": {},
225+
"source": [
226+
"### `nb_export`"
227+
]
228+
},
149229
{
150230
"cell_type": "code",
151231
"execution_count": null,
152232
"metadata": {},
153233
"outputs": [],
154234
"source": [
155235
"#|export\n",
156-
"def nb_export(nbname, lib_path=None, procs=[black_format,scrub_magics], debug=False, mod_maker=ModuleMaker, name=None):\n",
236+
"def nb_export(nbname, lib_path=None, procs=None, debug=False, mod_maker=ModuleMaker, name=None):\n",
157237
" \"Create module(s) from notebook\"\n",
158238
" if lib_path is None: lib_path = get_config().lib_path\n",
159239
" exp = ExportModuleProc()\n",

nbs/api/05_doclinks.ipynb

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -347,19 +347,34 @@
347347
"outputs": [],
348348
"source": [
349349
"#|export\n",
350+
"# the import is needed to call getattr on it\n",
351+
"import nbdev.export\n",
352+
"\n",
350353
"@call_parse\n",
351354
"@delegates(nbglob_cli)\n",
352355
"def nbdev_export(\n",
353356
" path:str=None, # Path or filename\n",
357+
" procs:str=\"black_format\", # whitespace delimited tokens of processors to use.\n",
354358
" **kwargs):\n",
355359
" \"Export notebooks in `path` to Python modules\"\n",
356360
" if os.environ.get('IN_TEST',0): return\n",
361+
" if procs: procs = [getattr(nbdev.export, p) for p in procs.split(\" \") if p in optional_procs()]\n",
357362
" files = nbglob(path=path, as_path=True, **kwargs).sorted('name')\n",
358-
" for f in files: nb_export(f)\n",
363+
" for f in files: nb_export(f, procs=procs)\n",
359364
" add_init(get_config().lib_path)\n",
360365
" _build_modidx()"
361366
]
362367
},
368+
{
369+
"cell_type": "markdown",
370+
"metadata": {},
371+
"source": [
372+
"`procs` takes the names of any optional processors you with to run on the exported cells of your notebook.\n",
373+
"It can be a list of processors\n",
374+
"\n",
375+
"N.B.: the `black_format` processor is passed in by default. But it is a no-op, unless `black_formatting=True` is set in your `settings.ini` configuration."
376+
]
377+
},
363378
{
364379
"cell_type": "markdown",
365380
"metadata": {},
@@ -556,7 +571,7 @@
556571
"text/markdown": [
557572
"---\n",
558573
"\n",
559-
"[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L211){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
574+
"[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L216){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
560575
"\n",
561576
"### NbdevLookup.doc\n",
562577
"\n",
@@ -567,7 +582,7 @@
567582
"text/plain": [
568583
"---\n",
569584
"\n",
570-
"[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L211){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
585+
"[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L216){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
571586
"\n",
572587
"### NbdevLookup.doc\n",
573588
"\n",
@@ -650,7 +665,7 @@
650665
"text/markdown": [
651666
"---\n",
652667
"\n",
653-
"[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L216){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
668+
"[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L221){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
654669
"\n",
655670
"### NbdevLookup.code\n",
656671
"\n",
@@ -661,7 +676,7 @@
661676
"text/plain": [
662677
"---\n",
663678
"\n",
664-
"[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L216){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
679+
"[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L221){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
665680
"\n",
666681
"### NbdevLookup.code\n",
667682
"\n",
@@ -709,7 +724,7 @@
709724
"text/markdown": [
710725
"---\n",
711726
"\n",
712-
"[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L233){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
727+
"[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L238){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
713728
"\n",
714729
"### NbdevLookup.linkify\n",
715730
"\n",
@@ -718,7 +733,7 @@
718733
"text/plain": [
719734
"---\n",
720735
"\n",
721-
"[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L233){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
736+
"[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L238){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
722737
"\n",
723738
"### NbdevLookup.linkify\n",
724739
"\n",
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,26 @@
1212
" 3\n",
1313
"]"
1414
]
15+
},
16+
{
17+
"cell_type": "markdown",
18+
"id": "40855489-6543-4f63-81c4-1127f4e09c31",
19+
"metadata": {},
20+
"source": [
21+
"to test `scrub_magics`:"
22+
]
23+
},
24+
{
25+
"cell_type": "code",
26+
"execution_count": null,
27+
"id": "22aa79e5-80ba-4b11-a655-ad8d830fa2ef",
28+
"metadata": {},
29+
"outputs": [],
30+
"source": [
31+
"%%spark\n",
32+
"#|export bar\n",
33+
"\"hello nbdev\""
34+
]
1535
}
1636
],
1737
"metadata": {

0 commit comments

Comments
 (0)