nbdev 3.2.2__tar.gz → 3.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nbdev-3.2.2/nbdev.egg-info → nbdev-3.3.0}/PKG-INFO +5 -5
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/__init__.py +1 -1
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/_modidx.py +20 -9
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/clean.py +109 -19
- nbdev-3.3.0/nbdev/diff.py +198 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/migrate.py +1 -0
- nbdev-3.3.0/nbdev/process.py +88 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/processors.py +3 -3
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/release.py +3 -1
- nbdev-3.3.0/nbdev/skill.py +181 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/sync.py +1 -2
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/test.py +1 -1
- {nbdev-3.2.2 → nbdev-3.3.0/nbdev.egg-info}/PKG-INFO +5 -5
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev.egg-info/SOURCES.txt +1 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev.egg-info/entry_points.txt +5 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev.egg-info/requires.txt +3 -3
- {nbdev-3.2.2 → nbdev-3.3.0}/pyproject.toml +7 -2
- nbdev-3.2.2/nbdev/diff.py +0 -94
- nbdev-3.2.2/nbdev/process.py +0 -138
- {nbdev-3.2.2 → nbdev-3.3.0}/CONTRIBUTING.md +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/LICENSE +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/MANIFEST.in +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/README.md +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/cli.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/config.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/doclinks.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/export.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/extract_attachments.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/frontmatter.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/imports.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/maker.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/merge.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/qmd.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/quarto.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/scrubmagics.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/serve.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/serve_drv.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev/showdoc.py +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev.egg-info/dependency_links.txt +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/nbdev.egg-info/top_level.txt +0 -0
- {nbdev-3.2.2 → nbdev-3.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nbdev
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: Create delightful software with Jupyter Notebooks
|
|
5
5
|
Author-email: "Jeremy Howard and the fast.ai community" <j@fast.ai>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -13,13 +13,13 @@ Classifier: Natural Language :: English
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
15
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
-
Requires-Python: >=3.
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: fastcore>=1.
|
|
20
|
-
Requires-Dist: execnb>=0.2.
|
|
19
|
+
Requires-Dist: fastcore>=2.1.3
|
|
20
|
+
Requires-Dist: execnb>=0.2.8
|
|
21
21
|
Requires-Dist: astunparse
|
|
22
|
-
Requires-Dist: ghapi>=2.0.
|
|
22
|
+
Requires-Dist: ghapi>=2.0.2
|
|
23
23
|
Requires-Dist: watchdog
|
|
24
24
|
Requires-Dist: asttokens
|
|
25
25
|
Requires-Dist: setuptools
|
|
@@ -5,14 +5,23 @@ d = { 'settings': { 'branch': 'main',
|
|
|
5
5
|
'doc_host': 'https://nbdev.fast.ai',
|
|
6
6
|
'git_url': 'https://github.com/AnswerDotAI/nbdev',
|
|
7
7
|
'lib_path': 'nbdev'},
|
|
8
|
-
'syms': { 'nbdev.clean': { 'nbdev.clean.
|
|
8
|
+
'syms': { 'nbdev.clean': { 'nbdev.clean._add_attrs': ('api/clean.html#_add_attrs', 'nbdev/clean.py'),
|
|
9
|
+
'nbdev.clean._add_jupyter_hooks': ('api/clean.html#_add_jupyter_hooks', 'nbdev/clean.py'),
|
|
10
|
+
'nbdev.clean._canon_dirs': ('api/clean.html#_canon_dirs', 'nbdev/clean.py'),
|
|
11
|
+
'nbdev.clean._cfg_drivers': ('api/clean.html#_cfg_drivers', 'nbdev/clean.py'),
|
|
9
12
|
'nbdev.clean._clean_cell': ('api/clean.html#_clean_cell', 'nbdev/clean.py'),
|
|
10
13
|
'nbdev.clean._clean_cell_output': ('api/clean.html#_clean_cell_output', 'nbdev/clean.py'),
|
|
11
14
|
'nbdev.clean._clean_cell_output_id': ('api/clean.html#_clean_cell_output_id', 'nbdev/clean.py'),
|
|
15
|
+
'nbdev.clean._cmt_dirs': ('api/clean.html#_cmt_dirs', 'nbdev/clean.py'),
|
|
16
|
+
'nbdev.clean._dir_moves': ('api/clean.html#_dir_moves', 'nbdev/clean.py'),
|
|
12
17
|
'nbdev.clean._git_root': ('api/clean.html#_git_root', 'nbdev/clean.py'),
|
|
18
|
+
'nbdev.clean._hoist_nb_meta': ('api/clean.html#_hoist_nb_meta', 'nbdev/clean.py'),
|
|
13
19
|
'nbdev.clean._nbdev_clean': ('api/clean.html#_nbdev_clean', 'nbdev/clean.py'),
|
|
14
20
|
'nbdev.clean._reconfigure': ('api/clean.html#_reconfigure', 'nbdev/clean.py'),
|
|
21
|
+
'nbdev.clean._rm_dir_lines': ('api/clean.html#_rm_dir_lines', 'nbdev/clean.py'),
|
|
15
22
|
'nbdev.clean._skip_or_sub': ('api/clean.html#_skip_or_sub', 'nbdev/clean.py'),
|
|
23
|
+
'nbdev.clean._to_comments': ('api/clean.html#_to_comments', 'nbdev/clean.py'),
|
|
24
|
+
'nbdev.clean._to_meta': ('api/clean.html#_to_meta', 'nbdev/clean.py'),
|
|
16
25
|
'nbdev.clean.clean_jupyter': ('api/clean.html#clean_jupyter', 'nbdev/clean.py'),
|
|
17
26
|
'nbdev.clean.clean_nb': ('api/clean.html#clean_nb', 'nbdev/clean.py'),
|
|
18
27
|
'nbdev.clean.nbdev_clean': ('api/clean.html#nbdev_clean', 'nbdev/clean.py'),
|
|
@@ -61,11 +70,20 @@ d = { 'settings': { 'branch': 'main',
|
|
|
61
70
|
'nbdev.config.update_version': ('api/config.html#update_version', 'nbdev/config.py'),
|
|
62
71
|
'nbdev.config.write_cells': ('api/config.html#write_cells', 'nbdev/config.py')},
|
|
63
72
|
'nbdev.diff': { 'nbdev.diff._cell_changes': ('api/diff.html#_cell_changes', 'nbdev/diff.py'),
|
|
73
|
+
'nbdev.diff._diff_order': ('api/diff.html#_diff_order', 'nbdev/diff.py'),
|
|
74
|
+
'nbdev.diff._dline': ('api/diff.html#_dline', 'nbdev/diff.py'),
|
|
75
|
+
'nbdev.diff._file_srcs': ('api/diff.html#_file_srcs', 'nbdev/diff.py'),
|
|
64
76
|
'nbdev.diff._nb_srcdict': ('api/diff.html#_nb_srcdict', 'nbdev/diff.py'),
|
|
77
|
+
'nbdev.diff._src': ('api/diff.html#_src', 'nbdev/diff.py'),
|
|
78
|
+
'nbdev.diff._srcdict': ('api/diff.html#_srcdict', 'nbdev/diff.py'),
|
|
65
79
|
'nbdev.diff.cell_diffs': ('api/diff.html#cell_diffs', 'nbdev/diff.py'),
|
|
66
80
|
'nbdev.diff.changed_cells': ('api/diff.html#changed_cells', 'nbdev/diff.py'),
|
|
81
|
+
'nbdev.diff.nb_diff': ('api/diff.html#nb_diff', 'nbdev/diff.py'),
|
|
82
|
+
'nbdev.diff.nbdev_diff': ('api/diff.html#nbdev_diff', 'nbdev/diff.py'),
|
|
83
|
+
'nbdev.diff.nbdev_diff_driver': ('api/diff.html#nbdev_diff_driver', 'nbdev/diff.py'),
|
|
67
84
|
'nbdev.diff.nbs_pair': ('api/diff.html#nbs_pair', 'nbdev/diff.py'),
|
|
68
85
|
'nbdev.diff.read_nb_from_git': ('api/diff.html#read_nb_from_git', 'nbdev/diff.py'),
|
|
86
|
+
'nbdev.diff.render_diff': ('api/diff.html#render_diff', 'nbdev/diff.py'),
|
|
69
87
|
'nbdev.diff.source_diff': ('api/diff.html#source_diff', 'nbdev/diff.py')},
|
|
70
88
|
'nbdev.doclinks': { 'nbdev.doclinks.NbdevLookup': ('api/doclinks.html#nbdevlookup', 'nbdev/doclinks.py'),
|
|
71
89
|
'nbdev.doclinks.NbdevLookup.__getitem__': ( 'api/doclinks.html#nbdevlookup.__getitem__',
|
|
@@ -193,17 +211,9 @@ d = { 'settings': { 'branch': 'main',
|
|
|
193
211
|
'nbdev.process.Processor.__call__': ('api/process.html#processor.__call__', 'nbdev/process.py'),
|
|
194
212
|
'nbdev.process.Processor.__init__': ('api/process.html#processor.__init__', 'nbdev/process.py'),
|
|
195
213
|
'nbdev.process.Processor.cell': ('api/process.html#processor.cell', 'nbdev/process.py'),
|
|
196
|
-
'nbdev.process._dir_pre': ('api/process.html#_dir_pre', 'nbdev/process.py'),
|
|
197
|
-
'nbdev.process._directive': ('api/process.html#_directive', 'nbdev/process.py'),
|
|
198
214
|
'nbdev.process._is_direc': ('api/process.html#_is_direc', 'nbdev/process.py'),
|
|
199
215
|
'nbdev.process._mk_procs': ('api/process.html#_mk_procs', 'nbdev/process.py'),
|
|
200
|
-
'nbdev.process._norm_quarto': ('api/process.html#_norm_quarto', 'nbdev/process.py'),
|
|
201
|
-
'nbdev.process._partition_cell': ('api/process.html#_partition_cell', 'nbdev/process.py'),
|
|
202
|
-
'nbdev.process._quarto_re': ('api/process.html#_quarto_re', 'nbdev/process.py'),
|
|
203
|
-
'nbdev.process.extract_directives': ('api/process.html#extract_directives', 'nbdev/process.py'),
|
|
204
|
-
'nbdev.process.first_code_ln': ('api/process.html#first_code_ln', 'nbdev/process.py'),
|
|
205
216
|
'nbdev.process.instantiate': ('api/process.html#instantiate', 'nbdev/process.py'),
|
|
206
|
-
'nbdev.process.nb_lang': ('api/process.html#nb_lang', 'nbdev/process.py'),
|
|
207
217
|
'nbdev.process.opt_set': ('api/process.html#opt_set', 'nbdev/process.py')},
|
|
208
218
|
'nbdev.processors': { 'nbdev.processors.FilterDefaults': ('api/processors.html#filterdefaults', 'nbdev/processors.py'),
|
|
209
219
|
'nbdev.processors.FilterDefaults.__call__': ( 'api/processors.html#filterdefaults.__call__',
|
|
@@ -357,6 +367,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
357
367
|
'nbdev.showdoc.doc': ('api/showdoc.html#doc', 'nbdev/showdoc.py'),
|
|
358
368
|
'nbdev.showdoc.show_doc': ('api/showdoc.html#show_doc', 'nbdev/showdoc.py'),
|
|
359
369
|
'nbdev.showdoc.showdoc_nm': ('api/showdoc.html#showdoc_nm', 'nbdev/showdoc.py')},
|
|
370
|
+
'nbdev.skill': {},
|
|
360
371
|
'nbdev.sync': { 'nbdev.sync._mod_files': ('api/sync.html#_mod_files', 'nbdev/sync.py'),
|
|
361
372
|
'nbdev.sync._to_absolute': ('api/sync.html#_to_absolute', 'nbdev/sync.py'),
|
|
362
373
|
'nbdev.sync._update_mod': ('api/sync.html#_update_mod', 'nbdev/sync.py'),
|
|
@@ -13,6 +13,7 @@ from astunparse import unparse
|
|
|
13
13
|
from textwrap import indent
|
|
14
14
|
|
|
15
15
|
from fastcore.nbio import *
|
|
16
|
+
from fastcore.nbio import _directive, _dir_line, _meta_directives, _unparse_dir
|
|
16
17
|
from fastcore.script import *
|
|
17
18
|
from fastcore.utils import *
|
|
18
19
|
from fastcore.xtras import *
|
|
@@ -93,11 +94,13 @@ def clean_nb(
|
|
|
93
94
|
allowed_cell_metadata_keys:list=None, # Preserve the list of keys in cell level metadata
|
|
94
95
|
clean_ids=True, # Remove ids from plaintext reprs?
|
|
95
96
|
allowed_out_metadata_keys:list=None, # Preserve the list of keys in output metadata
|
|
97
|
+
repair:bool=True, # Fix structural problems first (see `repair_nb`)?
|
|
96
98
|
):
|
|
97
99
|
"Clean `nb` from superfluous metadata"
|
|
98
|
-
|
|
100
|
+
if repair: repair_nb(nb)
|
|
101
|
+
metadata_keys = {"kernelspec", "jekyll", "jupytext", "doc", "widgets", "nbdev"}
|
|
99
102
|
if allowed_metadata_keys: metadata_keys.update(allowed_metadata_keys)
|
|
100
|
-
cell_metadata_keys = {"hide_input"}
|
|
103
|
+
cell_metadata_keys = {"hide_input", "nbdev"}
|
|
101
104
|
if allowed_cell_metadata_keys: cell_metadata_keys.update(allowed_cell_metadata_keys)
|
|
102
105
|
out_meta_keys = set()
|
|
103
106
|
if allowed_out_metadata_keys: out_meta_keys.update(allowed_out_metadata_keys)
|
|
@@ -126,14 +129,73 @@ def process_write(warn_msg, proc_nb, f_in, f_out=None, disp=False):
|
|
|
126
129
|
warn(f'{warn_msg}')
|
|
127
130
|
warn(e)
|
|
128
131
|
|
|
132
|
+
# %% ../nbs/api/11_clean.ipynb #f6e854ac
|
|
133
|
+
def _cmt_dirs(cell):
|
|
134
|
+
"Comment directives in `cell` as `{name: value}`, plus the partitioned `(dirs,code)` lines"
|
|
135
|
+
dirs,code = cell._partition()
|
|
136
|
+
return dict(t for t in (_directive(s, cell.lang_) for s in dirs) if t),dirs,code
|
|
137
|
+
|
|
138
|
+
def _rm_dir_lines(cell, dirs, code, names):
|
|
139
|
+
"Rewrite `cell` source without the directive lines in `names`"
|
|
140
|
+
cell.set_source(''.join([o for o in dirs if (t:=_directive(o, cell.lang_)) is None or t[0] not in names] + code))
|
|
141
|
+
|
|
142
|
+
def _to_meta(cell, names):
|
|
143
|
+
"Move comment directives in `names` to the cell's `nbdev` metadata key"
|
|
144
|
+
cmts,dirs,code = _cmt_dirs(cell)
|
|
145
|
+
move = {k:v for k,v in cmts.items() if k in names}
|
|
146
|
+
if not move: return
|
|
147
|
+
cell.setdefault('metadata',{}).setdefault('nbdev',{}).update({k:_unparse_dir(v) for k,v in move.items()})
|
|
148
|
+
_rm_dir_lines(cell, dirs, code, move)
|
|
149
|
+
|
|
150
|
+
def _to_comments(cell, names):
|
|
151
|
+
"Move directives in `names` from the cell's `nbdev` metadata key to comments"
|
|
152
|
+
move = {k:v for k,v in _meta_directives(cell).items() if k in names}
|
|
153
|
+
if not move: return
|
|
154
|
+
nbd = cell.metadata['nbdev']
|
|
155
|
+
for k in move: nbd.pop(k, None)
|
|
156
|
+
if not nbd: del cell.metadata['nbdev']
|
|
157
|
+
dirs,code = cell._partition()
|
|
158
|
+
cell.set_source(''.join(dirs + [_dir_line(k, v, cell.lang_) for k,v in move.items()] + code))
|
|
159
|
+
|
|
160
|
+
# %% ../nbs/api/11_clean.ipynb #6e8d013d
|
|
161
|
+
def _canon_dirs(cell):
|
|
162
|
+
"Rewrite `cell`'s comment directives in canonical form (colon-separated, bare for true)"
|
|
163
|
+
dirs,code = cell._partition()
|
|
164
|
+
new = [o if (t:=_directive(o, cell.lang_)) is None else _dir_line(*t, lang=cell.lang_) for o in dirs]
|
|
165
|
+
if new != dirs: cell.set_source(''.join(new + code))
|
|
166
|
+
|
|
167
|
+
def _hoist_nb_meta(nb, names=('default_exp',)):
|
|
168
|
+
"Move notebook-scope directives in `names` to notebook metadata, dropping any cell left empty"
|
|
169
|
+
for c in list(nb.cells):
|
|
170
|
+
cmts,dirs,code = _cmt_dirs(c)
|
|
171
|
+
move = {k:v for k,v in cmts.items() if k in names}
|
|
172
|
+
if not move: continue
|
|
173
|
+
nb.setdefault('metadata',{}).setdefault('nbdev',{}).update({k:_unparse_dir(v) for k,v in move.items()})
|
|
174
|
+
_rm_dir_lines(c, dirs, code, move)
|
|
175
|
+
left = set(c.directives) - {'hide'}
|
|
176
|
+
if not left and not ''.join(c._partition()[1]).strip(): nb.cells.remove(c)
|
|
177
|
+
|
|
178
|
+
def _dir_moves(nb, dirs=False, to_meta=None, to_comments=None, nb_meta=False):
|
|
179
|
+
"Apply directive migrations to loaded notebook dict `nb` in place"
|
|
180
|
+
nbo = dict2nb(nb)
|
|
181
|
+
if to_meta:
|
|
182
|
+
for c in nbo.cells: _to_meta(c, to_meta.split())
|
|
183
|
+
if to_comments:
|
|
184
|
+
for c in nbo.cells: _to_comments(c, to_comments.split())
|
|
185
|
+
if nb_meta: _hoist_nb_meta(nbo)
|
|
186
|
+
if dirs:
|
|
187
|
+
for c in nbo.cells: _canon_dirs(c)
|
|
188
|
+
nb['cells'],nb['metadata'] = nbo.cells,nbo.metadata
|
|
189
|
+
|
|
129
190
|
# %% ../nbs/api/11_clean.ipynb #714357ce
|
|
130
|
-
def _nbdev_clean(nb, path=None, clear_all=None):
|
|
191
|
+
def _nbdev_clean(nb, path=None, clear_all=None, repair=True, dirs=False, to_meta=None, to_comments=None, nb_meta=False):
|
|
131
192
|
cfg = get_config(path=path)
|
|
132
193
|
clear_all = clear_all or cfg.clear_all
|
|
133
194
|
allowed_metadata_keys = cfg.get("allowed_metadata_keys") or []
|
|
134
195
|
allowed_cell_metadata_keys = cfg.get("allowed_cell_metadata_keys") or []
|
|
135
196
|
allowed_out_metadata_keys = cfg.get("allowed_out_metadata_keys") or []
|
|
136
|
-
|
|
197
|
+
if dirs or to_meta or to_comments or nb_meta: _dir_moves(nb, dirs, to_meta, to_comments, nb_meta)
|
|
198
|
+
clean_nb(nb, clear_all, allowed_metadata_keys, allowed_cell_metadata_keys, cfg.clean_ids, allowed_out_metadata_keys, repair=repair)
|
|
137
199
|
if path: nbdev_trust.__wrapped__(path)
|
|
138
200
|
|
|
139
201
|
# %% ../nbs/api/11_clean.ipynb #6af3b9d4
|
|
@@ -142,11 +204,16 @@ def nbdev_clean(
|
|
|
142
204
|
fname:str=None, # A notebook name or glob to clean
|
|
143
205
|
clear_all:bool=False, # Remove all cell metadata and cell outputs?
|
|
144
206
|
disp:bool=False, # Print the cleaned outputs
|
|
145
|
-
stdin:bool=False # Read notebook from input stream
|
|
207
|
+
stdin:bool=False, # Read notebook from input stream
|
|
208
|
+
repair:bool_arg=True, # Fix structural problems, e.g. stray outputs on non-code cells (see `repair_nb`)?
|
|
209
|
+
dirs:bool=False, # Rewrite comment directives in canonical form?
|
|
210
|
+
to_meta:str=None, # Space-separated directive names to move from comments to cell metadata
|
|
211
|
+
to_comments:str=None, # Space-separated directive names to move from cell metadata to comments
|
|
212
|
+
nb_meta:bool=False # Move `default_exp` into notebook metadata?
|
|
146
213
|
):
|
|
147
214
|
"Clean all notebooks in `fname` to avoid merge conflicts"
|
|
148
215
|
# Git hooks will pass the notebooks in stdin
|
|
149
|
-
_clean = partial(_nbdev_clean, clear_all=clear_all)
|
|
216
|
+
_clean = partial(_nbdev_clean, clear_all=clear_all, repair=repair, dirs=dirs, to_meta=to_meta, to_comments=to_comments, nb_meta=nb_meta)
|
|
150
217
|
_write = partial(process_write, warn_msg='Failed to clean notebook', proc_nb=_clean)
|
|
151
218
|
if stdin: return _write(f_in=sys.stdin, f_out=sys.stdout)
|
|
152
219
|
if fname is None: fname = get_config().nbs_path
|
|
@@ -193,8 +260,29 @@ def _git_root():
|
|
|
193
260
|
except OSError: return None
|
|
194
261
|
|
|
195
262
|
# %% ../nbs/api/11_clean.ipynb #e6083614
|
|
263
|
+
def _add_attrs(path, attrs):
|
|
264
|
+
"Append missing attribute lines to git attributes file at `path`"
|
|
265
|
+
txt = path.read_text() if path.exists() else ''
|
|
266
|
+
have = [l.split() for l in txt.splitlines()] # whitespace-insensitive: nbdime writes its lines with tabs
|
|
267
|
+
for attr in attrs:
|
|
268
|
+
if attr.split() not in have:
|
|
269
|
+
if txt and not txt.endswith('\n'): txt+='\n'
|
|
270
|
+
txt += attr+'\n'
|
|
271
|
+
path.write_text(txt)
|
|
272
|
+
|
|
273
|
+
def _cfg_drivers(loc, merge, diff):
|
|
274
|
+
"Define the `jupyternotebook` merge/diff drivers via `git config <loc>`"
|
|
275
|
+
if merge:
|
|
276
|
+
run(f'git config {loc} merge.jupyternotebook.name "resolve conflicts with nbdev_fix"')
|
|
277
|
+
run(f'git config {loc} merge.jupyternotebook.driver "nbdev-merge %O %A %B %P"')
|
|
278
|
+
if diff: run(f'git config {loc} diff.jupyternotebook.command nbdev-diff-driver')
|
|
279
|
+
|
|
196
280
|
@call_parse
|
|
197
|
-
def nbdev_install_hooks(
|
|
281
|
+
def nbdev_install_hooks(
|
|
282
|
+
merge:bool_arg=True, # Install the notebook merge driver?
|
|
283
|
+
diff:bool_arg=True, # Install the notebook diff driver?
|
|
284
|
+
globally:bool_arg=False # Define the drivers in `~/.gitconfig` and the global attributes file, instead of repo files?
|
|
285
|
+
):
|
|
198
286
|
"Install Jupyter and git hooks to automatically clean, trust, and fix merge conflicts in notebooks"
|
|
199
287
|
cfg_path = Path.home()/'.jupyter'
|
|
200
288
|
cfg_path.mkdir(exist_ok=True)
|
|
@@ -204,6 +292,16 @@ def nbdev_install_hooks():
|
|
|
204
292
|
upd = _add_jupyter_hooks(src, fn)
|
|
205
293
|
if upd is not None: fn.write_text(upd)
|
|
206
294
|
|
|
295
|
+
nbdev_attrs = (['*.ipynb merge=jupyternotebook'] if merge else []) + (['*.ipynb diff=jupyternotebook'] if diff else [])
|
|
296
|
+
if globally:
|
|
297
|
+
_cfg_drivers('--global', merge, diff)
|
|
298
|
+
rc,p = run('git config --global --get core.attributesFile', ignore_ex=True)
|
|
299
|
+
if rc: p = os.environ.get('XDG_CONFIG_HOME', '~/.config') + '/git/attributes'
|
|
300
|
+
attrs_path = Path(p).expanduser()
|
|
301
|
+
attrs_path.parent.mkdir(parents=True, exist_ok=True)
|
|
302
|
+
_add_attrs(attrs_path, nbdev_attrs)
|
|
303
|
+
return print("Hooks are installed globally.")
|
|
304
|
+
|
|
207
305
|
repo_path = _git_root()
|
|
208
306
|
if repo_path is None:
|
|
209
307
|
sys.stderr.write('Not in a git repository, git hooks cannot be installed.\n')
|
|
@@ -215,7 +313,8 @@ def nbdev_install_hooks():
|
|
|
215
313
|
os.chmod(fn, os.stat(fn).st_mode | stat.S_IEXEC)
|
|
216
314
|
|
|
217
315
|
cmd = 'git config --local include.path ../.gitconfig'
|
|
218
|
-
|
|
316
|
+
cfg_fn = repo_path/'.gitconfig'
|
|
317
|
+
cfg_fn.write_text(f'''# Generated by nbdev-install-hooks
|
|
219
318
|
#
|
|
220
319
|
# If you need to disable this instrumentation do:
|
|
221
320
|
# git config --local --unset include.path
|
|
@@ -223,19 +322,10 @@ def nbdev_install_hooks():
|
|
|
223
322
|
# To restore:
|
|
224
323
|
# {cmd}
|
|
225
324
|
#
|
|
226
|
-
[merge "nbdev-merge"]
|
|
227
|
-
name = resolve conflicts with nbdev_fix
|
|
228
|
-
driver = nbdev-merge %O %A %B %P
|
|
229
325
|
''')
|
|
326
|
+
_cfg_drivers(f'--file "{cfg_fn}"', merge, diff)
|
|
230
327
|
run(cmd)
|
|
231
328
|
|
|
232
|
-
|
|
233
|
-
nbdev_attr = '*.ipynb merge=nbdev-merge\n'
|
|
234
|
-
try:
|
|
235
|
-
attrs = attrs_path.read_text()
|
|
236
|
-
if nbdev_attr not in attrs:
|
|
237
|
-
if not attrs.endswith('\n'): attrs+='\n'
|
|
238
|
-
attrs_path.write_text(attrs+nbdev_attr)
|
|
239
|
-
except FileNotFoundError: attrs_path.write_text(nbdev_attr)
|
|
329
|
+
_add_attrs(repo_path/'.gitattributes', nbdev_attrs)
|
|
240
330
|
|
|
241
331
|
print("Hooks are installed.")
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Cell-level git diffs for notebooks
|
|
2
|
+
|
|
3
|
+
Docs: https://nbdev.fast.ai/api/diff.html.md"""
|
|
4
|
+
|
|
5
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/19_diff.ipynb.
|
|
6
|
+
|
|
7
|
+
# %% auto #0
|
|
8
|
+
__all__ = ['read_nb_from_git', 'nbs_pair', 'changed_cells', 'source_diff', 'cell_diffs', 'render_diff', 'nb_diff',
|
|
9
|
+
'nbdev_diff_driver', 'nbdev_diff']
|
|
10
|
+
|
|
11
|
+
# %% ../nbs/api/19_diff.ipynb #10c8ad0b
|
|
12
|
+
import json
|
|
13
|
+
from fastcore.utils import *
|
|
14
|
+
from fastcore.meta import delegates
|
|
15
|
+
from fastcore.script import *
|
|
16
|
+
from difflib import unified_diff
|
|
17
|
+
from subprocess import CalledProcessError
|
|
18
|
+
from typing import Annotated
|
|
19
|
+
from fastgit import Git
|
|
20
|
+
from fastcore.nbio import *
|
|
21
|
+
from .doclinks import nbglob
|
|
22
|
+
|
|
23
|
+
# %% ../nbs/api/19_diff.ipynb #a8981115
|
|
24
|
+
def read_nb_from_git(
|
|
25
|
+
g:Git, # The git object
|
|
26
|
+
path, # The path to the notebook (absolute or relative to git root)
|
|
27
|
+
ref=None # The git ref to read from (e.g. HEAD); None for working dir
|
|
28
|
+
)->AttrDict: # The notebook; empty if `path` doesn't exist at `ref`
|
|
29
|
+
"Read notebook from git ref (e.g. HEAD) at path, or working dir if ref is None"
|
|
30
|
+
path = Path(path)
|
|
31
|
+
if path.is_absolute(): path = path.relative_to(g.top())
|
|
32
|
+
if ref is None: return read_nb(g.top()/path)
|
|
33
|
+
try: raw = g.show(f'{ref}:{path}', raise_exc=True)
|
|
34
|
+
except CalledProcessError as e:
|
|
35
|
+
if " in '" not in str(e.stderr): raise
|
|
36
|
+
return new_nb([])
|
|
37
|
+
return dict2nb(json.loads(raw))
|
|
38
|
+
|
|
39
|
+
# %% ../nbs/api/19_diff.ipynb #3ac25702
|
|
40
|
+
def _src(c): return c.get('source','')
|
|
41
|
+
|
|
42
|
+
def _srcdict(nb, f=noop):
|
|
43
|
+
"Dict of cell id->`f(cell)`, with positional fallback ids for pre-4.5 notebooks"
|
|
44
|
+
return {c.get('id', f'c{c.idx_}'): f(c) for c in nb.cells}
|
|
45
|
+
|
|
46
|
+
def _nb_srcdict(g:Git, nb_path, ref=None, f=noop): return _srcdict(read_nb_from_git(g, nb_path, ref), f)
|
|
47
|
+
|
|
48
|
+
# %% ../nbs/api/19_diff.ipynb #27ff9a7b
|
|
49
|
+
def nbs_pair(
|
|
50
|
+
nb_path, # Path to the notebook
|
|
51
|
+
ref_a='HEAD', # First git ref (None for working dir)
|
|
52
|
+
ref_b=None, # Second git ref (None for working dir)
|
|
53
|
+
f=noop # Function to call on contents
|
|
54
|
+
): # Tuple of two notebooks
|
|
55
|
+
"NBs at two refs; None means working dir. By default provides HEAD and working dir"
|
|
56
|
+
nb_path = Path(nb_path).resolve()
|
|
57
|
+
g = Git(nb_path.parent)
|
|
58
|
+
return _nb_srcdict(g, nb_path, ref_a, f), _nb_srcdict(g, nb_path, ref_b, f)
|
|
59
|
+
|
|
60
|
+
# %% ../nbs/api/19_diff.ipynb #958aeaf8
|
|
61
|
+
def _cell_changes(
|
|
62
|
+
nb_path, # Path to the notebook
|
|
63
|
+
fn, # function to call to get dict values
|
|
64
|
+
ref_a='HEAD', # First git ref (None for working dir)
|
|
65
|
+
ref_b=None, # Second git ref (None for working dir)
|
|
66
|
+
adds=True, # Include cells in b but not in a
|
|
67
|
+
changes=True, # Include cells with different content
|
|
68
|
+
dels=False, # Include cells in a but not in b
|
|
69
|
+
metadata=False, # Consider cell metadata when comparing
|
|
70
|
+
outputs=False # Consider cell outputs when comparing
|
|
71
|
+
): # Dict of results
|
|
72
|
+
"Apply fn(cell_id, old_content, new_content) to changed cells between two refs"
|
|
73
|
+
def cell_content(c):
|
|
74
|
+
res = c.get('source', '')
|
|
75
|
+
if metadata: res += '\n# metadata: ' + json.dumps(c.get('metadata', {}), sort_keys=True)
|
|
76
|
+
if outputs: res += '\n# outputs: ' + json.dumps(c.get('outputs', []), sort_keys=True)
|
|
77
|
+
return res
|
|
78
|
+
old,new = nbs_pair(nb_path, ref_a, ref_b, f=cell_content)
|
|
79
|
+
res = {}
|
|
80
|
+
if adds: res |= {cid: fn(cid, '', new[cid]) for cid in new if cid not in old}
|
|
81
|
+
if changes: res |= {cid: fn(cid, old[cid], new[cid]) for cid in new if cid in old and new[cid] != old[cid]}
|
|
82
|
+
if dels: res |= {cid: fn(cid, old[cid], '') for cid in old if cid not in new}
|
|
83
|
+
return res
|
|
84
|
+
|
|
85
|
+
# %% ../nbs/api/19_diff.ipynb #6a723bc8
|
|
86
|
+
@delegates(_cell_changes)
|
|
87
|
+
def changed_cells(nb_path, **kwargs):
|
|
88
|
+
"Return set of cell IDs for changed/added/deleted cells between two refs"
|
|
89
|
+
def f(cid,o,n): return cid
|
|
90
|
+
return set(_cell_changes(nb_path, f, **kwargs).keys())
|
|
91
|
+
|
|
92
|
+
# %% ../nbs/api/19_diff.ipynb #079eedb0
|
|
93
|
+
def source_diff(
|
|
94
|
+
old_source, # Original source string
|
|
95
|
+
new_source # New source string
|
|
96
|
+
): # Unified diff string
|
|
97
|
+
"Return unified diff string for source change"
|
|
98
|
+
return '\n'.join(unified_diff(old_source.splitlines(), new_source.splitlines(), lineterm=''))
|
|
99
|
+
|
|
100
|
+
# %% ../nbs/api/19_diff.ipynb #38216afa
|
|
101
|
+
@delegates(_cell_changes)
|
|
102
|
+
def cell_diffs(nb_path, **kwargs):
|
|
103
|
+
"{cell_id:diff} for changed/added/deleted cells between two refs"
|
|
104
|
+
def f(cid,o,n): return source_diff(o,n)
|
|
105
|
+
return _cell_changes(nb_path, f, **kwargs)
|
|
106
|
+
|
|
107
|
+
# %% ../nbs/api/19_diff.ipynb #d670e58e
|
|
108
|
+
_dcolors = {'-':'\x1b[31m', '+':'\x1b[32m', '@':'\x1b[36m', '#':'\x1b[1m', '# ':'\x1b[1;7m'}
|
|
109
|
+
|
|
110
|
+
def _dline(l, maxlen=120, color=False):
|
|
111
|
+
"Truncate `l` to `maxlen` with an ellipsis, optionally colored by its leading diff marker"
|
|
112
|
+
if maxlen and len(l)>maxlen: l = l[:maxlen]+'…'
|
|
113
|
+
c = (_dcolors.get(l[:2]) or _dcolors.get(l[:1])) if color else None
|
|
114
|
+
return f'{c}{l}\x1b[0m' if c else l
|
|
115
|
+
|
|
116
|
+
# %% ../nbs/api/19_diff.ipynb #342fc231
|
|
117
|
+
def _diff_order(old, new):
|
|
118
|
+
"All cell ids in notebook order: `new`'s order, with deleted ids after their predecessor in `old`"
|
|
119
|
+
res,olds = list(new),list(old)
|
|
120
|
+
for i,cid in enumerate(olds):
|
|
121
|
+
if cid in new: continue
|
|
122
|
+
pred = first(o for o in reversed(olds[:i]) if o in res)
|
|
123
|
+
res.insert(res.index(pred)+1 if pred else 0, cid)
|
|
124
|
+
return res
|
|
125
|
+
|
|
126
|
+
# %% ../nbs/api/19_diff.ipynb #eb3fc128
|
|
127
|
+
def render_diff(
|
|
128
|
+
old, # `{id: source}` for the old version of the notebook
|
|
129
|
+
new, # `{id: source}` for the new version
|
|
130
|
+
maxlen:int=120, # Truncate diff lines to this width (falsy: no limit)
|
|
131
|
+
color:bool=False # Add ANSI colors?
|
|
132
|
+
): # One section per changed cell, in notebook order
|
|
133
|
+
"Render cell-level changes between two notebooks as truncated unified diffs"
|
|
134
|
+
res = []
|
|
135
|
+
for cid in _diff_order(old, new):
|
|
136
|
+
o,n = old.get(cid,''), new.get(cid,'')
|
|
137
|
+
if cid in old and cid in new and o==n: continue
|
|
138
|
+
kind = 'modified' if cid in old and cid in new else 'added' if cid in new else 'deleted'
|
|
139
|
+
lines = [f'## {kind} {cid}:'] + source_diff(o,n).splitlines()[2:]
|
|
140
|
+
res.append('\n'.join(_dline(l, maxlen, color) for l in lines))
|
|
141
|
+
return '\n\n'.join(res)
|
|
142
|
+
|
|
143
|
+
# %% ../nbs/api/19_diff.ipynb #7f90d675
|
|
144
|
+
def nb_diff(
|
|
145
|
+
nb_path, # Path to the notebook
|
|
146
|
+
ref_a='HEAD', # First git ref
|
|
147
|
+
ref_b=None, # Second git ref; None for working dir
|
|
148
|
+
maxlen:int=120, # Truncate diff lines to this width (falsy: no limit)
|
|
149
|
+
color:bool=False # Add ANSI colors?
|
|
150
|
+
): # Rendered diff of changed cells
|
|
151
|
+
"Rendered cell diff for `nb_path` between two refs"
|
|
152
|
+
a,b = nbs_pair(nb_path, ref_a, ref_b, f=_src)
|
|
153
|
+
return render_diff(a, b, maxlen=maxlen, color=color)
|
|
154
|
+
|
|
155
|
+
# %% ../nbs/api/19_diff.ipynb #ab9f0b26
|
|
156
|
+
def _file_srcs(path):
|
|
157
|
+
"`{id: source}` from an ipynb file; `{}` when missing or empty (e.g. `/dev/null`)"
|
|
158
|
+
p = Path(path)
|
|
159
|
+
if not p.exists() or not p.stat().st_size: return {}
|
|
160
|
+
return _srcdict(read_nb(p), _src)
|
|
161
|
+
|
|
162
|
+
_pos = Annotated[str, {'opt':False, 'nargs':'?'}] # optional positional CLI arg, as git passes them
|
|
163
|
+
|
|
164
|
+
@call_parse
|
|
165
|
+
def nbdev_diff_driver(
|
|
166
|
+
path:str, # Repo path of the notebook being diffed
|
|
167
|
+
old_file:_pos=None, # Pre-change file (git temp file, or /dev/null)
|
|
168
|
+
old_hex:_pos=None, # Pre-change blob hash
|
|
169
|
+
old_mode:_pos=None, # Pre-change file mode
|
|
170
|
+
new_file:_pos=None, # Post-change file
|
|
171
|
+
new_hex:_pos=None, # Post-change blob hash
|
|
172
|
+
new_mode:_pos=None, # Post-change file mode
|
|
173
|
+
rename_to:_pos=None, # New repo path, when git detected a rename
|
|
174
|
+
similarity:_pos=None, # Similarity score, when git detected a rename
|
|
175
|
+
maxlen:int=120 # Truncate diff lines to this width (0 for no limit)
|
|
176
|
+
):
|
|
177
|
+
"Git external diff driver for notebooks; installed by `nbdev-install-hooks`"
|
|
178
|
+
if not new_file: return print(f'unmerged: {path}')
|
|
179
|
+
color = sys.stdout.isatty() or os.environ.get('GIT_PAGER_IN_USE')=='true'
|
|
180
|
+
res = render_diff(_file_srcs(old_file), _file_srcs(new_file), maxlen=maxlen, color=color)
|
|
181
|
+
if not res: return
|
|
182
|
+
print(_dline(f'# {path}', 0, color))
|
|
183
|
+
print(res, end='\n\n')
|
|
184
|
+
|
|
185
|
+
# %% ../nbs/api/19_diff.ipynb #2604c719
|
|
186
|
+
@call_parse
|
|
187
|
+
def nbdev_diff(
|
|
188
|
+
path:str=None, # Notebook or directory (default: project's notebooks folder)
|
|
189
|
+
ref_a:str='HEAD', # First git ref
|
|
190
|
+
ref_b:str=None, # Second git ref (default: working directory)
|
|
191
|
+
maxlen:int=120, # Truncate diff lines to this width (0 for no limit)
|
|
192
|
+
color:bool_arg=None # Add ANSI colors? (default: only if stdout is a tty)
|
|
193
|
+
):
|
|
194
|
+
"Cell-level diffs for changed notebooks between two git refs"
|
|
195
|
+
if color is None: color = sys.stdout.isatty()
|
|
196
|
+
for p in sorted(nbglob(path)):
|
|
197
|
+
res = nb_diff(p, ref_a, ref_b, maxlen=maxlen, color=color)
|
|
198
|
+
if res: print(_dline(f'# {p}', 0, color), res, sep='\n', end='\n\n')
|
|
@@ -9,6 +9,7 @@ __all__ = ['MigrateProc', 'fp_md_fm', 'migrate_nb', 'migrate_md', 'nbdev_migrate
|
|
|
9
9
|
|
|
10
10
|
# %% ../nbs/api/16_migrate.ipynb #5b687fa0-dc50-48df-8bfc-e98df34e7572
|
|
11
11
|
from .process import *
|
|
12
|
+
from fastcore.nbio import first_code_ln, langs
|
|
12
13
|
from .frontmatter import *
|
|
13
14
|
from .frontmatter import _fm2dict, _re_fm_md, _dict2fm, _insertfm
|
|
14
15
|
from .processors import *
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""A notebook processor
|
|
2
|
+
|
|
3
|
+
Docs: https://nbdev.fast.ai/api/process.html.md"""
|
|
4
|
+
|
|
5
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/03_process.ipynb.
|
|
6
|
+
|
|
7
|
+
# %% auto #0
|
|
8
|
+
__all__ = ['opt_set', 'instantiate', 'NBProcessor', 'Processor']
|
|
9
|
+
|
|
10
|
+
# %% ../nbs/api/03_process.ipynb #59172c3d
|
|
11
|
+
from .config import *
|
|
12
|
+
from .maker import *
|
|
13
|
+
from .imports import *
|
|
14
|
+
|
|
15
|
+
from fastcore.nbio import *
|
|
16
|
+
from fastcore.nbio import _meta_directives
|
|
17
|
+
from fastcore.script import *
|
|
18
|
+
from fastcore.imports import *
|
|
19
|
+
|
|
20
|
+
from collections import defaultdict
|
|
21
|
+
|
|
22
|
+
# %% ../nbs/api/03_process.ipynb #e6701805
|
|
23
|
+
def opt_set(var, newval):
|
|
24
|
+
"newval if newval else var"
|
|
25
|
+
return newval if newval else var
|
|
26
|
+
|
|
27
|
+
# %% ../nbs/api/03_process.ipynb #98c9d556
|
|
28
|
+
def instantiate(x, **kwargs):
|
|
29
|
+
"Instantiate `x` if it's a type"
|
|
30
|
+
return x(**kwargs) if isinstance(x,type) else x
|
|
31
|
+
|
|
32
|
+
def _mk_procs(procs, nb): return L(procs).map(instantiate, nb=nb)
|
|
33
|
+
|
|
34
|
+
# %% ../nbs/api/03_process.ipynb #ab147efe
|
|
35
|
+
def _is_direc(f): return getattr(f, '__name__', '-')[-1]=='_'
|
|
36
|
+
|
|
37
|
+
# %% ../nbs/api/03_process.ipynb #7c81f109
|
|
38
|
+
class NBProcessor:
|
|
39
|
+
"Process cells and nbdev comments in a notebook"
|
|
40
|
+
def __init__(self, path=None, procs=None, nb=None, debug=False, rm_directives=True, process=False):
|
|
41
|
+
self.nb = read_nb(path) if nb is None else nb
|
|
42
|
+
self.lang = nb_lang(self.nb)
|
|
43
|
+
for cell in self.nb.cells: cell.directives_ = cell.directives
|
|
44
|
+
nbdirs = {k:v for k,v in _meta_directives(self.nb).items() if not any(k in c.directives_ for c in self.nb.cells)}
|
|
45
|
+
if nbdirs:
|
|
46
|
+
fc = first(c for c in self.nb.cells if c.cell_type=='code')
|
|
47
|
+
if fc is not None: fc.directives_ = nbdirs | fc.directives_
|
|
48
|
+
if rm_directives:
|
|
49
|
+
for cell in self.nb.cells: cell.remove_directives(quarto=rm_directives=='quarto')
|
|
50
|
+
self.procs = _mk_procs(procs, nb=self.nb)
|
|
51
|
+
self.debug,self.rm_directives = debug,rm_directives
|
|
52
|
+
if process: self.process()
|
|
53
|
+
|
|
54
|
+
def _process_cell(self, proc, cell):
|
|
55
|
+
if not hasattr(cell,'source'): return
|
|
56
|
+
if cell.cell_type=='code' and cell.directives_:
|
|
57
|
+
# Option 1: `proc` is directive name with `_` suffix
|
|
58
|
+
f = getattr(proc, '__name__', '-').rstrip('_')
|
|
59
|
+
if f in cell.directives_: self._process_comment(proc, cell, f)
|
|
60
|
+
|
|
61
|
+
# Option 2: `proc` contains a method named `_{directive}_`
|
|
62
|
+
for cmd in cell.directives_:
|
|
63
|
+
f = getattr(proc, f'_{cmd}_', None)
|
|
64
|
+
if f: self._process_comment(f, cell, cmd)
|
|
65
|
+
if callable(proc) and not _is_direc(proc): cell = opt_set(cell, proc(cell))
|
|
66
|
+
|
|
67
|
+
def _process_comment(self, proc, cell, cmd):
|
|
68
|
+
args = cell.directives_[cmd].split()
|
|
69
|
+
if self.debug: print(cmd, args, proc)
|
|
70
|
+
return proc(cell, *args)
|
|
71
|
+
|
|
72
|
+
def _proc(self, proc):
|
|
73
|
+
if hasattr(proc,'begin'): proc.begin()
|
|
74
|
+
for cell in self.nb.cells: self._process_cell(proc, cell)
|
|
75
|
+
if hasattr(proc,'end'): proc.end()
|
|
76
|
+
self.nb.cells = [c for c in self.nb.cells if c and getattr(c,'source',None) is not None]
|
|
77
|
+
for i,cell in enumerate(self.nb.cells): cell.idx_ = i
|
|
78
|
+
|
|
79
|
+
def process(self):
|
|
80
|
+
"Process all cells with all processors"
|
|
81
|
+
for proc in self.procs: self._proc(proc)
|
|
82
|
+
|
|
83
|
+
# %% ../nbs/api/03_process.ipynb #fa1f8668
|
|
84
|
+
class Processor:
|
|
85
|
+
"Base class for processors"
|
|
86
|
+
def __init__(self, nb): self.nb = nb
|
|
87
|
+
def cell(self, cell): pass
|
|
88
|
+
def __call__(self, cell): return self.cell(cell)
|
|
@@ -68,7 +68,7 @@ def cell_lang(cell): return nested_attr(cell, 'metadata.language', 'python')
|
|
|
68
68
|
def _want_doc(c):
|
|
69
69
|
d = c.directives_
|
|
70
70
|
show_d = set(['export', 'exports', 'exec_doc']).intersection(d)
|
|
71
|
-
return c.source and c.cell_type=='code' and show_d and 'hide' not in d and d.get('include
|
|
71
|
+
return c.source and c.cell_type=='code' and show_d and 'hide' not in d and d.get('include') != 'false'
|
|
72
72
|
|
|
73
73
|
class add_show_docs(Processor):
|
|
74
74
|
"Add show_doc cells after exported cells, unless they are already documented"
|
|
@@ -234,7 +234,7 @@ def _do_eval(cell):
|
|
|
234
234
|
if not cell.source or 'nbdev_export'+'()' in cell.source: return
|
|
235
235
|
trees = cell.parsed_()
|
|
236
236
|
if cell.cell_type != 'code' or not trees: return
|
|
237
|
-
if cell.directives_.get('eval
|
|
237
|
+
if cell.directives_.get('eval', '').lower() == 'false': return
|
|
238
238
|
|
|
239
239
|
_show_dirs = {'export','exports','exporti','exec_doc'}
|
|
240
240
|
if cell.directives_.keys() & _show_dirs: return True
|
|
@@ -292,6 +292,6 @@ class FilterDefaults:
|
|
|
292
292
|
|
|
293
293
|
def nb_proc(self, nb):
|
|
294
294
|
"Get an `NBProcessor` with these processors"
|
|
295
|
-
return NBProcessor(nb=nb, procs=self.procs())
|
|
295
|
+
return NBProcessor(nb=nb, procs=self.procs(), rm_directives='quarto')
|
|
296
296
|
|
|
297
297
|
def __call__(self, nb): return self.nb_proc(nb).process()
|
|
@@ -121,7 +121,9 @@ async def release(self:Release):
|
|
|
121
121
|
"Tag and create a release in GitHub for the current version"
|
|
122
122
|
ver = self.cfg.version
|
|
123
123
|
notes = self.latest_notes()
|
|
124
|
-
await self.gh.
|
|
124
|
+
default = (await self.gh.repos.get()).default_branch
|
|
125
|
+
latest = 'true' if _release_branch()==default else 'false'
|
|
126
|
+
await self.gh.create_release(ver, branch=_release_head(), body=notes, make_latest=latest)
|
|
125
127
|
return ver
|
|
126
128
|
|
|
127
129
|
# %% ../nbs/api/18_release.ipynb #22101171
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Author clear, executable nbdev notebooks where code, prose, examples, outputs, and tests form one coherent narrative.
|
|
2
|
+
|
|
3
|
+
# The notebook is the product
|
|
4
|
+
|
|
5
|
+
An nbdev notebook is simultaneously source code, documentation, examples, and tests. Write it to be read from top to bottom. The rendered page should explain the public API, while executing the same cells should build and verify it.
|
|
6
|
+
|
|
7
|
+
Do not treat an nbdev notebook as a Python module divided arbitrarily into cells. Use the notebook medium deliberately: interleave implementation with explanation, executable examples, useful outputs, plots, images, tables, diagrams, and demonstrations of failure whenever they communicate the idea better than prose alone.
|
|
8
|
+
|
|
9
|
+
These conventions matter most for published libraries with rendered documentation sites. Internal projects with no docs page can be looser: match the surrounding notebooks' style rather than imposing every rule here.
|
|
10
|
+
|
|
11
|
+
# Notebooks generate modules
|
|
12
|
+
|
|
13
|
+
The notebook is the source of truth: `nbdev-export` writes exported cells to the module named by `#| default_exp`, and generated `.py` files are never edited by hand. Each exported section carries a marker like `# %% ../nbs/04_usage.ipynb #a45f753a` naming its source notebook and cell, so module code always leads back to the cell to change. `__all__`, `_modidx.py`, and module docstrings likewise regenerate on export - renames and additions propagate automatically, so never hand-edit them.
|
|
14
|
+
|
|
15
|
+
The module docstring comes from the notebook's opening markdown: the title cell's `>` description line, joined with every *exported* markdown cell after the H1 (a markdown cell whose first line is `#| export`). For a long module docstring, prefer one exported markdown cell per section over packing everything into the title blockquote; both work.
|
|
16
|
+
|
|
17
|
+
Not every module must come from a notebook, and projects often mix the two deliberately. Choose per module: notebook-sourced when narrative earns its keep - public API whose docs page matters, code best explained through interleaved prose and examples, tests that double as documentation; plain `.py` when the code is dense interlocking mechanism, iterated quickly, and covered by pytest, where cell-by-cell narrative would add friction rather than clarity. Check the file, never the repo: a `# %%` autogen marker at the top means notebook-sourced, no marker means hand-written and edited directly, and `nbdev-export` only rewrites marked files. Tests follow the source form: example and test cells for notebook modules, `tests/*.py` pytest files for plain ones.
|
|
18
|
+
|
|
19
|
+
# Choose the kind of page
|
|
20
|
+
|
|
21
|
+
Decide which form of documentation the notebook provides:
|
|
22
|
+
|
|
23
|
+
- A **tutorial** teaches through a guided learning experience.
|
|
24
|
+
- A **how-to guide** helps a reader accomplish a practical task.
|
|
25
|
+
- An **explanation** develops understanding of a focused topic.
|
|
26
|
+
- A **reference** describes a technical component and its API.
|
|
27
|
+
|
|
28
|
+
Do not mix these forms accidentally. A reference page can contain examples, but should remain easy to scan for symbols and behavior. A tutorial should advance in a purposeful sequence rather than becoming an API inventory. A how-to should solve its stated problem without expanding into a general course. An explanation should clarify concepts and trade-offs rather than masquerading as step-by-step instructions.
|
|
29
|
+
|
|
30
|
+
# Open the notebook well
|
|
31
|
+
|
|
32
|
+
Start with a markdown cell containing an H1 title and a blockquote subtitle:
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
# Great title
|
|
36
|
+
|
|
37
|
+
> A short description of what this page provides
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then introduce the page briefly. For a reference notebook, describe the component and orient the reader to its main symbols. For a tutorial or how-to, state what the reader will learn or accomplish. For an explanation, name the question or idea under discussion. Get to the subject quickly.
|
|
41
|
+
|
|
42
|
+
Use headings to make longer pages navigable. H2 headings normally divide major concepts or groups of symbols. Lower-level headings can organize longer discussions, but avoid fragmenting a short narrative into many tiny sections.
|
|
43
|
+
|
|
44
|
+
# Develop one idea at a time
|
|
45
|
+
|
|
46
|
+
A productive nbdev rhythm is:
|
|
47
|
+
|
|
48
|
+
1. Add the smallest useful implementation.
|
|
49
|
+
2. Explain what it does and why it has that form.
|
|
50
|
+
3. Demonstrate it with executable code.
|
|
51
|
+
4. Assert the behavior where an assertion improves the example.
|
|
52
|
+
5. Display the result a reader should notice.
|
|
53
|
+
6. Continue to the next idea.
|
|
54
|
+
|
|
55
|
+
This is a pattern, not a demand that every code cell contain one statement. A test or example cell may demonstrate several closely related facts. It must have a markdown introduction that tells the reader what the cell is about to establish. If that introduction becomes complicated, does not naturally introduce everything shown, or reads like an explanation of unrelated checks, the cell is doing too much and should be split. Conversely, prefer extending an existing example cell with a closely related check over adding a near-duplicate cell.
|
|
56
|
+
|
|
57
|
+
Keep definitions small enough to understand in context. When a class benefits from incremental development, define its core first and add methods later with `@patch`. This lets each method appear beside its explanation and examples. Keep a class together when splitting it would make the API harder, not easier, to understand.
|
|
58
|
+
|
|
59
|
+
# Prefer helpers with a public purpose
|
|
60
|
+
|
|
61
|
+
A helper introduced to make the notebook's own examples or tests clearer may also be a useful part of the library. Internal use is often the first evidence that an abstraction is worth exposing, not a reason to hide it.
|
|
62
|
+
|
|
63
|
+
Ask whether the helper gives users a concise, coherent operation they would otherwise need to reconstruct from lower-level internals. A good public helper:
|
|
64
|
+
|
|
65
|
+
- removes repeated knowledge of internal data structures
|
|
66
|
+
- gives a common operation a clear name and contract
|
|
67
|
+
- makes examples and downstream code easier to read
|
|
68
|
+
- provides one place to adapt callers when internals change
|
|
69
|
+
- is useful independently of the implementation that first needed it
|
|
70
|
+
|
|
71
|
+
For example, a function that constructs a canonical tool-call object may first appear while building a test fixture, but it is also useful to developers constructing those objects themselves. The fixture is its first consumer, not its justification for being private.
|
|
72
|
+
|
|
73
|
+
Treat private helpers with some suspicion. An underscore is appropriate for implementation machinery that has no coherent independent contract, but not merely because a function was first written for internal use. If a helper is only useful to the library itself, consider whether it exposes an awkward internal design that should instead become a small, reusable public abstraction.
|
|
74
|
+
|
|
75
|
+
# Make prose earn its place
|
|
76
|
+
|
|
77
|
+
Markdown should explain information the code does not express well:
|
|
78
|
+
|
|
79
|
+
- why an abstraction exists
|
|
80
|
+
- what distinction matters
|
|
81
|
+
- what behavior the next example demonstrates
|
|
82
|
+
- why a design choice was made
|
|
83
|
+
- what limitation or failure motivated the implementation
|
|
84
|
+
- how a symbol relates to the rest of the API
|
|
85
|
+
- which details are guarantees and which merely describe an example
|
|
86
|
+
|
|
87
|
+
Do not translate code line by line into English. Do not write empty transitions such as “Now we test the function.” State the lesson instead: “A missing leaf returns `None`, so success status alone does not establish existence.”
|
|
88
|
+
|
|
89
|
+
Place prose where it reads correctly in the generated page. An exported definition is normally followed by markdown explaining the symbol and introducing its first example.
|
|
90
|
+
|
|
91
|
+
# Keep docstrings short
|
|
92
|
+
|
|
93
|
+
Use a short docstring to say what a public symbol does. Put extended explanations, examples, trade-offs, warnings, and rich media in markdown cells, where they render properly and can include executable results.
|
|
94
|
+
|
|
95
|
+
Use docments beside parameters and return values. They keep argument documentation close to the signature without repeating the signature in a long docstring.
|
|
96
|
+
|
|
97
|
+
Use backticks around related symbols in prose. nbdev can turn symbol references into documentation links, so prefer symbol names over manually maintained documentation URLs.
|
|
98
|
+
|
|
99
|
+
# Examples are documentation and tests
|
|
100
|
+
|
|
101
|
+
Write every example as part of the page first, then make it verify behavior where useful. Good examples:
|
|
102
|
+
|
|
103
|
+
- use realistic, comprehensible values
|
|
104
|
+
- show the shortest path to the idea being taught
|
|
105
|
+
- produce an informative representation or result
|
|
106
|
+
- include direct assertions that reinforce rather than obscure the lesson
|
|
107
|
+
- reuse objects introduced naturally earlier in the notebook
|
|
108
|
+
- demonstrate important errors with executable failing examples
|
|
109
|
+
|
|
110
|
+
Avoid test-suite plumbing in reader-facing cells. Dense mocks, deeply nested comprehensions, long setup blocks, and many unrelated assertions make poor documentation even when they test correctly. Extract a tiny helper when setup obscures the behavior. Hide necessary but unreadable checks rather than forcing them into the page narrative.
|
|
111
|
+
|
|
112
|
+
Do not weaken a clear example merely to tolerate a future change that would invalidate its premise. If an example exists to show that an object contains a particular behavior, it should fail when that behavior disappears so the author notices and reassesses the page.
|
|
113
|
+
|
|
114
|
+
A code cell often ends with the value that should be displayed. The final line might show an object’s representation, a table, a plot, an image, a diff, a count, or another visual result of what was created or tested. Assertions verify; the final expression teaches. Prefer a useful stored output over ending every cell silently.
|
|
115
|
+
|
|
116
|
+
# Use notebook outputs deliberately
|
|
117
|
+
|
|
118
|
+
Notebooks can communicate with more than text. Use plots, images, tables, diagrams, videos, terminal recordings, rich HTML, and custom representations when they make behavior easier to grasp.
|
|
119
|
+
|
|
120
|
+
Design useful object representations when appropriate. A compact `_repr_markdown_`, table, plot, or structured summary can turn later examples into clear documentation without repeated formatting code.
|
|
121
|
+
|
|
122
|
+
Stored outputs are part of the explanation. They show the reader what an example produced and preserve evidence from an executed notebook. Keep them focused and readable; do not dump large structures without saying what matters in them.
|
|
123
|
+
|
|
124
|
+
# Show failures as behavior
|
|
125
|
+
|
|
126
|
+
Errors are part of an API. When a failure mode matters, demonstrate it with executable code and an assertion such as `expect_fail`, rather than describing it only in prose. A reader should be able to see which input fails and what rule is being enforced.
|
|
127
|
+
|
|
128
|
+
Keep failure examples focused. A large defensive test covering many hypothetical errors is less useful than one example for each important contract.
|
|
129
|
+
|
|
130
|
+
# Let state flow downward
|
|
131
|
+
|
|
132
|
+
Notebook state is sequential. Make that sequence easy to follow:
|
|
133
|
+
|
|
134
|
+
- keep imports in dedicated import cells
|
|
135
|
+
- define values shortly before they are first used
|
|
136
|
+
- reuse established objects instead of rebuilding near-duplicates
|
|
137
|
+
- avoid reassigning a name when later cells depend on its earlier meaning
|
|
138
|
+
- move genuinely shared setup into a small, clearly introduced helper
|
|
139
|
+
- end exploratory cells with the expression whose output records what was learned
|
|
140
|
+
|
|
141
|
+
A reader should not need to search far upward to understand where a value came from. If several later examples rely on setup, introduce the setup explicitly and explain its role.
|
|
142
|
+
|
|
143
|
+
The import rule is strict, and covers every cell including tests: the docs build executes each cell that contains an import in a fresh namespace where the other cells have not run, so a cell mixing imports with other code either breaks the build with a NameError or silently runs at documentation time.
|
|
144
|
+
|
|
145
|
+
# Use directives purposefully
|
|
146
|
+
|
|
147
|
+
`#| default_exp` selects the generated module. `#| export` marks implementation for export. Private underscore-prefixed helpers may be exported with their public consumers without becoming part of the public API. `#| hide` keeps necessary but distracting material off the rendered page. `#| eval: false` is for examples that genuinely must not run automatically, not for suppressing broken cells.
|
|
148
|
+
|
|
149
|
+
Directives affect both execution and documentation, so apply them according to the role of the whole cell. In particular, an unevaluated cell cannot create state required by later evaluated cells - so newly marking a cell `eval: false` means cascading the directive to the cells that depend on it.
|
|
150
|
+
|
|
151
|
+
# Prefer executable evidence
|
|
152
|
+
|
|
153
|
+
Whenever practical, show rather than claim:
|
|
154
|
+
|
|
155
|
+
- display the object instead of describing its representation
|
|
156
|
+
- run the transformation instead of paraphrasing the result
|
|
157
|
+
- draw the plot instead of only discussing its shape
|
|
158
|
+
- trigger the error instead of merely listing it
|
|
159
|
+
- compare outputs when a distinction matters
|
|
160
|
+
|
|
161
|
+
Executable evidence keeps documentation synchronized with behavior. Assertions turn important examples into regression tests, while visible outputs keep those tests useful to human readers.
|
|
162
|
+
|
|
163
|
+
# Common authoring failures
|
|
164
|
+
|
|
165
|
+
Avoid these patterns:
|
|
166
|
+
|
|
167
|
+
- a large implementation followed much later by one large test section
|
|
168
|
+
- several abstractions introduced before any is demonstrated
|
|
169
|
+
- markdown that merely narrates the next line of code
|
|
170
|
+
- long docstrings carrying material better expressed in notebook cells
|
|
171
|
+
- imports mixed into example or test cells
|
|
172
|
+
- repeated setup copied across examples
|
|
173
|
+
- reader-facing cells dominated by mocks or cleanup machinery
|
|
174
|
+
- many unrelated checks under one vague introduction
|
|
175
|
+
- assertions that hide the result the reader needs to see
|
|
176
|
+
- pages with no clear reader, purpose, or documentation form
|
|
177
|
+
- descriptions of behavior that could be demonstrated directly
|
|
178
|
+
- large outputs with no guidance about what to notice
|
|
179
|
+
|
|
180
|
+
The governing test is simple: the notebook should be pleasant and informative to read, convincing when executed, and useful as the source of the library it documents.
|
|
181
|
+
"""
|
|
@@ -12,7 +12,6 @@ from .imports import *
|
|
|
12
12
|
from .config import *
|
|
13
13
|
from .maker import *
|
|
14
14
|
from .process import *
|
|
15
|
-
from .process import _partition_cell
|
|
16
15
|
from .export import *
|
|
17
16
|
from .doclinks import _iter_py_cells
|
|
18
17
|
|
|
@@ -61,7 +60,7 @@ def _update_nb(nb_path, cells, lib_dir):
|
|
|
61
60
|
nbcell = nb_cells_by_id.get(cell.cell_id)
|
|
62
61
|
if nbcell is None:
|
|
63
62
|
raise ValueError(f"Cell ID '{cell.cell_id}' not found in notebook '{nb_path}'")
|
|
64
|
-
dirs,_ =
|
|
63
|
+
dirs,_ = nbcell._partition()
|
|
65
64
|
nbcell.source = ''.join(dirs) + _to_absolute(cell.code, cell.py_path, lib_dir)
|
|
66
65
|
write_nb(nbp.nb, nb_path)
|
|
67
66
|
|
|
@@ -52,7 +52,7 @@ def test_nb(
|
|
|
52
52
|
if cell.cell_type != 'code': return True
|
|
53
53
|
if 'nbdev_export'+'(' in cell.source: return True
|
|
54
54
|
direc = getattr(cell, 'directives_', {}) or {}
|
|
55
|
-
if direc.get('eval
|
|
55
|
+
if direc.get('eval', '').lower() == 'false': return True
|
|
56
56
|
return flags & direc.keys()
|
|
57
57
|
|
|
58
58
|
start = time.time()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nbdev
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: Create delightful software with Jupyter Notebooks
|
|
5
5
|
Author-email: "Jeremy Howard and the fast.ai community" <j@fast.ai>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -13,13 +13,13 @@ Classifier: Natural Language :: English
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
15
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
-
Requires-Python: >=3.
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: fastcore>=1.
|
|
20
|
-
Requires-Dist: execnb>=0.2.
|
|
19
|
+
Requires-Dist: fastcore>=2.1.3
|
|
20
|
+
Requires-Dist: execnb>=0.2.8
|
|
21
21
|
Requires-Dist: astunparse
|
|
22
|
-
Requires-Dist: ghapi>=2.0.
|
|
22
|
+
Requires-Dist: ghapi>=2.0.2
|
|
23
23
|
Requires-Dist: watchdog
|
|
24
24
|
Requires-Dist: asttokens
|
|
25
25
|
Requires-Dist: setuptools
|
|
@@ -6,6 +6,8 @@ nbdev-clean = nbdev.clean:nbdev_clean
|
|
|
6
6
|
nbdev-conda = nbdev.release:release_conda
|
|
7
7
|
nbdev-contributing = nbdev.quarto:nbdev_contributing
|
|
8
8
|
nbdev-create-config = nbdev.config:nbdev_create_config
|
|
9
|
+
nbdev-diff = nbdev.diff:nbdev_diff
|
|
10
|
+
nbdev-diff-driver = nbdev.diff:nbdev_diff_driver
|
|
9
11
|
nbdev-docs = nbdev.quarto:nbdev_docs
|
|
10
12
|
nbdev-export = nbdev.doclinks:nbdev_export
|
|
11
13
|
nbdev-filter = nbdev.cli:nbdev_filter
|
|
@@ -36,3 +38,6 @@ watch-export = nbdev.cli:watch_export
|
|
|
36
38
|
|
|
37
39
|
[nbdev]
|
|
38
40
|
nbdev = nbdev._modidx:d
|
|
41
|
+
|
|
42
|
+
[pyskills]
|
|
43
|
+
nbdev.skill = nbdev.skill
|
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
name = "nbdev"
|
|
7
7
|
description = "Create delightful software with Jupyter Notebooks"
|
|
8
8
|
readme = "README.md"
|
|
9
|
-
requires-python = ">=3.
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
11
11
|
authors = [{name = "Jeremy Howard and the fast.ai community", email = "j@fast.ai"}]
|
|
12
12
|
keywords = ["nbdev", "fastai", "jupyter", "notebook", "export"]
|
|
@@ -19,7 +19,7 @@ classifiers = [
|
|
|
19
19
|
"License :: OSI Approved :: Apache Software License",
|
|
20
20
|
]
|
|
21
21
|
dynamic = ["version"]
|
|
22
|
-
dependencies = [ "fastcore>=1.
|
|
22
|
+
dependencies = [ "fastcore>=2.1.3", "execnb>=0.2.8", "astunparse", "ghapi>=2.0.2", "watchdog", "asttokens",
|
|
23
23
|
"setuptools", "build", "fastgit>=0.0.7", "pyyaml", "tomli; python_version < '3.11'", ]
|
|
24
24
|
|
|
25
25
|
[project.optional-dependencies]
|
|
@@ -37,6 +37,8 @@ nbdev-update-license = "nbdev.cli:nbdev_update_license"
|
|
|
37
37
|
nbdev-export = "nbdev.doclinks:nbdev_export"
|
|
38
38
|
nbdev-fix = "nbdev.merge:nbdev_fix"
|
|
39
39
|
nbdev-merge = "nbdev.merge:nbdev_merge"
|
|
40
|
+
nbdev-diff = "nbdev.diff:nbdev_diff"
|
|
41
|
+
nbdev-diff-driver = "nbdev.diff:nbdev_diff_driver"
|
|
40
42
|
nbdev-trust = "nbdev.clean:nbdev_trust"
|
|
41
43
|
nbdev-clean = "nbdev.clean:nbdev_clean"
|
|
42
44
|
nbdev-install-hooks = "nbdev.clean:nbdev_install_hooks"
|
|
@@ -69,6 +71,9 @@ watch-export = "nbdev.cli:watch_export"
|
|
|
69
71
|
[project.entry-points.nbdev]
|
|
70
72
|
nbdev = "nbdev._modidx:d"
|
|
71
73
|
|
|
74
|
+
[project.entry-points.pyskills]
|
|
75
|
+
"nbdev.skill" = "nbdev.skill"
|
|
76
|
+
|
|
72
77
|
[tool.setuptools.dynamic]
|
|
73
78
|
version = {attr = "nbdev.__version__"}
|
|
74
79
|
|
nbdev-3.2.2/nbdev/diff.py
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
"""Get ipynb diffs by cell
|
|
2
|
-
|
|
3
|
-
Docs: https://nbdev.fast.ai/api/diff.html.md"""
|
|
4
|
-
|
|
5
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/19_diff.ipynb.
|
|
6
|
-
|
|
7
|
-
# %% auto #0
|
|
8
|
-
__all__ = ['read_nb_from_git', 'nbs_pair', 'changed_cells', 'source_diff', 'cell_diffs']
|
|
9
|
-
|
|
10
|
-
# %% ../nbs/api/19_diff.ipynb #10c8ad0b
|
|
11
|
-
import json
|
|
12
|
-
from fastcore.utils import *
|
|
13
|
-
from fastcore.meta import delegates
|
|
14
|
-
from difflib import unified_diff
|
|
15
|
-
from fastgit import Git
|
|
16
|
-
from fastcore.nbio import *
|
|
17
|
-
|
|
18
|
-
# %% ../nbs/api/19_diff.ipynb #a8981115
|
|
19
|
-
def read_nb_from_git(
|
|
20
|
-
g:Git, # The git object
|
|
21
|
-
path, # The path to the notebook (absolute or relative to git root)
|
|
22
|
-
ref=None # The git ref to read from (e.g. HEAD); None for working dir
|
|
23
|
-
)->AttrDict: # The notebook
|
|
24
|
-
"Read notebook from git ref (e.g. HEAD) at path, or working dir if ref is None"
|
|
25
|
-
path = Path(path)
|
|
26
|
-
if path.is_absolute(): path = path.relative_to(g.top())
|
|
27
|
-
if ref is None: return read_nb(g.top()/path)
|
|
28
|
-
raw = g.show(f'{ref}:{path}')
|
|
29
|
-
return dict2nb(json.loads(raw))
|
|
30
|
-
|
|
31
|
-
# %% ../nbs/api/19_diff.ipynb #3ac25702
|
|
32
|
-
def _nb_srcdict(g:Git, nb_path, ref=None, f=noop):
|
|
33
|
-
"Dict of id->source"
|
|
34
|
-
nb = read_nb_from_git(g, nb_path, ref)
|
|
35
|
-
return {c['id']: f(c) for c in nb.cells}
|
|
36
|
-
|
|
37
|
-
# %% ../nbs/api/19_diff.ipynb #27ff9a7b
|
|
38
|
-
def nbs_pair(
|
|
39
|
-
nb_path, # Path to the notebook
|
|
40
|
-
ref_a='HEAD', # First git ref (None for working dir)
|
|
41
|
-
ref_b=None, # Second git ref (None for working dir)
|
|
42
|
-
f=noop # Function to call on contents
|
|
43
|
-
): # Tuple of two notebooks
|
|
44
|
-
"NBs at two refs; None means working dir. By default provides HEAD and working dir"
|
|
45
|
-
nb_path = Path(nb_path).resolve()
|
|
46
|
-
g = Git(nb_path.parent)
|
|
47
|
-
return _nb_srcdict(g, nb_path, ref_a, f), _nb_srcdict(g, nb_path, ref_b, f)
|
|
48
|
-
|
|
49
|
-
# %% ../nbs/api/19_diff.ipynb #958aeaf8
|
|
50
|
-
def _cell_changes(
|
|
51
|
-
nb_path, # Path to the notebook
|
|
52
|
-
fn, # function to call to get dict values
|
|
53
|
-
ref_a='HEAD', # First git ref (None for working dir)
|
|
54
|
-
ref_b=None, # Second git ref (None for working dir)
|
|
55
|
-
adds=True, # Include cells in b but not in a
|
|
56
|
-
changes=True, # Include cells with different content
|
|
57
|
-
dels=False, # Include cells in a but not in b
|
|
58
|
-
metadata=False, # Consider cell metadata when comparing
|
|
59
|
-
outputs=False # Consider cell outputs when comparing
|
|
60
|
-
): # Dict of results
|
|
61
|
-
"Apply fn(cell_id, old_content, new_content) to changed cells between two refs"
|
|
62
|
-
def cell_content(c):
|
|
63
|
-
res = c.get('source', '')
|
|
64
|
-
if metadata: res += '\n# metadata: ' + json.dumps(c.get('metadata', {}), sort_keys=True)
|
|
65
|
-
if outputs: res += '\n# outputs: ' + json.dumps(c.get('outputs', []), sort_keys=True)
|
|
66
|
-
return res
|
|
67
|
-
old,new = nbs_pair(nb_path, ref_a, ref_b, f=cell_content)
|
|
68
|
-
res = {}
|
|
69
|
-
if adds: res |= {cid: fn(cid, '', new[cid]) for cid in new if cid not in old}
|
|
70
|
-
if changes: res |= {cid: fn(cid, old[cid], new[cid]) for cid in new if cid in old and new[cid] != old[cid]}
|
|
71
|
-
if dels: res |= {cid: fn(cid, old[cid], '') for cid in old if cid not in new}
|
|
72
|
-
return res
|
|
73
|
-
|
|
74
|
-
# %% ../nbs/api/19_diff.ipynb #6a723bc8
|
|
75
|
-
@delegates(_cell_changes)
|
|
76
|
-
def changed_cells(nb_path, **kwargs):
|
|
77
|
-
"Return set of cell IDs for changed/added/deleted cells between two refs"
|
|
78
|
-
def f(cid,o,n): return cid
|
|
79
|
-
return set(_cell_changes(nb_path, f, **kwargs).keys())
|
|
80
|
-
|
|
81
|
-
# %% ../nbs/api/19_diff.ipynb #079eedb0
|
|
82
|
-
def source_diff(
|
|
83
|
-
old_source, # Original source string
|
|
84
|
-
new_source # New source string
|
|
85
|
-
): # Unified diff string
|
|
86
|
-
"Return unified diff string for source change"
|
|
87
|
-
return '\n'.join(unified_diff(old_source.splitlines(), new_source.splitlines(), lineterm=''))
|
|
88
|
-
|
|
89
|
-
# %% ../nbs/api/19_diff.ipynb #38216afa
|
|
90
|
-
@delegates(_cell_changes)
|
|
91
|
-
def cell_diffs(nb_path, **kwargs):
|
|
92
|
-
"{cell_id:diff} for changed/added/deleted cells between two refs"
|
|
93
|
-
def f(cid,o,n): return source_diff(o,n)
|
|
94
|
-
return _cell_changes(nb_path, f, **kwargs)
|
nbdev-3.2.2/nbdev/process.py
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
"""A notebook processor
|
|
2
|
-
|
|
3
|
-
Docs: https://nbdev.fast.ai/api/process.html.md"""
|
|
4
|
-
|
|
5
|
-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/03_process.ipynb.
|
|
6
|
-
|
|
7
|
-
# %% auto #0
|
|
8
|
-
__all__ = ['langs', 'nb_lang', 'first_code_ln', 'extract_directives', 'opt_set', 'instantiate', 'NBProcessor', 'Processor']
|
|
9
|
-
|
|
10
|
-
# %% ../nbs/api/03_process.ipynb #59172c3d
|
|
11
|
-
from .config import *
|
|
12
|
-
from .maker import *
|
|
13
|
-
from .imports import *
|
|
14
|
-
|
|
15
|
-
from fastcore.nbio import *
|
|
16
|
-
from fastcore.script import *
|
|
17
|
-
from fastcore.imports import *
|
|
18
|
-
|
|
19
|
-
from collections import defaultdict
|
|
20
|
-
|
|
21
|
-
# %% ../nbs/api/03_process.ipynb #48e04902-f4a1-4247-ae0c-1e30bc054166
|
|
22
|
-
# from https://github.com/quarto-dev/quarto-cli/blob/main/src/resources/jupyter/notebook.py
|
|
23
|
-
langs = defaultdict(
|
|
24
|
-
lambda: '#', r = "#", python = "#", julia = "#", scala = "//", matlab = "%", csharp = "//", fsharp = "//",
|
|
25
|
-
c = ["/*","*/"], css = ["/*","*/"], sas = ["*",";"], powershell = "#", bash = "#", sql = "--", mysql = "--", psql = "--",
|
|
26
|
-
lua = "--", cpp = "//", cc = "//", stan = "#", octave = "#", fortran = "!", fortran95 = "!", awk = "#", gawk = "#", stata = "*",
|
|
27
|
-
java = "//", groovy = "//", sed = "#", perl = "#", ruby = "#", tikz = "%", javascript = "//", js = "//", d3 = "//", node = "//",
|
|
28
|
-
sass = "//", coffee = "#", go = "//", asy = "//", haskell = "--", dot = "//", apl = "⍝")
|
|
29
|
-
|
|
30
|
-
# %% ../nbs/api/03_process.ipynb #c54390ab-1cb9-4146-9e88-609f1fc5544a
|
|
31
|
-
def nb_lang(nb): return nested_attr(nb, 'metadata.kernelspec.language', 'python')
|
|
32
|
-
|
|
33
|
-
# %% ../nbs/api/03_process.ipynb #2974d8cc-870c-4362-b2b1-e35f94c28dd9
|
|
34
|
-
def _dir_pre(lang=None): return fr"\s*{langs[lang]}\s*\|"
|
|
35
|
-
def _quarto_re(lang=None): return re.compile(_dir_pre(lang) + r'\s*[\w|-]+\s*:')
|
|
36
|
-
|
|
37
|
-
# %% ../nbs/api/03_process.ipynb #484a1df8
|
|
38
|
-
def _directive(s, lang='python'):
|
|
39
|
-
s = re.sub('^'+_dir_pre(lang), f"{langs[lang]}|", s)
|
|
40
|
-
if s.strip().endswith(':'): s = s.replace(':', '') # You can append colon at the end to be Quarto compliant. Ex: #| hide:
|
|
41
|
-
if ':' in s: s = s.replace(':', ': ')
|
|
42
|
-
s = (s.strip()[2:]).strip().split()
|
|
43
|
-
if not s: return None
|
|
44
|
-
direc,*args = s
|
|
45
|
-
return direc,args
|
|
46
|
-
|
|
47
|
-
# %% ../nbs/api/03_process.ipynb #100a91fb-8b46-4feb-afbf-d1e78c14cfd6
|
|
48
|
-
def _norm_quarto(s, lang='python'):
|
|
49
|
-
"normalize quarto directives so they have a space after the colon"
|
|
50
|
-
m = _quarto_re(lang).match(s)
|
|
51
|
-
return m.group(0) + ' ' + _quarto_re(lang).sub('', s).lstrip() if m else s
|
|
52
|
-
|
|
53
|
-
# %% ../nbs/api/03_process.ipynb #69befb76-aa77-4923-bfcc-2606613d706f
|
|
54
|
-
_cell_mgc = re.compile(r"^\s*%%\w+")
|
|
55
|
-
|
|
56
|
-
def first_code_ln(code_list, re_pattern=None, lang='python'):
|
|
57
|
-
"get first line number where code occurs, where `code_list` is a list of code"
|
|
58
|
-
if re_pattern is None: re_pattern = _dir_pre(lang)
|
|
59
|
-
return first(i for i,o in enumerate(code_list) if o.strip() != '' and not re.match(re_pattern, o) and not _cell_mgc.match(o))
|
|
60
|
-
|
|
61
|
-
# %% ../nbs/api/03_process.ipynb #b3eee2d6-59b9-45f3-affd-4de2bd1284d1
|
|
62
|
-
def _partition_cell(cell, lang):
|
|
63
|
-
if not cell.source: return [],[]
|
|
64
|
-
lines = cell.source.splitlines(True)
|
|
65
|
-
first_code = first_code_ln(lines, lang=lang)
|
|
66
|
-
return lines[:first_code],lines[first_code:]
|
|
67
|
-
|
|
68
|
-
# %% ../nbs/api/03_process.ipynb #3cca78a4
|
|
69
|
-
def extract_directives(cell, remove=True, lang='python'):
|
|
70
|
-
"Take leading comment directives from lines of code in `ss`, remove `#|`, and split"
|
|
71
|
-
dirs,code = _partition_cell(cell, lang)
|
|
72
|
-
if not dirs: return {}
|
|
73
|
-
if remove:
|
|
74
|
-
# Leave Quarto directives and cell magic in place for later processing
|
|
75
|
-
cell['source'] = ''.join([_norm_quarto(o, lang) for o in dirs if _quarto_re(lang).match(o) or _cell_mgc.match(o)] + code)
|
|
76
|
-
return dict(L(_directive(s, lang) for s in dirs).filter())
|
|
77
|
-
|
|
78
|
-
# %% ../nbs/api/03_process.ipynb #e6701805
|
|
79
|
-
def opt_set(var, newval):
|
|
80
|
-
"newval if newval else var"
|
|
81
|
-
return newval if newval else var
|
|
82
|
-
|
|
83
|
-
# %% ../nbs/api/03_process.ipynb #98c9d556
|
|
84
|
-
def instantiate(x, **kwargs):
|
|
85
|
-
"Instantiate `x` if it's a type"
|
|
86
|
-
return x(**kwargs) if isinstance(x,type) else x
|
|
87
|
-
|
|
88
|
-
def _mk_procs(procs, nb): return L(procs).map(instantiate, nb=nb)
|
|
89
|
-
|
|
90
|
-
# %% ../nbs/api/03_process.ipynb #ab147efe
|
|
91
|
-
def _is_direc(f): return getattr(f, '__name__', '-')[-1]=='_'
|
|
92
|
-
|
|
93
|
-
# %% ../nbs/api/03_process.ipynb #7c81f109
|
|
94
|
-
class NBProcessor:
|
|
95
|
-
"Process cells and nbdev comments in a notebook"
|
|
96
|
-
def __init__(self, path=None, procs=None, nb=None, debug=False, rm_directives=True, process=False):
|
|
97
|
-
self.nb = read_nb(path) if nb is None else nb
|
|
98
|
-
self.lang = nb_lang(self.nb)
|
|
99
|
-
for cell in self.nb.cells: cell.directives_ = extract_directives(cell, remove=rm_directives, lang=self.lang)
|
|
100
|
-
self.procs = _mk_procs(procs, nb=self.nb)
|
|
101
|
-
self.debug,self.rm_directives = debug,rm_directives
|
|
102
|
-
if process: self.process()
|
|
103
|
-
|
|
104
|
-
def _process_cell(self, proc, cell):
|
|
105
|
-
if not hasattr(cell,'source'): return
|
|
106
|
-
if cell.cell_type=='code' and cell.directives_:
|
|
107
|
-
# Option 1: `proc` is directive name with `_` suffix
|
|
108
|
-
f = getattr(proc, '__name__', '-').rstrip('_')
|
|
109
|
-
if f in cell.directives_: self._process_comment(proc, cell, f)
|
|
110
|
-
|
|
111
|
-
# Option 2: `proc` contains a method named `_{directive}_`
|
|
112
|
-
for cmd in cell.directives_:
|
|
113
|
-
f = getattr(proc, f'_{cmd}_', None)
|
|
114
|
-
if f: self._process_comment(f, cell, cmd)
|
|
115
|
-
if callable(proc) and not _is_direc(proc): cell = opt_set(cell, proc(cell))
|
|
116
|
-
|
|
117
|
-
def _process_comment(self, proc, cell, cmd):
|
|
118
|
-
args = cell.directives_[cmd]
|
|
119
|
-
if self.debug: print(cmd, args, proc)
|
|
120
|
-
return proc(cell, *args)
|
|
121
|
-
|
|
122
|
-
def _proc(self, proc):
|
|
123
|
-
if hasattr(proc,'begin'): proc.begin()
|
|
124
|
-
for cell in self.nb.cells: self._process_cell(proc, cell)
|
|
125
|
-
if hasattr(proc,'end'): proc.end()
|
|
126
|
-
self.nb.cells = [c for c in self.nb.cells if c and getattr(c,'source',None) is not None]
|
|
127
|
-
for i,cell in enumerate(self.nb.cells): cell.idx_ = i
|
|
128
|
-
|
|
129
|
-
def process(self):
|
|
130
|
-
"Process all cells with all processors"
|
|
131
|
-
for proc in self.procs: self._proc(proc)
|
|
132
|
-
|
|
133
|
-
# %% ../nbs/api/03_process.ipynb #fa1f8668
|
|
134
|
-
class Processor:
|
|
135
|
-
"Base class for processors"
|
|
136
|
-
def __init__(self, nb): self.nb = nb
|
|
137
|
-
def cell(self, cell): pass
|
|
138
|
-
def __call__(self, cell): return self.cell(cell)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|