nbdev 3.3.16__tar.gz → 3.3.18__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.3.16/nbdev.egg-info → nbdev-3.3.18}/PKG-INFO +2 -2
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/__init__.py +1 -1
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/_modidx.py +10 -1
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/diff.py +127 -30
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/skill.py +1 -1
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/test.py +32 -13
- {nbdev-3.3.16 → nbdev-3.3.18/nbdev.egg-info}/PKG-INFO +2 -2
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev.egg-info/requires.txt +1 -1
- {nbdev-3.3.16 → nbdev-3.3.18}/pyproject.toml +1 -1
- {nbdev-3.3.16 → nbdev-3.3.18}/CONTRIBUTING.md +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/LICENSE +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/README.md +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/clean.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/cli.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/config.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/doclinks.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/export.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/extract_attachments.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/frontmatter.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/imports.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/maker.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/merge.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/migrate.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/moddocs.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/process.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/processors.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/qmd.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/quarto.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/release.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/scrubmagics.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/serve.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/serve_drv.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/showdoc.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/sync.py +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev.egg-info/SOURCES.txt +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev.egg-info/dependency_links.txt +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev.egg-info/entry_points.txt +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/nbdev.egg-info/top_level.txt +0 -0
- {nbdev-3.3.16 → nbdev-3.3.18}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nbdev
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.18
|
|
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
|
|
@@ -16,7 +16,7 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
16
16
|
Requires-Python: >=3.10
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: fastcore>=2.2.
|
|
19
|
+
Requires-Dist: fastcore>=2.2.23
|
|
20
20
|
Requires-Dist: execnb>=0.3.3
|
|
21
21
|
Requires-Dist: astunparse
|
|
22
22
|
Requires-Dist: ghapi>=2.0.2
|
|
@@ -6,7 +6,7 @@ Modules:
|
|
|
6
6
|
- `nbdev.moddocs`: # Module docs: creating them from existing notebooks
|
|
7
7
|
- `nbdev.skill`: Author nbdev notebooks as source, documentation, examples, and tests. Ensure this guidance is in context before any notebook edit."""
|
|
8
8
|
|
|
9
|
-
__version__ = "3.3.
|
|
9
|
+
__version__ = "3.3.18"
|
|
10
10
|
|
|
11
11
|
from .doclinks import nbdev_export
|
|
12
12
|
from .showdoc import show_doc
|
|
@@ -82,10 +82,17 @@ d = { 'settings': { 'branch': 'main',
|
|
|
82
82
|
'nbdev.diff': { 'nbdev.diff._cell_changes': ('api/diff.html#_cell_changes', 'nbdev/diff.py'),
|
|
83
83
|
'nbdev.diff._diff_order': ('api/diff.html#_diff_order', 'nbdev/diff.py'),
|
|
84
84
|
'nbdev.diff._dline': ('api/diff.html#_dline', 'nbdev/diff.py'),
|
|
85
|
-
'nbdev.diff.
|
|
85
|
+
'nbdev.diff._dtag': ('api/diff.html#_dtag', 'nbdev/diff.py'),
|
|
86
|
+
'nbdev.diff._file_cells': ('api/diff.html#_file_cells', 'nbdev/diff.py'),
|
|
86
87
|
'nbdev.diff._nb_srcdict': ('api/diff.html#_nb_srcdict', 'nbdev/diff.py'),
|
|
88
|
+
'nbdev.diff._out_lines': ('api/diff.html#_out_lines', 'nbdev/diff.py'),
|
|
89
|
+
'nbdev.diff._refine': ('api/diff.html#_refine', 'nbdev/diff.py'),
|
|
87
90
|
'nbdev.diff._src': ('api/diff.html#_src', 'nbdev/diff.py'),
|
|
88
91
|
'nbdev.diff._srcdict': ('api/diff.html#_srcdict', 'nbdev/diff.py'),
|
|
92
|
+
'nbdev.diff._tokens': ('api/diff.html#_tokens', 'nbdev/diff.py'),
|
|
93
|
+
'nbdev.diff._trunc': ('api/diff.html#_trunc', 'nbdev/diff.py'),
|
|
94
|
+
'nbdev.diff._wline': ('api/diff.html#_wline', 'nbdev/diff.py'),
|
|
95
|
+
'nbdev.diff._word_diff': ('api/diff.html#_word_diff', 'nbdev/diff.py'),
|
|
89
96
|
'nbdev.diff.cell_diffs': ('api/diff.html#cell_diffs', 'nbdev/diff.py'),
|
|
90
97
|
'nbdev.diff.changed_cells': ('api/diff.html#changed_cells', 'nbdev/diff.py'),
|
|
91
98
|
'nbdev.diff.nb_diff': ('api/diff.html#nb_diff', 'nbdev/diff.py'),
|
|
@@ -381,5 +388,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
381
388
|
'nbdev.test': { 'nbdev.test._await_chain': ('api/test.html#_await_chain', 'nbdev/test.py'),
|
|
382
389
|
'nbdev.test._int_handler': ('api/test.html#_int_handler', 'nbdev/test.py'),
|
|
383
390
|
'nbdev.test._keep_file': ('api/test.html#_keep_file', 'nbdev/test.py'),
|
|
391
|
+
'nbdev.test._skip_cell': ('api/test.html#_skip_cell', 'nbdev/test.py'),
|
|
392
|
+
'nbdev.test.nb_test_cells': ('api/test.html#nb_test_cells', 'nbdev/test.py'),
|
|
384
393
|
'nbdev.test.nbdev_test': ('api/test.html#nbdev_test', 'nbdev/test.py'),
|
|
385
394
|
'nbdev.test.test_nb': ('api/test.html#test_nb', 'nbdev/test.py')}}}
|
|
@@ -5,19 +5,20 @@ Docs: https://nbdev.fast.ai/api/diff.html.md"""
|
|
|
5
5
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/19_diff.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
|
-
__all__ = ['MAXLEN', '
|
|
9
|
-
'nbdev_diff_driver', 'nbdev_diff']
|
|
8
|
+
__all__ = ['MAXLEN', 'OUT_MAXLEN', 'MIN_RATIO', 'read_nb_from_git', 'nbs_pair', 'changed_cells', 'source_diff', 'cell_diffs',
|
|
9
|
+
'render_diff', 'nb_diff', 'nbdev_diff_driver', 'nbdev_diff']
|
|
10
10
|
|
|
11
11
|
# %% ../nbs/api/19_diff.ipynb #10c8ad0b
|
|
12
12
|
import json
|
|
13
13
|
from fastcore.utils import *
|
|
14
14
|
from fastcore.meta import delegates
|
|
15
15
|
from fastcore.script import *
|
|
16
|
-
from difflib import unified_diff
|
|
16
|
+
from difflib import unified_diff, SequenceMatcher
|
|
17
17
|
from subprocess import CalledProcessError
|
|
18
18
|
from typing import Annotated
|
|
19
19
|
from fastgit import Git
|
|
20
20
|
from fastcore.nbio import *
|
|
21
|
+
from fastcore.nbio import _directive
|
|
21
22
|
from .doclinks import nbglob
|
|
22
23
|
|
|
23
24
|
# %% ../nbs/api/19_diff.ipynb #a8981115
|
|
@@ -37,7 +38,10 @@ def read_nb_from_git(
|
|
|
37
38
|
return dict2nb(json.loads(raw))
|
|
38
39
|
|
|
39
40
|
# %% ../nbs/api/19_diff.ipynb #3ac25702
|
|
40
|
-
def _src(c):
|
|
41
|
+
def _src(c):
|
|
42
|
+
"Source of cell `c` without its leading directive lines"
|
|
43
|
+
dirs,code = c._partition()
|
|
44
|
+
return ''.join([l for l in dirs if not _directive(l, c.lang_)] + code)
|
|
41
45
|
|
|
42
46
|
def _srcdict(nb, f=noop):
|
|
43
47
|
"Dict of cell id->`f(cell)`, with positional fallback ids for pre-4.5 notebooks"
|
|
@@ -45,6 +49,13 @@ def _srcdict(nb, f=noop):
|
|
|
45
49
|
|
|
46
50
|
def _nb_srcdict(g:Git, nb_path, ref=None, f=noop): return _srcdict(read_nb_from_git(g, nb_path, ref), f)
|
|
47
51
|
|
|
52
|
+
# %% ../nbs/api/19_diff.ipynb #17749428
|
|
53
|
+
def _dtag(c):
|
|
54
|
+
"Cell `c`'s merged directives as a ` [k k=v]` header tag, in `dir_tag`'s form; `''` if none"
|
|
55
|
+
tag = ' '.join(k if not v else f'{k}={v}' for k,v in c.directives.items())
|
|
56
|
+
return f' [{tag}]' if tag else ''
|
|
57
|
+
|
|
58
|
+
|
|
48
59
|
# %% ../nbs/api/19_diff.ipynb #27ff9a7b
|
|
49
60
|
def nbs_pair(
|
|
50
61
|
nb_path, # Path to the notebook
|
|
@@ -82,7 +93,6 @@ def _cell_changes(
|
|
|
82
93
|
if dels: res |= {cid: fn(cid, old[cid], '') for cid in old if cid not in new}
|
|
83
94
|
return res
|
|
84
95
|
|
|
85
|
-
# %% ../nbs/api/19_diff.ipynb #6a723bc8
|
|
86
96
|
@delegates(_cell_changes)
|
|
87
97
|
def changed_cells(nb_path, **kwargs):
|
|
88
98
|
"Return set of cell IDs for changed/added/deleted cells between two refs"
|
|
@@ -106,14 +116,78 @@ def cell_diffs(nb_path, **kwargs):
|
|
|
106
116
|
|
|
107
117
|
# %% ../nbs/api/19_diff.ipynb #d670e58e
|
|
108
118
|
MAXLEN = 180 # Most characters shown per displayed line
|
|
109
|
-
|
|
119
|
+
OUT_MAXLEN = 512 # Most characters shown of a cell's stored output
|
|
120
|
+
MIN_RATIO = 0.5 # Least similar (by shared words) a changed line pair can be and still merge into one word-diff line
|
|
121
|
+
_dcolors = {'## …':'\x1b[34m', '# ':'\x1b[1;7m', '#':'\x1b[1m', '-':'\x1b[31m', '+':'\x1b[32m', '@':'\x1b[36m'}
|
|
122
|
+
|
|
123
|
+
def _trunc(s, maxlen):
|
|
124
|
+
"`s` cut to `maxlen`, ending `…[n]` with `n` the number of characters missing"
|
|
125
|
+
if not maxlen or len(s)<=maxlen: return s
|
|
126
|
+
return truncstr(s, maxlen, suf=lambda n: f'…[{humanize(n)}]')
|
|
110
127
|
|
|
111
128
|
def _dline(l, maxlen=MAXLEN, color=False):
|
|
112
|
-
"Truncate `l`
|
|
113
|
-
if
|
|
114
|
-
|
|
129
|
+
"Truncate `l` with `_trunc`, optionally colored by its leading diff marker; word-diff spans render via `_wline`"
|
|
130
|
+
if not isinstance(l, str): return _wline(l, maxlen, color)
|
|
131
|
+
l = _trunc(l, maxlen)
|
|
132
|
+
c = first(v for k,v in _dcolors.items() if l.startswith(k)) if color else None
|
|
115
133
|
return f'{c}{l}\x1b[0m' if c else l
|
|
116
134
|
|
|
135
|
+
# %% ../nbs/api/19_diff.ipynb #7a1d7b28
|
|
136
|
+
_wcolors = {'-':'\x1b[31;9m', '+':'\x1b[32m'}
|
|
137
|
+
_wmarks = {'-':('[-','-]'), '+':('{+','+}')}
|
|
138
|
+
|
|
139
|
+
def _tokens(s): return re.findall(r'\w+|\s+|[^\w\s]', s)
|
|
140
|
+
|
|
141
|
+
def _word_diff(a, b, maxlen=MAXLEN):
|
|
142
|
+
"`(kind, text)` spans merging lines `a` and `b` (' ' shared, '-' only in `a`, '+' only in `b`); None when too different, or longer than `maxlen`"
|
|
143
|
+
if maxlen and max(len(a),len(b))>maxlen: return None
|
|
144
|
+
if SequenceMatcher(None, re.findall(r'\w+', a), re.findall(r'\w+', b), autojunk=False).ratio()<MIN_RATIO: return None
|
|
145
|
+
ta,tb = _tokens(a),_tokens(b)
|
|
146
|
+
res = []
|
|
147
|
+
for tag,i1,i2,j1,j2 in SequenceMatcher(None, ta, tb, autojunk=False).get_opcodes():
|
|
148
|
+
if tag=='equal': res.append((' ', ''.join(ta[i1:i2])))
|
|
149
|
+
else:
|
|
150
|
+
if i2>i1: res.append(('-', ''.join(ta[i1:i2])))
|
|
151
|
+
if j2>j1: res.append(('+', ''.join(tb[j1:j2])))
|
|
152
|
+
return res
|
|
153
|
+
|
|
154
|
+
def _wline(spans, maxlen=MAXLEN, color=False):
|
|
155
|
+
"One `!` line from word-diff `spans`: deletions red and struck out, additions green, or `[-…-]`/`{+…+}` markers without `color`"
|
|
156
|
+
if not color: return _dline('!'+''.join(t if k==' ' else _wmarks[k][0]+t+_wmarks[k][1] for k,t in spans), maxlen)
|
|
157
|
+
full = '!'+''.join(t for _,t in spans)
|
|
158
|
+
cut = _trunc(full, maxlen)
|
|
159
|
+
suf = cut[cut.rfind('…['):] if cut!=full else ''
|
|
160
|
+
res,room = '!',len(cut)-len(suf)-1
|
|
161
|
+
for k,t in spans:
|
|
162
|
+
t = t[:room]
|
|
163
|
+
room -= len(t)
|
|
164
|
+
res += t if k==' ' else f'{_wcolors[k]}{t}\x1b[0m'
|
|
165
|
+
if not room: break
|
|
166
|
+
return res+suf
|
|
167
|
+
|
|
168
|
+
# %% ../nbs/api/19_diff.ipynb #2bf82340
|
|
169
|
+
def _refine(lines, maxlen=MAXLEN):
|
|
170
|
+
"Diff `lines` with each positionally paired `-`/`+` line merged into word-diff spans where `_word_diff` allows"
|
|
171
|
+
res,i = [],0
|
|
172
|
+
while i<len(lines):
|
|
173
|
+
if not lines[i].startswith('-'):
|
|
174
|
+
res.append(lines[i])
|
|
175
|
+
i += 1
|
|
176
|
+
continue
|
|
177
|
+
j = i
|
|
178
|
+
while j<len(lines) and lines[j].startswith('-'): j += 1
|
|
179
|
+
k = j
|
|
180
|
+
while k<len(lines) and lines[k].startswith('+'): k += 1
|
|
181
|
+
olds,news = lines[i:j],lines[j:k]
|
|
182
|
+
bo,bn = [],[]
|
|
183
|
+
for o,n in zip(olds,news):
|
|
184
|
+
w = _word_diff(o[1:], n[1:], maxlen)
|
|
185
|
+
if w: res,bo,bn = res+bo+bn+[w],[],[]
|
|
186
|
+
else: bo,bn = bo+[o],bn+[n]
|
|
187
|
+
res += bo+olds[len(news):]+bn+news[len(olds):]
|
|
188
|
+
i = k
|
|
189
|
+
return res
|
|
190
|
+
|
|
117
191
|
# %% ../nbs/api/19_diff.ipynb #342fc231
|
|
118
192
|
def _diff_order(old, new):
|
|
119
193
|
"All cell ids in notebook order: `new`'s order, with deleted ids after their predecessor in `old`"
|
|
@@ -125,19 +199,36 @@ def _diff_order(old, new):
|
|
|
125
199
|
return res
|
|
126
200
|
|
|
127
201
|
# %% ../nbs/api/19_diff.ipynb #eb3fc128
|
|
202
|
+
def _out_lines(c):
|
|
203
|
+
"Stored outputs of cell `c` rendered as text, capped at `OUT_MAXLEN` chars, each line prefixed `| `"
|
|
204
|
+
r = _trunc(render_text(c.get('outputs', [])), OUT_MAXLEN)
|
|
205
|
+
return [f'| {l}' for l in r.splitlines()]
|
|
206
|
+
|
|
128
207
|
def render_diff(
|
|
129
|
-
old, # `{id:
|
|
130
|
-
new, # `{id:
|
|
208
|
+
old, # `{id: cell}` for the old version of the notebook
|
|
209
|
+
new, # `{id: cell}` for the new version
|
|
131
210
|
maxlen:int=MAXLEN, # Truncate diff lines to this width (falsy: no limit)
|
|
132
|
-
color:bool=False # Add ANSI colors?
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
211
|
+
color:bool=False, # Add ANSI colors?
|
|
212
|
+
context:int=1, # Unchanged cells to show either side of each change
|
|
213
|
+
show_out:bool=True # Show the stored outputs of changed and added cells?
|
|
214
|
+
): # One section per shown cell, in notebook order
|
|
215
|
+
"Render cell-level changes between two notebooks as truncated unified diffs, with context cells and outputs"
|
|
216
|
+
order = _diff_order(old, new)
|
|
217
|
+
def _key(cid, d): return (_src(d[cid]), d[cid].directives) if cid in d else None
|
|
218
|
+
def _changed(cid): return _key(cid, old)!=_key(cid, new)
|
|
219
|
+
show = {j for i,cid in enumerate(order) if _changed(cid) for j in range(i-context, i+context+1)}
|
|
220
|
+
res,prev = [],-1
|
|
221
|
+
for i,cid in enumerate(order):
|
|
222
|
+
if i not in show: continue
|
|
223
|
+
if 0<=prev<i-1: res.append(_dline(f'## … {i-prev-1} cells', maxlen, color))
|
|
224
|
+
prev = i
|
|
225
|
+
c = new.get(cid) or old[cid]
|
|
226
|
+
if not _changed(cid): lines = [f'## {cid}{_dtag(c)}:'] + [' '+l for l in _src(c).splitlines()]
|
|
227
|
+
else:
|
|
228
|
+
o,n = (_src(old[cid]) if cid in old else ''), (_src(new[cid]) if cid in new else '')
|
|
229
|
+
kind = 'modified' if cid in old and cid in new else 'added' if cid in new else 'deleted'
|
|
230
|
+
lines = [f'## {kind} {cid}{_dtag(c)}:'] + _refine(source_diff(o,n).splitlines()[2:], maxlen)
|
|
231
|
+
if show_out and cid in new: lines += _out_lines(new[cid])
|
|
141
232
|
res.append('\n'.join(_dline(l, maxlen, color) for l in lines))
|
|
142
233
|
return '\n\n'.join(res)
|
|
143
234
|
|
|
@@ -147,18 +238,20 @@ def nb_diff(
|
|
|
147
238
|
ref_a='HEAD', # First git ref
|
|
148
239
|
ref_b=None, # Second git ref; None for working dir
|
|
149
240
|
maxlen:int=MAXLEN, # Truncate diff lines to this width (falsy: no limit)
|
|
150
|
-
color:bool=False # Add ANSI colors?
|
|
241
|
+
color:bool=False, # Add ANSI colors?
|
|
242
|
+
context:int=1, # Unchanged cells to show either side of each change
|
|
243
|
+
show_out:bool=True # Show the stored outputs of changed and added cells?
|
|
151
244
|
): # Rendered diff of changed cells
|
|
152
245
|
"Rendered cell diff for `nb_path` between two refs"
|
|
153
|
-
a,b = nbs_pair(nb_path, ref_a, ref_b
|
|
154
|
-
return render_diff(a, b, maxlen=maxlen, color=color)
|
|
246
|
+
a,b = nbs_pair(nb_path, ref_a, ref_b)
|
|
247
|
+
return render_diff(a, b, maxlen=maxlen, color=color, context=context, show_out=show_out)
|
|
155
248
|
|
|
156
249
|
# %% ../nbs/api/19_diff.ipynb #ab9f0b26
|
|
157
|
-
def
|
|
158
|
-
"`{id:
|
|
250
|
+
def _file_cells(path):
|
|
251
|
+
"`{id: cell}` from an ipynb file; `{}` when missing or empty (e.g. `/dev/null`)"
|
|
159
252
|
p = Path(path)
|
|
160
253
|
if not p.exists() or not p.stat().st_size: return {}
|
|
161
|
-
return _srcdict(read_nb(p)
|
|
254
|
+
return _srcdict(read_nb(p))
|
|
162
255
|
|
|
163
256
|
_pos = Annotated[str, {'opt':False, 'nargs':'?'}] # optional positional CLI arg, as git passes them
|
|
164
257
|
|
|
@@ -173,12 +266,14 @@ def nbdev_diff_driver(
|
|
|
173
266
|
new_mode:_pos=None, # Post-change file mode
|
|
174
267
|
rename_to:_pos=None, # New repo path, when git detected a rename
|
|
175
268
|
similarity:_pos=None, # Similarity score, when git detected a rename
|
|
176
|
-
maxlen:int=MAXLEN # Truncate diff lines to this width (0 for no limit)
|
|
269
|
+
maxlen:int=MAXLEN, # Truncate diff lines to this width (0 for no limit)
|
|
270
|
+
context:int=1, # Unchanged cells to show either side of each change
|
|
271
|
+
show_out:bool=True # Show the stored outputs of changed and added cells?
|
|
177
272
|
):
|
|
178
273
|
"Git external diff driver for notebooks; installed by `nbdev-install-hooks`"
|
|
179
274
|
if not new_file: return print(f'unmerged: {path}')
|
|
180
275
|
color = sys.stdout.isatty() or os.environ.get('GIT_PAGER_IN_USE')=='true'
|
|
181
|
-
res = render_diff(
|
|
276
|
+
res = render_diff(_file_cells(old_file), _file_cells(new_file), maxlen=maxlen, color=color, context=context, show_out=show_out)
|
|
182
277
|
if not res: return
|
|
183
278
|
print(_dline(f'# {path}', 0, color))
|
|
184
279
|
print(res, end='\n\n')
|
|
@@ -190,10 +285,12 @@ def nbdev_diff(
|
|
|
190
285
|
ref_a:str='HEAD', # First git ref
|
|
191
286
|
ref_b:str=None, # Second git ref (default: working directory)
|
|
192
287
|
maxlen:int=MAXLEN, # Truncate diff lines to this width (0 for no limit)
|
|
193
|
-
color:bool_arg=None # Add ANSI colors? (default: only if stdout is a tty)
|
|
288
|
+
color:bool_arg=None, # Add ANSI colors? (default: only if stdout is a tty)
|
|
289
|
+
context:int=1, # Unchanged cells to show either side of each change
|
|
290
|
+
show_out:bool=True # Show the stored outputs of changed and added cells?
|
|
194
291
|
):
|
|
195
292
|
"Cell-level diffs for changed notebooks between two git refs"
|
|
196
293
|
if color is None: color = sys.stdout.isatty()
|
|
197
294
|
for p in sorted(nbglob(path)):
|
|
198
|
-
res = nb_diff(p, ref_a, ref_b, maxlen=maxlen, color=color)
|
|
295
|
+
res = nb_diff(p, ref_a, ref_b, maxlen=maxlen, color=color, context=context, show_out=show_out)
|
|
199
296
|
if res: print(_dline(f'# {p}', 0, color), res, sep='\n', end='\n\n')
|
|
@@ -18,7 +18,7 @@ Develop one idea at a time: small implementation, explanation, executable lesson
|
|
|
18
18
|
|
|
19
19
|
For new behavior, choose its narrative position, write the lesson first, run preceding cells as needed, and see it fail. Add its markdown introduction and implementation above it, then run implementation and lesson to see it pass. For bugs, revise an existing example to exercise the failing path; if no revision fits, add an assertion to it. If no documented behavior changes, change no lesson cells. Never add a cell merely to witness a fix.
|
|
20
20
|
|
|
21
|
-
Read existing examples before experimenting. Explore in the notebook, not in a separate kernel check; retain useful explorations as lessons and delete those that teach nothing worth keeping.
|
|
21
|
+
Read existing examples before experimenting. Explore in the notebook, not in a separate kernel check; retain useful explorations as lessons and delete those that teach nothing worth keeping. Always try to leave a notebook better than you found it.
|
|
22
22
|
|
|
23
23
|
Reserve underscore names for machinery without a coherent independent contract. Internal use can justify exposing an abstraction; helpers useful only internally may indicate awkward design.
|
|
24
24
|
|
|
@@ -5,10 +5,10 @@ Docs: https://nbdev.fast.ai/api/test.html.md"""
|
|
|
5
5
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/12_test.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
|
-
__all__ = ['test_nb', 'nbdev_test']
|
|
8
|
+
__all__ = ['nb_test_cells', 'test_nb', 'nbdev_test']
|
|
9
9
|
|
|
10
10
|
# %% ../nbs/api/12_test.ipynb #45e10c3f
|
|
11
|
-
import time,os,sys,io,traceback,contextlib,
|
|
11
|
+
import time,os,sys,io,traceback,contextlib,signal,asyncio,threading
|
|
12
12
|
from fastcore.basics import *
|
|
13
13
|
from fastcore.imports import *
|
|
14
14
|
from fastcore.foundation import *
|
|
@@ -54,6 +54,29 @@ def _int_handler(signum, frame):
|
|
|
54
54
|
os.write(2, f'\n=== nbdev-test interrupted: {_cur_nb[0]} ===\n{buf.getvalue()}'.encode())
|
|
55
55
|
os._exit(130)
|
|
56
56
|
|
|
57
|
+
# %% ../nbs/api/12_test.ipynb #b831e0b8
|
|
58
|
+
def _skip_cell(nb, skip_flags=None, force_flags=None):
|
|
59
|
+
"Predicate for the cells of `nb` that `test_nb` leaves out"
|
|
60
|
+
fm = nb_frontmatter(nb)
|
|
61
|
+
if str2bool(fm.get('skip_exec', False)) or nb_lang(nb) != 'python': return lambda cell: True
|
|
62
|
+
flags = set(L(skip_flags)) - set(L(force_flags))
|
|
63
|
+
dflt = fm_default_eval(fm)
|
|
64
|
+
def _f(cell):
|
|
65
|
+
if cell.cell_type != 'code': return True
|
|
66
|
+
if not does_cell_eval(cell, dflt): return True
|
|
67
|
+
return flags & (getattr(cell, 'directives_', {}) or {}).keys()
|
|
68
|
+
return _f
|
|
69
|
+
|
|
70
|
+
def nb_test_cells(
|
|
71
|
+
fn, # Notebook path
|
|
72
|
+
skip_flags=None, # Flags marking cells to skip
|
|
73
|
+
force_flags=None, # Flags marking cells to always run
|
|
74
|
+
):
|
|
75
|
+
"The cells `test_nb` would run in the notebook at `fn`"
|
|
76
|
+
nb = NBProcessor(fn, rm_directives=False, process=True).nb
|
|
77
|
+
skip = _skip_cell(nb, skip_flags, force_flags)
|
|
78
|
+
return [c for c in nb.cells if not skip(c)]
|
|
79
|
+
|
|
57
80
|
# %% ../nbs/api/12_test.ipynb #3f4fa1ad
|
|
58
81
|
def test_nb(
|
|
59
82
|
fn, # file name of notebook to test
|
|
@@ -76,16 +99,10 @@ def test_nb(
|
|
|
76
99
|
prev_test = os.environ.get('IN_TEST')
|
|
77
100
|
if not IN_NOTEBOOK: os.environ['IN_TEST'] = '1'
|
|
78
101
|
try:
|
|
79
|
-
flags=set(L(skip_flags)) - set(L(force_flags))
|
|
80
102
|
nb = NBProcessor(fn, rm_directives=False, process=True).nb
|
|
81
103
|
fm = nb_frontmatter(nb)
|
|
82
104
|
if str2bool(fm.get('skip_exec', False)) or nb_lang(nb) != 'python': return True, 0
|
|
83
|
-
|
|
84
|
-
dflt = fm_default_eval(fm)
|
|
85
|
-
def _no_eval(cell):
|
|
86
|
-
if cell.cell_type != 'code': return True
|
|
87
|
-
if not does_cell_eval(cell, dflt): return True
|
|
88
|
-
return flags & (getattr(cell, 'directives_', {}) or {}).keys()
|
|
105
|
+
_no_eval = _skip_cell(nb, skip_flags, force_flags)
|
|
89
106
|
|
|
90
107
|
def _postproc(cell):
|
|
91
108
|
elapsed = cell.metadata['execution']['total']
|
|
@@ -102,9 +119,9 @@ def test_nb(
|
|
|
102
119
|
if save: write_nb(nb, fn)
|
|
103
120
|
res = True
|
|
104
121
|
except:
|
|
105
|
-
if showerr: sys.
|
|
122
|
+
if showerr: sys.__stderr__.write((k.prettytb(fname=fn) or traceback.format_exc())+'\n')
|
|
106
123
|
res=False
|
|
107
|
-
if k.leaks and showerr: sys.
|
|
124
|
+
if k.leaks and showerr: sys.__stderr__.write(f'{fn}: {len(k.leaks)} leaked task(s) survived cancellation\n')
|
|
108
125
|
if do_print: print(f'- Completed {fn}')
|
|
109
126
|
return res,time.time()-start
|
|
110
127
|
finally:
|
|
@@ -154,10 +171,12 @@ def nbdev_test(
|
|
|
154
171
|
else: kw = {'method':'spawn'} if sys.platform=='darwin' else {}
|
|
155
172
|
wd_pth = cfg.nbs_path
|
|
156
173
|
with working_directory(wd_pth if (wd_pth and wd_pth.exists()) else os.getcwd()):
|
|
174
|
+
setup = cfg.get('test_setup')
|
|
175
|
+
if setup: import_obj(setup)([c for f in files for c in nb_test_cells(f, skip_flags, force_flags)])
|
|
157
176
|
try:
|
|
158
177
|
results = parallel(test_nb, files, skip_flags=skip_flags, force_flags=force_flags, n_workers=n_workers,
|
|
159
|
-
|
|
160
|
-
|
|
178
|
+
basepath=cfg.config_path, pause=pause, do_print=do_print, verbose=verbose, save=save,
|
|
179
|
+
cell_timeout=cell_timeout or None, cell_timing_min=cell_timing_min, **kw)
|
|
161
180
|
except KeyboardInterrupt:
|
|
162
181
|
sys.stderr.write('\nnbdev-test interrupted; in-flight notebook stacks shown above\n')
|
|
163
182
|
sys.exit(130)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nbdev
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.18
|
|
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
|
|
@@ -16,7 +16,7 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
16
16
|
Requires-Python: >=3.10
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: fastcore>=2.2.
|
|
19
|
+
Requires-Dist: fastcore>=2.2.23
|
|
20
20
|
Requires-Dist: execnb>=0.3.3
|
|
21
21
|
Requires-Dist: astunparse
|
|
22
22
|
Requires-Dist: ghapi>=2.0.2
|
|
@@ -19,7 +19,7 @@ classifiers = [
|
|
|
19
19
|
"License :: OSI Approved :: Apache Software License",
|
|
20
20
|
]
|
|
21
21
|
dynamic = ["version"]
|
|
22
|
-
dependencies = [ "fastcore>=2.2.
|
|
22
|
+
dependencies = [ "fastcore>=2.2.23", "execnb>=0.3.3", "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]
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|