nbdev 2.3.25__py3-none-any.whl → 2.4.8__py3-none-any.whl
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/__init__.py +1 -1
- nbdev/_modidx.py +22 -32
- nbdev/clean.py +18 -16
- nbdev/cli.py +57 -22
- nbdev/config.py +65 -37
- nbdev/diff.py +92 -0
- nbdev/doclinks.py +109 -50
- nbdev/export.py +33 -18
- nbdev/frontmatter.py +5 -3
- nbdev/maker.py +35 -33
- nbdev/merge.py +11 -9
- nbdev/migrate.py +20 -18
- nbdev/process.py +17 -15
- nbdev/processors.py +43 -30
- nbdev/qmd.py +9 -7
- nbdev/quarto.py +68 -29
- nbdev/release.py +46 -36
- nbdev/serve.py +8 -5
- nbdev/showdoc.py +45 -162
- nbdev/sync.py +17 -11
- nbdev/test.py +6 -4
- {nbdev-2.3.25.dist-info → nbdev-2.4.8.dist-info}/METADATA +40 -19
- nbdev-2.4.8.dist-info/RECORD +30 -0
- {nbdev-2.3.25.dist-info → nbdev-2.4.8.dist-info}/WHEEL +1 -1
- {nbdev-2.3.25.dist-info → nbdev-2.4.8.dist-info}/entry_points.txt +3 -0
- nbdev-2.3.25.dist-info/RECORD +0 -29
- {nbdev-2.3.25.dist-info → nbdev-2.4.8.dist-info/licenses}/LICENSE +0 -0
- {nbdev-2.3.25.dist-info → nbdev-2.4.8.dist-info}/top_level.txt +0 -0
nbdev/config.py
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
"""
|
|
2
|
-
`get_config` is the main function for reading settings."""
|
|
1
|
+
"""Configuring nbdev and bootstrapping notebook export"""
|
|
3
2
|
|
|
4
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/01_config.ipynb.
|
|
5
4
|
|
|
6
5
|
# %% auto 0
|
|
7
|
-
__all__ = ['
|
|
8
|
-
'write_cells']
|
|
6
|
+
__all__ = ['pyproj_tmpl', 'nbdev_create_config', 'get_config', 'config_key', 'is_nbdev', 'create_output', 'show_src',
|
|
7
|
+
'update_version', 'update_proj', 'add_init', 'write_cells']
|
|
9
8
|
|
|
10
|
-
# %% ../nbs/api/01_config.ipynb
|
|
11
|
-
_doc_ = """Read and write nbdev's `settings.ini` file.
|
|
12
|
-
`get_config` is the main function for reading settings."""
|
|
13
|
-
|
|
14
|
-
# %% ../nbs/api/01_config.ipynb 3
|
|
9
|
+
# %% ../nbs/api/01_config.ipynb
|
|
15
10
|
from datetime import datetime
|
|
16
11
|
from fastcore.docments import *
|
|
17
12
|
from fastcore.utils import *
|
|
@@ -25,16 +20,16 @@ from IPython.display import Markdown
|
|
|
25
20
|
from execnb.nbio import read_nb,NbCell
|
|
26
21
|
from urllib.error import HTTPError
|
|
27
22
|
|
|
28
|
-
# %% ../nbs/api/01_config.ipynb
|
|
23
|
+
# %% ../nbs/api/01_config.ipynb
|
|
29
24
|
_nbdev_home_dir = 'nbdev' # sub-directory of xdg base dir
|
|
30
25
|
_nbdev_cfg_name = 'settings.ini'
|
|
31
26
|
|
|
32
|
-
# %% ../nbs/api/01_config.ipynb
|
|
27
|
+
# %% ../nbs/api/01_config.ipynb
|
|
33
28
|
def _git_repo():
|
|
34
29
|
try: return repo_details(run('git config --get remote.origin.url'))[1]
|
|
35
30
|
except OSError: return
|
|
36
31
|
|
|
37
|
-
# %% ../nbs/api/01_config.ipynb
|
|
32
|
+
# %% ../nbs/api/01_config.ipynb
|
|
38
33
|
# When adding a named default to the list below, be sure that that name
|
|
39
34
|
# is also added to one of the sections in `_nbdev_cfg_sections` as well,
|
|
40
35
|
# or it won't get written by `nbdev_create_config`:
|
|
@@ -54,7 +49,7 @@ def _apply_defaults(
|
|
|
54
49
|
license='apache2', # License for the package
|
|
55
50
|
copyright:str=None, # Copyright for the package, defaults to '`current_year` onwards, `author`'
|
|
56
51
|
status='3', # Development status PyPI classifier
|
|
57
|
-
min_python='3.
|
|
52
|
+
min_python='3.9', # Minimum Python version PyPI classifier
|
|
58
53
|
audience='Developers', # Intended audience PyPI classifier
|
|
59
54
|
language='English', # Language PyPI classifier
|
|
60
55
|
recursive:bool_arg=True, # Include subfolders in notebook globs?
|
|
@@ -66,7 +61,10 @@ def _apply_defaults(
|
|
|
66
61
|
jupyter_hooks:bool_arg=False, # Run Jupyter hooks?
|
|
67
62
|
clean_ids:bool_arg=True, # Remove ids from plaintext reprs?
|
|
68
63
|
clear_all:bool_arg=False, # Remove all cell metadata and cell outputs?
|
|
64
|
+
cell_number:bool_arg=True, # Add cell number to the exported file
|
|
69
65
|
put_version_in_init:bool_arg=True, # Add the version to the main __init__.py in nbdev_export
|
|
66
|
+
update_pyproject:bool_arg=True, # Create/update pyproject.toml with correct project name
|
|
67
|
+
skip_procs:str='', # A comma-separated list of processors that you want to skip
|
|
70
68
|
):
|
|
71
69
|
"Apply default settings where missing in `cfg`."
|
|
72
70
|
if getattr(cfg,'repo',None) is None:
|
|
@@ -81,7 +79,7 @@ def _apply_defaults(
|
|
|
81
79
|
cfg[k] = v
|
|
82
80
|
return cfg
|
|
83
81
|
|
|
84
|
-
# %% ../nbs/api/01_config.ipynb
|
|
82
|
+
# %% ../nbs/api/01_config.ipynb
|
|
85
83
|
def _get_info(owner, repo, default_branch='main', default_kw='nbdev'):
|
|
86
84
|
from ghapi.all import GhApi
|
|
87
85
|
api = GhApi(owner=owner, repo=repo, token=os.getenv('GITHUB_TOKEN'))
|
|
@@ -97,7 +95,7 @@ https://nbdev.fast.ai/api/release.html#setup"""]
|
|
|
97
95
|
|
|
98
96
|
return r.default_branch, default_kw if not getattr(r, 'topics', []) else ' '.join(r.topics), r.description
|
|
99
97
|
|
|
100
|
-
# %% ../nbs/api/01_config.ipynb
|
|
98
|
+
# %% ../nbs/api/01_config.ipynb
|
|
101
99
|
def _fetch_from_git(raise_err=False):
|
|
102
100
|
"Get information for settings.ini from the user."
|
|
103
101
|
res={}
|
|
@@ -113,7 +111,7 @@ def _fetch_from_git(raise_err=False):
|
|
|
113
111
|
else: res['lib_name'] = res['repo'].replace('-','_')
|
|
114
112
|
return res
|
|
115
113
|
|
|
116
|
-
# %% ../nbs/api/01_config.ipynb
|
|
114
|
+
# %% ../nbs/api/01_config.ipynb
|
|
117
115
|
def _prompt_user(cfg, inferred):
|
|
118
116
|
"Let user input values not in `cfg` or `inferred`."
|
|
119
117
|
res = cfg.copy()
|
|
@@ -127,7 +125,7 @@ def _prompt_user(cfg, inferred):
|
|
|
127
125
|
print(msg+res[k]+' # Automatically inferred from git')
|
|
128
126
|
return res
|
|
129
127
|
|
|
130
|
-
# %% ../nbs/api/01_config.ipynb
|
|
128
|
+
# %% ../nbs/api/01_config.ipynb
|
|
131
129
|
def _cfg2txt(cfg, head, sections, tail=''):
|
|
132
130
|
"Render `cfg` with commented sections."
|
|
133
131
|
nm = cfg.d.name
|
|
@@ -139,13 +137,13 @@ def _cfg2txt(cfg, head, sections, tail=''):
|
|
|
139
137
|
res += tail
|
|
140
138
|
return res.strip()
|
|
141
139
|
|
|
142
|
-
# %% ../nbs/api/01_config.ipynb
|
|
140
|
+
# %% ../nbs/api/01_config.ipynb
|
|
143
141
|
_nbdev_cfg_head = '''# All sections below are required unless otherwise specified.
|
|
144
|
-
# See https://github.com/
|
|
142
|
+
# See https://github.com/AnswerDotAI/nbdev/blob/main/settings.ini for examples.
|
|
145
143
|
|
|
146
144
|
'''
|
|
147
145
|
_nbdev_cfg_sections = {'Python library': 'repo lib_name version min_python license black_formatting',
|
|
148
|
-
'nbdev': 'doc_path lib_path nbs_path recursive tst_flags put_version_in_init',
|
|
146
|
+
'nbdev': 'doc_path lib_path nbs_path recursive tst_flags put_version_in_init update_pyproject',
|
|
149
147
|
'Docs': 'branch custom_sidebar doc_host doc_baseurl git_url title',
|
|
150
148
|
'PyPI': 'audience author author_email copyright description keywords language status user'}
|
|
151
149
|
_nbdev_cfg_tail = '''### Optional ###
|
|
@@ -156,7 +154,7 @@ _nbdev_cfg_tail = '''### Optional ###
|
|
|
156
154
|
# package_data =
|
|
157
155
|
'''
|
|
158
156
|
|
|
159
|
-
# %% ../nbs/api/01_config.ipynb
|
|
157
|
+
# %% ../nbs/api/01_config.ipynb
|
|
160
158
|
@call_parse
|
|
161
159
|
@delegates(_apply_defaults, but='cfg')
|
|
162
160
|
def nbdev_create_config(
|
|
@@ -182,19 +180,17 @@ def nbdev_create_config(
|
|
|
182
180
|
cfg_fn = Path(path)/cfg_name
|
|
183
181
|
print(f'{cfg_fn} created.')
|
|
184
182
|
|
|
185
|
-
# %% ../nbs/api/01_config.ipynb
|
|
183
|
+
# %% ../nbs/api/01_config.ipynb
|
|
186
184
|
def _nbdev_config_file(cfg_name=_nbdev_cfg_name, path=None):
|
|
187
|
-
cfg_path =
|
|
188
|
-
|
|
189
|
-
if not (cfg_path/cfg_name).exists(): cfg_path = path
|
|
190
|
-
return cfg_path/cfg_name
|
|
185
|
+
cfg_path = Path.cwd() if path is None else Path(path)
|
|
186
|
+
return getattr(Config.find(cfg_name), 'config_file', cfg_path/cfg_name)
|
|
191
187
|
|
|
192
|
-
# %% ../nbs/api/01_config.ipynb
|
|
188
|
+
# %% ../nbs/api/01_config.ipynb
|
|
193
189
|
def _xdg_config_paths(cfg_name=_nbdev_cfg_name):
|
|
194
190
|
xdg_config_paths = reversed([xdg_config_home()]+xdg_config_dirs())
|
|
195
191
|
return [o/_nbdev_home_dir/cfg_name for o in xdg_config_paths]
|
|
196
192
|
|
|
197
|
-
# %% ../nbs/api/01_config.ipynb
|
|
193
|
+
# %% ../nbs/api/01_config.ipynb
|
|
198
194
|
def _type(t): return bool if t==bool_arg else t
|
|
199
195
|
_types = {k:_type(v['anno']) for k,v in docments(_apply_defaults,full=True,returns=False).items() if k != 'cfg'}
|
|
200
196
|
|
|
@@ -206,24 +202,44 @@ def get_config(cfg_name=_nbdev_cfg_name, path=None):
|
|
|
206
202
|
cfg = Config(cfg_file.parent, cfg_file.name, extra_files=extra_files, types=_types)
|
|
207
203
|
return _apply_defaults(cfg)
|
|
208
204
|
|
|
209
|
-
# %% ../nbs/api/01_config.ipynb
|
|
205
|
+
# %% ../nbs/api/01_config.ipynb
|
|
210
206
|
def config_key(c, default=None, path=True, missing_ok=None):
|
|
211
207
|
"Deprecated: use `get_config().get` or `get_config().path` instead."
|
|
212
208
|
warn("`config_key` is deprecated. Use `get_config().get` or `get_config().path` instead.", DeprecationWarning)
|
|
213
209
|
return get_config().path(c, default) if path else get_config().get(c, default)
|
|
214
210
|
|
|
215
|
-
# %% ../nbs/api/01_config.ipynb
|
|
211
|
+
# %% ../nbs/api/01_config.ipynb
|
|
212
|
+
def is_nbdev(): return _nbdev_config_file().exists()
|
|
213
|
+
|
|
214
|
+
# %% ../nbs/api/01_config.ipynb
|
|
216
215
|
def create_output(txt, mime):
|
|
217
216
|
"Add a cell output containing `txt` of the `mime` text MIME sub-type"
|
|
218
217
|
return [{"data": { f"text/{mime}": str(txt).splitlines(True) },
|
|
219
218
|
"execution_count": 1, "metadata": {}, "output_type": "execute_result"}]
|
|
220
219
|
|
|
221
|
-
# %% ../nbs/api/01_config.ipynb
|
|
220
|
+
# %% ../nbs/api/01_config.ipynb
|
|
222
221
|
def show_src(src, lang='python'): return Markdown(f'```{lang}\n{src}\n```')
|
|
223
222
|
|
|
224
|
-
# %% ../nbs/api/01_config.ipynb
|
|
225
|
-
|
|
223
|
+
# %% ../nbs/api/01_config.ipynb
|
|
224
|
+
pyproj_tmpl = """[build-system]
|
|
225
|
+
requires = ["setuptools>=64.0"]
|
|
226
|
+
build-backend = "setuptools.build_meta"
|
|
227
|
+
|
|
228
|
+
[project]
|
|
229
|
+
name = "FILL_IN"
|
|
230
|
+
requires-python="FILL_IN"
|
|
231
|
+
dynamic = [ "keywords", "description", "version", "dependencies", "optional-dependencies", "readme", "license", "authors", "classifiers", "entry-points", "scripts", "urls"]
|
|
232
|
+
|
|
233
|
+
[tool.uv]
|
|
234
|
+
cache-keys = [{ file = "pyproject.toml" }, { file = "settings.ini" }, { file = "setup.py" }]
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
# %% ../nbs/api/01_config.ipynb
|
|
238
|
+
_re_version = re.compile(r'^__version__\s*=.*$', re.MULTILINE)
|
|
239
|
+
_re_proj = re.compile(r'^name\s*=\s*".*$', re.MULTILINE)
|
|
240
|
+
_re_reqpy = re.compile(r'^requires-python\s*=\s*".*$', re.MULTILINE)
|
|
226
241
|
_init = '__init__.py'
|
|
242
|
+
_pyproj = 'pyproject.toml'
|
|
227
243
|
|
|
228
244
|
def update_version(path=None):
|
|
229
245
|
"Add or update `__version__` in the main `__init__.py` of the library."
|
|
@@ -238,6 +254,15 @@ def update_version(path=None):
|
|
|
238
254
|
|
|
239
255
|
def _has_py(fs): return any(1 for f in fs if f.endswith('.py'))
|
|
240
256
|
|
|
257
|
+
def update_proj(path):
|
|
258
|
+
"Create or update `pyproject.toml` in the project root."
|
|
259
|
+
fname = path/_pyproj
|
|
260
|
+
if not fname.exists(): fname.write_text(pyproj_tmpl)
|
|
261
|
+
txt = fname.read_text()
|
|
262
|
+
txt = _re_proj.sub(f'name="{get_config().lib_name}"', txt)
|
|
263
|
+
txt = _re_reqpy.sub(f'requires-python=">={get_config().min_python}"', txt)
|
|
264
|
+
fname.write_text(txt)
|
|
265
|
+
|
|
241
266
|
def add_init(path=None):
|
|
242
267
|
"Add `__init__.py` in all subdirs of `path` containing python files if it's not there already."
|
|
243
268
|
# we add the lowest-level `__init__.py` files first, which ensures _has_py succeeds for parent modules
|
|
@@ -249,14 +274,17 @@ def add_init(path=None):
|
|
|
249
274
|
subds = (os.listdir(r/d) for d in ds)
|
|
250
275
|
if _has_py(fs) or any(filter(_has_py, subds)) and not (r/_init).exists(): (r/_init).touch()
|
|
251
276
|
if get_config().get('put_version_in_init', True): update_version(path)
|
|
277
|
+
if get_config().get('update_pyproject', True): update_proj(path.parent)
|
|
252
278
|
|
|
253
|
-
# %% ../nbs/api/01_config.ipynb
|
|
254
|
-
def write_cells(cells, hdr, file, offset=0):
|
|
279
|
+
# %% ../nbs/api/01_config.ipynb
|
|
280
|
+
def write_cells(cells, hdr, file, offset=0, cell_number=True, solo_nb=False):
|
|
255
281
|
"Write `cells` to `file` along with header `hdr` starting at index `offset` (mainly for nbdev internal use)."
|
|
256
282
|
for cell in cells:
|
|
257
|
-
if cell.source.strip():
|
|
283
|
+
if cell.cell_type=='code' and cell.source.strip():
|
|
284
|
+
idx = f" {cell.idx_+offset}" if cell_number else ""
|
|
285
|
+
file.write(f'\n\n{hdr}{idx}\n{cell.source}') if not solo_nb else file.write(f'\n\n{cell.source}')
|
|
258
286
|
|
|
259
|
-
# %% ../nbs/api/01_config.ipynb
|
|
287
|
+
# %% ../nbs/api/01_config.ipynb
|
|
260
288
|
def _basic_export_nb(fname, name, dest=None):
|
|
261
289
|
"Basic exporter to bootstrap nbdev."
|
|
262
290
|
if dest is None: dest = get_config().lib_path
|
nbdev/diff.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Get ipynb diffs by cell"""
|
|
2
|
+
|
|
3
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/19_diff.ipynb.
|
|
4
|
+
|
|
5
|
+
# %% auto 0
|
|
6
|
+
__all__ = ['read_nb_from_git', 'nbs_pair', 'changed_cells', 'source_diff', 'cell_diffs']
|
|
7
|
+
|
|
8
|
+
# %% ../nbs/api/19_diff.ipynb
|
|
9
|
+
import json
|
|
10
|
+
from fastcore.utils import *
|
|
11
|
+
from fastcore.meta import delegates
|
|
12
|
+
from difflib import unified_diff
|
|
13
|
+
from fastgit import Git
|
|
14
|
+
from execnb.nbio import *
|
|
15
|
+
|
|
16
|
+
# %% ../nbs/api/19_diff.ipynb
|
|
17
|
+
def read_nb_from_git(
|
|
18
|
+
g:Git, # The git object
|
|
19
|
+
path, # The path to the notebook (absolute or relative to git root)
|
|
20
|
+
ref=None # The git ref to read from (e.g. HEAD); None for working dir
|
|
21
|
+
)->AttrDict: # The notebook
|
|
22
|
+
"Read notebook from git ref (e.g. HEAD) at path, or working dir if ref is None"
|
|
23
|
+
path = Path(path)
|
|
24
|
+
if path.is_absolute(): path = path.relative_to(g.top())
|
|
25
|
+
if ref is None: return read_nb(g.top()/path)
|
|
26
|
+
raw = g.show(f'{ref}:{path}', split=False)
|
|
27
|
+
return dict2nb(json.loads(raw))
|
|
28
|
+
|
|
29
|
+
# %% ../nbs/api/19_diff.ipynb
|
|
30
|
+
def _nb_srcdict(g:Git, nb_path, ref=None, f=noop):
|
|
31
|
+
"Dict of id->source"
|
|
32
|
+
nb = read_nb_from_git(g, nb_path, ref)
|
|
33
|
+
return {c['id']: f(c) for c in nb.cells}
|
|
34
|
+
|
|
35
|
+
# %% ../nbs/api/19_diff.ipynb
|
|
36
|
+
def nbs_pair(
|
|
37
|
+
nb_path, # Path to the notebook
|
|
38
|
+
ref_a='HEAD', # First git ref (None for working dir)
|
|
39
|
+
ref_b=None, # Second git ref (None for working dir)
|
|
40
|
+
f=noop # Function to call on contents
|
|
41
|
+
): # Tuple of two notebooks
|
|
42
|
+
"NBs at two refs; None means working dir. By default provides HEAD and working dir"
|
|
43
|
+
nb_path = Path(nb_path).resolve()
|
|
44
|
+
g = Git(nb_path.parent)
|
|
45
|
+
return _nb_srcdict(g, nb_path, ref_a, f), _nb_srcdict(g, nb_path, ref_b, f)
|
|
46
|
+
|
|
47
|
+
# %% ../nbs/api/19_diff.ipynb
|
|
48
|
+
def _cell_changes(
|
|
49
|
+
nb_path, # Path to the notebook
|
|
50
|
+
fn, # function to call to get dict values
|
|
51
|
+
ref_a='HEAD', # First git ref (None for working dir)
|
|
52
|
+
ref_b=None, # Second git ref (None for working dir)
|
|
53
|
+
adds=True, # Include cells in b but not in a
|
|
54
|
+
changes=True, # Include cells with different content
|
|
55
|
+
dels=False, # Include cells in a but not in b
|
|
56
|
+
metadata=False, # Consider cell metadata when comparing
|
|
57
|
+
outputs=False # Consider cell outputs when comparing
|
|
58
|
+
): # Dict of results
|
|
59
|
+
"Apply fn(cell_id, old_content, new_content) to changed cells between two refs"
|
|
60
|
+
def cell_content(c):
|
|
61
|
+
res = c.get('source', '')
|
|
62
|
+
if metadata: res += '\n# metadata: ' + json.dumps(c.get('metadata', {}), sort_keys=True)
|
|
63
|
+
if outputs: res += '\n# outputs: ' + json.dumps(c.get('outputs', []), sort_keys=True)
|
|
64
|
+
return res
|
|
65
|
+
old,new = nbs_pair(nb_path, ref_a, ref_b, f=cell_content)
|
|
66
|
+
res = {}
|
|
67
|
+
if adds: res |= {cid: fn(cid, '', new[cid]) for cid in new if cid not in old}
|
|
68
|
+
if changes: res |= {cid: fn(cid, old[cid], new[cid]) for cid in new if cid in old and new[cid] != old[cid]}
|
|
69
|
+
if dels: res |= {cid: fn(cid, old[cid], '') for cid in old if cid not in new}
|
|
70
|
+
return res
|
|
71
|
+
|
|
72
|
+
# %% ../nbs/api/19_diff.ipynb
|
|
73
|
+
@delegates(_cell_changes)
|
|
74
|
+
def changed_cells(nb_path, **kwargs):
|
|
75
|
+
"Return set of cell IDs for changed/added/deleted cells between two refs"
|
|
76
|
+
def f(cid,o,n): return cid
|
|
77
|
+
return set(_cell_changes(nb_path, f, **kwargs).keys())
|
|
78
|
+
|
|
79
|
+
# %% ../nbs/api/19_diff.ipynb
|
|
80
|
+
def source_diff(
|
|
81
|
+
old_source, # Original source string
|
|
82
|
+
new_source # New source string
|
|
83
|
+
): # Unified diff string
|
|
84
|
+
"Return unified diff string for source change"
|
|
85
|
+
return '\n'.join(unified_diff(old_source.splitlines(), new_source.splitlines(), lineterm=''))
|
|
86
|
+
|
|
87
|
+
# %% ../nbs/api/19_diff.ipynb
|
|
88
|
+
@delegates(_cell_changes)
|
|
89
|
+
def cell_diffs(nb_path, **kwargs):
|
|
90
|
+
"{cell_id:diff} for changed/added/deleted cells between two refs"
|
|
91
|
+
def f(cid,o,n): return source_diff(o,n)
|
|
92
|
+
return _cell_changes(nb_path, f, **kwargs)
|
nbdev/doclinks.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
"""Generating a documentation index from a module"""
|
|
2
|
+
|
|
1
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/05_doclinks.ipynb.
|
|
2
4
|
|
|
3
5
|
# %% auto 0
|
|
4
|
-
__all__ = ['patch_name', 'nbglob', 'nbglob_cli', 'nbdev_export', 'NbdevLookup']
|
|
6
|
+
__all__ = ['typs', 'bset', 'patch_name', 'nbglob', 'nbglob_cli', 'nbdev_export', 'create_index', 'NbdevLookup']
|
|
5
7
|
|
|
6
|
-
# %% ../nbs/api/05_doclinks.ipynb
|
|
8
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
7
9
|
from .config import *
|
|
8
10
|
from .maker import *
|
|
9
11
|
from .export import *
|
|
@@ -12,16 +14,22 @@ from .imports import *
|
|
|
12
14
|
from fastcore.script import *
|
|
13
15
|
from fastcore.utils import *
|
|
14
16
|
from fastcore.meta import delegates
|
|
17
|
+
from fastcore.net import urlread
|
|
15
18
|
|
|
16
|
-
import ast,contextlib
|
|
17
|
-
import
|
|
18
|
-
from astunparse import unparse
|
|
19
|
+
import ast,builtins,contextlib
|
|
20
|
+
import importlib
|
|
19
21
|
|
|
22
|
+
from astunparse import unparse
|
|
23
|
+
from io import BytesIO
|
|
24
|
+
from collections import defaultdict
|
|
20
25
|
from pprint import pformat
|
|
21
26
|
from urllib.parse import urljoin
|
|
22
27
|
from functools import lru_cache
|
|
28
|
+
from types import ModuleType
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
from importlib.metadata import entry_points
|
|
31
|
+
|
|
32
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
25
33
|
def _sym_nm(klas, sym): return f'{unparse(klas).strip()}.{sym.name}'
|
|
26
34
|
|
|
27
35
|
def _binop_leafs(bo, o):
|
|
@@ -42,26 +50,32 @@ def patch_name(o):
|
|
|
42
50
|
else: return o.name
|
|
43
51
|
return _sym_nm(a,o)
|
|
44
52
|
|
|
45
|
-
# %% ../nbs/api/05_doclinks.ipynb
|
|
53
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
46
54
|
def _iter_py_cells(p):
|
|
47
55
|
"Yield cells from an exported Python file."
|
|
48
56
|
p = Path(p)
|
|
49
57
|
cells = p.read_text(encoding='utf-8').split("\n# %% ")
|
|
58
|
+
has_cell_number = get_config().cell_number
|
|
50
59
|
for cell in cells[1:]:
|
|
51
60
|
top,code = cell.split('\n', 1)
|
|
52
61
|
try:
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
if has_cell_number:
|
|
63
|
+
*nb,idx = top.split()
|
|
64
|
+
nb = ' '.join(nb)
|
|
65
|
+
idx = int(idx)
|
|
66
|
+
else:
|
|
67
|
+
nb = top
|
|
68
|
+
idx = None
|
|
55
69
|
except ValueError: raise ValueError(f"Unexpected format in '{p}' at cell:\n```\n# %% {cell.strip()}.\n```\n"
|
|
56
70
|
"The expected format is: '# %% {nb_path} {cell_idx}'.")
|
|
57
71
|
nb_path = None if nb=='auto' else (p.parent/nb).resolve() # NB paths are stored relative to .py file
|
|
58
72
|
if code.endswith('\n'): code=code[:-1]
|
|
59
|
-
yield AttrDict(nb=nb, idx=
|
|
73
|
+
yield AttrDict(nb=nb, idx=idx, code=code, nb_path=nb_path, py_path=p.resolve())
|
|
60
74
|
|
|
61
|
-
# %% ../nbs/api/05_doclinks.ipynb
|
|
75
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
62
76
|
def _nbpath2html(p): return p.with_name(re.sub(r'^\d+[a-zA-Z0-9]*_', '', p.name.lower())).with_suffix('.html')
|
|
63
77
|
|
|
64
|
-
# %% ../nbs/api/05_doclinks.ipynb
|
|
78
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
65
79
|
def _get_modidx(py_path, code_root, nbs_path):
|
|
66
80
|
"Get module symbol index for a Python source file"
|
|
67
81
|
cfg = get_config()
|
|
@@ -71,7 +85,7 @@ def _get_modidx(py_path, code_root, nbs_path):
|
|
|
71
85
|
_def_types = ast.FunctionDef,ast.AsyncFunctionDef,ast.ClassDef
|
|
72
86
|
d = {}
|
|
73
87
|
for cell in _iter_py_cells(py_path):
|
|
74
|
-
if cell.nb
|
|
88
|
+
if 'auto' in cell.nb: continue
|
|
75
89
|
loc = _nbpath2html(cell.nb_path.relative_to(nbs_path))
|
|
76
90
|
|
|
77
91
|
def _stor(nm):
|
|
@@ -83,7 +97,7 @@ def _get_modidx(py_path, code_root, nbs_path):
|
|
|
83
97
|
if isinstance(t2, _def_types): _stor(f'{tree.name}.{t2.name}')
|
|
84
98
|
return {mod_name: d}
|
|
85
99
|
|
|
86
|
-
# %% ../nbs/api/05_doclinks.ipynb
|
|
100
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
87
101
|
def _build_modidx(dest=None, nbs_path=None, skip_exists=False):
|
|
88
102
|
"Create _modidx.py"
|
|
89
103
|
if dest is None: dest = get_config().lib_path
|
|
@@ -98,11 +112,12 @@ def _build_modidx(dest=None, nbs_path=None, skip_exists=False):
|
|
|
98
112
|
res['settings'] = {k:v for k,v in get_config().d.items()
|
|
99
113
|
if k in ('doc_host','doc_baseurl','lib_path','git_url','branch')}
|
|
100
114
|
code_root = dest.parent.resolve()
|
|
101
|
-
for file in globtastic(dest, file_glob="*.py", skip_file_re='^_', skip_folder_re="\.ipynb_checkpoints"):
|
|
102
|
-
res['syms'].update(_get_modidx((dest.parent/file).resolve(), code_root, nbs_path=nbs_path))
|
|
115
|
+
for file in globtastic(dest, file_glob="*.py", skip_file_re='^_', skip_folder_re=r"\.ipynb_checkpoints"):
|
|
116
|
+
try: res['syms'].update(_get_modidx((dest.parent/file).resolve(), code_root, nbs_path=nbs_path))
|
|
117
|
+
except ValueError: pass
|
|
103
118
|
idxfile.write_text("# Autogenerated by nbdev\n\nd = "+pformat(res, width=140, indent=2, compact=True)+'\n')
|
|
104
119
|
|
|
105
|
-
# %% ../nbs/api/05_doclinks.ipynb
|
|
120
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
106
121
|
@delegates(globtastic)
|
|
107
122
|
def nbglob(path=None, skip_folder_re = '^[_.]', file_glob='*.ipynb', skip_file_re='^[_.]', key='nbs_path', as_path=False, **kwargs):
|
|
108
123
|
"Find all files in a directory matching an extension given a config key."
|
|
@@ -112,7 +127,7 @@ def nbglob(path=None, skip_folder_re = '^[_.]', file_glob='*.ipynb', skip_file_r
|
|
|
112
127
|
skip_file_re=skip_file_re, recursive=recursive, **kwargs)
|
|
113
128
|
return res.map(Path) if as_path else res
|
|
114
129
|
|
|
115
|
-
# %% ../nbs/api/05_doclinks.ipynb
|
|
130
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
116
131
|
def nbglob_cli(
|
|
117
132
|
path:str=None, # Path to notebooks
|
|
118
133
|
symlinks:bool=False, # Follow symlinks?
|
|
@@ -126,7 +141,7 @@ def nbglob_cli(
|
|
|
126
141
|
return nbglob(path, symlinks=symlinks, file_glob=file_glob, file_re=file_re, folder_re=folder_re,
|
|
127
142
|
skip_file_glob=skip_file_glob, skip_file_re=skip_file_re, skip_folder_re=skip_folder_re)
|
|
128
143
|
|
|
129
|
-
# %% ../nbs/api/05_doclinks.ipynb
|
|
144
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
130
145
|
@call_parse
|
|
131
146
|
@delegates(nbglob_cli)
|
|
132
147
|
def nbdev_export(
|
|
@@ -135,19 +150,42 @@ def nbdev_export(
|
|
|
135
150
|
**kwargs):
|
|
136
151
|
"Export notebooks in `path` to Python modules"
|
|
137
152
|
if os.environ.get('IN_TEST',0): return
|
|
153
|
+
if not is_nbdev(): raise Exception('`nbdev_export` must be called from a directory within a nbdev project.')
|
|
138
154
|
if procs:
|
|
139
|
-
|
|
140
|
-
|
|
155
|
+
import nbdev.export
|
|
156
|
+
procs = [getattr(nbdev.export, p) for p in L(procs)]
|
|
141
157
|
files = nbglob(path=path, as_path=True, **kwargs).sorted('name')
|
|
142
158
|
for f in files: nb_export(f, procs=procs)
|
|
143
159
|
add_init(get_config().lib_path)
|
|
144
160
|
_build_modidx()
|
|
145
161
|
|
|
146
|
-
# %% ../nbs/api/05_doclinks.ipynb
|
|
162
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
163
|
+
typs = 'module','class','method','function'
|
|
164
|
+
bset = set(dir(builtins))
|
|
165
|
+
|
|
166
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
167
|
+
def create_index(url, pre=None):
|
|
168
|
+
"Create a documentation index from a sphinx inventory file at `url`, with optional prefix `pre`"
|
|
169
|
+
try: from sphinx.util.inventory import InventoryFile
|
|
170
|
+
except ImportError: raise ImportError('`sphinx` is a dependency for building indexes. Run `pip install sphinx` to use `create_index`.')
|
|
171
|
+
pre = ifnone(pre, f"{url}/")
|
|
172
|
+
invs = urlread(f'{url}/objects.inv', decode=False)
|
|
173
|
+
idx = InventoryFile.load(stream=BytesIO(invs), uri=pre, joinfunc=urljoin)
|
|
174
|
+
_get = lambda o: {k:v[2] for k,v in idx[f'py:{o}'].items() if k[0]!='_'}
|
|
175
|
+
d = {o:_get(o) for o in typs}
|
|
176
|
+
syms = defaultdict(dict)
|
|
177
|
+
for o in typs:
|
|
178
|
+
for k,v in d[o].items():
|
|
179
|
+
if k.split('.')[0] in bset: k = 'builtins.' + k
|
|
180
|
+
modparts = k.split(".")[:-2 if o=='method' else -1]
|
|
181
|
+
if modparts: syms['.'.join(modparts)][k] = v
|
|
182
|
+
return syms
|
|
183
|
+
|
|
184
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
147
185
|
import importlib,ast
|
|
148
186
|
from functools import lru_cache
|
|
149
187
|
|
|
150
|
-
# %% ../nbs/api/05_doclinks.ipynb
|
|
188
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
151
189
|
def _find_mod(mod):
|
|
152
190
|
mp,_,mr = mod.partition('/')
|
|
153
191
|
spec = importlib.util.find_spec(mp)
|
|
@@ -157,6 +195,7 @@ def _find_mod(mod):
|
|
|
157
195
|
|
|
158
196
|
@lru_cache(None)
|
|
159
197
|
def _get_exps(mod):
|
|
198
|
+
"Get the line numbers for function and class definitions in module"
|
|
160
199
|
mf = _find_mod(mod)
|
|
161
200
|
if not mf: return {}
|
|
162
201
|
txt = mf.read_text(encoding='utf-8')
|
|
@@ -170,8 +209,9 @@ def _get_exps(mod):
|
|
|
170
209
|
|
|
171
210
|
def _lineno(sym, fname): return _get_exps(fname).get(sym, None) if fname else None
|
|
172
211
|
|
|
173
|
-
# %% ../nbs/api/05_doclinks.ipynb
|
|
212
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
174
213
|
def _qual_sym(s, settings):
|
|
214
|
+
"Get qualified nb, py, and github paths for a symbol s"
|
|
175
215
|
if not isinstance(s,tuple): return s
|
|
176
216
|
nb,py = s
|
|
177
217
|
nbbase = urljoin(settings["doc_host"]+'/',settings["doc_baseurl"])
|
|
@@ -185,35 +225,53 @@ def _qual_syms(entries):
|
|
|
185
225
|
if 'doc_host' not in settings: return entries
|
|
186
226
|
return {'syms': {mod:_qual_mod(d, settings) for mod,d in entries['syms'].items()}, 'settings':settings}
|
|
187
227
|
|
|
188
|
-
# %% ../nbs/api/05_doclinks.ipynb
|
|
189
|
-
_re_backticks = re.compile(r'`([^`\s]
|
|
228
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
229
|
+
_re_backticks = re.compile(r'`([^`\s]+?)(?:\(\))?`')
|
|
190
230
|
|
|
191
|
-
# %% ../nbs/api/05_doclinks.ipynb
|
|
231
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
192
232
|
@lru_cache(None)
|
|
233
|
+
def _build_lookup_table(strip_libs=None, incl_libs=None, skip_mods=None):
|
|
234
|
+
cfg = get_config()
|
|
235
|
+
if strip_libs is None:
|
|
236
|
+
try: strip_libs = cfg.get('strip_libs', cfg.get('lib_path', 'nbdev').name).split()
|
|
237
|
+
except FileNotFoundError: strip_libs = 'nbdev'
|
|
238
|
+
skip_mods = setify(skip_mods)
|
|
239
|
+
strip_libs = L(strip_libs)
|
|
240
|
+
if incl_libs is not None: incl_libs = (L(incl_libs)+strip_libs).unique()
|
|
241
|
+
entries = {}
|
|
242
|
+
try: eps = entry_points(group='nbdev')
|
|
243
|
+
# Python 3.9 fallback - entry_points() doesn't accept group parameter
|
|
244
|
+
except TypeError: eps = entry_points().get('nbdev', [])
|
|
245
|
+
|
|
246
|
+
for o in eps:
|
|
247
|
+
if incl_libs is not None and o.dist.name not in incl_libs: continue
|
|
248
|
+
try: entries[o.name] = _qual_syms(o.load())
|
|
249
|
+
except Exception: pass
|
|
250
|
+
py_syms = merge(*L(o['syms'].values() for o in entries.values()).concat())
|
|
251
|
+
for m in strip_libs:
|
|
252
|
+
if m in entries:
|
|
253
|
+
_d = entries[m]
|
|
254
|
+
stripped = {}
|
|
255
|
+
for mod, dets in _d['syms'].items():
|
|
256
|
+
if mod not in skip_mods:
|
|
257
|
+
for k,v in dets.items():
|
|
258
|
+
k = remove_prefix(k,f"{mod}.")
|
|
259
|
+
if k not in stripped: stripped[k] = v
|
|
260
|
+
py_syms = merge(stripped, py_syms)
|
|
261
|
+
return entries,py_syms
|
|
262
|
+
|
|
263
|
+
# %% ../nbs/api/05_doclinks.ipynb
|
|
193
264
|
class NbdevLookup:
|
|
194
265
|
"Mapping from symbol names to docs and source URLs"
|
|
195
|
-
def __init__(self, strip_libs=None, incl_libs=None, skip_mods=None):
|
|
196
|
-
|
|
197
|
-
if
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
self.entries = {o.name: _qual_syms(o.resolve()) for o in list(pkg_resources.iter_entry_points(group='nbdev'))
|
|
205
|
-
if incl_libs is None or o.dist.key in incl_libs}
|
|
206
|
-
py_syms = merge(*L(o['syms'].values() for o in self.entries.values()).concat())
|
|
207
|
-
for m in strip_libs:
|
|
208
|
-
if m in self.entries:
|
|
209
|
-
_d = self.entries[m]
|
|
210
|
-
stripped = {remove_prefix(k,f"{mod}."):v
|
|
211
|
-
for mod,dets in _d['syms'].items() if mod not in skip_mods
|
|
212
|
-
for k,v in dets.items()}
|
|
213
|
-
py_syms = merge(stripped, py_syms)
|
|
214
|
-
self.syms = py_syms
|
|
215
|
-
|
|
216
|
-
def __getitem__(self, s): return self.syms.get(s, None)
|
|
266
|
+
def __init__(self, strip_libs=None, incl_libs=None, skip_mods=None, ns=None):
|
|
267
|
+
self.entries,self.syms = _build_lookup_table(strip_libs, incl_libs, skip_mods)
|
|
268
|
+
self.aliases = {n:o.__name__ for n,o in (ns or {}).items() if isinstance(o, ModuleType)}
|
|
269
|
+
|
|
270
|
+
def __getitem__(self, s):
|
|
271
|
+
if '.' in s:
|
|
272
|
+
pre,post = s.split('.', 1)
|
|
273
|
+
if pre in self.aliases: s = f"{self.aliases[pre]}.{post}"
|
|
274
|
+
return self.syms.get(s, None)
|
|
217
275
|
|
|
218
276
|
def doc(self, sym):
|
|
219
277
|
"Link to docs for `sym`"
|
|
@@ -227,12 +285,13 @@ class NbdevLookup:
|
|
|
227
285
|
_,py,gh = res
|
|
228
286
|
line = _lineno(sym, py)
|
|
229
287
|
return f'{gh}#L{line}'
|
|
230
|
-
|
|
288
|
+
|
|
231
289
|
def _link_sym(self, m):
|
|
232
290
|
l = m.group(1)
|
|
233
291
|
s = self.doc(l)
|
|
234
292
|
if s is None: return m.group(0)
|
|
235
293
|
l = l.replace('\\', r'\\')
|
|
294
|
+
if m.group(0).endswith('()`'): l += '()'
|
|
236
295
|
return rf"[`{l}`]({s})"
|
|
237
296
|
|
|
238
297
|
def link_line(self, l): return _re_backticks.sub(self._link_sym, l)
|