nbdev 2.4.3__py3-none-any.whl → 2.4.11__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 +8 -35
- nbdev/clean.py +23 -20
- nbdev/cli.py +25 -19
- nbdev/config.py +29 -28
- nbdev/diff.py +92 -0
- nbdev/doclinks.py +35 -34
- nbdev/export.py +8 -8
- nbdev/frontmatter.py +4 -4
- nbdev/maker.py +22 -22
- nbdev/merge.py +10 -10
- nbdev/migrate.py +18 -18
- nbdev/process.py +16 -16
- nbdev/processors.py +26 -26
- nbdev/qmd.py +9 -9
- nbdev/quarto.py +27 -27
- nbdev/release.py +36 -32
- nbdev/serve.py +5 -5
- nbdev/showdoc.py +11 -199
- nbdev/sync.py +14 -11
- nbdev/test.py +5 -5
- {nbdev-2.4.3.dist-info → nbdev-2.4.11.dist-info}/METADATA +3 -2
- nbdev-2.4.11.dist-info/RECORD +30 -0
- nbdev-2.4.3.dist-info/RECORD +0 -29
- {nbdev-2.4.3.dist-info → nbdev-2.4.11.dist-info}/WHEEL +0 -0
- {nbdev-2.4.3.dist-info → nbdev-2.4.11.dist-info}/entry_points.txt +0 -0
- {nbdev-2.4.3.dist-info → nbdev-2.4.11.dist-info}/licenses/LICENSE +0 -0
- {nbdev-2.4.3.dist-info → nbdev-2.4.11.dist-info}/top_level.txt +0 -0
nbdev/release.py
CHANGED
|
@@ -2,30 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/18_release.ipynb.
|
|
4
4
|
|
|
5
|
-
# %% auto 0
|
|
5
|
+
# %% auto #0
|
|
6
6
|
__all__ = ['GH_HOST', 'CONDA_WARNING', 'Release', 'changelog', 'release_git', 'release_gh', 'pypi_json', 'latest_pypi',
|
|
7
7
|
'pypi_details', 'conda_output_path', 'write_conda_meta', 'write_requirements', 'anaconda_upload',
|
|
8
8
|
'release_conda', 'chk_conda_rel', 'release_pypi', 'release_both', 'bump_version', 'nbdev_bump_version']
|
|
9
9
|
|
|
10
|
-
# %% ../nbs/api/18_release.ipynb
|
|
10
|
+
# %% ../nbs/api/18_release.ipynb #c35cc2b8
|
|
11
11
|
from fastcore.all import *
|
|
12
12
|
from ghapi.core import *
|
|
13
13
|
|
|
14
14
|
from datetime import datetime
|
|
15
|
+
from packaging.version import Version
|
|
15
16
|
import shutil,subprocess
|
|
16
17
|
|
|
17
18
|
from .doclinks import *
|
|
18
19
|
|
|
19
|
-
# %% ../nbs/api/18_release.ipynb
|
|
20
|
+
# %% ../nbs/api/18_release.ipynb #7e554523
|
|
20
21
|
GH_HOST = "https://api.github.com"
|
|
21
22
|
|
|
22
|
-
# %% ../nbs/api/18_release.ipynb
|
|
23
|
+
# %% ../nbs/api/18_release.ipynb #e220cefa
|
|
23
24
|
def _find_config(cfg_name="settings.ini"):
|
|
24
25
|
cfg_path = Path().absolute()
|
|
25
26
|
while cfg_path != cfg_path.parent and not (cfg_path/cfg_name).exists(): cfg_path = cfg_path.parent
|
|
26
27
|
return Config(cfg_path, cfg_name)
|
|
27
28
|
|
|
28
|
-
# %% ../nbs/api/18_release.ipynb
|
|
29
|
+
# %% ../nbs/api/18_release.ipynb #1972609a
|
|
29
30
|
def _issue_txt(issue):
|
|
30
31
|
res = '- {} ([#{}]({}))'.format(issue.title.strip(), issue.number, issue.html_url)
|
|
31
32
|
if hasattr(issue, 'pull_request'): res += ', thanks to [@{}]({})'.format(issue.user.login, issue.user.html_url)
|
|
@@ -42,7 +43,7 @@ def _load_json(cfg, k):
|
|
|
42
43
|
try: return json.loads(cfg[k])
|
|
43
44
|
except json.JSONDecodeError as e: raise Exception(f"Key: `{k}` in .ini file is not a valid JSON string: {e}")
|
|
44
45
|
|
|
45
|
-
# %% ../nbs/api/18_release.ipynb
|
|
46
|
+
# %% ../nbs/api/18_release.ipynb #0b36471a
|
|
46
47
|
class Release:
|
|
47
48
|
def __init__(self, owner=None, repo=None, token=None, **groups):
|
|
48
49
|
"Create CHANGELOG.md from GitHub issues"
|
|
@@ -62,17 +63,20 @@ class Release:
|
|
|
62
63
|
|
|
63
64
|
def _issues(self, label):
|
|
64
65
|
return self.gh.issues.list_for_repo(state='closed', sort='created', filter='all', since=self.commit_date, labels=label)
|
|
65
|
-
def _issue_groups(self): return parallel(self._issues, self.groups.keys(), progress=False)
|
|
66
|
+
def _issue_groups(self): return parallel(self._issues, self.groups.keys(), progress=False, threadpool=True)
|
|
66
67
|
|
|
67
|
-
# %% ../nbs/api/18_release.ipynb
|
|
68
|
+
# %% ../nbs/api/18_release.ipynb #aa22dfe3
|
|
68
69
|
@patch
|
|
69
70
|
def changelog(self:Release,
|
|
70
71
|
debug=False): # Just print the latest changes, instead of updating file
|
|
71
72
|
"Create the CHANGELOG.md file, or return the proposed text if `debug` is `True`"
|
|
72
73
|
if not self.changefile.exists(): self.changefile.write_text("# Release notes\n\n<!-- do not remove -->\n")
|
|
73
74
|
marker = '<!-- do not remove -->\n'
|
|
74
|
-
try: self.commit_date = self.gh.repos.get_latest_release().published_at
|
|
75
|
-
except HTTP404NotFoundError: self.commit_date = '2000-01-01T00:00:004Z'
|
|
75
|
+
try: self.commit_date = (lr:=self.gh.repos.get_latest_release()).published_at
|
|
76
|
+
except HTTP404NotFoundError: lr,self.commit_date = None,'2000-01-01T00:00:004Z'
|
|
77
|
+
if lr and (Version(self.cfg.version) <= Version(lr.tag_name)):
|
|
78
|
+
print(f'Error: Version bump required: expected: >{lr.tag_name}, got: {self.cfg.version}.')
|
|
79
|
+
raise SystemExit(1)
|
|
76
80
|
res = f"\n## {self.cfg.version}\n"
|
|
77
81
|
issues = self._issue_groups()
|
|
78
82
|
res += '\n'.join(_issues_txt(*o) for o in zip(issues, self.groups.values()))
|
|
@@ -82,7 +86,7 @@ def changelog(self:Release,
|
|
|
82
86
|
self.changefile.write_text(res)
|
|
83
87
|
run(f'git add {self.changefile}')
|
|
84
88
|
|
|
85
|
-
# %% ../nbs/api/18_release.ipynb
|
|
89
|
+
# %% ../nbs/api/18_release.ipynb #068421c6
|
|
86
90
|
@patch
|
|
87
91
|
def release(self:Release):
|
|
88
92
|
"Tag and create a release in GitHub for the current version"
|
|
@@ -91,7 +95,7 @@ def release(self:Release):
|
|
|
91
95
|
self.gh.create_release(ver, branch=self.cfg.branch, body=notes)
|
|
92
96
|
return ver
|
|
93
97
|
|
|
94
|
-
# %% ../nbs/api/18_release.ipynb
|
|
98
|
+
# %% ../nbs/api/18_release.ipynb #22101171
|
|
95
99
|
@patch
|
|
96
100
|
def latest_notes(self:Release):
|
|
97
101
|
"Latest CHANGELOG entry"
|
|
@@ -100,7 +104,7 @@ def latest_notes(self:Release):
|
|
|
100
104
|
if not len(its)>0: return ''
|
|
101
105
|
return '\n'.join(its[1].splitlines()[1:]).strip()
|
|
102
106
|
|
|
103
|
-
# %% ../nbs/api/18_release.ipynb
|
|
107
|
+
# %% ../nbs/api/18_release.ipynb #01cb1ca4
|
|
104
108
|
@call_parse
|
|
105
109
|
def changelog(
|
|
106
110
|
debug:store_true=False, # Print info to be added to CHANGELOG, instead of updating file
|
|
@@ -110,7 +114,7 @@ def changelog(
|
|
|
110
114
|
res = Release(repo=repo).changelog(debug=debug)
|
|
111
115
|
if debug: print(res)
|
|
112
116
|
|
|
113
|
-
# %% ../nbs/api/18_release.ipynb
|
|
117
|
+
# %% ../nbs/api/18_release.ipynb #6d3c5cd8
|
|
114
118
|
@call_parse
|
|
115
119
|
def release_git(
|
|
116
120
|
token:str=None # Optional GitHub token (otherwise `token` file is used)
|
|
@@ -119,7 +123,7 @@ def release_git(
|
|
|
119
123
|
ver = Release(token=token).release()
|
|
120
124
|
print(f"Released {ver}")
|
|
121
125
|
|
|
122
|
-
# %% ../nbs/api/18_release.ipynb
|
|
126
|
+
# %% ../nbs/api/18_release.ipynb #ad73518a
|
|
123
127
|
@call_parse
|
|
124
128
|
def release_gh(
|
|
125
129
|
token:str=None # Optional GitHub token (otherwise `token` file is used)
|
|
@@ -134,7 +138,7 @@ def release_gh(
|
|
|
134
138
|
ver = Release(token=token).release()
|
|
135
139
|
print(f"Released {ver}")
|
|
136
140
|
|
|
137
|
-
# %% ../nbs/api/18_release.ipynb
|
|
141
|
+
# %% ../nbs/api/18_release.ipynb #5b4d4aa2
|
|
138
142
|
from fastcore.all import *
|
|
139
143
|
from .config import *
|
|
140
144
|
from .cli import *
|
|
@@ -146,18 +150,18 @@ except ImportError: from pip._vendor.packaging.version import parse
|
|
|
146
150
|
|
|
147
151
|
_PYPI_URL = 'https://pypi.org/pypi/'
|
|
148
152
|
|
|
149
|
-
# %% ../nbs/api/18_release.ipynb
|
|
153
|
+
# %% ../nbs/api/18_release.ipynb #4070bbef
|
|
150
154
|
def pypi_json(s):
|
|
151
155
|
"Dictionary decoded JSON for PYPI path `s`"
|
|
152
156
|
return urljson(f'{_PYPI_URL}{s}/json')
|
|
153
157
|
|
|
154
|
-
# %% ../nbs/api/18_release.ipynb
|
|
158
|
+
# %% ../nbs/api/18_release.ipynb #1985c8b1
|
|
155
159
|
def latest_pypi(name):
|
|
156
160
|
"Latest version of `name` on pypi"
|
|
157
161
|
return max(parse(r) for r,o in pypi_json(name)['releases'].items()
|
|
158
162
|
if not parse(r).is_prerelease and not nested_idx(o, 0, 'yanked'))
|
|
159
163
|
|
|
160
|
-
# %% ../nbs/api/18_release.ipynb
|
|
164
|
+
# %% ../nbs/api/18_release.ipynb #ab0bfd9a
|
|
161
165
|
def pypi_details(name):
|
|
162
166
|
"Version, URL, and SHA256 for `name` from pypi"
|
|
163
167
|
ver = str(latest_pypi(name))
|
|
@@ -165,7 +169,7 @@ def pypi_details(name):
|
|
|
165
169
|
rel = [o for o in pypi['urls'] if o['packagetype']=='sdist'][0]
|
|
166
170
|
return ver,rel['url'],rel['digests']['sha256']
|
|
167
171
|
|
|
168
|
-
# %% ../nbs/api/18_release.ipynb
|
|
172
|
+
# %% ../nbs/api/18_release.ipynb #431f625b
|
|
169
173
|
import shlex
|
|
170
174
|
from subprocess import Popen, PIPE, CalledProcessError
|
|
171
175
|
|
|
@@ -179,12 +183,12 @@ def _run(cmd):
|
|
|
179
183
|
if p.returncode != 0: raise CalledProcessError(p.returncode, p.args)
|
|
180
184
|
return res
|
|
181
185
|
|
|
182
|
-
# %% ../nbs/api/18_release.ipynb
|
|
186
|
+
# %% ../nbs/api/18_release.ipynb #6a9d34ab
|
|
183
187
|
def conda_output_path(name, build='build'):
|
|
184
188
|
"Output path for conda build"
|
|
185
189
|
return run(f'conda {build} --output {name}').strip().replace('\\', '/')
|
|
186
190
|
|
|
187
|
-
# %% ../nbs/api/18_release.ipynb
|
|
191
|
+
# %% ../nbs/api/18_release.ipynb #99fb71c2
|
|
188
192
|
def _write_yaml(path, name, d1, d2):
|
|
189
193
|
path = Path(path)
|
|
190
194
|
p = path/name
|
|
@@ -194,7 +198,7 @@ def _write_yaml(path, name, d1, d2):
|
|
|
194
198
|
yaml.safe_dump(d1, f)
|
|
195
199
|
yaml.safe_dump(d2, f)
|
|
196
200
|
|
|
197
|
-
# %% ../nbs/api/18_release.ipynb
|
|
201
|
+
# %% ../nbs/api/18_release.ipynb #5c78f115
|
|
198
202
|
def _get_conda_meta():
|
|
199
203
|
cfg = get_config()
|
|
200
204
|
name,ver = cfg.lib_name,cfg.version
|
|
@@ -236,12 +240,12 @@ def _get_conda_meta():
|
|
|
236
240
|
}
|
|
237
241
|
return name,d1,d2
|
|
238
242
|
|
|
239
|
-
# %% ../nbs/api/18_release.ipynb
|
|
243
|
+
# %% ../nbs/api/18_release.ipynb #b911bd4d
|
|
240
244
|
def write_conda_meta(path='conda'):
|
|
241
245
|
"Writes a `meta.yaml` file to the `conda` directory of the current directory"
|
|
242
246
|
_write_yaml(path, *_get_conda_meta())
|
|
243
247
|
|
|
244
|
-
# %% ../nbs/api/18_release.ipynb
|
|
248
|
+
# %% ../nbs/api/18_release.ipynb #7550557f
|
|
245
249
|
@call_parse
|
|
246
250
|
def write_requirements(path:str=''):
|
|
247
251
|
"Writes a `requirements.txt` file to `directory` based on settings.ini."
|
|
@@ -250,7 +254,7 @@ def write_requirements(path:str=''):
|
|
|
250
254
|
req = '\n'.join([cfg.get(k, '').replace(' ', '\n') for k in ['requirements', 'pip_requirements']])
|
|
251
255
|
(d/'requirements.txt').mk_write(req)
|
|
252
256
|
|
|
253
|
-
# %% ../nbs/api/18_release.ipynb
|
|
257
|
+
# %% ../nbs/api/18_release.ipynb #715ae3ac
|
|
254
258
|
CONDA_WARNING='Conda support for nbdev is deprecated and scheduled for removal in a future version.'
|
|
255
259
|
|
|
256
260
|
def anaconda_upload(name, loc=None, user=None, token=None, env_token=None):
|
|
@@ -263,7 +267,7 @@ def anaconda_upload(name, loc=None, user=None, token=None, env_token=None):
|
|
|
263
267
|
if not loc: raise Exception("Failed to find output")
|
|
264
268
|
return _run(f'anaconda {token} upload {user} {loc} --skip-existing')
|
|
265
269
|
|
|
266
|
-
# %% ../nbs/api/18_release.ipynb
|
|
270
|
+
# %% ../nbs/api/18_release.ipynb #952bc33d
|
|
267
271
|
@call_parse
|
|
268
272
|
def release_conda(
|
|
269
273
|
path:str='conda', # Path where package will be created
|
|
@@ -295,7 +299,7 @@ def release_conda(
|
|
|
295
299
|
if 'anaconda upload' not in res: return print(f"{res}\n\nFailed. Check auto-upload not set in .condarc. Try `--do_build False`.")
|
|
296
300
|
return anaconda_upload(name, loc)
|
|
297
301
|
|
|
298
|
-
# %% ../nbs/api/18_release.ipynb
|
|
302
|
+
# %% ../nbs/api/18_release.ipynb #0500d972
|
|
299
303
|
def chk_conda_rel(
|
|
300
304
|
nm:str, # Package name on pypi
|
|
301
305
|
apkg:str=None, # Anaconda Package (defaults to {nm})
|
|
@@ -309,7 +313,7 @@ def chk_conda_rel(
|
|
|
309
313
|
pypitag = latest_pypi(nm)
|
|
310
314
|
if force or not condatag or pypitag > max(condatag): return f'{pypitag}'
|
|
311
315
|
|
|
312
|
-
# %% ../nbs/api/18_release.ipynb
|
|
316
|
+
# %% ../nbs/api/18_release.ipynb #bf55df9b
|
|
313
317
|
@call_parse
|
|
314
318
|
def release_pypi(
|
|
315
319
|
repository:str="pypi" # Respository to upload to (defined in ~/.pypirc)
|
|
@@ -319,7 +323,7 @@ def release_pypi(
|
|
|
319
323
|
system(f'cd {_dir} && rm -rf dist build && python -m build')
|
|
320
324
|
system(f'twine upload --repository {repository} {_dir}/dist/*')
|
|
321
325
|
|
|
322
|
-
# %% ../nbs/api/18_release.ipynb
|
|
326
|
+
# %% ../nbs/api/18_release.ipynb #06edfcb0
|
|
323
327
|
@call_parse
|
|
324
328
|
def release_both(
|
|
325
329
|
path:str='conda', # Path where package will be created
|
|
@@ -335,7 +339,7 @@ def release_both(
|
|
|
335
339
|
release_conda.__wrapped__(path, do_build=do_build, build_args=build_args, skip_upload=skip_upload, mambabuild=mambabuild, upload_user=upload_user)
|
|
336
340
|
nbdev_bump_version.__wrapped__()
|
|
337
341
|
|
|
338
|
-
# %% ../nbs/api/18_release.ipynb
|
|
342
|
+
# %% ../nbs/api/18_release.ipynb #6380dd5a
|
|
339
343
|
def bump_version(version, part=2, unbump=False):
|
|
340
344
|
version = version.split('.')
|
|
341
345
|
incr = -1 if unbump else 1
|
|
@@ -343,7 +347,7 @@ def bump_version(version, part=2, unbump=False):
|
|
|
343
347
|
for i in range(part+1, 3): version[i] = '0'
|
|
344
348
|
return '.'.join(version)
|
|
345
349
|
|
|
346
|
-
# %% ../nbs/api/18_release.ipynb
|
|
350
|
+
# %% ../nbs/api/18_release.ipynb #c0f64b2c
|
|
347
351
|
@call_parse
|
|
348
352
|
def nbdev_bump_version(
|
|
349
353
|
part:int=2, # Part of version to bump
|
nbdev/serve.py
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/17_serve.ipynb.
|
|
4
4
|
|
|
5
|
-
# %% auto 0
|
|
5
|
+
# %% auto #0
|
|
6
6
|
__all__ = ['proc_nbs']
|
|
7
7
|
|
|
8
|
-
# %% ../nbs/api/17_serve.ipynb
|
|
8
|
+
# %% ../nbs/api/17_serve.ipynb #6899a335
|
|
9
9
|
import ast,subprocess,threading,sys
|
|
10
10
|
from shutil import rmtree,copy2
|
|
11
11
|
|
|
@@ -19,7 +19,7 @@ from .doclinks import nbglob_cli,nbglob
|
|
|
19
19
|
from .processors import FilterDefaults
|
|
20
20
|
import nbdev.serve_drv
|
|
21
21
|
|
|
22
|
-
# %% ../nbs/api/17_serve.ipynb
|
|
22
|
+
# %% ../nbs/api/17_serve.ipynb #65766a33
|
|
23
23
|
def _is_qpy(path:Path):
|
|
24
24
|
"Is `path` a py script starting with frontmatter?"
|
|
25
25
|
path = Path(path)
|
|
@@ -34,7 +34,7 @@ def _is_qpy(path:Path):
|
|
|
34
34
|
vl = v.splitlines()
|
|
35
35
|
if vl[0]=='---' and vl[-1]=='---': return '\n'.join(vl[1:-1])
|
|
36
36
|
|
|
37
|
-
# %% ../nbs/api/17_serve.ipynb
|
|
37
|
+
# %% ../nbs/api/17_serve.ipynb #abc3835a
|
|
38
38
|
def _proc_file(s, cache, path, mtime=None):
|
|
39
39
|
skips = ('_proc', '_docs', '_site', 'settings.ini')
|
|
40
40
|
if not s.is_file() or any(o[0]=='.' or o in skips for o in s.parts): return
|
|
@@ -51,7 +51,7 @@ def _proc_file(s, cache, path, mtime=None):
|
|
|
51
51
|
if md is not None: return s,d,md.strip()
|
|
52
52
|
else: copy2(s,d)
|
|
53
53
|
|
|
54
|
-
# %% ../nbs/api/17_serve.ipynb
|
|
54
|
+
# %% ../nbs/api/17_serve.ipynb #14463227
|
|
55
55
|
@delegates(nbglob_cli)
|
|
56
56
|
def proc_nbs(
|
|
57
57
|
path:str='', # Path to notebooks
|
nbdev/showdoc.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/08_showdoc.ipynb.
|
|
4
4
|
|
|
5
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
5
|
+
# %% ../nbs/api/08_showdoc.ipynb #7f371f15
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
from .doclinks import *
|
|
8
8
|
from .config import get_config
|
|
@@ -16,153 +16,13 @@ from collections import OrderedDict
|
|
|
16
16
|
from textwrap import fill
|
|
17
17
|
from types import FunctionType
|
|
18
18
|
|
|
19
|
-
# %% auto 0
|
|
20
|
-
__all__ = ['
|
|
21
|
-
'colab_link']
|
|
19
|
+
# %% auto #0
|
|
20
|
+
__all__ = ['BasicMarkdownRenderer', 'show_doc', 'doc', 'showdoc_nm', 'colab_link']
|
|
22
21
|
|
|
23
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
24
|
-
def _non_empty_keys(d:dict): return L([k for k,v in d.items() if v != inspect._empty])
|
|
25
|
-
def _bold(s): return f'**{s}**' if s.strip() else s
|
|
26
|
-
|
|
27
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
28
|
-
def _escape_markdown(s):
|
|
29
|
-
for c in '|^': s = re.sub(rf'\\?\{c}', rf'\{c}', s)
|
|
30
|
-
return s.replace('\n', '<br>')
|
|
31
|
-
|
|
32
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
33
|
-
def _maybe_nm(o):
|
|
34
|
-
if (o == inspect._empty): return ''
|
|
35
|
-
else: return o.__name__ if hasattr(o, '__name__') else _escape_markdown(str(o))
|
|
36
|
-
|
|
37
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
38
|
-
def _list2row(l:list): return '| '+' | '.join([_maybe_nm(o) for o in l]) + ' |'
|
|
39
|
-
|
|
40
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
41
|
-
class DocmentTbl:
|
|
42
|
-
# this is the column order we want these items to appear
|
|
43
|
-
_map = OrderedDict({'anno':'Type', 'default':'Default', 'docment':'Details'})
|
|
44
|
-
|
|
45
|
-
def __init__(self, obj, verbose=True, returns=True):
|
|
46
|
-
"Compute the docment table string"
|
|
47
|
-
self.verbose = verbose
|
|
48
|
-
self.returns = False if isdataclass(obj) else returns
|
|
49
|
-
try: self.params = L(signature_ex(obj, eval_str=True).parameters.keys())
|
|
50
|
-
except (ValueError,TypeError): self.params=[]
|
|
51
|
-
try: _dm = docments(obj, full=True, returns=returns)
|
|
52
|
-
except: _dm = {}
|
|
53
|
-
if 'self' in _dm: del _dm['self']
|
|
54
|
-
for d in _dm.values(): d['docment'] = ifnone(d['docment'], inspect._empty)
|
|
55
|
-
self.dm = _dm
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
def _columns(self):
|
|
59
|
-
"Compute the set of fields that have at least one non-empty value so we don't show tables empty columns"
|
|
60
|
-
cols = set(flatten(L(self.dm.values()).filter().map(_non_empty_keys)))
|
|
61
|
-
candidates = self._map if self.verbose else {'docment': 'Details'}
|
|
62
|
-
return OrderedDict({k:v for k,v in candidates.items() if k in cols})
|
|
63
|
-
|
|
64
|
-
@property
|
|
65
|
-
def has_docment(self): return 'docment' in self._columns and self._row_list
|
|
66
|
-
|
|
67
|
-
@property
|
|
68
|
-
def has_return(self): return self.returns and bool(_non_empty_keys(self.dm.get('return', {})))
|
|
69
|
-
|
|
70
|
-
def _row(self, nm, props):
|
|
71
|
-
"unpack data for single row to correspond with column names."
|
|
72
|
-
return [nm] + [props[c] for c in self._columns]
|
|
73
|
-
|
|
74
|
-
@property
|
|
75
|
-
def _row_list(self):
|
|
76
|
-
"unpack data for all rows."
|
|
77
|
-
ordered_params = [(p, self.dm[p]) for p in self.params if p != 'self' and p in self.dm]
|
|
78
|
-
return L([self._row(nm, props) for nm,props in ordered_params])
|
|
79
|
-
|
|
80
|
-
@property
|
|
81
|
-
def _hdr_list(self): return [' '] + [_bold(l) for l in L(self._columns.values())]
|
|
82
|
-
|
|
83
|
-
@property
|
|
84
|
-
def hdr_str(self):
|
|
85
|
-
"The markdown string for the header portion of the table"
|
|
86
|
-
md = _list2row(self._hdr_list)
|
|
87
|
-
return md + '\n' + _list2row(['-' * len(l) for l in self._hdr_list])
|
|
88
|
-
|
|
89
|
-
@property
|
|
90
|
-
def params_str(self):
|
|
91
|
-
"The markdown string for the parameters portion of the table."
|
|
92
|
-
return '\n'.join(self._row_list.map(_list2row))
|
|
93
|
-
|
|
94
|
-
@property
|
|
95
|
-
def return_str(self):
|
|
96
|
-
"The markdown string for the returns portion of the table."
|
|
97
|
-
return _list2row(['**Returns**']+[_bold(_maybe_nm(self.dm['return'][c])) for c in self._columns])
|
|
98
|
-
|
|
99
|
-
def _repr_markdown_(self):
|
|
100
|
-
if not self.has_docment: return ''
|
|
101
|
-
_tbl = [self.hdr_str, self.params_str]
|
|
102
|
-
if self.has_return: _tbl.append(self.return_str)
|
|
103
|
-
return '\n'.join(_tbl)
|
|
104
|
-
|
|
105
|
-
def __eq__(self,other): return self.__str__() == str(other).strip()
|
|
106
|
-
|
|
107
|
-
__str__ = _repr_markdown_
|
|
108
|
-
__repr__ = basic_repr()
|
|
109
|
-
|
|
110
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
111
|
-
def _docstring(sym):
|
|
112
|
-
npdoc = parse_docstring(sym)
|
|
113
|
-
return '\n\n'.join([npdoc['Summary'], npdoc['Extended']]).strip()
|
|
114
|
-
|
|
115
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
116
|
-
def _fullname(o):
|
|
117
|
-
module,name = getattr(o, "__module__", None),qual_name(o)
|
|
118
|
-
return name if module is None or module in ('__main__','builtins') else module + '.' + name
|
|
119
|
-
|
|
120
|
-
class ShowDocRenderer:
|
|
121
|
-
def __init__(self, sym, name:str|None=None, title_level:int=3):
|
|
122
|
-
"Show documentation for `sym`"
|
|
123
|
-
sym = getattr(sym, '__wrapped__', sym)
|
|
124
|
-
sym = getattr(sym, 'fget', None) or getattr(sym, 'fset', None) or sym
|
|
125
|
-
store_attr()
|
|
126
|
-
self.nm = name or qual_name(sym)
|
|
127
|
-
self.isfunc = inspect.isfunction(sym)
|
|
128
|
-
try: self.sig = signature_ex(sym, eval_str=True)
|
|
129
|
-
except (ValueError,TypeError): self.sig = None
|
|
130
|
-
self.docs = _docstring(sym)
|
|
131
|
-
self.dm = DocmentTbl(sym)
|
|
132
|
-
self.fn = _fullname(sym)
|
|
133
|
-
|
|
134
|
-
__repr__ = basic_repr()
|
|
135
|
-
|
|
136
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
137
|
-
def _f_name(o): return f'<function {o.__name__}>' if isinstance(o, FunctionType) else None
|
|
138
|
-
def _fmt_anno(o): return inspect.formatannotation(o).strip("'").replace(' ','')
|
|
139
|
-
|
|
140
|
-
def _show_param(param):
|
|
141
|
-
"Like `Parameter.__str__` except removes: quotes in annos, spaces, ids in reprs"
|
|
142
|
-
kind,res,anno,default = param.kind,param._name,param._annotation,param._default
|
|
143
|
-
kind = '*' if kind==inspect._VAR_POSITIONAL else '**' if kind==inspect._VAR_KEYWORD else ''
|
|
144
|
-
res = kind+res
|
|
145
|
-
if anno is not inspect._empty: res += f':{_f_name(anno) or _fmt_anno(anno)}'
|
|
146
|
-
if default is not inspect._empty: res += f'={_f_name(default) or repr(default)}'
|
|
147
|
-
return res
|
|
148
|
-
|
|
149
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
150
|
-
def _fmt_sig(sig):
|
|
151
|
-
if sig is None: return ''
|
|
152
|
-
p = {k:v for k,v in sig.parameters.items()}
|
|
153
|
-
_params = [_show_param(p[k]) for k in p.keys() if k != 'self']
|
|
154
|
-
return "(" + ', '.join(_params) + ")"
|
|
155
|
-
|
|
156
|
-
def _wrap_sig(s):
|
|
157
|
-
"wrap a signature to appear on multiple lines if necessary."
|
|
158
|
-
pad = '> ' + ' ' * 5
|
|
159
|
-
indent = pad + ' ' * (s.find('(') + 1)
|
|
160
|
-
return fill(s, width=80, initial_indent=pad, subsequent_indent=indent)
|
|
161
|
-
|
|
162
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
22
|
+
# %% ../nbs/api/08_showdoc.ipynb #abe98f5c
|
|
163
23
|
def _ext_link(url, txt, xtra=""): return f'[{txt}]({url}){{target="_blank" {xtra}}}'
|
|
164
24
|
|
|
165
|
-
class BasicMarkdownRenderer(
|
|
25
|
+
class BasicMarkdownRenderer(MarkdownRenderer):
|
|
166
26
|
"Markdown renderer for `show_doc`"
|
|
167
27
|
def _repr_markdown_(self):
|
|
168
28
|
doc = '---\n\n'
|
|
@@ -170,14 +30,9 @@ class BasicMarkdownRenderer(ShowDocRenderer):
|
|
|
170
30
|
if src: doc += _ext_link(src, 'source', 'style="float:right; font-size:smaller"') + '\n\n'
|
|
171
31
|
h = '#'*self.title_level
|
|
172
32
|
doc += f'{h} {self.nm}\n\n'
|
|
173
|
-
|
|
174
|
-
doc += f'{sig}'
|
|
175
|
-
if self.docs: doc += f"\n\n*{self.docs}*"
|
|
176
|
-
if self.dm.has_docment: doc += f"\n\n{self.dm}"
|
|
177
|
-
return doc
|
|
178
|
-
__repr__=__str__=_repr_markdown_
|
|
33
|
+
return doc+super()._repr_markdown_()
|
|
179
34
|
|
|
180
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
35
|
+
# %% ../nbs/api/08_showdoc.ipynb #1256ef79
|
|
181
36
|
def show_doc(sym, # Symbol to document
|
|
182
37
|
renderer=None, # Optional renderer (defaults to markdown)
|
|
183
38
|
name:str|None=None, # Optionally override displayed name of `sym`
|
|
@@ -192,60 +47,17 @@ def show_doc(sym, # Symbol to document
|
|
|
192
47
|
elif isinstance_str(sym, "TypeDispatch"): pass # use _str as TypeDispatch will be removed from fastcore
|
|
193
48
|
else:return renderer(sym or show_doc, name=name, title_level=title_level)
|
|
194
49
|
|
|
195
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
196
|
-
def _create_html_table(table_str):
|
|
197
|
-
def split_row(row):
|
|
198
|
-
return re.findall(r'\|(?:(?:\\.|[^|\\])*)', row)
|
|
199
|
-
|
|
200
|
-
def unescape_cell(cell):
|
|
201
|
-
return cell.strip(' *|').replace(r'\|', '|')
|
|
202
|
-
|
|
203
|
-
lines = table_str.strip().split('\n')
|
|
204
|
-
header = [f"<th>{unescape_cell(cell)}</th>" for cell in split_row(lines[0])]
|
|
205
|
-
rows = [[f"<td>{unescape_cell(cell)}</td>" for cell in split_row(line)] for line in lines[2:]]
|
|
206
|
-
|
|
207
|
-
return f'''<table>
|
|
208
|
-
<thead><tr>{' '.join(header)}</tr></thead>
|
|
209
|
-
<tbody>{''.join(f'<tr>{" ".join(row)}</tr>' for row in rows)}</tbody>
|
|
210
|
-
</table>'''
|
|
211
|
-
|
|
212
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
213
|
-
def _html_link(url, txt): return f'<a href="{url}" target="_blank" rel="noreferrer noopener">{txt}</a>'
|
|
214
|
-
|
|
215
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
216
|
-
class BasicHtmlRenderer(ShowDocRenderer):
|
|
217
|
-
"HTML renderer for `show_doc`"
|
|
218
|
-
def _repr_html_(self):
|
|
219
|
-
doc = '<hr/>\n'
|
|
220
|
-
src = NbdevLookup().code(self.fn)
|
|
221
|
-
doc += f'<h{self.title_level}>{self.nm}</h{self.title_level}>\n'
|
|
222
|
-
sig = _fmt_sig(self.sig) if self.sig else ''
|
|
223
|
-
# Escape < and > characters in the signature
|
|
224
|
-
sig = sig.replace('<', '<').replace('>', '>')
|
|
225
|
-
doc += f'<blockquote><pre><code>{self.nm} {sig}</code></pre></blockquote>'
|
|
226
|
-
if self.docs:
|
|
227
|
-
doc += f"<p><i>{self.docs}</i></p>"
|
|
228
|
-
if src: doc += f"<br/>{_html_link(src, 'source')}"
|
|
229
|
-
if self.dm.has_docment: doc += _create_html_table(str(self.dm))
|
|
230
|
-
return doc
|
|
231
|
-
|
|
232
|
-
def doc(self):
|
|
233
|
-
"Show `show_doc` info along with link to docs"
|
|
234
|
-
from IPython.display import display,HTML
|
|
235
|
-
res = self._repr_html_()
|
|
236
|
-
display(HTML(res))
|
|
237
|
-
|
|
238
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
50
|
+
# %% ../nbs/api/08_showdoc.ipynb #eb1f0530
|
|
239
51
|
def doc(elt):
|
|
240
52
|
"Show `show_doc` info along with link to docs"
|
|
241
|
-
|
|
53
|
+
return BasicMarkdownRenderer(elt)
|
|
242
54
|
|
|
243
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
55
|
+
# %% ../nbs/api/08_showdoc.ipynb #35043aa7-6b60-4ddf-8949-39a78577f23d
|
|
244
56
|
def showdoc_nm(tree):
|
|
245
57
|
"Get the fully qualified name for showdoc."
|
|
246
58
|
return ifnone(patch_name(tree), tree.name)
|
|
247
59
|
|
|
248
|
-
# %% ../nbs/api/08_showdoc.ipynb
|
|
60
|
+
# %% ../nbs/api/08_showdoc.ipynb #e947414d
|
|
249
61
|
def colab_link(path):
|
|
250
62
|
"Get a link to the notebook at `path` on Colab"
|
|
251
63
|
from IPython.display import Markdown
|
nbdev/sync.py
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/06_sync.ipynb.
|
|
4
4
|
|
|
5
|
-
# %% auto 0
|
|
5
|
+
# %% auto #0
|
|
6
6
|
__all__ = ['absolute_import', 'nbdev_update']
|
|
7
7
|
|
|
8
|
-
# %% ../nbs/api/06_sync.ipynb
|
|
8
|
+
# %% ../nbs/api/06_sync.ipynb #ea6a935c
|
|
9
9
|
from .imports import *
|
|
10
10
|
from .config import *
|
|
11
11
|
from .maker import *
|
|
@@ -21,7 +21,7 @@ from fastcore.xtras import *
|
|
|
21
21
|
import ast
|
|
22
22
|
import importlib
|
|
23
23
|
|
|
24
|
-
# %% ../nbs/api/06_sync.ipynb
|
|
24
|
+
# %% ../nbs/api/06_sync.ipynb #fde5667d
|
|
25
25
|
def absolute_import(name, fname, level):
|
|
26
26
|
"Unwarps a relative import in `name` according to `fname`"
|
|
27
27
|
if not level: return name
|
|
@@ -29,7 +29,7 @@ def absolute_import(name, fname, level):
|
|
|
29
29
|
if not name: return '.'.join(mods)
|
|
30
30
|
return '.'.join(mods[:len(mods)-level+1]) + f".{name}"
|
|
31
31
|
|
|
32
|
-
# %% ../nbs/api/06_sync.ipynb
|
|
32
|
+
# %% ../nbs/api/06_sync.ipynb #54ab1e17
|
|
33
33
|
@functools.lru_cache(maxsize=None)
|
|
34
34
|
def _mod_files():
|
|
35
35
|
midx_spec = importlib.util.spec_from_file_location("_modidx", get_config().lib_path / "_modidx.py")
|
|
@@ -38,41 +38,44 @@ def _mod_files():
|
|
|
38
38
|
|
|
39
39
|
return L(files for mod in midx.d['syms'].values() for _,files in mod.values()).unique()
|
|
40
40
|
|
|
41
|
-
# %% ../nbs/api/06_sync.ipynb
|
|
41
|
+
# %% ../nbs/api/06_sync.ipynb #64d51ab6
|
|
42
42
|
_re_import = re.compile(r"from\s+\S+\s+import\s+\S")
|
|
43
43
|
|
|
44
|
-
# %% ../nbs/api/06_sync.ipynb
|
|
44
|
+
# %% ../nbs/api/06_sync.ipynb #9075f993
|
|
45
45
|
def _to_absolute(code, py_path, lib_dir):
|
|
46
46
|
if not _re_import.search(code): return code
|
|
47
47
|
res = update_import(code, ast.parse(code).body, str(py_path.relative_to(lib_dir).parent), absolute_import)
|
|
48
48
|
return ''.join(res) if res else code
|
|
49
49
|
|
|
50
|
-
# %% ../nbs/api/06_sync.ipynb
|
|
50
|
+
# %% ../nbs/api/06_sync.ipynb #27ae8834
|
|
51
51
|
def _update_nb(nb_path, cells, lib_dir):
|
|
52
52
|
"Update notebook `nb_path` with contents from `cells`"
|
|
53
53
|
nbp = NBProcessor(nb_path, ExportModuleProc(), rm_directives=False)
|
|
54
54
|
nbp.process()
|
|
55
|
+
# Build a dict of cell_id -> cell for fast lookup
|
|
56
|
+
nb_cells_by_id = {c['id']: c for c in nbp.nb.cells if c.get('id')}
|
|
55
57
|
for cell in cells:
|
|
56
58
|
assert cell.nb_path == nb_path
|
|
57
|
-
nbcell =
|
|
59
|
+
nbcell = nb_cells_by_id.get(cell.cell_id)
|
|
60
|
+
if nbcell is None:
|
|
61
|
+
raise ValueError(f"Cell ID '{cell.cell_id}' not found in notebook '{nb_path}'")
|
|
58
62
|
dirs,_ = _partition_cell(nbcell, 'python')
|
|
59
63
|
nbcell.source = ''.join(dirs) + _to_absolute(cell.code, cell.py_path, lib_dir)
|
|
60
64
|
write_nb(nbp.nb, nb_path)
|
|
61
65
|
|
|
62
|
-
# %% ../nbs/api/06_sync.ipynb
|
|
66
|
+
# %% ../nbs/api/06_sync.ipynb #46c9e605
|
|
63
67
|
def _update_mod(py_path, lib_dir):
|
|
64
68
|
"Propagate changes from cells in module `py_path` to corresponding notebooks"
|
|
65
69
|
py_cells = L(_iter_py_cells(py_path)).filter(lambda o: o.nb != 'auto')
|
|
66
70
|
for nb_path,cells in groupby(py_cells, 'nb_path').items(): _update_nb(nb_path, cells, lib_dir)
|
|
67
71
|
|
|
68
|
-
# %% ../nbs/api/06_sync.ipynb
|
|
72
|
+
# %% ../nbs/api/06_sync.ipynb #60b5b528
|
|
69
73
|
@call_parse
|
|
70
74
|
def nbdev_update(fname:str=None): # A Python file name to update
|
|
71
75
|
"Propagate change in modules matching `fname` to notebooks that created them"
|
|
72
76
|
if fname and fname.endswith('.ipynb'): raise ValueError("`nbdev_update` operates on .py files. If you wish to convert notebooks instead, see `nbdev_export`.")
|
|
73
77
|
if os.environ.get('IN_TEST',0): return
|
|
74
78
|
cfg = get_config()
|
|
75
|
-
if not cfg.cell_number: raise ValueError("`nbdev_update` does not support without cell_number in .py files. Please check your settings.ini")
|
|
76
79
|
fname = Path(fname or cfg.lib_path)
|
|
77
80
|
lib_dir = cfg.lib_path.parent
|
|
78
81
|
files = globtastic(fname, file_glob='*.py', skip_folder_re='^[_.]').filter(lambda x: str(Path(x).absolute().relative_to(lib_dir) in _mod_files()))
|
nbdev/test.py
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/12_test.ipynb.
|
|
4
4
|
|
|
5
|
-
# %% auto 0
|
|
5
|
+
# %% auto #0
|
|
6
6
|
__all__ = ['test_nb', 'nbdev_test']
|
|
7
7
|
|
|
8
|
-
# %% ../nbs/api/12_test.ipynb
|
|
8
|
+
# %% ../nbs/api/12_test.ipynb #45e10c3f
|
|
9
9
|
import time,os,sys,traceback,contextlib, inspect
|
|
10
10
|
from fastcore.basics import *
|
|
11
11
|
from fastcore.imports import *
|
|
@@ -22,7 +22,7 @@ from .frontmatter import FrontmatterProc
|
|
|
22
22
|
from execnb.nbio import *
|
|
23
23
|
from execnb.shell import *
|
|
24
24
|
|
|
25
|
-
# %% ../nbs/api/12_test.ipynb
|
|
25
|
+
# %% ../nbs/api/12_test.ipynb #3f4fa1ad
|
|
26
26
|
def test_nb(fn, # file name of notebook to test
|
|
27
27
|
skip_flags=None, # list of flags marking cells to skip
|
|
28
28
|
force_flags=None, # list of flags marking cells to always run
|
|
@@ -57,7 +57,7 @@ def test_nb(fn, # file name of notebook to test
|
|
|
57
57
|
if do_print: print(f'- Completed {fn}')
|
|
58
58
|
return res,time.time()-start
|
|
59
59
|
|
|
60
|
-
# %% ../nbs/api/12_test.ipynb
|
|
60
|
+
# %% ../nbs/api/12_test.ipynb #d8bf1f1b-935d-4b69-ba96-827c5d7213f0
|
|
61
61
|
def _keep_file(p:Path, # filename for which to check for `indicator_fname`
|
|
62
62
|
ignore_fname:str # filename that will result in siblings being ignored
|
|
63
63
|
) -> bool:
|
|
@@ -65,7 +65,7 @@ def _keep_file(p:Path, # filename for which to check for `indicator_fname`
|
|
|
65
65
|
if p.exists(): return not bool(p.parent.ls().attrgot('name').filter(lambda x: x == ignore_fname))
|
|
66
66
|
else: True
|
|
67
67
|
|
|
68
|
-
# %% ../nbs/api/12_test.ipynb
|
|
68
|
+
# %% ../nbs/api/12_test.ipynb #f8cc6a61-a48e-4ab1-89a9-18316ca795d6
|
|
69
69
|
@call_parse
|
|
70
70
|
@delegates(nbglob_cli)
|
|
71
71
|
def nbdev_test(
|