nbdev 3.3.1__tar.gz → 3.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nbdev-3.3.1/nbdev.egg-info → nbdev-3.3.3}/PKG-INFO +2 -2
- nbdev-3.3.3/nbdev/__init__.py +12 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/_modidx.py +12 -1
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/cli.py +14 -9
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/config.py +117 -16
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/doclinks.py +1 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/export.py +12 -4
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/maker.py +6 -2
- nbdev-3.3.3/nbdev/skill.py +78 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/test.py +2 -2
- {nbdev-3.3.1 → nbdev-3.3.3/nbdev.egg-info}/PKG-INFO +2 -2
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev.egg-info/requires.txt +1 -1
- {nbdev-3.3.1 → nbdev-3.3.3}/pyproject.toml +1 -1
- nbdev-3.3.1/nbdev/__init__.py +0 -5
- nbdev-3.3.1/nbdev/skill.py +0 -56
- {nbdev-3.3.1 → nbdev-3.3.3}/CONTRIBUTING.md +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/LICENSE +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/MANIFEST.in +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/README.md +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/clean.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/diff.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/extract_attachments.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/frontmatter.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/imports.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/merge.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/migrate.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/process.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/processors.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/qmd.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/quarto.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/release.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/scrubmagics.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/serve.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/serve_drv.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/showdoc.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev/sync.py +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev.egg-info/SOURCES.txt +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev.egg-info/dependency_links.txt +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev.egg-info/entry_points.txt +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/nbdev.egg-info/top_level.txt +0 -0
- {nbdev-3.3.1 → nbdev-3.3.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nbdev
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.3
|
|
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.
|
|
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.3"
|
|
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',
|
|
@@ -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
|
-
"
|
|
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(
|
|
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
|
-
|
|
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,10 +7,10 @@ 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', '
|
|
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
|
-
from datetime import datetime
|
|
14
14
|
from fastcore.docments import *
|
|
15
15
|
from fastcore.utils import *
|
|
16
16
|
from fastcore.meta import *
|
|
@@ -18,10 +18,9 @@ from fastcore.script import *
|
|
|
18
18
|
from fastcore.style import *
|
|
19
19
|
from fastcore.xdg import *
|
|
20
20
|
|
|
21
|
-
import ast,importlib,re
|
|
21
|
+
import ast,importlib,re
|
|
22
22
|
from IPython.display import Markdown
|
|
23
23
|
from fastcore.nbio import read_nb,NbCell
|
|
24
|
-
from urllib.error import HTTPError
|
|
25
24
|
|
|
26
25
|
try: import tomllib
|
|
27
26
|
except ImportError: import tomli as tomllib
|
|
@@ -41,6 +40,7 @@ def _get_info(owner, repo, default_branch='main', default_kw='nbdev'):
|
|
|
41
40
|
from ghapi.all import call_gh, APIError
|
|
42
41
|
try: r = call_gh('repos.get', owner=owner, repo=repo)
|
|
43
42
|
except APIError:
|
|
43
|
+
# chkstyle: ignore-node
|
|
44
44
|
msg= [f"""Could not access repo: {owner}/{repo} to find your default branch - `{default_branch}` assumed.
|
|
45
45
|
Edit `pyproject.toml` if this is incorrect.
|
|
46
46
|
In the future, you can allow nbdev to see private repos by setting the environment variable GITHUB_TOKEN as described here:
|
|
@@ -54,16 +54,19 @@ https://nbdev.fast.ai/api/release.html#setup"""]
|
|
|
54
54
|
def _fetch_from_git(raise_err=False):
|
|
55
55
|
"Get information for pyproject.toml from git."
|
|
56
56
|
res={}
|
|
57
|
+
try:
|
|
58
|
+
res['author'] = run('git config --get user.name').strip()
|
|
59
|
+
res['author_email'] = run('git config --get user.email').strip()
|
|
60
|
+
except OSError as e:
|
|
61
|
+
if raise_err: raise e
|
|
57
62
|
try:
|
|
58
63
|
url = run('git config --get remote.origin.url')
|
|
59
64
|
res['user'],res['repo'] = repo_details(url)
|
|
60
65
|
res['branch'],res['keywords'],desc = _get_info(owner=res['user'], repo=res['repo'])
|
|
61
66
|
if desc: res['description'] = desc
|
|
62
|
-
res['
|
|
63
|
-
res['author_email'] = run('git config --get user.email').strip()
|
|
67
|
+
res['lib_name'] = res['repo'].replace('-','_')
|
|
64
68
|
except OSError as e:
|
|
65
|
-
if raise_err: raise
|
|
66
|
-
else: res['lib_name'] = res['repo'].replace('-','_')
|
|
69
|
+
if raise_err: raise e
|
|
67
70
|
return res
|
|
68
71
|
|
|
69
72
|
# %% ../nbs/api/01_config.ipynb #05aae09f
|
|
@@ -201,8 +204,7 @@ class ConfigToml(AttrDict):
|
|
|
201
204
|
if 'lib_path' not in self: self['lib_path'] = self.lib_name.replace('-', '_')
|
|
202
205
|
|
|
203
206
|
@property
|
|
204
|
-
def version(self):
|
|
205
|
-
return read_version(self.config_path / self['lib_path']) or '0.0.1'
|
|
207
|
+
def version(self): return read_version(self.config_path / self['lib_path']) or '0.0.1'
|
|
206
208
|
|
|
207
209
|
@property
|
|
208
210
|
def d(self): return {k:v for k,v in super().items()}
|
|
@@ -249,7 +251,7 @@ def is_nbdev(path=None): return _find_nbdev_pyproject(path) is not None
|
|
|
249
251
|
def create_output(txt, mime):
|
|
250
252
|
"Add a cell output containing `txt` of the `mime` text MIME sub-type"
|
|
251
253
|
return [dict(data={f"text/{mime}": str(txt).splitlines(True)},
|
|
252
|
-
|
|
254
|
+
execution_count=1, metadata={}, output_type="execute_result")]
|
|
253
255
|
|
|
254
256
|
# %% ../nbs/api/01_config.ipynb #5a4d8e52
|
|
255
257
|
def show_src(src, lang='python'): return Markdown(f'```{lang}\n{src}\n```')
|
|
@@ -314,12 +316,13 @@ def set_version(path, version):
|
|
|
314
316
|
|
|
315
317
|
# %% ../nbs/api/01_config.ipynb #d00889e5
|
|
316
318
|
def bump_version(v, part=2, unbump=False):
|
|
317
|
-
"Bump semver
|
|
319
|
+
"Bump semver part `part` (0=major, 1=minor, 2=patch), counted from the right"
|
|
318
320
|
parts = (v or '0.0.0').split('.')
|
|
319
321
|
parts += ['0'] * (3 - len(parts))
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
322
|
+
idx = len(parts) - 3 + part
|
|
323
|
+
parts[idx] = str(int(parts[idx]) + (-1 if unbump else 1))
|
|
324
|
+
for i in range(idx+1, len(parts)): parts[i] = '0'
|
|
325
|
+
return '.'.join(parts)
|
|
323
326
|
|
|
324
327
|
# %% ../nbs/api/01_config.ipynb #e32583e6
|
|
325
328
|
def update_version(path=None):
|
|
@@ -352,6 +355,104 @@ def add_init(path=None):
|
|
|
352
355
|
if _has_py(fs) or any(filter(_has_py, subds)) and not (r/_init).exists(): (r/_init).touch()
|
|
353
356
|
if get_config().get('put_version_in_init', True): update_version(path)
|
|
354
357
|
if get_config().get('update_pyproject', True): update_proj(path.parent)
|
|
358
|
+
update_init_mdoc(path)
|
|
359
|
+
|
|
360
|
+
# %% ../nbs/api/01_config.ipynb #a01b98bb
|
|
361
|
+
def _mod_docstr(py_path):
|
|
362
|
+
"Module docstring of `py_path` via ast, without importing ('' if none or unparseable)"
|
|
363
|
+
try: return ast.get_docstring(ast.parse(Path(py_path).read_text(encoding='utf-8'))) or ''
|
|
364
|
+
except SyntaxError: return ''
|
|
365
|
+
|
|
366
|
+
def _substantive(docstr):
|
|
367
|
+
"Does `docstr` say more than the default summary line plus `Docs:` link?"
|
|
368
|
+
return len(docstr.strip().splitlines())>3
|
|
369
|
+
|
|
370
|
+
def _pkg_mods(path):
|
|
371
|
+
"`(name, summary, url)` per module of `path` with a substantive docstring: dotted name, one-line first para, `Docs:` url"
|
|
372
|
+
res = []
|
|
373
|
+
for p in sorted(Path(path).rglob('*.py')):
|
|
374
|
+
if p.name.startswith('_'): continue
|
|
375
|
+
d = _mod_docstr(p)
|
|
376
|
+
if not _substantive(d): continue
|
|
377
|
+
nm = '.'.join((Path(path).name, *p.relative_to(path).with_suffix('').parts))
|
|
378
|
+
summ = ' '.join(d.split('\n\n')[0].splitlines())
|
|
379
|
+
last = d.strip().splitlines()[-1]
|
|
380
|
+
res.append((nm, summ, last[5:].strip() if last.startswith('Docs:') else ''))
|
|
381
|
+
return res
|
|
382
|
+
|
|
383
|
+
def _index_mdoc(idx_path):
|
|
384
|
+
"Package docstring intro: bodies of md cells with an export directive, and fenced `exportd` cells, from the index nb"
|
|
385
|
+
if not idx_path or not Path(idx_path).exists(): return ''
|
|
386
|
+
docs = []
|
|
387
|
+
for c in read_nb(idx_path).cells:
|
|
388
|
+
lines = c.source.splitlines()
|
|
389
|
+
n = 0
|
|
390
|
+
while n<len(lines) and lines[n].startswith('#|'): n += 1
|
|
391
|
+
dirs = {l[2:].strip().split()[0] for l in lines[:n] if l[2:].strip()}
|
|
392
|
+
body = '\n'.join(lines[n:]).strip()
|
|
393
|
+
if not body or not {'export','exportd'}&dirs: continue
|
|
394
|
+
docs.append(body if c.cell_type=='markdown' else fenced(body, 'python'))
|
|
395
|
+
return '\n\n'.join(docs)
|
|
396
|
+
|
|
397
|
+
def _pkg_docparts(path=None, desc=None, index_nb=None):
|
|
398
|
+
"`(path, desc, intro, mods)` shared by `pkg_mdoc` and `pkg_llms`"
|
|
399
|
+
path = Path(path or get_config().lib_path)
|
|
400
|
+
mods = _pkg_mods(path)
|
|
401
|
+
if index_nb is None:
|
|
402
|
+
cfg = get_config()
|
|
403
|
+
if path==Path(cfg.lib_path): index_nb = cfg.nbs_path/cfg.readme_nb
|
|
404
|
+
intro = _index_mdoc(index_nb)
|
|
405
|
+
if (mods or intro) and desc is None: desc = get_config().description
|
|
406
|
+
return path,desc,intro,mods
|
|
407
|
+
|
|
408
|
+
def pkg_mdoc(path=None, desc=None, index_nb=None):
|
|
409
|
+
"Package docstring for `path`: `desc`, intro from `index_nb`, and a line per module with a substantive docstring"
|
|
410
|
+
path,desc,intro,mods = _pkg_docparts(path, desc, index_nb)
|
|
411
|
+
if not mods and not intro: return ''
|
|
412
|
+
mods = ['Modules:', '\n'.join(f'- `{nm}`: {summ}' for nm,summ,url in mods)] if mods else []
|
|
413
|
+
return '\n\n'.join(filter(None, [desc, intro, *mods]))
|
|
414
|
+
|
|
415
|
+
# %% ../nbs/api/01_config.ipynb #a03a0007
|
|
416
|
+
def update_init_mdoc(path=None, desc=None):
|
|
417
|
+
"Write `pkg_mdoc` as the docstring of `path/__init__.py`, replacing any existing docstring"
|
|
418
|
+
path = Path(path or get_config().lib_path)
|
|
419
|
+
mdoc = pkg_mdoc(path, desc=desc)
|
|
420
|
+
if not mdoc: return
|
|
421
|
+
fn = path/'__init__.py'
|
|
422
|
+
txt = fn.read_text(encoding='utf-8') if fn.exists() else ''
|
|
423
|
+
body = ast.parse(txt).body
|
|
424
|
+
rest = txt
|
|
425
|
+
if body and isinstance(body[0], ast.Expr) and isinstance(getattr(body[0].value, 'value', None), str):
|
|
426
|
+
rest = ''.join(txt.splitlines(keepends=True)[body[0].end_lineno:])
|
|
427
|
+
res = f'"""{mdoc}"""\n'
|
|
428
|
+
if rest.strip(): res += '\n'+rest.lstrip('\n')
|
|
429
|
+
fn.write_text(res, encoding='utf-8')
|
|
430
|
+
|
|
431
|
+
# %% ../nbs/api/01_config.ipynb #f9912ef7
|
|
432
|
+
_llms_marker = '<!-- Generated by nbdev-export; edit the index notebook, not this file -->'
|
|
433
|
+
|
|
434
|
+
def _modline(nm, summ, url):
|
|
435
|
+
"llms.txt list line: linked when `url` is a single URL, else the `Docs:` text carried verbatim"
|
|
436
|
+
if url and ' ' not in url: return f'- [{nm}]({url}): {summ}'
|
|
437
|
+
return f'- `{nm}`: {summ} (Docs: {url})' if url else f'- `{nm}`: {summ}'
|
|
438
|
+
|
|
439
|
+
def pkg_llms(path=None, desc=None, index_nb=None):
|
|
440
|
+
"llms.txt for the package at `path`, from the same sources as `pkg_mdoc` ('' if no intro or modules)"
|
|
441
|
+
path,desc,intro,mods = _pkg_docparts(path, desc, index_nb)
|
|
442
|
+
if not mods and not intro: return ''
|
|
443
|
+
if mods:
|
|
444
|
+
links = '\n'.join(_modline(*o) for o in mods)
|
|
445
|
+
mods = [f'## Modules\n\n{links}']
|
|
446
|
+
parts = [f'# {path.name}', f'> {desc}' if desc else '', intro, *mods, _llms_marker]
|
|
447
|
+
return '\n\n'.join(filter(None, parts)) + '\n'
|
|
448
|
+
|
|
449
|
+
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"
|
|
451
|
+
if fn is None: fn = get_config().nbs_path/'llms.txt'
|
|
452
|
+
txt = pkg_llms(path, desc=desc, index_nb=index_nb)
|
|
453
|
+
if not txt: return
|
|
454
|
+
if fn.exists() and _llms_marker not in fn.read_text(encoding='utf-8'): return
|
|
455
|
+
fn.write_text(txt, encoding='utf-8')
|
|
355
456
|
|
|
356
457
|
# %% ../nbs/api/01_config.ipynb #95cebda6
|
|
357
458
|
def import_obj(s):
|
|
@@ -364,7 +465,7 @@ def import_obj(s):
|
|
|
364
465
|
def write_cells(cells, hdr, file, solo_nb=False):
|
|
365
466
|
"Write `cells` to `file` along with header `hdr` (mainly for nbdev internal use)."
|
|
366
467
|
for cell in cells:
|
|
367
|
-
if cell.cell_type=='code' and cell.source.strip():
|
|
468
|
+
if cell.cell_type=='code' and cell.source.strip() and 'exportd' not in getattr(cell,'directives_',{}):
|
|
368
469
|
cell_id = f" #{cell.id}" if cell.get('id') else ""
|
|
369
470
|
file.write(f'\n\n{hdr}{cell_id}\n{cell.source}') if not solo_nb else file.write(f'\n\n{cell.source}')
|
|
370
471
|
|
|
@@ -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'
|
|
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
|
-
"
|
|
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(
|
|
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()
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
# Module docstrings
|
|
36
|
+
|
|
37
|
+
`doc(module)` shows the docstring with the API listing, and for an LLM that is usually the whole read. Treat the docstring as the module's TL;DR. Write a short summary under each major section heading and tag it `#| export`, teaching what a reader cannot guess from signatures and leaving per-function detail to the page.
|
|
38
|
+
|
|
39
|
+
`#| exportd` on a code cell puts its source in the docstring as a fenced block and keeps it out of the module code. Use it for a compact runnable demo, placed after the definitions it needs. End the summary before it with a colon so the two read as one. Only exported cells join the docstring, so the pair stays adjacent there even when definition cells sit between them in the notebook.
|
|
40
|
+
|
|
41
|
+
Preview the assembled docstring with `nbdev.export.nb_mdoc`, and run code to check each claim before writing it. `nbdev-export` adds the module to the generated package docstring and `llms.txt` once its docstring says more than the default summary line, so write these cells only for modules meant to be read on their own. For the package intro, tag the opening paragraphs of `index.ipynb` with `#| export`.
|
|
42
|
+
|
|
43
|
+
# Examples are documentation and tests
|
|
44
|
+
|
|
45
|
+
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.
|
|
46
|
+
|
|
47
|
+
# Tells
|
|
48
|
+
|
|
49
|
+
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.
|
|
50
|
+
|
|
51
|
+
- 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.
|
|
52
|
+
- Comments numbering steps mark a tutorial sequence. Give each step its own markdown and code pair.
|
|
53
|
+
- 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.
|
|
54
|
+
- A `print(x)` as a cell's last line hides the rich repr. End with bare `x` instead.
|
|
55
|
+
- 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.
|
|
56
|
+
- 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.
|
|
57
|
+
- Blank lines dividing a cell into groups mark candidate cell boundaries, and each group needs its own sentence of markdown.
|
|
58
|
+
- 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...").
|
|
59
|
+
- `try`/`except` written to demonstrate an error is what `expect_fail` is for.
|
|
60
|
+
- A triple-quoted docstring holds explanation that belongs in markdown cells. Keep the docstring to a single short line (see "Prose and docstrings").
|
|
61
|
+
- `# TODO` or `# FIXME` in an example belongs nowhere on a docs page. It is an issue, or it is fixed.
|
|
62
|
+
|
|
63
|
+
# State flows downward
|
|
64
|
+
|
|
65
|
+
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.
|
|
66
|
+
|
|
67
|
+
# Directives
|
|
68
|
+
|
|
69
|
+
`#| default_exp` names the module. `#| export` marks exported cells. Underscore-prefixed helpers may export without joining the public API. `#| exportd` exports a code cell's source to the module docstring instead of the module. `#| 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.
|
|
70
|
+
|
|
71
|
+
# nbdev v3
|
|
72
|
+
|
|
73
|
+
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`).
|
|
74
|
+
|
|
75
|
+
# Before you finish
|
|
76
|
+
|
|
77
|
+
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.
|
|
78
|
+
"""
|
|
@@ -108,8 +108,8 @@ def nbdev_test(
|
|
|
108
108
|
if len(files)==0: return print('No files were eligible for testing')
|
|
109
109
|
|
|
110
110
|
if n_workers is None: n_workers = 0 if len(files)==1 else min(num_cpus(), 8)
|
|
111
|
-
if IN_NOTEBOOK: kw = {'method':'spawn'} if os.name=='nt' else {'method':'forkserver'}
|
|
112
|
-
else: kw = {'method':'
|
|
111
|
+
if IN_NOTEBOOK: kw = {'method':'spawn'} if os.name=='nt' or sys.platform=='darwin' else {'method':'forkserver'}
|
|
112
|
+
else: kw = {'method':'spawn'} if sys.platform=='darwin' else {}
|
|
113
113
|
wd_pth = cfg.nbs_path
|
|
114
114
|
with working_directory(wd_pth if (wd_pth and wd_pth.exists()) else os.getcwd()):
|
|
115
115
|
results = parallel(test_nb, files, skip_flags=skip_flags, force_flags=force_flags, n_workers=n_workers,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nbdev
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.3
|
|
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.
|
|
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
|
|
@@ -19,7 +19,7 @@ classifiers = [
|
|
|
19
19
|
"License :: OSI Approved :: Apache Software License",
|
|
20
20
|
]
|
|
21
21
|
dynamic = ["version"]
|
|
22
|
-
dependencies = [ "fastcore>=2.1.
|
|
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]
|
nbdev-3.3.1/nbdev/__init__.py
DELETED
nbdev-3.3.1/nbdev/skill.py
DELETED
|
@@ -1,56 +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 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 deliberately: notebooks where narrative earns its keep (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: view its first few lines - 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 deliberately 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: Jupyter collapses sections by cell.
|
|
20
|
-
|
|
21
|
-
# Develop one idea at a time
|
|
22
|
-
|
|
23
|
-
The rhythm: 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 ("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; the final expression teaches: end cells with the value worth displaying, 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. Show rather than claim; executable evidence keeps documentation synchronized with behavior.
|
|
38
|
-
|
|
39
|
-
# State flows downward
|
|
40
|
-
|
|
41
|
-
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.
|
|
42
|
-
|
|
43
|
-
# Directives
|
|
44
|
-
|
|
45
|
-
`#| 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.
|
|
46
|
-
|
|
47
|
-
# nbdev v3
|
|
48
|
-
|
|
49
|
-
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` - 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`).
|
|
50
|
-
|
|
51
|
-
# Before you finish
|
|
52
|
-
|
|
53
|
-
Each new test cell ran red then green: it failed before the change and passed after. Re-read the touched section as a reader would, against the conventions above - style damage fails silently: tests pass, export succeeds, and the harm only shows on the docs page.
|
|
54
|
-
|
|
55
|
-
The governing test: pleasant and informative to read, convincing when executed, useful as the source of the library it documents.
|
|
56
|
-
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|