nbdev 3.3.0__tar.gz → 3.3.2__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 (41) hide show
  1. {nbdev-3.3.0/nbdev.egg-info → nbdev-3.3.2}/PKG-INFO +2 -2
  2. nbdev-3.3.2/nbdev/__init__.py +12 -0
  3. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/_modidx.py +12 -1
  4. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/clean.py +1 -1
  5. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/cli.py +14 -9
  6. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/config.py +108 -6
  7. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/doclinks.py +1 -0
  8. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/export.py +12 -4
  9. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/maker.py +6 -2
  10. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/process.py +1 -1
  11. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/release.py +1 -1
  12. nbdev-3.3.2/nbdev/skill.py +70 -0
  13. {nbdev-3.3.0 → nbdev-3.3.2/nbdev.egg-info}/PKG-INFO +2 -2
  14. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev.egg-info/requires.txt +1 -1
  15. {nbdev-3.3.0 → nbdev-3.3.2}/pyproject.toml +1 -1
  16. nbdev-3.3.0/nbdev/__init__.py +0 -5
  17. nbdev-3.3.0/nbdev/skill.py +0 -181
  18. {nbdev-3.3.0 → nbdev-3.3.2}/CONTRIBUTING.md +0 -0
  19. {nbdev-3.3.0 → nbdev-3.3.2}/LICENSE +0 -0
  20. {nbdev-3.3.0 → nbdev-3.3.2}/MANIFEST.in +0 -0
  21. {nbdev-3.3.0 → nbdev-3.3.2}/README.md +0 -0
  22. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/diff.py +0 -0
  23. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/extract_attachments.py +0 -0
  24. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/frontmatter.py +0 -0
  25. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/imports.py +0 -0
  26. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/merge.py +0 -0
  27. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/migrate.py +0 -0
  28. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/processors.py +0 -0
  29. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/qmd.py +0 -0
  30. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/quarto.py +0 -0
  31. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/scrubmagics.py +0 -0
  32. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/serve.py +0 -0
  33. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/serve_drv.py +0 -0
  34. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/showdoc.py +0 -0
  35. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/sync.py +0 -0
  36. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev/test.py +0 -0
  37. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev.egg-info/SOURCES.txt +0 -0
  38. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev.egg-info/dependency_links.txt +0 -0
  39. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev.egg-info/entry_points.txt +0 -0
  40. {nbdev-3.3.0 → nbdev-3.3.2}/nbdev.egg-info/top_level.txt +0 -0
  41. {nbdev-3.3.0 → nbdev-3.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nbdev
3
- Version: 3.3.0
3
+ Version: 3.3.2
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.1.3
19
+ Requires-Dist: fastcore>=2.1.11
20
20
  Requires-Dist: execnb>=0.2.8
21
21
  Requires-Dist: astunparse
22
22
  Requires-Dist: ghapi>=2.0.2
@@ -0,0 +1,12 @@
1
+ """Create delightful software with Jupyter Notebooks
2
+
3
+ Modules:
4
+
5
+ - `nbdev.extract_attachments`: A preprocessor that extracts all of the attachments from the notebook file. The extracted attachments are returned in the 'resources' dictionary.
6
+ - `nbdev.skill`: Author clear, executable nbdev notebooks where code, prose, examples, outputs, and tests form one coherent narrative."""
7
+
8
+ __version__ = "3.3.2"
9
+
10
+ from .doclinks import nbdev_export
11
+ from .showdoc import show_doc
12
+
@@ -52,7 +52,13 @@ d = { 'settings': { 'branch': 'main',
52
52
  'nbdev.config._git_repo': ('api/config.html#_git_repo', 'nbdev/config.py'),
53
53
  'nbdev.config._has_nbdev': ('api/config.html#_has_nbdev', 'nbdev/config.py'),
54
54
  'nbdev.config._has_py': ('api/config.html#_has_py', 'nbdev/config.py'),
55
+ 'nbdev.config._index_mdoc': ('api/config.html#_index_mdoc', 'nbdev/config.py'),
55
56
  'nbdev.config._load_toml': ('api/config.html#_load_toml', 'nbdev/config.py'),
57
+ 'nbdev.config._mod_docstr': ('api/config.html#_mod_docstr', 'nbdev/config.py'),
58
+ 'nbdev.config._modline': ('api/config.html#_modline', 'nbdev/config.py'),
59
+ 'nbdev.config._pkg_docparts': ('api/config.html#_pkg_docparts', 'nbdev/config.py'),
60
+ 'nbdev.config._pkg_mods': ('api/config.html#_pkg_mods', 'nbdev/config.py'),
61
+ 'nbdev.config._substantive': ('api/config.html#_substantive', 'nbdev/config.py'),
56
62
  'nbdev.config._user_config': ('api/config.html#_user_config', 'nbdev/config.py'),
57
63
  'nbdev.config.add_init': ('api/config.html#add_init', 'nbdev/config.py'),
58
64
  'nbdev.config.bump_version': ('api/config.html#bump_version', 'nbdev/config.py'),
@@ -63,9 +69,13 @@ d = { 'settings': { 'branch': 'main',
63
69
  'nbdev.config.nbdev_create_config': ('api/config.html#nbdev_create_config', 'nbdev/config.py'),
64
70
  'nbdev.config.nbpath2docurl': ('api/config.html#nbpath2docurl', 'nbdev/config.py'),
65
71
  'nbdev.config.nbpath2html': ('api/config.html#nbpath2html', 'nbdev/config.py'),
72
+ 'nbdev.config.pkg_llms': ('api/config.html#pkg_llms', 'nbdev/config.py'),
73
+ 'nbdev.config.pkg_mdoc': ('api/config.html#pkg_mdoc', 'nbdev/config.py'),
66
74
  'nbdev.config.read_version': ('api/config.html#read_version', 'nbdev/config.py'),
67
75
  'nbdev.config.set_version': ('api/config.html#set_version', 'nbdev/config.py'),
68
76
  'nbdev.config.show_src': ('api/config.html#show_src', 'nbdev/config.py'),
77
+ 'nbdev.config.update_init_mdoc': ('api/config.html#update_init_mdoc', 'nbdev/config.py'),
78
+ 'nbdev.config.update_llms_txt': ('api/config.html#update_llms_txt', 'nbdev/config.py'),
69
79
  'nbdev.config.update_proj': ('api/config.html#update_proj', 'nbdev/config.py'),
70
80
  'nbdev.config.update_version': ('api/config.html#update_version', 'nbdev/config.py'),
71
81
  'nbdev.config.write_cells': ('api/config.html#write_cells', 'nbdev/config.py')},
@@ -118,7 +128,8 @@ d = { 'settings': { 'branch': 'main',
118
128
  'nbdev.export.ExportModuleProc._export_': ('api/export.html#exportmoduleproc._export_', 'nbdev/export.py'),
119
129
  'nbdev.export.ExportModuleProc._exporti_': ('api/export.html#exportmoduleproc._exporti_', 'nbdev/export.py'),
120
130
  'nbdev.export.ExportModuleProc.begin': ('api/export.html#exportmoduleproc.begin', 'nbdev/export.py'),
121
- 'nbdev.export.nb_export': ('api/export.html#nb_export', 'nbdev/export.py')},
131
+ 'nbdev.export.nb_export': ('api/export.html#nb_export', 'nbdev/export.py'),
132
+ 'nbdev.export.nb_mdoc': ('api/export.html#nb_mdoc', 'nbdev/export.py')},
122
133
  'nbdev.extract_attachments': {},
123
134
  'nbdev.frontmatter': { 'nbdev.frontmatter.FrontmatterProc': ('api/frontmatter.html#frontmatterproc', 'nbdev/frontmatter.py'),
124
135
  'nbdev.frontmatter.FrontmatterProc._update': ( 'api/frontmatter.html#frontmatterproc._update',
@@ -149,7 +149,7 @@ def _to_meta(cell, names):
149
149
 
150
150
  def _to_comments(cell, names):
151
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}
152
+ move = {k:v for k,v in _meta_directives(cell.get('metadata')).items() if k in names}
153
153
  if not move: return
154
154
  nbd = cell.metadata['nbdev']
155
155
  for k in move: nbd.pop(k, None)
@@ -11,6 +11,7 @@ __all__ = ['mapping', 'nbdev_filter', 'extract_tgz', 'nbdev_new', 'nbdev_update_
11
11
  # %% ../nbs/api/13_cli.ipynb #6a35c7c4-748f-4c82-a9bf-c780a8d83e90
12
12
  import warnings
13
13
  import time
14
+ from typing import Annotated
14
15
 
15
16
  from .config import *
16
17
  from .process import *
@@ -72,7 +73,7 @@ def _render_nb(fn, cfg):
72
73
 
73
74
  # %% ../nbs/api/13_cli.ipynb #dd385911-aa8f-44e7-8d46-7b8a20f3b010
74
75
  async def _update_repo_meta(cfg):
75
- "Enable gh pages and update the homepage and description in your GitHub repo."
76
+ "Update the homepage and description in your GitHub repo."
76
77
  token=os.getenv('GITHUB_TOKEN')
77
78
  if token:
78
79
  from ghapi.core import GhApi, APIError
@@ -82,24 +83,28 @@ async def _update_repo_meta(cfg):
82
83
  "Use a token with the correction permissions or perform these steps manually.")
83
84
 
84
85
  # %% ../nbs/api/13_cli.ipynb #c4a663d9
86
+ _pos = Annotated[str, {'opt':False, 'nargs':'?'}] # optional positional CLI arg
87
+
85
88
  @call_parse
86
89
  @delegates(nbdev_create_config)
87
- async def nbdev_new(**kwargs):
90
+ async def nbdev_new(
91
+ name:_pos=None, # Directory to create and scaffold in (defaults to the current directory)
92
+ template:str='answerdotai/nbdev3-template', # Template repo
93
+ tag:str=None, # Template release tag (defaults to the latest release)
94
+ **kwargs):
88
95
  "Create an nbdev project."
89
96
  from ghapi.core import GhApi
97
+ if name:
98
+ Path(name).mkdir(exist_ok=True)
99
+ os.chdir(name)
100
+ kwargs.setdefault('repo', name)
90
101
  nbdev_create_config.__wrapped__(**kwargs)
91
102
  cfg = get_config()
92
103
  if (Path('.git')).exists(): await _update_repo_meta(cfg)
93
104
  else: print(f"No git repo found. Run: gh repo create {cfg.user}/{cfg.repo} --public --source=.")
94
105
  path = Path()
95
106
 
96
- _ORG_OR_USR,_REPOSITORY = 'answerdotai','nbdev3-template'
97
- _TEMPLATE = f'{_ORG_OR_USR}/{_REPOSITORY}'
98
- template = kwargs.get('template', _TEMPLATE)
99
- try: org_or_usr, repo = template.split('/')
100
- except ValueError: org_or_usr, repo = _ORG_OR_USR, _REPOSITORY
101
-
102
- tag = kwargs.get('tag', None)
107
+ org_or_usr,repo = template.split('/')
103
108
  if tag is None:
104
109
  with warnings.catch_warnings():
105
110
  warnings.simplefilter('ignore', UserWarning)
@@ -7,7 +7,8 @@ Docs: https://nbdev.fast.ai/api/config.html.md"""
7
7
  # %% auto #0
8
8
  __all__ = ['pyproject_nm', 'pyproject_tmpl', 'nbdev_defaults', 'pyproj_tmpl', 'nbdev_create_config', 'ConfigToml', 'get_config',
9
9
  'is_nbdev', 'create_output', 'show_src', 'nbpath2html', 'nbpath2docurl', 'read_version', 'set_version',
10
- 'bump_version', 'update_version', 'update_proj', 'add_init', 'import_obj', 'write_cells']
10
+ 'bump_version', 'update_version', 'update_proj', 'add_init', 'pkg_mdoc', 'update_init_mdoc', 'pkg_llms',
11
+ 'update_llms_txt', 'import_obj', 'write_cells']
11
12
 
12
13
  # %% ../nbs/api/01_config.ipynb #6fd14ecd
13
14
  from datetime import datetime
@@ -54,16 +55,19 @@ https://nbdev.fast.ai/api/release.html#setup"""]
54
55
  def _fetch_from_git(raise_err=False):
55
56
  "Get information for pyproject.toml from git."
56
57
  res={}
58
+ try:
59
+ res['author'] = run('git config --get user.name').strip()
60
+ res['author_email'] = run('git config --get user.email').strip()
61
+ except OSError as e:
62
+ if raise_err: raise e
57
63
  try:
58
64
  url = run('git config --get remote.origin.url')
59
65
  res['user'],res['repo'] = repo_details(url)
60
66
  res['branch'],res['keywords'],desc = _get_info(owner=res['user'], repo=res['repo'])
61
67
  if desc: res['description'] = desc
62
- res['author'] = run('git config --get user.name').strip()
63
- res['author_email'] = run('git config --get user.email').strip()
68
+ res['lib_name'] = res['repo'].replace('-','_')
64
69
  except OSError as e:
65
- if raise_err: raise(e)
66
- else: res['lib_name'] = res['repo'].replace('-','_')
70
+ if raise_err: raise e
67
71
  return res
68
72
 
69
73
  # %% ../nbs/api/01_config.ipynb #05aae09f
@@ -352,6 +356,104 @@ def add_init(path=None):
352
356
  if _has_py(fs) or any(filter(_has_py, subds)) and not (r/_init).exists(): (r/_init).touch()
353
357
  if get_config().get('put_version_in_init', True): update_version(path)
354
358
  if get_config().get('update_pyproject', True): update_proj(path.parent)
359
+ update_init_mdoc(path)
360
+
361
+ # %% ../nbs/api/01_config.ipynb #a01b98bb
362
+ def _mod_docstr(py_path):
363
+ "Module docstring of `py_path` via ast, without importing ('' if none or unparseable)"
364
+ try: return ast.get_docstring(ast.parse(Path(py_path).read_text(encoding='utf-8'))) or ''
365
+ except SyntaxError: return ''
366
+
367
+ def _substantive(docstr):
368
+ "Does `docstr` say more than the default summary line plus `Docs:` link?"
369
+ return len(docstr.strip().splitlines())>3
370
+
371
+ def _pkg_mods(path):
372
+ "`(name, summary, url)` per module of `path` with a substantive docstring: dotted name, one-line first para, `Docs:` url"
373
+ res = []
374
+ for p in sorted(Path(path).rglob('*.py')):
375
+ if p.name.startswith('_'): continue
376
+ d = _mod_docstr(p)
377
+ if not _substantive(d): continue
378
+ nm = '.'.join((Path(path).name, *p.relative_to(path).with_suffix('').parts))
379
+ summ = ' '.join(d.split('\n\n')[0].splitlines())
380
+ last = d.strip().splitlines()[-1]
381
+ res.append((nm, summ, last[5:].strip() if last.startswith('Docs:') else ''))
382
+ return res
383
+
384
+ def _index_mdoc(idx_path):
385
+ "Package docstring intro: bodies of md cells with an export directive, and fenced `exportd` cells, from the index nb"
386
+ if not idx_path or not Path(idx_path).exists(): return ''
387
+ docs = []
388
+ for c in read_nb(idx_path).cells:
389
+ lines = c.source.splitlines()
390
+ n = 0
391
+ while n<len(lines) and lines[n].startswith('#|'): n += 1
392
+ dirs = {l[2:].strip().split()[0] for l in lines[:n] if l[2:].strip()}
393
+ body = '\n'.join(lines[n:]).strip()
394
+ if not body or not {'export','exportd'}&dirs: continue
395
+ docs.append(body if c.cell_type=='markdown' else fenced(body, 'python'))
396
+ return '\n\n'.join(docs)
397
+
398
+ def _pkg_docparts(path=None, desc=None, index_nb=None):
399
+ "`(path, desc, intro, mods)` shared by `pkg_mdoc` and `pkg_llms`"
400
+ path = Path(path or get_config().lib_path)
401
+ mods = _pkg_mods(path)
402
+ if index_nb is None:
403
+ cfg = get_config()
404
+ if path==Path(cfg.lib_path): index_nb = cfg.nbs_path/cfg.readme_nb
405
+ intro = _index_mdoc(index_nb)
406
+ if (mods or intro) and desc is None: desc = get_config().description
407
+ return path,desc,intro,mods
408
+
409
+ def pkg_mdoc(path=None, desc=None, index_nb=None):
410
+ "Package docstring for `path`: `desc`, intro from `index_nb`, and a line per module with a substantive docstring"
411
+ path,desc,intro,mods = _pkg_docparts(path, desc, index_nb)
412
+ if not mods and not intro: return ''
413
+ mods = ['Modules:', '\n'.join(f'- `{nm}`: {summ}' for nm,summ,url in mods)] if mods else []
414
+ return '\n\n'.join(filter(None, [desc, intro, *mods]))
415
+
416
+ # %% ../nbs/api/01_config.ipynb #a03a0007
417
+ def update_init_mdoc(path=None, desc=None):
418
+ "Write `pkg_mdoc` as the docstring of `path/__init__.py`, replacing any existing docstring"
419
+ path = Path(path or get_config().lib_path)
420
+ mdoc = pkg_mdoc(path, desc=desc)
421
+ if not mdoc: return
422
+ fn = path/'__init__.py'
423
+ txt = fn.read_text(encoding='utf-8') if fn.exists() else ''
424
+ body = ast.parse(txt).body
425
+ rest = txt
426
+ if body and isinstance(body[0], ast.Expr) and isinstance(getattr(body[0].value, 'value', None), str):
427
+ rest = ''.join(txt.splitlines(keepends=True)[body[0].end_lineno:])
428
+ res = f'"""{mdoc}"""\n'
429
+ if rest.strip(): res += '\n'+rest.lstrip('\n')
430
+ fn.write_text(res, encoding='utf-8')
431
+
432
+ # %% ../nbs/api/01_config.ipynb #f9912ef7
433
+ _llms_marker = '<!-- Generated by nbdev-export; edit the index notebook, not this file -->'
434
+
435
+ def _modline(nm, summ, url):
436
+ "llms.txt list line: linked when `url` is a single URL, else the `Docs:` text carried verbatim"
437
+ if url and ' ' not in url: return f'- [{nm}]({url}): {summ}'
438
+ return f'- `{nm}`: {summ} (Docs: {url})' if url else f'- `{nm}`: {summ}'
439
+
440
+ def pkg_llms(path=None, desc=None, index_nb=None):
441
+ "llms.txt for the package at `path`, from the same sources as `pkg_mdoc` ('' if no intro or modules)"
442
+ path,desc,intro,mods = _pkg_docparts(path, desc, index_nb)
443
+ if not mods and not intro: return ''
444
+ if mods:
445
+ links = '\n'.join(_modline(*o) for o in mods)
446
+ mods = [f'## Modules\n\n{links}']
447
+ parts = [f'# {path.name}', f'> {desc}' if desc else '', intro, *mods, _llms_marker]
448
+ return '\n\n'.join(filter(None, parts)) + '\n'
449
+
450
+ def update_llms_txt(path=None, fn=None, desc=None, index_nb=None):
451
+ "Write `pkg_llms` to `fn` when it's absent or generated (has the nbdev marker); never touch a hand-written file"
452
+ if fn is None: fn = get_config().nbs_path/'llms.txt'
453
+ txt = pkg_llms(path, desc=desc, index_nb=index_nb)
454
+ if not txt: return
455
+ if fn.exists() and _llms_marker not in fn.read_text(encoding='utf-8'): return
456
+ fn.write_text(txt, encoding='utf-8')
355
457
 
356
458
  # %% ../nbs/api/01_config.ipynb #95cebda6
357
459
  def import_obj(s):
@@ -364,7 +466,7 @@ def import_obj(s):
364
466
  def write_cells(cells, hdr, file, solo_nb=False):
365
467
  "Write `cells` to `file` along with header `hdr` (mainly for nbdev internal use)."
366
468
  for cell in cells:
367
- if cell.cell_type=='code' and cell.source.strip():
469
+ if cell.cell_type=='code' and cell.source.strip() and 'exportd' not in getattr(cell,'directives_',{}):
368
470
  cell_id = f" #{cell.id}" if cell.get('id') else ""
369
471
  file.write(f'\n\n{hdr}{cell_id}\n{cell.source}') if not solo_nb else file.write(f'\n\n{cell.source}')
370
472
 
@@ -155,6 +155,7 @@ def nbdev_export(
155
155
  files = nbglob(path=path, as_path=True, **kwargs).sorted('name')
156
156
  for f in files: nb_export(f, procs=procs)
157
157
  add_init(cfg.lib_path)
158
+ update_llms_txt()
158
159
  _build_modidx()
159
160
 
160
161
  # %% ../nbs/api/05_doclinks.ipynb #3134c22b
@@ -5,11 +5,12 @@ Docs: https://nbdev.fast.ai/api/export.html.md"""
5
5
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/04_export.ipynb.
6
6
 
7
7
  # %% auto #0
8
- __all__ = ['ExportModuleProc', 'nb_export']
8
+ __all__ = ['ExportModuleProc', 'nb_export', 'nb_mdoc']
9
9
 
10
10
  # %% ../nbs/api/04_export.ipynb #3b932371
11
11
  from .config import *
12
12
  from .maker import *
13
+ from .maker import _retr_mdoc
13
14
  from .imports import *
14
15
  from .process import *
15
16
 
@@ -32,8 +33,8 @@ class ExportModuleProc:
32
33
  def __call__(self, cell):
33
34
  src = cell.source
34
35
  if not src: return
35
- if cell.cell_type=='markdown' and (src.startswith('# ') or 'export' in cell.directives_): self._exporti_(cell)
36
- _exports_=_export_
36
+ if cell.cell_type=='markdown' and (src.startswith('# ') or {'export','exportd'}&set(cell.directives_)): self._exporti_(cell)
37
+ _exports_,_exportd_=_export_,_exporti_
37
38
 
38
39
  # %% ../nbs/api/04_export.ipynb #76717e36
39
40
  def nb_export(
@@ -51,7 +52,7 @@ def nb_export(
51
52
  nb = NBProcessor(nbname, [exp]+L(procs), debug=debug)
52
53
  nb.process()
53
54
  for mod,cells in exp.modules.items():
54
- if first(1 for o in cells if o.cell_type=='code'):
55
+ if first(1 for o in cells if o.cell_type=='code' and 'exportd' not in o.directives_):
55
56
  all_cells = exp.in_all[mod]
56
57
  nm = ifnone(name, getattr(exp, 'default_exp', None) if mod=='#' else mod)
57
58
  if not nm:
@@ -61,3 +62,10 @@ def nb_export(
61
62
  return
62
63
  mm = mod_maker(dest=lib_path, name=nm, nb_path=nbname, is_new=bool(name) or mod=='#', solo_nb=solo_nb)
63
64
  mm.make(cells, all_cells, lib_path=lib_path)
65
+
66
+ # %% ../nbs/api/04_export.ipynb #f7ce4b21
67
+ def nb_mdoc(nbname:str): # Filename of notebook
68
+ "The module docstring `nb_export` would write for `nbname`, for previewing while authoring"
69
+ exp = ExportModuleProc()
70
+ NBProcessor(nbname, exp).process()
71
+ return PrettyString(_retr_mdoc(exp.modules['#'], nbname))
@@ -177,13 +177,17 @@ def _import2relative(cells, lib_path=None):
177
177
 
178
178
  # %% ../nbs/api/02_maker.ipynb #5bff9d71
179
179
  def _retr_mdoc(cells, nb_path=None):
180
- "Search for markdown cells used to create module docstring"
180
+ "Module docstring from md cells with an export directive, plus `exportd` cells (code fenced)"
181
+ def _doc(o):
182
+ d = getattr(o,'directives_',{})
183
+ if o.cell_type=='markdown' and {'export','exportd'}&set(d): return o.source.rstrip()
184
+ if o.cell_type=='code' and 'exportd' in d: return fenced(o.source.rstrip(), 'python')
181
185
  md1 = first(o for o in cells if o.cell_type=='markdown' and o.source.startswith('# '))
182
186
  if not md1: return ''
183
187
  lines = dropwhile(lambda l: not l.startswith('> '), md1.source.splitlines())
184
188
  lines = list(takewhile(lambda l: l.startswith('> '), lines))
185
189
  summ = '\n'.join(l.lstrip('> ').strip() for l in lines)
186
- docs = L(o.source.rstrip() for o in cells if o.cell_type=='markdown' and 'export' in getattr(o,'directives_',{}))
190
+ docs = L(cells).map(_doc).filter()
187
191
  mdoc = '\n\n'.join(L(summ)+docs).strip()
188
192
  url = nbpath2docurl(nb_path) if nb_path else ''
189
193
  if url: mdoc = (mdoc + f'\n\nDocs: {url}').strip()
@@ -41,7 +41,7 @@ class NBProcessor:
41
41
  self.nb = read_nb(path) if nb is None else nb
42
42
  self.lang = nb_lang(self.nb)
43
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)}
44
+ nbdirs = {k:v for k,v in _meta_directives(self.nb.get('metadata')).items() if not any(k in c.directives_ for c in self.nb.cells)}
45
45
  if nbdirs:
46
46
  fc = first(c for c in self.nb.cells if c.cell_type=='code')
47
47
  if fc is not None: fc.directives_ = nbdirs | fc.directives_
@@ -63,7 +63,7 @@ def _release_head():
63
63
  def update_changelog(txt, ver, notes, marker='<!-- do not remove -->\n'):
64
64
  "Insert `notes` into changelog `txt` after `marker`, replacing any existing section for `ver`"
65
65
  txt = re.sub(rf'\n## {re.escape(ver)}\n.*?(?=\n## |\Z)', '', txt, flags=re.S)
66
- return txt.replace(marker, marker+notes+'\n')
66
+ return txt.replace(marker, marker+notes.rstrip('\n')+'\n\n').rstrip('\n')+'\n'
67
67
 
68
68
  # %% ../nbs/api/18_release.ipynb #0b36471a
69
69
  class Release:
@@ -0,0 +1,70 @@
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 source, docs, examples, and tests at once. The rendered page explains the public API, and executing the same cells builds and verifies it. Write it to be read top to bottom, interleaving implementation with explanation, examples, outputs, and failure demonstrations. These conventions matter most for published libraries with docs sites. In internal projects, match the surrounding notebooks instead.
6
+
7
+ # Notebooks generate modules
8
+
9
+ `nbdev-export` writes exported cells to the module named by `#| default_exp`. Never hand-edit generated `.py` files, `__all__`, `_modidx.py`, or generated module docstrings. The next export overwrites them. Each exported section's marker (`# %% ../nbs/04_usage.ipynb #a45f753a`) leads back to the cell to change. The module docstring is the title cell's `>` description joined with every exported markdown cell after the H1. For a long docstring, prefer one exported markdown cell per section.
10
+
11
+ Projects mix notebook-sourced and plain modules: notebooks where the narrative is worth having (public API with a docs page, code best explained through examples, tests that double as documentation), plain `.py` for dense mechanism iterated quickly under `pytest`. Check the file, never the repo. A generated module opens with the `# AUTOGENERATED! DO NOT EDIT!` warning naming its source notebook, and `# %%` cell markers precede each definition, so a few lines of context around any `def` also answer it. No marker means hand-written and edited directly. Tests follow the source form: cells for notebook modules, `tests/*.py` for plain ones.
12
+
13
+ For project-level questions, read `nbs/index.ipynb`, not `README.md`. The README is generated from it, renders worse, and can be stale if regeneration lagged. When they disagree, `index.ipynb` is the truth. Regenerate with `nbdev-readme` after editing it.
14
+
15
+ # Structure
16
+
17
+ Pick one documentation form rather than mixing accidentally: tutorial (guided learning), how-to (a practical task), explanation (a focused topic), or reference (a component and its API, easy to scan).
18
+
19
+ Open with an H1 title cell plus `>` subtitle line, introduce the page in a sentence or two, and get to the subject quickly. H2 headings divide major concepts. Don't fragment a short narrative into tiny sections. Every heading goes in its own markdown cell, separate from body prose, since Jupyter collapses sections by cell.
20
+
21
+ # Develop one idea at a time
22
+
23
+ Add the smallest useful implementation, explain what it does and why it has that form, demonstrate it executably, assert where that improves the example, display the result a reader should notice, move on. A cell may bundle several closely related checks, but needs a markdown introduction saying what it establishes. If the introduction gets complicated, split the cell. Prefer extending an existing example over adding a near-duplicate cell. Build classes incrementally with `@patch` so each method sits beside its explanation, unless splitting makes the API harder to understand.
24
+
25
+ # Helpers
26
+
27
+ Internal use is often the first evidence an abstraction is worth exposing, not a reason to hide it. Reserve underscores for machinery with no coherent independent contract. A helper only useful to the library itself hints at an awkward internal design.
28
+
29
+ # Prose and docstrings
30
+
31
+ Markdown explains what the code cannot: why an abstraction exists, what distinction the next example demonstrates, which details are guarantees and which merely describe an example. Never narrate code line by line or write empty transitions ("Now we test the function"). State the lesson instead ("A missing leaf returns `None`, so success status alone does not establish existence"). Place prose where it reads correctly on the generated page, normally right after the definition it explains.
32
+
33
+ Keep docstrings to a short statement of what the symbol does. Extended explanation, examples, and warnings go in markdown cells, where they render properly and can include executable results. Document parameters and returns with docments, keeping their docs beside the signature instead of repeating it in the docstring. Backtick symbol names in prose. nbdev links them, so prefer names over hand-maintained URLs.
34
+
35
+ # Examples are documentation and tests
36
+
37
+ Write each example as page content first, then make it verify behavior: realistic values, the shortest path to the idea, an informative displayed result, direct assertions that reinforce the lesson, reuse of objects introduced earlier, and important errors demonstrated executably with `expect_fail` (one focused example per contract). Keep test plumbing out of reader-facing cells. Mocks, dense comprehensions, and long setup make poor documentation, so extract a tiny helper or hide the check. Assertions verify, but only the final expression's display teaches. End cells with the value worth showing, and design a compact `_repr_markdown_` or structured summary when it turns later examples into documentation for free. Plots, tables, images, and rich HTML all count as evidence. Stored outputs are part of the explanation. Keep them focused, and never dump a large structure without saying what matters in it. Test helpers come from `fastcore.test` (`test_eq`, `expect_fail`, ...), in plain code cells.
38
+
39
+ # Tells
40
+
41
+ Some patterns in a test or example cell can be spotted mechanically, and each reliably signals a rewrite that would improve the page. Each is a strong hint rather than a law. In particular, a comment sometimes states a constraint the code cannot show, and such a comment stays.
42
+
43
+ - A comment in an example cell usually marks where the cell should split in two. Split there, and grow the comment into a markdown cell introducing what the next code cell shows.
44
+ - Comments numbering steps mark a tutorial sequence. Give each step its own markdown and code pair.
45
+ - A `print` whose f-string wraps a result in a sentence is prose in code. The sentence belongs in markdown, and the value belongs at the end of the cell as its displayed result.
46
+ - A `print(x)` as a cell's last line hides the rich repr. End with bare `x` instead.
47
+ - A test or example cell that doesn't end in an evaluation to display (its last line is an assertion or an assignment) verifies without teaching. End with the value the checks are about.
48
+ - One name reassigned through stages in a single cell hides the intermediate values, which are the point. Give each stage its own cell, ending with a display.
49
+ - Blank lines dividing a cell into groups mark candidate cell boundaries, and each group needs its own sentence of markdown.
50
+ - Adjacent near-duplicate cells differing in one argument are a comparison written as copies. Make the difference the narrative ("with `strict=True` the same call raises...").
51
+ - `try`/`except` written to demonstrate an error is what `expect_fail` is for.
52
+ - A triple-quoted docstring holds explanation that belongs in markdown cells. Keep the docstring to a single short line (see "Prose and docstrings").
53
+ - `# TODO` or `# FIXME` in an example belongs nowhere on a docs page. It is an issue, or it is fixed.
54
+
55
+ # State flows downward
56
+
57
+ Keep imports in dedicated import cells, define values near first use, reuse established objects, don't reassign names later cells depend on, introduce shared setup explicitly, and end exploratory cells with the expression whose output records what was learned, so a reader never searches far upward for where a value came from. The import rule is strict and covers test cells. The docs build runs each import-containing cell in a fresh namespace where no other cell has run, so a cell mixing imports with other code breaks the build or silently runs at documentation time.
58
+
59
+ # Directives
60
+
61
+ `#| default_exp` names the module. `#| export` marks exported cells. Underscore-prefixed helpers may export without joining the public API. `#| hide` keeps necessary but distracting material off the page. `#| eval: false` is for cells that genuinely must not run, not for suppressing broken ones, and it cascades. An unevaluated cell cannot create state for later evaluated cells.
62
+
63
+ # nbdev v3
64
+
65
+ Everything here describes nbdev v3 (released Jan 2026, possibly after a model's training cutoff). Key user-visible changes from v2: config moved from `settings.ini` to `pyproject.toml`, with standard metadata in `[project]`, nbdev-specific keys in `[tool.nbdev]` (defaults `nbs_path='nbs'`, `doc_path='_docs'`), version in `__init__.py` via `dynamic = ["version"]`, and `_modidx` registered under `[project.entry-points.nbdev]`. CLI commands use hyphens (`nbdev-export`, `nbdev-test`, ...) though Python functions keep underscores. GitHub workflows use the v3 actions (`fastai/workflows/nbdev3-ci`, `quarto-ghp3`).
66
+
67
+ # Before you finish
68
+
69
+ Each new test cell ran red then green, failing before the change and passing after. Re-read the touched section as a reader would, against the conventions above. Style damage breaks no test and no export, so the harm only shows on the docs page.
70
+ """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nbdev
3
- Version: 3.3.0
3
+ Version: 3.3.2
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.1.3
19
+ Requires-Dist: fastcore>=2.1.11
20
20
  Requires-Dist: execnb>=0.2.8
21
21
  Requires-Dist: astunparse
22
22
  Requires-Dist: ghapi>=2.0.2
@@ -1,4 +1,4 @@
1
- fastcore>=2.1.3
1
+ fastcore>=2.1.11
2
2
  execnb>=0.2.8
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.1.3", "execnb>=0.2.8", "astunparse", "ghapi>=2.0.2", "watchdog", "asttokens",
22
+ dependencies = [ "fastcore>=2.1.11", "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]
@@ -1,5 +0,0 @@
1
- __version__ = "3.3.0"
2
-
3
- from .doclinks import nbdev_export
4
- from .showdoc import show_doc
5
-
@@ -1,181 +0,0 @@
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
- """
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