nbdev 3.3.21__tar.gz → 3.3.22__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 (42) hide show
  1. {nbdev-3.3.21/nbdev.egg-info → nbdev-3.3.22}/PKG-INFO +1 -1
  2. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/__init__.py +2 -2
  3. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/_modidx.py +1 -0
  4. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/maker.py +3 -1
  5. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/quarto.py +11 -6
  6. nbdev-3.3.22/nbdev/quarto_render.py +71 -0
  7. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/serve.py +14 -6
  8. nbdev-3.3.22/nbdev/skill.py +82 -0
  9. {nbdev-3.3.21 → nbdev-3.3.22/nbdev.egg-info}/PKG-INFO +1 -1
  10. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev.egg-info/SOURCES.txt +3 -1
  11. nbdev-3.3.22/tests/test_quarto_render.py +54 -0
  12. nbdev-3.3.21/nbdev/skill.py +0 -70
  13. {nbdev-3.3.21 → nbdev-3.3.22}/CONTRIBUTING.md +0 -0
  14. {nbdev-3.3.21 → nbdev-3.3.22}/LICENSE +0 -0
  15. {nbdev-3.3.21 → nbdev-3.3.22}/README.md +0 -0
  16. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/clean.py +0 -0
  17. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/cli.py +0 -0
  18. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/config.py +0 -0
  19. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/diff.py +0 -0
  20. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/doclinks.py +0 -0
  21. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/export.py +0 -0
  22. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/extract_attachments.py +0 -0
  23. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/frontmatter.py +0 -0
  24. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/imports.py +0 -0
  25. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/merge.py +0 -0
  26. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/migrate.py +0 -0
  27. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/moddocs.py +0 -0
  28. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/process.py +0 -0
  29. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/processors.py +0 -0
  30. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/qmd.py +0 -0
  31. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/release.py +0 -0
  32. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/scrubmagics.py +0 -0
  33. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/serve_drv.py +0 -0
  34. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/showdoc.py +0 -0
  35. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/sync.py +0 -0
  36. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev/test.py +0 -0
  37. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev.egg-info/dependency_links.txt +0 -0
  38. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev.egg-info/entry_points.txt +0 -0
  39. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev.egg-info/requires.txt +0 -0
  40. {nbdev-3.3.21 → nbdev-3.3.22}/nbdev.egg-info/top_level.txt +0 -0
  41. {nbdev-3.3.21 → nbdev-3.3.22}/pyproject.toml +0 -0
  42. {nbdev-3.3.21 → nbdev-3.3.22}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nbdev
3
- Version: 3.3.21
3
+ Version: 3.3.22
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-Expression: Apache-2.0
@@ -4,9 +4,9 @@ Modules:
4
4
 
5
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
6
  - `nbdev.moddocs`: # Module docs: creating them from existing notebooks
7
- - `nbdev.skill`: Author nbdev notebooks as source, documentation, examples, and tests. Ensure this guidance is in context before any notebook edit."""
7
+ - `nbdev.skill`: Write, read and review nbdev notebooks as source, documentation, examples and tests. You MUST read this before any work on a project with notebooks."""
8
8
 
9
- __version__ = "3.3.21"
9
+ __version__ = "3.3.22"
10
10
 
11
11
  from .doclinks import nbdev_export
12
12
  from .showdoc import show_doc
@@ -364,6 +364,7 @@ d = { 'settings': { 'branch': 'main',
364
364
  'nbdev.serve': { 'nbdev.serve._is_qpy': ('api/serve.html#_is_qpy', 'nbdev/serve.py'),
365
365
  'nbdev.serve._keep_file': ('api/serve.html#_keep_file', 'nbdev/serve.py'),
366
366
  'nbdev.serve._proc_file': ('api/serve.html#_proc_file', 'nbdev/serve.py'),
367
+ 'nbdev.serve._rm_stale': ('api/serve.html#_rm_stale', 'nbdev/serve.py'),
367
368
  'nbdev.serve.proc_nbs': ('api/serve.html#proc_nbs', 'nbdev/serve.py')},
368
369
  'nbdev.showdoc': { 'nbdev.showdoc.BasicMarkdownRenderer': ('api/showdoc.html#basicmarkdownrenderer', 'nbdev/showdoc.py'),
369
370
  'nbdev.showdoc.BasicMarkdownRenderer._repr_markdown_': ( 'api/showdoc.html#basicmarkdownrenderer._repr_markdown_',
@@ -191,7 +191,9 @@ def _retr_mdoc(cells, nb_path=None):
191
191
  mdoc = '\n\n'.join(L(summ)+docs).strip()
192
192
  url = nbpath2docurl(nb_path) if nb_path else ''
193
193
  if url: mdoc = (mdoc + f'\n\nDocs: {url}').strip()
194
- return f'"""{mdoc}"""\n\n' if mdoc else ''
194
+ if not mdoc: return ''
195
+ r = 'r' if '\\' in mdoc else ''
196
+ return f'{r}"""{mdoc}"""\n\n'
195
197
 
196
198
  # %% ../nbs/api/02_maker.ipynb #cdd205d6
197
199
  @patch
@@ -23,6 +23,7 @@ from fastcore.shutil import rmtree,move,copytree
23
23
  from fastcore.meta import delegates
24
24
  from .serve import proc_nbs,_proc_file
25
25
  from . import serve_drv
26
+ from .quarto_render import render_quarto
26
27
  import yaml
27
28
 
28
29
  # %% ../nbs/api/14_quarto.ipynb #aae2d2be-ad03-4536-bf70-c4575f39cea3
@@ -138,7 +139,7 @@ def nbdev_sidebar(
138
139
  yml = yaml.dump(parsed_struct, Dumper=IndentDumper, sort_keys=False)
139
140
 
140
141
  if printit: return print(yml)
141
- yml_path.write_text(yml)
142
+ if not yml_path.exists() or yml_path.read_text() != yml: yml_path.write_text(yml)
142
143
 
143
144
  # %% ../nbs/api/14_quarto.ipynb #aabf2f15
144
145
  _quarto_yml="""project:
@@ -189,7 +190,8 @@ def refresh_quarto_yml():
189
190
  vals = {k:cfg[k] for k in ['title', 'description', 'branch', 'git_url', 'doc_host', 'doc_baseurl']}
190
191
  vals['doc_path'] = cfg.doc_path.name
191
192
  if 'title' not in vals: vals['title'] = vals['lib_name']
192
- ny.write_text(_nbdev_yml.format(**vals))
193
+ text = _nbdev_yml.format(**vals)
194
+ if not ny.exists() or ny.read_text() != text: ny.write_text(text)
193
195
  qy = cfg.nbs_path/'_quarto.yml'
194
196
  if 'custom_quarto_yml' in cfg: print("NB: `_quarto.yml` is no longer auto-updated. Remove `custom_quarto_yml` from `pyproject.toml`")
195
197
  if qy.exists() and not str2bool(cfg.get('custom_quarto_yml', True)): qy.unlink()
@@ -335,11 +337,11 @@ def _fix_quarto_nav(doc_path):
335
337
  @delegates(_nbglob_docs)
336
338
  def nbdev_docs(
337
339
  path:str=None, # Path to notebooks
338
- n_workers:int=defaults.cpus, # Number of workers
340
+ n_workers:int=defaults.cpus, # Preprocessing and Quarto workers (0 or 1: serial)
339
341
  **kwargs):
340
342
  "Create Quarto docs and README.md"
341
343
  cache,cfg,path = _pre_docs(path, n_workers=n_workers, **kwargs)
342
- _sprun(f'cd "{cache}" && quarto render --no-cache')
344
+ render_quarto(cache, cache/cfg.doc_path.name, n_workers=n_workers)
343
345
  nbdev_readme.__wrapped__(path=path, chk_time=True)
344
346
  nbdev_contributing.__wrapped__(path=path, chk_time=True)
345
347
  shutil.rmtree(cfg.doc_path, ignore_errors=True)
@@ -382,11 +384,15 @@ def nbdev_preview(
382
384
  port:int=None, # The port on which to run preview
383
385
  host:str=None, # The host on which to run preview
384
386
  no_browser:bool=False, # Do not open a browser
385
- n_workers:int=defaults.cpus, # Number of workers
387
+ render:bool=False, # Render all pages before previewing
388
+ n_workers:int=defaults.cpus, # Preprocessing and Quarto workers (0 or 1: serial)
386
389
  **kwargs):
387
390
  "Preview docs locally"
388
391
  os.environ['QUARTO_PREVIEW']='1'
389
392
  cache,cfg,path = _pre_docs(path, n_workers=n_workers, **kwargs)
393
+ site = cache/cfg.doc_path.name
394
+ if not (render or site.exists()) and cfg.doc_path.exists(): copytree(cfg.doc_path, site)
395
+ if render or not site.exists(): render_quarto(cache, site, n_workers=n_workers)
390
396
  xtra = []
391
397
  if port: xtra += ['--port', str(port)]
392
398
  if host: xtra += ['--host', host]
@@ -399,5 +405,4 @@ def nbdev_preview(
399
405
  except: traceback.print_exc()
400
406
 
401
407
  os.chdir(cache)
402
- xtra = xtra or []
403
408
  with fs_watchdog(_f, path): subprocess.run(['quarto','preview']+xtra)
@@ -0,0 +1,71 @@
1
+ "Parallel Quarto website rendering in isolated project copies."
2
+
3
+ import json, shutil, subprocess, tempfile
4
+ from pathlib import Path
5
+ from xml.etree import ElementTree as ET
6
+ from fastcore.parallel import parallel
7
+ from fastcore.utils import defaults
8
+
9
+
10
+ def _quarto(path, *args):
11
+ res = subprocess.run(['quarto', *map(str, args)], cwd=path, capture_output=True, text=True)
12
+ if res.returncode: raise RuntimeError(res.stderr or res.stdout or f'Quarto exited with status {res.returncode}')
13
+ return res.stdout
14
+
15
+
16
+ def _render_batch(job):
17
+ path, inputs, output = job
18
+ _quarto(path, 'render', *inputs, '--no-cache', '--no-clean', '--output-dir', output)
19
+ return output
20
+
21
+
22
+ def _merge_site(outputs, dest):
23
+ search, urls, homepage = {}, {}, None
24
+ sitemap = None
25
+ for output in outputs:
26
+ idx = output/'index.html'
27
+ if idx.exists() and (homepage is None or 'http-equiv="refresh"' not in idx.read_text()): homepage = idx
28
+ if (output/'search.json').exists():
29
+ for entry in json.loads((output/'search.json').read_text()): search[entry['href']] = entry
30
+ if (output/'sitemap.xml').exists():
31
+ sitemap = ET.parse(output/'sitemap.xml')
32
+ for entry in sitemap.getroot(): urls[entry.find('{*}loc').text] = entry
33
+ shutil.copytree(output, dest, dirs_exist_ok=True)
34
+ if homepage: shutil.copy2(homepage, dest/'index.html')
35
+ if search: (dest/'search.json').write_text(json.dumps(list(search.values())))
36
+ if sitemap is not None:
37
+ sitemap.getroot()[:] = urls.values()
38
+ ET.register_namespace('', 'http://www.sitemaps.org/schemas/sitemap/0.9')
39
+ sitemap.write(dest/'sitemap.xml', encoding='utf-8', xml_declaration=True)
40
+
41
+
42
+ def render_quarto(path, output_dir, n_workers=defaults.cpus):
43
+ "Render a project, parallelizing website pages with isolated Quarto workers."
44
+ path, output_dir = Path(path).resolve(), Path(output_dir).resolve()
45
+ if output_dir == path or output_dir in path.parents: raise ValueError('Output directory must not contain the project')
46
+ if n_workers <= 1:
47
+ _quarto(path, 'render', '--no-cache', '--output-dir', output_dir)
48
+ return
49
+ info = json.loads(_quarto(path, 'inspect'))
50
+ project = info['config']['project']
51
+ website = info['config'].get('website', {})
52
+ if project['type'] != 'website' or project.get('pre-render') or project.get('post-render') or website.get('llms-txt'):
53
+ return render_quarto(path, output_dir, n_workers=1)
54
+ inputs = [str(Path(p).relative_to(path)) for p in info['files']['input']]
55
+ if len(inputs) < 2: return render_quarto(path, output_dir, n_workers=1)
56
+ n_workers = min(n_workers, len(inputs))
57
+ print(f'Rendering {len(inputs)} pages with {n_workers} Quarto workers')
58
+ excluded = {path/'.quarto', (path/project.get('output-dir', '_site')).resolve(), output_dir}
59
+ def ignore(src, names): return [n for n in names if Path(src)/n in excluded]
60
+ with tempfile.TemporaryDirectory(prefix='nbdev-quarto-') as tmp:
61
+ jobs = []
62
+ for i in range(n_workers):
63
+ worker = Path(tmp)/str(i)
64
+ shutil.copytree(path, worker, ignore=ignore)
65
+ jobs.append((worker, inputs[i::n_workers], Path(tmp)/f'output-{i}'))
66
+ outputs = parallel(_render_batch, jobs, n_workers=n_workers, threadpool=True)
67
+ merged = Path(tmp)/'merged'
68
+ _merge_site(outputs, merged)
69
+ if output_dir.exists(): shutil.rmtree(output_dir)
70
+ output_dir.parent.mkdir(parents=True, exist_ok=True)
71
+ shutil.move(merged, output_dir)
@@ -38,15 +38,12 @@ def _is_qpy(path:Path):
38
38
  if vl[0]=='---' and vl[-1]=='---': return '\n'.join(vl[1:-1])
39
39
 
40
40
  # %% ../nbs/api/17_serve.ipynb #abc3835a
41
- def _proc_file(s, cache, path, mtime=None):
41
+ def _proc_file(s, cache, path):
42
42
  skips = ('_proc', '_docs', '_site', 'pyproject.toml')
43
43
  if not s.is_file() or any(o[0]=='.' or o in skips for o in s.parts): return
44
44
  d = cache/s.relative_to(path)
45
45
  if s.suffix=='.py': d = d.with_suffix('')
46
- if d.exists():
47
- dtime = d.stat().st_mtime
48
- if mtime: dtime = max(dtime, mtime)
49
- if s.stat().st_mtime<=dtime: return
46
+ if d.exists() and s.stat().st_mtime<=d.stat().st_mtime: return
50
47
 
51
48
  d.parent.mkdir(parents=True, exist_ok=True)
52
49
  if s.suffix=='.ipynb': return s,d,FilterDefaults
@@ -60,6 +57,15 @@ def _keep_file(f:Path, file_glob='', file_re=''):
60
57
  if f.suffix!='.ipynb': return True
61
58
  return (not file_glob or fnmatch(f.name, file_glob)) and (not file_re or bool(re.search(file_re, f.name)))
62
59
 
60
+ # %% ../nbs/api/17_serve.ipynb #ff47fad5
61
+ def _rm_stale(cache:Path, path:Path):
62
+ "Remove pages in `cache` whose source in `path` no longer exists"
63
+ for d in list(cache.rglob('*')):
64
+ rel = d.relative_to(cache)
65
+ if d.suffix not in ('.ipynb','.qmd','.md') or any(o[0]=='.' for o in rel.parts): continue
66
+ s = path/rel
67
+ if not (s.exists() or s.with_name(s.name+'.py').exists()): d.unlink()
68
+
63
69
  # %% ../nbs/api/17_serve.ipynb #14463227
64
70
  @delegates(nbglob_cli)
65
71
  def proc_nbs(
@@ -77,6 +83,7 @@ def proc_nbs(
77
83
  if file_glob or file_re: files = files.filter(_keep_file, file_glob=file_glob, file_re=file_re)
78
84
  if (path/'_quarto.yml').exists(): files.append(path/'_quarto.yml')
79
85
  if (path/'_brand.yml').exists(): files.append(path/'_brand.yml')
86
+ files.extend(nbglob(path, func=Path, file_glob='_metadata.y*ml', skip_file_re='^[.]'))
80
87
  if (path/'_extensions').exists(): files.extend(nbglob(path/'_extensions', func=Path, file_glob='', file_re='', skip_file_re='^[.]'))
81
88
 
82
89
  # If pyproject.toml or filter script newer than cache folder modified, delete cache
@@ -85,8 +92,9 @@ def proc_nbs(
85
92
  cache_mtime = cache.stat().st_mtime
86
93
  if force or (cache.exists() and cache_mtime<chk_mtime): rmtree(cache)
87
94
  (cache/'_quarto.yml').unlink(missing_ok=True) # single-doc renders strip the staged copy in place (`_strip_sidebar`), so always restage it
95
+ _rm_stale(cache, path)
88
96
 
89
- files = files.map(_proc_file, mtime=cache_mtime, cache=cache, path=path).filter()
97
+ files = files.map(_proc_file, cache=cache, path=path).filter()
90
98
  kw = {} if in_notebook() else {'method':'spawn'}
91
99
  parallel(nbdev.serve_drv.main, files, n_workers=n_workers, pause=0.01, **kw)
92
100
  if cache.exists(): cache.touch()
@@ -0,0 +1,82 @@
1
+ """Write, read and review nbdev notebooks as source, documentation, examples and tests. You MUST read this before any work on a project with notebooks.
2
+
3
+ # Source
4
+
5
+ Write notebooks to be read top-to-bottom, interleaving implementation, explanation, examples, outputs, and failure demonstrations. Follow these conventions in published libraries. In internal projects, match the surrounding notebooks.
6
+
7
+ Check the file, not the repo: `# AUTOGENERATED! DO NOT EDIT!` names the source notebook; `# %% ../nbs/04_usage.ipynb #a45f753a` identifies the source cell. Edit that notebook, never generated modules, `__all__`, `_modidx.py`, or module docstrings; `nbdev-export` regenerates them. Unmarked modules are hand-written. Use notebooks to teach the API through narrative and examples, and plain `.py` for dense mechanism iterated under pytest (rarely; never add pytests to a project without them unless asked). Lessons test notebook code. Test plain modules in `tests/*.py`.
8
+
9
+ Understand and review work through the notebooks and their `git diff`, never generated modules. Those lack the prose, examples and tests.
10
+
11
+ For project-level questions, read `nbs/index.ipynb`, the source of README.md; regenerate with `nbdev-readme` after changes.
12
+
13
+ # Structure and authoring
14
+
15
+ Choose tutorial, how-to, explanation, or reference. Open with an H1 title cell and `>` subtitle, then a brief introduction. Use H2s for major concepts without fragmenting short narratives. Each heading belongs in its own markdown cell so Jupyter can collapse sections.
16
+
17
+ Before changing a notebook, read the whole notebook or a summary of every cell. For a new notebook, read a role model such as claudette's `00_core.ipynb` to learn the literate form.
18
+
19
+ Develop one idea at a time: a small implementation, its explanation, and a lesson if it's a substantive API. Introduce each lesson in markdown; split it when the introduction becomes complicated. Extend existing examples rather than adding near-duplicates. Build classes with `@patch` beside each method's explanation unless that obscures the API.
20
+
21
+ When new behavior needs a lesson, 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 both 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.
22
+
23
+ Read existing examples before experimenting. Explore in the notebook, not in a separate kernel check; keep useful explorations as lessons and delete the rest. Always try to leave a notebook better than you found it.
24
+
25
+ Reserve underscore names for machinery without a coherent independent contract, not for everything used only internally. If a helper is only useful internally, the design is often awkward.
26
+
27
+ # Prose and module documentation
28
+
29
+ In markdown, explain rationale, distinctions and guarantees. Don't narrate code line by line or write filler transitions. Place markdown where it reads correctly on the generated page, normally after the definition. Use short symbol docstrings, parameter/return docments beside signatures, and backticked symbol names for nbdev links. Extended explanation, examples, and warnings belong in markdown.
30
+
31
+ `nbdev-export` builds the module docstring from the title's `>` description and exported markdown after the H1. Use one exported markdown cell per major section for long docstrings. `doc(module)` lists the functions. In module prose, explain only shared constraints and workflows.
32
+
33
+ `#| exportd` includes a code cell as fenced source in the module docstring, not module code. Use compact runnable demos after their required definitions; end the preceding exported summary with a colon. Unexported definitions between the summary and demo don't appear in the docstring.
34
+
35
+ Check claims by running code before documenting them; preview assembly with `nbdev.export.nb_mdoc`. `nbdev-export` lists a module in package documentation and `llms.txt` if its docstring has more than the default summary. Only extend docstrings of modules read on their own. Export the opening paragraphs of `index.ipynb` for the package intro.
36
+
37
+ # Lesson cells
38
+
39
+ A notebook guides a reader through what the code does. Readability comes first.
40
+
41
+ Every substantive API needs a lesson. Basic functionality outside the API, with little logic, generally doesn't.
42
+
43
+ Explain with code and its displayed output. When a displayed value is also worth testing, assign it, test it, then show it. Test results that are the point of the code, not incidentals such as formatting.
44
+
45
+ Show an error only when readers are likely to hit it.
46
+
47
+ A cell that serves a test instead of a reader doesn't belong on the page. Signs include setup that exists only so a check can run safely, and checks on glue code. Usually, remove such a test. If it checks substantive logic, move it to a `#| hide` cell or to pytest. Discuss such changes carefully.
48
+
49
+ Write page content: realistic values, minimal setup, reused objects, and direct assertions that support the lesson. Show evidence with compact `_repr_markdown_` or structured summaries, plots, tables, images or HTML. Explain what matters in large outputs and keep stored results focused.
50
+
51
+ Use `fastcore.test` assertions and `expect_fail` in ordinary code cells. Keep check plumbing, mocks, dense comprehensions, and long setup out of reader-facing examples: extract a small helper or hide the check. Use `nbdev-test --file-re` to run several named notebooks in parallel.
52
+
53
+ Consider a rewrite when you see these:
54
+
55
+ - Split commented examples into prose and code. Split numbered steps into markdown/code pairs. Keep comments that state constraints code cannot show.
56
+ - Replace a sentence-wrapped `print` with prose and a displayed value, and a final `print(x)` with bare `x`.
57
+ - Split reassignment stages and blank-line groups into cells, each with its own introduction.
58
+ - In near-duplicate examples, explain the argument that differs. Don't repeat the setup.
59
+ - Show errors with `expect_fail`, not `try`/`except`. Move long docstrings to markdown.
60
+ - Resolve TODO/FIXME example comments or move them to issues.
61
+
62
+ # State and execution
63
+
64
+ Define values near first use, introduce shared setup, reuse established objects, and don't reassign names later cells depend on. End exploratory cells with the expression recording what was learned when useful.
65
+
66
+ Keep imports in dedicated cells, including in lessons: docs builds execute import-containing cells in fresh namespaces, so mixing imports with state-dependent code can fail or run code at documentation time.
67
+
68
+ Stored outputs are generated and may be stale during editing. Don't stop work, clear them by hand, or edit notebook JSON because they no longer match the source. At PR time, regenerate them with `nbdev-test --save` if the project keeps saved outputs.
69
+
70
+ # Directives and nbdev v3
71
+
72
+ - `#| default_exp` names the generated module; `#| export` includes a cell there. Exported underscore helpers need not be public.
73
+ - `#| hide` hides distracting necessities from the page.
74
+ - `#| eval: false` is for code that must not run, not broken code. Later cells can't use its state.
75
+ - Directives may instead be metadata: `{"nbdev": {"export": "true"}}`. nbdev reads both; `fastcore.nbio.mk_cell(...).directives` merges them with content winning conflicts.
76
+
77
+ nbdev v3 (Jan 2026) uses `pyproject.toml`, not `settings.ini`: metadata in `[project]`, nbdev settings in `[tool.nbdev]` (default `nbs_path='nbs'`, `doc_path='_docs'`), version in `__init__.py` via `dynamic = ["version"]`, and `_modidx` in `[project.entry-points.nbdev]`. CLIs use hyphens (`nbdev-export`, `nbdev-test`); Python names retain underscores. GitHub actions are `fastai/workflows/nbdev3-ci` and `quarto-ghp3`.
78
+
79
+ # Finish
80
+
81
+ Revise affected prose, displays, and assertions together; changed assertions must fail on old code and pass on new. Re-read the touched narrative for readability. Run `nbdev-test <nb>`.
82
+ """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nbdev
3
- Version: 3.3.21
3
+ Version: 3.3.22
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-Expression: Apache-2.0
@@ -21,6 +21,7 @@ nbdev/process.py
21
21
  nbdev/processors.py
22
22
  nbdev/qmd.py
23
23
  nbdev/quarto.py
24
+ nbdev/quarto_render.py
24
25
  nbdev/release.py
25
26
  nbdev/scrubmagics.py
26
27
  nbdev/serve.py
@@ -34,4 +35,5 @@ nbdev.egg-info/SOURCES.txt
34
35
  nbdev.egg-info/dependency_links.txt
35
36
  nbdev.egg-info/entry_points.txt
36
37
  nbdev.egg-info/requires.txt
37
- nbdev.egg-info/top_level.txt
38
+ nbdev.egg-info/top_level.txt
39
+ tests/test_quarto_render.py
@@ -0,0 +1,54 @@
1
+ import json, shutil, subprocess, pytest
2
+ from xml.etree import ElementTree as ET
3
+ from fastcore.test import test_eq as teq
4
+ from nbdev.quarto_render import render_quarto
5
+
6
+
7
+ @pytest.mark.skipif(not shutil.which('quarto'), reason='requires Quarto')
8
+ def test_website(tmp_path):
9
+ root = tmp_path/'source'
10
+ root.mkdir()
11
+ (root/'_quarto.yml').write_text(r'''project:
12
+ type: website
13
+ website:
14
+ title: Parallel render
15
+ site-url: https://example.com/
16
+ sidebar:
17
+ contents: [index.qmd, alpha.qmd, beta.qmd]
18
+ format:
19
+ html: default
20
+ commonmark: default
21
+ execute:
22
+ enabled: false
23
+ ''')
24
+ for name in ('index', 'alpha', 'beta'):
25
+ (root/f'{name}.qmd').write_text(fr'''---
26
+ title: {name}
27
+ ---
28
+ ## Detail
29
+
30
+ Page {name}. [Alpha](alpha.qmd#detail). [Download](data.txt).
31
+ ''')
32
+ (root/'data.txt').write_text('shared resource')
33
+ serial, parallel = tmp_path/'serial', tmp_path/'parallel'
34
+ render_quarto(root, serial, n_workers=1)
35
+ render_quarto(root, parallel, n_workers=2)
36
+ search = lambda d: {e['href']: e['text'] for e in json.loads((d/'search.json').read_text())}
37
+ urls = lambda d: {e.text for e in ET.parse(d/'sitemap.xml').findall('.//{*}loc')}
38
+ teq(search(serial), search(parallel))
39
+ teq(urls(serial), urls(parallel))
40
+ teq(len(urls(parallel)), 3)
41
+ for name in ('index', 'alpha', 'beta'):
42
+ html = (parallel/f'{name}.html').read_text()
43
+ assert 'alpha.html#detail' in html and 'data.txt' in html
44
+ assert (parallel/f'{name}.md').exists()
45
+ teq((parallel/'data.txt').read_text(), 'shared resource')
46
+ assert (parallel/'site_libs').is_dir()
47
+ assert 'http-equiv="refresh"' not in (parallel/'index.html').read_text()
48
+ (root/'beta.qmd').write_text('---\ntitle: beta\n---\n## Detail\n\nUpdated beta.')
49
+ subprocess.run(['quarto', 'render', 'beta.qmd', '--quiet', '--output-dir', str(parallel)], cwd=root, check=True)
50
+ teq(set(search(serial)), set(search(parallel)))
51
+ assert 'Updated beta.' in search(parallel)['beta.html#detail']
52
+ (root/'beta.qmd').write_text('---\nfilters: [missing.lua]\n---\nBroken.')
53
+ with pytest.raises(RuntimeError, match='missing.lua'): render_quarto(root, parallel, n_workers=2)
54
+ assert 'Updated beta.' in search(parallel)['beta.html#detail']
@@ -1,70 +0,0 @@
1
- """Author nbdev notebooks as source, documentation, examples, and tests. Ensure this guidance is in context before any notebook edit.
2
-
3
- # Source
4
-
5
- Write notebooks to be read top-to-bottom, interleaving implementation, explanation, examples, outputs, and failure demonstrations. These conventions target published libraries; match surrounding notebooks in internal projects.
6
-
7
- Check the file, not the repo: `# AUTOGENERATED! DO NOT EDIT!` names the source notebook; `# %% ../nbs/04_usage.ipynb #a45f753a` identifies the source cell. Edit that notebook, never generated modules, `__all__`, `_modidx.py`, or module docstrings; `nbdev-export` regenerates them. Unmarked modules are hand-written. Use notebooks where narrative/examples teach the API, plain `.py` for dense mechanism iterated under pytest (which should be very rare; never add pytests to a project that doesn't have any, unless explicitly requested). Tests follow source form: lesson cells for notebooks, `tests/*.py` for plain modules.
8
-
9
- For project-level questions, read `nbs/index.ipynb`, the source of README.md; regenerate with `nbdev-readme` after changes.
10
-
11
- # Structure and authoring
12
-
13
- Choose tutorial, how-to, explanation, or reference. Open with an H1 title cell and `>` subtitle, then a brief introduction. Use H2s for major concepts without fragmenting short narratives. Each heading belongs in its own markdown cell so Jupyter can collapse sections.
14
-
15
- Before changing a notebook, read the whole notebook or a summary of every cell. For a new notebook, read a role model such as claudette's `00_core.ipynb` to learn the literate form.
16
-
17
- Develop one idea at a time: small implementation, explanation, executable lesson, assertions where useful. Introduce each lesson in markdown; split it when the introduction becomes complicated. Extend existing examples rather than adding near-duplicates. Build classes with `@patch` beside each method's explanation unless that obscures the API.
18
-
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
-
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
-
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
-
25
- # Prose and module documentation
26
-
27
- Markdown explains rationale, distinctions, and guarantees, not code line-by-line or empty transitions. Place it where it reads correctly on the generated page, normally after the definition. Use short symbol docstrings, parameter/return docments beside signatures, and backticked symbol names for nbdev links. Extended explanation, examples, and warnings belong in markdown.
28
-
29
- The module docstring combines the title's `>` description with exported markdown after H1. Use one exported markdown cell per major section for long docstrings. Since `doc(module)` also lists functions, module prose should teach shared constraints and workflows, not repeat individual API documentation.
30
-
31
- `#| exportd` includes a code cell as fenced source in the module docstring, not module code. Use compact runnable demos after their required definitions; end the preceding exported summary with a colon. Only exported cells join the docstring, so definitions between that summary and demo do not separate them there.
32
-
33
- Check claims by running code before documenting them; preview assembly with `nbdev.export.nb_mdoc`. A docstring beyond the default summary causes `nbdev-export` to include the module in package documentation and `llms.txt`; write these summaries for modules meant to be read independently. Export the opening paragraphs of `index.ipynb` for the package intro.
34
-
35
- # Lesson cells
36
-
37
- Write page content, not standalone test cells (except rare hidden checks): realistic values, minimal setup, reused objects, and direct assertions that support the lesson. End lesson cells with an informative displayed value when there is something useful to show. Compact `_repr_markdown_` or structured summaries can make later examples readable; plots, tables, images, and HTML also provide evidence. Explain what matters in large outputs and keep stored results focused.
38
-
39
- Use `fastcore.test` assertions and `expect_fail` in ordinary code cells, with one focused error example per contract. Keep check plumbing, mocks, dense comprehensions, and long setup out of reader-facing examples: extract a small helper or hide the check. Use `nbdev-test --file-re` for running >1 named notebook, since they'll run in parallel.
40
-
41
- Review these as rewrite signals, not absolute rules:
42
-
43
- - Comments often mark a split into prose and code; numbered steps suggest separate markdown/code pairs. Keep comments that state constraints code cannot show.
44
- - A sentence-wrapped `print` belongs in prose plus a displayed value; final `print(x)` loses rich repr compared with bare `x`.
45
- - Reassignments through stages can hide useful intermediate displays; blank-line groups suggest cell boundaries with their own introductions.
46
- - Near-duplicate examples should make the differing argument the narrative, rather than repeat setup.
47
- - Error-demonstration `try`/`except` usually wants `expect_fail`; long docstrings want markdown.
48
- - Resolve TODO/FIXME example comments or move them to issues.
49
-
50
- # State and execution
51
-
52
- Define values near first use, introduce shared setup, reuse established objects, and don't reassign names later cells depend on. End exploratory cells with the expression recording what was learned when useful.
53
-
54
- Keep imports in dedicated cells, including in lessons: docs builds execute import-containing cells in fresh namespaces, so mixing imports with state-dependent code can fail or run code at documentation time.
55
-
56
- Stored outputs are generated artifacts and may be stale during editing. Do not stop, clear them manually, or manipulate notebook JSON because output no longer matches source. At PR time, regenerate with `nbdev-test --save` when the project needs saved outputs updated.
57
-
58
- # Directives and nbdev v3
59
-
60
- - `#| default_exp` names the generated module; `#| export` includes a cell there. Exported underscore helpers need not join the public API.
61
- - `#| hide` hides distracting necessities from the page.
62
- - `#| eval: false` is for code that must not run, not broken code; it cannot supply state to evaluated cells.
63
- - Directives may instead be metadata: `{"nbdev": {"export": "true"}}`. The toolchain honors both; `mk_cell(...).directives` merges them with content winning conflicts.
64
-
65
- nbdev v3 (Jan 2026) uses `pyproject.toml`, not `settings.ini`: metadata in `[project]`, nbdev settings in `[tool.nbdev]` (default `nbs_path='nbs'`, `doc_path='_docs'`), version in `__init__.py` via `dynamic = ["version"]`, and `_modidx` in `[project.entry-points.nbdev]`. CLIs use hyphens (`nbdev-export`, `nbdev-test`); Python names retain underscores. GitHub actions are `fastai/workflows/nbdev3-ci` and `quarto-ghp3`.
66
-
67
- # Finish
68
-
69
- Revise affected prose, displays, and assertions together; changed assertions must fail on old code and pass on new. Re-read the touched narrative for readability: tests cannot catch documentation damage. Run `nbdev-test <nb>`.
70
- """
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