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.
Files changed (39) hide show
  1. {nbdev-3.3.16/nbdev.egg-info → nbdev-3.3.18}/PKG-INFO +2 -2
  2. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/__init__.py +1 -1
  3. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/_modidx.py +10 -1
  4. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/diff.py +127 -30
  5. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/skill.py +1 -1
  6. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/test.py +32 -13
  7. {nbdev-3.3.16 → nbdev-3.3.18/nbdev.egg-info}/PKG-INFO +2 -2
  8. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev.egg-info/requires.txt +1 -1
  9. {nbdev-3.3.16 → nbdev-3.3.18}/pyproject.toml +1 -1
  10. {nbdev-3.3.16 → nbdev-3.3.18}/CONTRIBUTING.md +0 -0
  11. {nbdev-3.3.16 → nbdev-3.3.18}/LICENSE +0 -0
  12. {nbdev-3.3.16 → nbdev-3.3.18}/README.md +0 -0
  13. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/clean.py +0 -0
  14. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/cli.py +0 -0
  15. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/config.py +0 -0
  16. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/doclinks.py +0 -0
  17. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/export.py +0 -0
  18. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/extract_attachments.py +0 -0
  19. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/frontmatter.py +0 -0
  20. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/imports.py +0 -0
  21. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/maker.py +0 -0
  22. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/merge.py +0 -0
  23. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/migrate.py +0 -0
  24. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/moddocs.py +0 -0
  25. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/process.py +0 -0
  26. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/processors.py +0 -0
  27. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/qmd.py +0 -0
  28. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/quarto.py +0 -0
  29. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/release.py +0 -0
  30. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/scrubmagics.py +0 -0
  31. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/serve.py +0 -0
  32. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/serve_drv.py +0 -0
  33. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/showdoc.py +0 -0
  34. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev/sync.py +0 -0
  35. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev.egg-info/SOURCES.txt +0 -0
  36. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev.egg-info/dependency_links.txt +0 -0
  37. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev.egg-info/entry_points.txt +0 -0
  38. {nbdev-3.3.16 → nbdev-3.3.18}/nbdev.egg-info/top_level.txt +0 -0
  39. {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.16
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.7
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.16"
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._file_srcs': ('api/diff.html#_file_srcs', 'nbdev/diff.py'),
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', 'read_nb_from_git', 'nbs_pair', 'changed_cells', 'source_diff', 'cell_diffs', 'render_diff', 'nb_diff',
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): return c.get('source','')
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
- _dcolors = {'-':'\x1b[31m', '+':'\x1b[32m', '@':'\x1b[36m', '#':'\x1b[1m', '# ':'\x1b[1;7m'}
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` to `maxlen` with an ellipsis, optionally colored by its leading diff marker"
113
- if maxlen and len(l)>maxlen: l = l[:maxlen]+'…'
114
- c = (_dcolors.get(l[:2]) or _dcolors.get(l[:1])) if color else None
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: source}` for the old version of the notebook
130
- new, # `{id: source}` for the new version
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
- ): # One section per changed cell, in notebook order
134
- "Render cell-level changes between two notebooks as truncated unified diffs"
135
- res = []
136
- for cid in _diff_order(old, new):
137
- o,n = old.get(cid,''), new.get(cid,'')
138
- if cid in old and cid in new and o==n: continue
139
- kind = 'modified' if cid in old and cid in new else 'added' if cid in new else 'deleted'
140
- lines = [f'## {kind} {cid}:'] + source_diff(o,n).splitlines()[2:]
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, f=_src)
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 _file_srcs(path):
158
- "`{id: source}` from an ipynb file; `{}` when missing or empty (e.g. `/dev/null`)"
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), _src)
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(_file_srcs(old_file), _file_srcs(new_file), maxlen=maxlen, color=color)
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,inspect,signal,asyncio,threading
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.stderr.write(k.prettytb(fname=fn)+'\n')
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.stderr.write(f'{fn}: {len(k.leaks)} leaked task(s) survived cancellation\n')
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
- basepath=cfg.config_path, pause=pause, do_print=do_print, verbose=verbose, save=save,
160
- cell_timeout=cell_timeout or None, cell_timing_min=cell_timing_min, **kw)
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.16
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.7
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
@@ -1,4 +1,4 @@
1
- fastcore>=2.2.7
1
+ fastcore>=2.2.23
2
2
  execnb>=0.3.3
3
3
  astunparse
4
4
  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.7", "execnb>=0.3.3", "astunparse", "ghapi>=2.0.2", "watchdog", "asttokens",
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