nbdev 3.3.10__tar.gz → 3.3.12__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 (40) hide show
  1. {nbdev-3.3.10/nbdev.egg-info → nbdev-3.3.12}/PKG-INFO +2 -2
  2. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/__init__.py +1 -1
  3. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/_modidx.py +3 -2
  4. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/config.py +9 -4
  5. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/diff.py +7 -6
  6. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/processors.py +1 -3
  7. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/test.py +48 -10
  8. {nbdev-3.3.10 → nbdev-3.3.12/nbdev.egg-info}/PKG-INFO +2 -2
  9. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev.egg-info/requires.txt +1 -1
  10. {nbdev-3.3.10 → nbdev-3.3.12}/pyproject.toml +1 -1
  11. {nbdev-3.3.10 → nbdev-3.3.12}/CONTRIBUTING.md +0 -0
  12. {nbdev-3.3.10 → nbdev-3.3.12}/LICENSE +0 -0
  13. {nbdev-3.3.10 → nbdev-3.3.12}/MANIFEST.in +0 -0
  14. {nbdev-3.3.10 → nbdev-3.3.12}/README.md +0 -0
  15. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/clean.py +0 -0
  16. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/cli.py +0 -0
  17. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/doclinks.py +0 -0
  18. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/export.py +0 -0
  19. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/extract_attachments.py +0 -0
  20. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/frontmatter.py +0 -0
  21. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/imports.py +0 -0
  22. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/maker.py +0 -0
  23. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/merge.py +0 -0
  24. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/migrate.py +0 -0
  25. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/moddocs.py +0 -0
  26. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/process.py +0 -0
  27. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/qmd.py +0 -0
  28. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/quarto.py +0 -0
  29. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/release.py +0 -0
  30. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/scrubmagics.py +0 -0
  31. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/serve.py +0 -0
  32. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/serve_drv.py +0 -0
  33. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/showdoc.py +0 -0
  34. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/skill.py +0 -0
  35. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev/sync.py +0 -0
  36. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev.egg-info/SOURCES.txt +0 -0
  37. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev.egg-info/dependency_links.txt +0 -0
  38. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev.egg-info/entry_points.txt +0 -0
  39. {nbdev-3.3.10 → nbdev-3.3.12}/nbdev.egg-info/top_level.txt +0 -0
  40. {nbdev-3.3.10 → nbdev-3.3.12}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nbdev
3
- Version: 3.3.10
3
+ Version: 3.3.12
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
@@ -17,7 +17,7 @@ Requires-Python: >=3.10
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: fastcore>=2.2.7
20
- Requires-Dist: execnb>=0.2.11
20
+ Requires-Dist: execnb>=0.3.2
21
21
  Requires-Dist: astunparse
22
22
  Requires-Dist: ghapi>=2.0.2
23
23
  Requires-Dist: watchdog
@@ -6,7 +6,7 @@ Modules:
6
6
  - `nbdev.moddocs`: # Module docs: creating them from existing notebooks
7
7
  - `nbdev.skill`: Author clear, executable nbdev notebooks where code, prose, examples, outputs, and tests form one coherent narrative."""
8
8
 
9
- __version__ = "3.3.10"
9
+ __version__ = "3.3.12"
10
10
 
11
11
  from .doclinks import nbdev_export
12
12
  from .showdoc import show_doc
@@ -378,7 +378,8 @@ d = { 'settings': { 'branch': 'main',
378
378
  'nbdev.sync._update_nb': ('api/sync.html#_update_nb', 'nbdev/sync.py'),
379
379
  'nbdev.sync.absolute_import': ('api/sync.html#absolute_import', 'nbdev/sync.py'),
380
380
  'nbdev.sync.nbdev_update': ('api/sync.html#nbdev_update', 'nbdev/sync.py')},
381
- 'nbdev.test': { 'nbdev.test._keep_file': ('api/test.html#_keep_file', 'nbdev/test.py'),
382
- 'nbdev.test._test_nb_sync': ('api/test.html#_test_nb_sync', 'nbdev/test.py'),
381
+ 'nbdev.test': { 'nbdev.test._await_chain': ('api/test.html#_await_chain', 'nbdev/test.py'),
382
+ 'nbdev.test._int_handler': ('api/test.html#_int_handler', 'nbdev/test.py'),
383
+ 'nbdev.test._keep_file': ('api/test.html#_keep_file', 'nbdev/test.py'),
383
384
  'nbdev.test.nbdev_test': ('api/test.html#nbdev_test', 'nbdev/test.py'),
384
385
  'nbdev.test.test_nb': ('api/test.html#test_nb', 'nbdev/test.py')}}}
@@ -417,13 +417,16 @@ def update_init_mdoc(path=None, desc=None):
417
417
  "Write `pkg_mdoc` as the docstring of `path/__init__.py`, replacing any existing docstring"
418
418
  path = Path(path or get_config().lib_path)
419
419
  mdoc = pkg_mdoc(path, desc=desc)
420
- if not mdoc: return
421
420
  fn = path/'__init__.py'
422
421
  txt = fn.read_text(encoding='utf-8') if fn.exists() else ''
423
422
  body = ast.parse(txt).body
424
- rest = txt
423
+ rest,cur = txt,''
425
424
  if body and isinstance(body[0], ast.Expr) and isinstance(getattr(body[0].value, 'value', None), str):
425
+ cur = body[0].value.value
426
426
  rest = ''.join(txt.splitlines(keepends=True)[body[0].end_lineno:])
427
+ if not mdoc:
428
+ if '\nModules:\n' not in cur: return
429
+ mdoc = desc if desc is not None else get_config().description
427
430
  res = f'"""{mdoc}"""\n'
428
431
  if rest.strip(): res += '\n'+rest.lstrip('\n')
429
432
  fn.write_text(res, encoding='utf-8')
@@ -447,11 +450,13 @@ def pkg_llms(path=None, desc=None, index_nb=None):
447
450
  return '\n\n'.join(filter(None, parts)) + '\n'
448
451
 
449
452
  def update_llms_txt(path=None, fn=None, desc=None, index_nb=None):
450
- "Write `pkg_llms` to `fn` when it's absent or generated (has the nbdev marker); never touch a hand-written file"
453
+ "Write `pkg_llms` to `fn` when it's absent or generated (has the nbdev marker)"
451
454
  if fn is None: fn = get_config().nbs_path/'llms.txt'
452
455
  txt = pkg_llms(path, desc=desc, index_nb=index_nb)
453
- if not txt: return
454
456
  if fn.exists() and _llms_marker not in fn.read_text(encoding='utf-8'): return
457
+ if not txt:
458
+ if fn.exists(): fn.unlink()
459
+ return
455
460
  fn.write_text(txt, encoding='utf-8')
456
461
 
457
462
  # %% ../nbs/api/01_config.ipynb #95cebda6
@@ -5,7 +5,7 @@ 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__ = ['read_nb_from_git', 'nbs_pair', 'changed_cells', 'source_diff', 'cell_diffs', 'render_diff', 'nb_diff',
8
+ __all__ = ['MAXLEN', 'read_nb_from_git', 'nbs_pair', 'changed_cells', 'source_diff', 'cell_diffs', 'render_diff', 'nb_diff',
9
9
  'nbdev_diff_driver', 'nbdev_diff']
10
10
 
11
11
  # %% ../nbs/api/19_diff.ipynb #10c8ad0b
@@ -105,9 +105,10 @@ def cell_diffs(nb_path, **kwargs):
105
105
  return _cell_changes(nb_path, f, **kwargs)
106
106
 
107
107
  # %% ../nbs/api/19_diff.ipynb #d670e58e
108
+ MAXLEN = 180 # Most characters shown per displayed line
108
109
  _dcolors = {'-':'\x1b[31m', '+':'\x1b[32m', '@':'\x1b[36m', '#':'\x1b[1m', '# ':'\x1b[1;7m'}
109
110
 
110
- def _dline(l, maxlen=120, color=False):
111
+ def _dline(l, maxlen=MAXLEN, color=False):
111
112
  "Truncate `l` to `maxlen` with an ellipsis, optionally colored by its leading diff marker"
112
113
  if maxlen and len(l)>maxlen: l = l[:maxlen]+'…'
113
114
  c = (_dcolors.get(l[:2]) or _dcolors.get(l[:1])) if color else None
@@ -127,7 +128,7 @@ def _diff_order(old, new):
127
128
  def render_diff(
128
129
  old, # `{id: source}` for the old version of the notebook
129
130
  new, # `{id: source}` for the new version
130
- maxlen:int=120, # Truncate diff lines to this width (falsy: no limit)
131
+ maxlen:int=MAXLEN, # Truncate diff lines to this width (falsy: no limit)
131
132
  color:bool=False # Add ANSI colors?
132
133
  ): # One section per changed cell, in notebook order
133
134
  "Render cell-level changes between two notebooks as truncated unified diffs"
@@ -145,7 +146,7 @@ def nb_diff(
145
146
  nb_path, # Path to the notebook
146
147
  ref_a='HEAD', # First git ref
147
148
  ref_b=None, # Second git ref; None for working dir
148
- maxlen:int=120, # Truncate diff lines to this width (falsy: no limit)
149
+ maxlen:int=MAXLEN, # Truncate diff lines to this width (falsy: no limit)
149
150
  color:bool=False # Add ANSI colors?
150
151
  ): # Rendered diff of changed cells
151
152
  "Rendered cell diff for `nb_path` between two refs"
@@ -172,7 +173,7 @@ def nbdev_diff_driver(
172
173
  new_mode:_pos=None, # Post-change file mode
173
174
  rename_to:_pos=None, # New repo path, when git detected a rename
174
175
  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
+ maxlen:int=MAXLEN # Truncate diff lines to this width (0 for no limit)
176
177
  ):
177
178
  "Git external diff driver for notebooks; installed by `nbdev-install-hooks`"
178
179
  if not new_file: return print(f'unmerged: {path}')
@@ -188,7 +189,7 @@ def nbdev_diff(
188
189
  path:str=None, # Notebook or directory (default: project's notebooks folder)
189
190
  ref_a:str='HEAD', # First git ref
190
191
  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
+ maxlen:int=MAXLEN, # Truncate diff lines to this width (0 for no limit)
192
193
  color:bool_arg=None # Add ANSI colors? (default: only if stdout is a tty)
193
194
  ):
194
195
  "Cell-level diffs for changed notebooks between two git refs"
@@ -12,8 +12,6 @@ __all__ = ['populate_language', 'insert_warning', 'cell_lang', 'add_show_docs',
12
12
  # %% ../nbs/api/10_processors.ipynb #2398f5ef-06d3-4890-8a54-7cf4f81f3894
13
13
  import ast
14
14
 
15
- from fastcore.aio import run_sync
16
-
17
15
  from .config import *
18
16
  from .imports import *
19
17
  from .process import *
@@ -257,7 +255,7 @@ class exec_show_docs(Processor):
257
255
  if not self.nb.has_docs_ or not hasattr(self, 'k'): return
258
256
  fm = getattr(self.nb, 'frontmatter_', {})
259
257
  if str2bool(fm.get('skip_showdoc', False)): return
260
- if _do_eval(cell): run_sync(self.k.cell(cell))
258
+ if _do_eval(cell): self.k.cell(cell)
261
259
  title = fm.get('title', '')
262
260
  if self.k.exc:
263
261
  raise Exception(f"Error{' in notebook: '+title if title else ''} in cell {cell.idx_} :\n{cell.source}") from self.k.exc
@@ -8,7 +8,7 @@ Docs: https://nbdev.fast.ai/api/test.html.md"""
8
8
  __all__ = ['test_nb', 'nbdev_test']
9
9
 
10
10
  # %% ../nbs/api/12_test.ipynb #45e10c3f
11
- import time,os,sys,traceback,contextlib,inspect,faulthandler,signal,asyncio
11
+ import time,os,sys,io,traceback,contextlib,inspect,signal,asyncio,threading
12
12
  from fastcore.basics import *
13
13
  from fastcore.imports import *
14
14
  from fastcore.foundation import *
@@ -24,8 +24,38 @@ from .frontmatter import nb_frontmatter
24
24
  from fastcore.nbio import *
25
25
  from execnb.shell import *
26
26
 
27
+ # %% ../nbs/api/12_test.ipynb #dc8994ac
28
+ _cur_nb = [None]
29
+ _cur_shell = [None]
30
+
31
+ def _await_chain(t):
32
+ "One frame per coroutine in task `t`'s await chain, deepest last: where a suspended hang actually sits"
33
+ co = t.get_coro()
34
+ while co is not None:
35
+ f = getattr(co, 'cr_frame', None) or getattr(co, 'ag_frame', None) or getattr(co, 'gi_frame', None)
36
+ if f is not None: yield f, f.f_lineno
37
+ co = getattr(co, 'cr_await', None) or getattr(co, 'ag_await', None) or getattr(co, 'gi_yieldfrom', None)
38
+
39
+ def _int_handler(signum, frame):
40
+ "Dump the running notebook's stack and exit; installed on SIGINT by `test_nb`"
41
+ if _cur_nb[0] is not None:
42
+ buf = io.StringIO()
43
+ traceback.print_stack(frame, file=buf)
44
+ k = _cur_shell[0]
45
+ if k is not None:
46
+ f = sys._current_frames().get(k._thread.ident)
47
+ if f is not None:
48
+ print('\n--- shell loop thread ---', file=buf)
49
+ traceback.print_stack(f, file=buf)
50
+ with contextlib.suppress(RuntimeError):
51
+ for t in asyncio.all_tasks(k.loop):
52
+ print(f'\n{t}', file=buf)
53
+ buf.writelines(traceback.StackSummary.extract(_await_chain(t)).format())
54
+ os.write(2, f'\n=== nbdev-test interrupted: {_cur_nb[0]} ===\n{buf.getvalue()}'.encode())
55
+ os._exit(130)
56
+
27
57
  # %% ../nbs/api/12_test.ipynb #3f4fa1ad
28
- async def test_nb(
58
+ def test_nb(
29
59
  fn, # file name of notebook to test
30
60
  skip_flags=None, # list of flags marking cells to skip
31
61
  force_flags=None, # list of flags marking cells to always run
@@ -34,11 +64,13 @@ async def test_nb(
34
64
  basepath=None, # path to add to sys.path
35
65
  verbose=False, # stream stdout/stderr from cells to console?
36
66
  save=False, # write outputs back to notebook on success?
37
- profile:bool=None # load the IPython profile, as `ipykernel` does? (default: `exec_profile` config key)
67
+ profile:bool=None, # load the IPython profile, as `ipykernel` does? (default: `exec_profile` config key)
68
+ cell_timeout:int=600 # seconds before each cell times out (None: no limit)
38
69
  ):
39
70
  "Execute tests in notebook in `fn` except those with `skip_flags`"
40
- faulthandler.register(signal.SIGINT, file=sys.__stderr__, all_threads=True, chain=True)
71
+ if not IN_NOTEBOOK and threading.current_thread() is threading.main_thread(): signal.signal(signal.SIGINT, _int_handler)
41
72
  fn = Path(fn)
73
+ _cur_nb[0] = fn
42
74
  if basepath: sys.path.insert(0, str(basepath))
43
75
  prev_test = os.environ.get('IN_TEST')
44
76
  if not IN_NOTEBOOK: os.environ['IN_TEST'] = '1'
@@ -57,25 +89,25 @@ async def test_nb(
57
89
  start = time.time()
58
90
  if profile is None: profile = bool(get_config(fn.parent).exec_profile)
59
91
  k = CaptureShell(fn, profile=profile)
92
+ _cur_shell[0] = k
60
93
  if do_print: print(f'Starting {fn}')
61
94
  try:
62
95
  with working_directory(fn.parent):
63
- await k.run_all(nb, exc_stop=True, preproc=_no_eval, verbose=verbose)
96
+ k.run_all(nb, exc_stop=True, preproc=_no_eval, verbose=verbose, cell_timeout=cell_timeout)
64
97
  if save: write_nb(nb, fn)
65
98
  res = True
66
99
  except:
67
100
  if showerr: sys.stderr.write(k.prettytb(fname=fn)+'\n')
68
101
  res=False
102
+ if k.leaks and showerr: sys.stderr.write(f'{fn}: {len(k.leaks)} leaked task(s) survived cancellation\n')
69
103
  if do_print: print(f'- Completed {fn}')
70
104
  return res,time.time()-start
71
105
  finally:
106
+ _cur_nb[0] = _cur_shell[0] = None
72
107
  if prev_test is None: os.environ.pop('IN_TEST', None)
73
108
  else: os.environ['IN_TEST'] = prev_test
74
109
 
75
110
 
76
- # %% ../nbs/api/12_test.ipynb #12516e0a
77
- def _test_nb_sync(fn, **kwargs): return asyncio.run(test_nb(fn, **kwargs))
78
-
79
111
  # %% ../nbs/api/12_test.ipynb #d8bf1f1b-935d-4b69-ba96-827c5d7213f0
80
112
  def _keep_file(
81
113
  p:Path, # filename for which to check for `indicator_fname`
@@ -98,6 +130,7 @@ def nbdev_test(
98
130
  ignore_fname:str='.notest', # Filename that will result in siblings being ignored
99
131
  verbose:bool=False, # Print stdout/stderr from notebook cells?
100
132
  save:bool=False, # Write outputs back to notebooks on success?
133
+ cell_timeout:int=600, # Seconds before each cell times out (0: no limit)
101
134
  **kwargs
102
135
  ):
103
136
  "Test in parallel notebooks matching `path`, passing along `flags`"
@@ -114,8 +147,13 @@ def nbdev_test(
114
147
  else: kw = {'method':'spawn'} if sys.platform=='darwin' else {}
115
148
  wd_pth = cfg.nbs_path
116
149
  with working_directory(wd_pth if (wd_pth and wd_pth.exists()) else os.getcwd()):
117
- results = parallel(_test_nb_sync, files, skip_flags=skip_flags, force_flags=force_flags, n_workers=n_workers,
118
- basepath=cfg.config_path, pause=pause, do_print=do_print, verbose=verbose, save=save, **kw)
150
+ try:
151
+ results = parallel(test_nb, files, skip_flags=skip_flags, force_flags=force_flags, n_workers=n_workers,
152
+ basepath=cfg.config_path, pause=pause, do_print=do_print, verbose=verbose, save=save,
153
+ cell_timeout=cell_timeout or None, **kw)
154
+ except KeyboardInterrupt:
155
+ sys.stderr.write('\nnbdev-test interrupted; in-flight notebook stacks shown above\n')
156
+ sys.exit(130)
119
157
  passed,times = zip(*results)
120
158
  if all(passed): print("Success.")
121
159
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nbdev
3
- Version: 3.3.10
3
+ Version: 3.3.12
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
@@ -17,7 +17,7 @@ Requires-Python: >=3.10
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: fastcore>=2.2.7
20
- Requires-Dist: execnb>=0.2.11
20
+ Requires-Dist: execnb>=0.3.2
21
21
  Requires-Dist: astunparse
22
22
  Requires-Dist: ghapi>=2.0.2
23
23
  Requires-Dist: watchdog
@@ -1,5 +1,5 @@
1
1
  fastcore>=2.2.7
2
- execnb>=0.2.11
2
+ execnb>=0.3.2
3
3
  astunparse
4
4
  ghapi>=2.0.2
5
5
  watchdog
@@ -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.2.11", "astunparse", "ghapi>=2.0.2", "watchdog", "asttokens",
22
+ dependencies = [ "fastcore>=2.2.7", "execnb>=0.3.2", "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