nbdev 3.0.18__tar.gz → 3.1.0__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.0.18/nbdev.egg-info → nbdev-3.1.0}/PKG-INFO +4 -4
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/__init__.py +1 -1
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/_modidx.py +2 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/cli.py +12 -11
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/config.py +5 -7
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/doclinks.py +10 -8
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/merge.py +2 -1
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/processors.py +5 -4
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/quarto.py +7 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/release.py +36 -24
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/test.py +39 -29
- {nbdev-3.0.18 → nbdev-3.1.0/nbdev.egg-info}/PKG-INFO +4 -4
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev.egg-info/requires.txt +3 -3
- {nbdev-3.0.18 → nbdev-3.1.0}/pyproject.toml +4 -1
- {nbdev-3.0.18 → nbdev-3.1.0}/CONTRIBUTING.md +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/LICENSE +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/MANIFEST.in +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/README.md +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/clean.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/diff.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/export.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/extract_attachments.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/frontmatter.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/imports.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/maker.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/migrate.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/process.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/qmd.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/scrubmagics.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/serve.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/serve_drv.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/showdoc.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/sync.py +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev.egg-info/SOURCES.txt +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev.egg-info/dependency_links.txt +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev.egg-info/entry_points.txt +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/nbdev.egg-info/top_level.txt +0 -0
- {nbdev-3.0.18 → nbdev-3.1.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nbdev
|
|
3
|
-
Version: 3.0
|
|
3
|
+
Version: 3.1.0
|
|
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,10 +16,10 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
16
16
|
Requires-Python: >=3.9
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: fastcore>=1.
|
|
20
|
-
Requires-Dist: execnb>=0.
|
|
19
|
+
Requires-Dist: fastcore>=1.14.1
|
|
20
|
+
Requires-Dist: execnb>=0.2.4
|
|
21
21
|
Requires-Dist: astunparse
|
|
22
|
-
Requires-Dist: ghapi>=
|
|
22
|
+
Requires-Dist: ghapi>=2.0.0
|
|
23
23
|
Requires-Dist: watchdog
|
|
24
24
|
Requires-Dist: asttokens
|
|
25
25
|
Requires-Dist: setuptools
|
|
@@ -278,6 +278,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
278
278
|
'nbdev/quarto.py'),
|
|
279
279
|
'nbdev.quarto._SidebarYmlRemoved.__init__': ( 'api/quarto.html#_sidebarymlremoved.__init__',
|
|
280
280
|
'nbdev/quarto.py'),
|
|
281
|
+
'nbdev.quarto._chk_nbdev_yml': ('api/quarto.html#_chk_nbdev_yml', 'nbdev/quarto.py'),
|
|
281
282
|
'nbdev.quarto._copytree': ('api/quarto.html#_copytree', 'nbdev/quarto.py'),
|
|
282
283
|
'nbdev.quarto._doc_mtime_not_older': ('api/quarto.html#_doc_mtime_not_older', 'nbdev/quarto.py'),
|
|
283
284
|
'nbdev.quarto._ensure_quarto': ('api/quarto.html#_ensure_quarto', 'nbdev/quarto.py'),
|
|
@@ -330,6 +331,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
330
331
|
'nbdev.release.release_gh': ('api/release.html#release_gh', 'nbdev/release.py'),
|
|
331
332
|
'nbdev.release.release_git': ('api/release.html#release_git', 'nbdev/release.py'),
|
|
332
333
|
'nbdev.release.release_pypi': ('api/release.html#release_pypi', 'nbdev/release.py'),
|
|
334
|
+
'nbdev.release.update_changelog': ('api/release.html#update_changelog', 'nbdev/release.py'),
|
|
333
335
|
'nbdev.release.write_conda_meta': ('api/release.html#write_conda_meta', 'nbdev/release.py'),
|
|
334
336
|
'nbdev.release.write_requirements': ('api/release.html#write_requirements', 'nbdev/release.py')},
|
|
335
337
|
'nbdev.scrubmagics': {},
|
|
@@ -71,25 +71,25 @@ def _render_nb(fn, cfg):
|
|
|
71
71
|
fn.write_text(txt)
|
|
72
72
|
|
|
73
73
|
# %% ../nbs/api/13_cli.ipynb #dd385911-aa8f-44e7-8d46-7b8a20f3b010
|
|
74
|
-
def _update_repo_meta(cfg):
|
|
74
|
+
async def _update_repo_meta(cfg):
|
|
75
75
|
"Enable gh pages and update the homepage and description in your GitHub repo."
|
|
76
76
|
token=os.getenv('GITHUB_TOKEN')
|
|
77
77
|
if token:
|
|
78
|
-
from ghapi.core import GhApi
|
|
78
|
+
from ghapi.core import GhApi, APIError
|
|
79
79
|
api = GhApi(owner=cfg.user, repo=cfg.repo, token=token)
|
|
80
|
-
try: api.repos.update(homepage=f'{cfg.doc_host}{cfg.doc_baseurl}', description=cfg.description)
|
|
81
|
-
except
|
|
80
|
+
try: await api.repos.update(homepage=f'{cfg.doc_host}{cfg.doc_baseurl}', description=cfg.description)
|
|
81
|
+
except APIError:print(f"Could not update the description & URL on the repo: {cfg.user}/{cfg.repo} using $GITHUB_TOKEN.\n"
|
|
82
82
|
"Use a token with the correction permissions or perform these steps manually.")
|
|
83
83
|
|
|
84
84
|
# %% ../nbs/api/13_cli.ipynb #c4a663d9
|
|
85
85
|
@call_parse
|
|
86
86
|
@delegates(nbdev_create_config)
|
|
87
|
-
def nbdev_new(**kwargs):
|
|
87
|
+
async def nbdev_new(**kwargs):
|
|
88
88
|
"Create an nbdev project."
|
|
89
89
|
from ghapi.core import GhApi
|
|
90
90
|
nbdev_create_config.__wrapped__(**kwargs)
|
|
91
91
|
cfg = get_config()
|
|
92
|
-
if (Path('.git')).exists(): _update_repo_meta(cfg)
|
|
92
|
+
if (Path('.git')).exists(): await _update_repo_meta(cfg)
|
|
93
93
|
else: print(f"No git repo found. Run: gh repo create {cfg.user}/{cfg.repo} --public --source=.")
|
|
94
94
|
path = Path()
|
|
95
95
|
|
|
@@ -103,8 +103,8 @@ def nbdev_new(**kwargs):
|
|
|
103
103
|
if tag is None:
|
|
104
104
|
with warnings.catch_warnings():
|
|
105
105
|
warnings.simplefilter('ignore', UserWarning)
|
|
106
|
-
tag = GhApi(gh_host='https://api.github.com', authenticate=False
|
|
107
|
-
).repos.get_latest_release(org_or_usr, repo).tag_name
|
|
106
|
+
tag = (await GhApi(gh_host='https://api.github.com', authenticate=False
|
|
107
|
+
).repos.get_latest_release(org_or_usr, repo)).tag_name
|
|
108
108
|
|
|
109
109
|
url = f"https://github.com/{org_or_usr}/{repo}/archive/{tag}.tar.gz"
|
|
110
110
|
extract_tgz(url)
|
|
@@ -137,20 +137,21 @@ mapping = {
|
|
|
137
137
|
|
|
138
138
|
# %% ../nbs/api/13_cli.ipynb #a1116ee1-f5cb-4b63-8630-b64cd2ae3c12
|
|
139
139
|
@call_parse
|
|
140
|
-
def nbdev_update_license(
|
|
140
|
+
async def nbdev_update_license(
|
|
141
141
|
to: str=None, # update license to
|
|
142
142
|
):
|
|
143
143
|
"Allows you to update the license of your project."
|
|
144
144
|
from ghapi.core import GhApi
|
|
145
145
|
warnings.filterwarnings("ignore")
|
|
146
|
-
|
|
146
|
+
api = GhApi()
|
|
147
|
+
avail_lic = (await api.licenses.get_all_commonly_used()).map(lambda x: x['key'])
|
|
147
148
|
|
|
148
149
|
cfg = get_config()
|
|
149
150
|
curr_lic = cfg.license
|
|
150
151
|
|
|
151
152
|
mapped = mapping.get(to, None)
|
|
152
153
|
if mapped not in avail_lic: raise ValueError(f"{to} is not an available license")
|
|
153
|
-
body =
|
|
154
|
+
body = (await api.licenses.get(mapped))['body']
|
|
154
155
|
|
|
155
156
|
copyright = f"{datetime.now().year}, {cfg.author}"
|
|
156
157
|
body = body.replace('[year], [fullname]', copyright)
|
|
@@ -38,18 +38,16 @@ def _git_repo():
|
|
|
38
38
|
|
|
39
39
|
# %% ../nbs/api/01_config.ipynb #6eeafafd
|
|
40
40
|
def _get_info(owner, repo, default_branch='main', default_kw='nbdev'):
|
|
41
|
-
from ghapi.all import
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
try: r = api.repos.get()
|
|
45
|
-
except HTTPError:
|
|
41
|
+
from ghapi.all import call_gh, APIError
|
|
42
|
+
try: r = call_gh('repos.get', owner=owner, repo=repo)
|
|
43
|
+
except APIError:
|
|
46
44
|
msg= [f"""Could not access repo: {owner}/{repo} to find your default branch - `{default_branch}` assumed.
|
|
47
45
|
Edit `pyproject.toml` if this is incorrect.
|
|
48
46
|
In the future, you can allow nbdev to see private repos by setting the environment variable GITHUB_TOKEN as described here:
|
|
49
47
|
https://nbdev.fast.ai/api/release.html#setup"""]
|
|
50
48
|
print(''.join(msg))
|
|
51
49
|
return default_branch,default_kw,''
|
|
52
|
-
|
|
50
|
+
|
|
53
51
|
return r.default_branch, default_kw if not getattr(r, 'topics', []) else ' '.join(r.topics), r.description
|
|
54
52
|
|
|
55
53
|
# %% ../nbs/api/01_config.ipynb #35d5c037
|
|
@@ -170,7 +168,7 @@ def _find_nbdev_pyproject(path=None):
|
|
|
170
168
|
# %% ../nbs/api/01_config.ipynb #3dac70e0
|
|
171
169
|
nbdev_defaults = dict(nbs_path='nbs', doc_path='_docs', tst_flags='notest', recursive=True, readme_nb='index.ipynb',
|
|
172
170
|
clean_ids=True, clear_all=False, put_version_in_init=True, jupyter_hooks=False, custom_sidebar=False, branch='main',
|
|
173
|
-
doc_procs=[], export_procs=[])
|
|
171
|
+
doc_procs=[], export_procs=[], exec_profile=True)
|
|
174
172
|
|
|
175
173
|
_path_keys = 'lib_path', 'nbs_path', 'doc_path'
|
|
176
174
|
|
|
@@ -146,14 +146,16 @@ def nbdev_export(
|
|
|
146
146
|
**kwargs):
|
|
147
147
|
"Export notebooks in `path` to Python modules"
|
|
148
148
|
if os.environ.get('IN_TEST',0): return
|
|
149
|
-
if
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
149
|
+
if path: path = Path(path).resolve()
|
|
150
|
+
if not is_nbdev(path): raise Exception('`nbdev-export` must be called from a directory within a nbdev project.')
|
|
151
|
+
with working_directory(get_config(path).config_path if path else '.'):
|
|
152
|
+
cfg = get_config()
|
|
153
|
+
procs = procs.split() if procs else cfg.get('export_procs', [])
|
|
154
|
+
procs = [import_obj(p) for p in procs] if procs else None
|
|
155
|
+
files = nbglob(path=path, as_path=True, **kwargs).sorted('name')
|
|
156
|
+
for f in files: nb_export(f, procs=procs)
|
|
157
|
+
add_init(cfg.lib_path)
|
|
158
|
+
_build_modidx()
|
|
157
159
|
|
|
158
160
|
# %% ../nbs/api/05_doclinks.ipynb #3134c22b
|
|
159
161
|
typs = 'module','class','method','function'
|
|
@@ -46,7 +46,8 @@ def _make_conflict(a,b, branch1, branch2):
|
|
|
46
46
|
return _make_md(f'{_BEG} {branch1}') + a+_make_md(_MID)+b + _make_md(f'{_END} {branch2}')
|
|
47
47
|
|
|
48
48
|
def _merge_cells(a, b, brancha, branchb, theirs):
|
|
49
|
-
|
|
49
|
+
def _k(cs): return [(c.cell_type,c.source) for c in cs]
|
|
50
|
+
matches = SequenceMatcher(None, _k(a), _k(b)).get_matching_blocks()
|
|
50
51
|
res,prev_sa,prev_sb,conflict = [],0,0,False
|
|
51
52
|
for sa,sb,sz in matches:
|
|
52
53
|
ca,cb = a[prev_sa:sa],b[prev_sb:sb]
|
|
@@ -128,7 +128,8 @@ def add_links(cell):
|
|
|
128
128
|
if cell.cell_type == 'markdown': cell.source = nl.linkify(cell.source)
|
|
129
129
|
for o in cell.get('outputs', []):
|
|
130
130
|
if hasattr(o, 'data') and hasattr(o['data'], 'text/markdown'):
|
|
131
|
-
|
|
131
|
+
md = o.data['text/markdown']
|
|
132
|
+
o.data['text/markdown'] = nl.linkify(''.join(md) if isinstance(md, list) else md)
|
|
132
133
|
|
|
133
134
|
# %% ../nbs/api/10_processors.ipynb #809e9225
|
|
134
135
|
def add_fold(cell):
|
|
@@ -142,7 +143,7 @@ _re_ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
|
|
142
143
|
def strip_ansi(cell):
|
|
143
144
|
"Strip Ansi Characters."
|
|
144
145
|
for outp in cell.get('outputs', []):
|
|
145
|
-
if outp.get('name')=='stdout': outp['text'] =
|
|
146
|
+
if outp.get('name')=='stdout': outp['text'] = _re_ansi_escape.sub('', outp.text)
|
|
146
147
|
|
|
147
148
|
# %% ../nbs/api/10_processors.ipynb #640b7eca
|
|
148
149
|
def strip_hidden_metadata(cell):
|
|
@@ -169,7 +170,7 @@ def filter_stream_(cell, *words):
|
|
|
169
170
|
if not words: return
|
|
170
171
|
for outp in cell.get('outputs', []):
|
|
171
172
|
if outp.output_type == 'stream':
|
|
172
|
-
outp['text'] =
|
|
173
|
+
outp['text'] = ''.join(l for l in outp.text.splitlines(True) if not re.search('|'.join(words), l))
|
|
173
174
|
|
|
174
175
|
# %% ../nbs/api/10_processors.ipynb #848fd452-3d63-4c41-aaa6-e14cbeb9fdcd
|
|
175
176
|
_aimagics_pattern = re.compile(r'^\s*(%%ai.? |%%ai.?$)', re.MULTILINE)
|
|
@@ -242,7 +243,7 @@ class exec_show_docs(Processor):
|
|
|
242
243
|
"Execute cells needed for `show_docs` output, including exported cells and imports"
|
|
243
244
|
def begin(self):
|
|
244
245
|
if nb_lang(self.nb) != 'python': return
|
|
245
|
-
self.k = CaptureShell()
|
|
246
|
+
self.k = CaptureShell(profile=bool(get_config().exec_profile))
|
|
246
247
|
self.k.run_cell('from nbdev.showdoc import show_doc')
|
|
247
248
|
|
|
248
249
|
def __call__(self, cell):
|
|
@@ -196,10 +196,16 @@ def _ensure_quarto():
|
|
|
196
196
|
print("Quarto is not installed. We will download and install it for you.")
|
|
197
197
|
install.__wrapped__()
|
|
198
198
|
|
|
199
|
+
# %% ../nbs/api/14_quarto.ipynb #66163c30
|
|
200
|
+
def _chk_nbdev_yml(path):
|
|
201
|
+
"Fail early if `nbdev.yml` is missing from `path`, since without it quarto silently renders to the wrong location"
|
|
202
|
+
if not (path/'nbdev.yml').exists(): raise FileNotFoundError(f"{path/'nbdev.yml'} not found, so docs/README can not be built correctly.")
|
|
203
|
+
|
|
199
204
|
# %% ../nbs/api/14_quarto.ipynb #6b880922
|
|
200
205
|
def _pre_docs(path=None, n_workers:int=defaults.cpus, **kwargs):
|
|
201
206
|
cfg = get_config()
|
|
202
207
|
path = Path(path) if path else cfg.nbs_path
|
|
208
|
+
_chk_nbdev_yml(path)
|
|
203
209
|
_ensure_quarto()
|
|
204
210
|
refresh_quarto_yml()
|
|
205
211
|
import nbdev.doclinks
|
|
@@ -261,6 +267,7 @@ def nbdev_readme(
|
|
|
261
267
|
"Create README.md from readme_nb (index.ipynb by default)"
|
|
262
268
|
cfg = get_config()
|
|
263
269
|
path = Path(path) if path else cfg.nbs_path
|
|
270
|
+
_chk_nbdev_yml(path)
|
|
264
271
|
if chk_time and _doc_mtime_not_older(cfg.config_path/'README.md', path/cfg.readme_nb): return
|
|
265
272
|
|
|
266
273
|
with _SidebarYmlRemoved(path): # to avoid rendering whole website
|
|
@@ -5,8 +5,8 @@ Docs: https://nbdev.fast.ai/api/release.html.md"""
|
|
|
5
5
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/18_release.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
|
-
__all__ = ['GH_HOST', 'CONDA_WARNING', 'Release', 'changelog', 'push_release', 'release_git', 'release_gh',
|
|
9
|
-
'latest_pypi', 'pypi_details', 'conda_output_path', 'write_conda_meta', 'write_requirements',
|
|
8
|
+
__all__ = ['GH_HOST', 'CONDA_WARNING', 'update_changelog', 'Release', 'changelog', 'push_release', 'release_git', 'release_gh',
|
|
9
|
+
'pypi_json', 'latest_pypi', 'pypi_details', 'conda_output_path', 'write_conda_meta', 'write_requirements',
|
|
10
10
|
'anaconda_upload', 'release_conda', 'chk_conda_rel', 'release_pypi', 'release_both', 'nbdev_bump_version']
|
|
11
11
|
|
|
12
12
|
# %% ../nbs/api/18_release.ipynb #c35cc2b8
|
|
@@ -15,7 +15,7 @@ from ghapi.core import *
|
|
|
15
15
|
|
|
16
16
|
from datetime import datetime
|
|
17
17
|
from packaging.version import Version
|
|
18
|
-
import shutil,subprocess
|
|
18
|
+
import shutil,subprocess,asyncio
|
|
19
19
|
|
|
20
20
|
from .doclinks import *
|
|
21
21
|
|
|
@@ -42,6 +42,12 @@ def _load_json(cfg, k):
|
|
|
42
42
|
try: return json.loads(cfg[k])
|
|
43
43
|
except json.JSONDecodeError as e: raise Exception(f"Key: `{k}` in .ini file is not a valid JSON string: {e}")
|
|
44
44
|
|
|
45
|
+
# %% ../nbs/api/18_release.ipynb #dcaba486
|
|
46
|
+
def update_changelog(txt, ver, notes, marker='<!-- do not remove -->\n'):
|
|
47
|
+
"Insert `notes` into changelog `txt` after `marker`, replacing any existing section for `ver`"
|
|
48
|
+
txt = re.sub(rf'\n## {re.escape(ver)}\n.*?(?=\n## |\Z)', '', txt, flags=re.S)
|
|
49
|
+
return txt.replace(marker, marker+notes+'\n')
|
|
50
|
+
|
|
45
51
|
# %% ../nbs/api/18_release.ipynb #0b36471a
|
|
46
52
|
class Release:
|
|
47
53
|
def __init__(self, owner=None, repo=None, token=None, **groups):
|
|
@@ -52,6 +58,7 @@ class Release:
|
|
|
52
58
|
default_groups=dict(breaking="Breaking Changes", enhancement="New Features", bug="Bugs Squashed")
|
|
53
59
|
groups=_load_json(self.cfg, 'label_groups') if 'label_groups' in self.cfg else default_groups
|
|
54
60
|
os.chdir(self.cfg.config_path)
|
|
61
|
+
if repo and '/' in repo: owner,repo = repo.split('/', 1)
|
|
55
62
|
owner,repo = owner or self.cfg.user, repo or self.cfg.repo
|
|
56
63
|
token = ifnone(token, os.getenv('NBDEV_TOKEN',None))
|
|
57
64
|
if not token and Path('token').exists(): token = Path('token').read_text().strip()
|
|
@@ -60,38 +67,40 @@ class Release:
|
|
|
60
67
|
self.gh = GhApi(owner, repo, token)
|
|
61
68
|
self.groups = groups
|
|
62
69
|
|
|
63
|
-
def _issues(self, label):
|
|
64
|
-
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
|
|
70
|
+
async def _issues(self, label):
|
|
71
|
+
return await self.gh.issues.list_for_repo(state='closed', sort='created', filter='all', since=self.commit_date, labels=label)
|
|
72
|
+
async def _issue_groups(self): return await asyncio.gather(*map(self._issues, self.groups.keys()))
|
|
66
73
|
|
|
67
74
|
# %% ../nbs/api/18_release.ipynb #aa22dfe3
|
|
68
75
|
@patch
|
|
69
|
-
def changelog(self:Release,
|
|
76
|
+
async def changelog(self:Release,
|
|
70
77
|
debug=False): # Just print the latest changes, instead of updating file
|
|
71
78
|
"Create the CHANGELOG.md file, or return the proposed text if `debug` is `True`"
|
|
72
79
|
if not self.changefile.exists(): self.changefile.write_text("# Release notes\n\n<!-- do not remove -->\n")
|
|
73
80
|
marker = '<!-- do not remove -->\n'
|
|
74
|
-
try: self.commit_date = (lr:=self.gh.repos.get_latest_release()).published_at
|
|
75
|
-
except
|
|
81
|
+
try: self.commit_date = (lr:=await self.gh.repos.get_latest_release()).published_at
|
|
82
|
+
except APIError as e:
|
|
83
|
+
if e.status_code != 404: raise
|
|
84
|
+
lr,self.commit_date = None,'2000-01-01T00:00:004Z'
|
|
76
85
|
if lr and (Version(self.cfg.version) <= Version(lr.tag_name)):
|
|
77
86
|
print(f'Error: Version bump required: expected: >{lr.tag_name}, got: {self.cfg.version}.')
|
|
78
87
|
raise SystemExit(1)
|
|
79
88
|
res = f"\n## {self.cfg.version}\n"
|
|
80
|
-
issues = self._issue_groups()
|
|
89
|
+
issues = await self._issue_groups()
|
|
81
90
|
res += '\n'.join(_issues_txt(*o) for o in zip(issues, self.groups.values()))
|
|
82
91
|
if debug: return res
|
|
83
|
-
res = self.changefile.read_text().
|
|
92
|
+
res = update_changelog(self.changefile.read_text(), self.cfg.version, res, marker)
|
|
84
93
|
shutil.copy(self.changefile, self.changefile.with_suffix(".bak"))
|
|
85
94
|
self.changefile.write_text(res)
|
|
86
95
|
run(f'git add {self.changefile}')
|
|
87
96
|
|
|
88
97
|
# %% ../nbs/api/18_release.ipynb #068421c6
|
|
89
98
|
@patch
|
|
90
|
-
def release(self:Release):
|
|
99
|
+
async def release(self:Release):
|
|
91
100
|
"Tag and create a release in GitHub for the current version"
|
|
92
101
|
ver = self.cfg.version
|
|
93
102
|
notes = self.latest_notes()
|
|
94
|
-
self.gh.create_release(ver, branch=self.cfg.branch, body=notes)
|
|
103
|
+
await self.gh.create_release(ver, branch=self.cfg.branch, body=notes)
|
|
95
104
|
return ver
|
|
96
105
|
|
|
97
106
|
# %% ../nbs/api/18_release.ipynb #22101171
|
|
@@ -105,38 +114,41 @@ def latest_notes(self:Release):
|
|
|
105
114
|
|
|
106
115
|
# %% ../nbs/api/18_release.ipynb #01cb1ca4
|
|
107
116
|
@call_parse
|
|
108
|
-
def changelog(
|
|
117
|
+
async def changelog(
|
|
109
118
|
debug:store_true=False, # Print info to be added to CHANGELOG, instead of updating file
|
|
110
|
-
repo:str=None, # repo to use instead of
|
|
119
|
+
repo:str=None, # "repo" or "owner/repo" to use instead of pyproject.toml values
|
|
120
|
+
token:str=None, # Optional GitHub token (otherwise `token` file is used)
|
|
111
121
|
):
|
|
112
122
|
"Create a CHANGELOG.md file from closed and labeled GitHub issues"
|
|
113
|
-
res = Release(repo=repo).changelog(debug=debug)
|
|
123
|
+
res = await Release(repo=repo, token=token).changelog(debug=debug)
|
|
114
124
|
if debug: print(res)
|
|
115
125
|
|
|
116
126
|
# %% ../nbs/api/18_release.ipynb #15b66643
|
|
117
|
-
def push_release(token:str=None):
|
|
127
|
+
async def push_release(token:str=None, repo:str=None):
|
|
118
128
|
"Create a GitHub release (changelog should already be committed/pushed). Returns the release."
|
|
119
|
-
return Release(token=token).release()
|
|
129
|
+
return await Release(repo=repo, token=token).release()
|
|
120
130
|
|
|
121
131
|
# %% ../nbs/api/18_release.ipynb #6d3c5cd8
|
|
122
132
|
@call_parse
|
|
123
|
-
def release_git(token:str=None):
|
|
133
|
+
async def release_git(token:str=None):
|
|
124
134
|
"Tag and create a release in GitHub for the current version"
|
|
125
|
-
print(f"Released {push_release(token)}")
|
|
135
|
+
print(f"Released {await push_release(token)}")
|
|
126
136
|
|
|
127
137
|
# %% ../nbs/api/18_release.ipynb #94ee72b1
|
|
128
138
|
@call_parse
|
|
129
|
-
def release_gh(
|
|
130
|
-
token:str=None # Optional GitHub token (otherwise `token` file is used)
|
|
139
|
+
async def release_gh(
|
|
140
|
+
token:str=None, # Optional GitHub token (otherwise `token` file is used)
|
|
141
|
+
repo:str=None, # "repo" or "owner/repo" to use instead of pyproject.toml values
|
|
142
|
+
no_changelog:store_true=False # Skip changelog creation (assumes CHANGELOG.md is up to date)
|
|
131
143
|
):
|
|
132
144
|
"Calls `nbdev-changelog`, lets you edit the result, then pushes to git and calls `nbdev-release-git`"
|
|
133
145
|
cfg = _find_config()
|
|
134
|
-
Release().changelog()
|
|
146
|
+
if not no_changelog: await Release(repo=repo).changelog()
|
|
135
147
|
subprocess.run([os.environ.get('EDITOR','nano'), cfg.config_path/'CHANGELOG.md'])
|
|
136
148
|
if not input("Make release now? (y/n) ").lower().startswith('y'): sys.exit(1)
|
|
137
149
|
run('git commit -am release')
|
|
138
150
|
run('git push')
|
|
139
|
-
print(f"Released {push_release(token)}")
|
|
151
|
+
print(f"Released {await push_release(token, repo=repo)}")
|
|
140
152
|
|
|
141
153
|
# %% ../nbs/api/18_release.ipynb #5b4d4aa2
|
|
142
154
|
from fastcore.all import *
|
|
@@ -32,36 +32,45 @@ def test_nb(fn, # file name of notebook to test
|
|
|
32
32
|
showerr=True, # print errors to stderr?
|
|
33
33
|
basepath=None, # path to add to sys.path
|
|
34
34
|
verbose=False, # stream stdout/stderr from cells to console?
|
|
35
|
-
save=False
|
|
35
|
+
save=False, # write outputs back to notebook on success?
|
|
36
|
+
profile:bool=None): # load the IPython profile, as `ipykernel` does? (default: `exec_profile` config key)
|
|
36
37
|
"Execute tests in notebook in `fn` except those with `skip_flags`"
|
|
37
38
|
faulthandler.register(signal.SIGINT, file=sys.__stderr__, all_threads=True, chain=True)
|
|
39
|
+
fn = Path(fn)
|
|
38
40
|
if basepath: sys.path.insert(0, str(basepath))
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
nb = NBProcessor(fn, rm_directives=False, process=True).nb
|
|
42
|
-
fm = nb_frontmatter(nb)
|
|
43
|
-
if str2bool(fm.get('skip_exec', False)) or nb_lang(nb) != 'python': return True, 0
|
|
44
|
-
|
|
45
|
-
def _no_eval(cell):
|
|
46
|
-
if cell.cell_type != 'code': return True
|
|
47
|
-
if 'nbdev_export'+'(' in cell.source: return True
|
|
48
|
-
direc = getattr(cell, 'directives_', {}) or {}
|
|
49
|
-
if direc.get('eval:', [''])[0].lower() == 'false': return True
|
|
50
|
-
return flags & direc.keys()
|
|
51
|
-
|
|
52
|
-
start = time.time()
|
|
53
|
-
k = CaptureShell(fn)
|
|
54
|
-
if do_print: print(f'Starting {fn}')
|
|
41
|
+
prev_test = os.environ.get('IN_TEST')
|
|
42
|
+
if not IN_NOTEBOOK: os.environ['IN_TEST'] = '1'
|
|
55
43
|
try:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
44
|
+
flags=set(L(skip_flags)) - set(L(force_flags))
|
|
45
|
+
nb = NBProcessor(fn, rm_directives=False, process=True).nb
|
|
46
|
+
fm = nb_frontmatter(nb)
|
|
47
|
+
if str2bool(fm.get('skip_exec', False)) or nb_lang(nb) != 'python': return True, 0
|
|
48
|
+
|
|
49
|
+
def _no_eval(cell):
|
|
50
|
+
if cell.cell_type != 'code': return True
|
|
51
|
+
if 'nbdev_export'+'(' in cell.source: return True
|
|
52
|
+
direc = getattr(cell, 'directives_', {}) or {}
|
|
53
|
+
if direc.get('eval:', [''])[0].lower() == 'false': return True
|
|
54
|
+
return flags & direc.keys()
|
|
55
|
+
|
|
56
|
+
start = time.time()
|
|
57
|
+
if profile is None: profile = bool(get_config(fn.parent).exec_profile)
|
|
58
|
+
k = CaptureShell(fn, profile=profile)
|
|
59
|
+
if do_print: print(f'Starting {fn}')
|
|
60
|
+
try:
|
|
61
|
+
with working_directory(fn.parent):
|
|
62
|
+
k.run_all(nb, exc_stop=True, preproc=_no_eval, verbose=verbose)
|
|
63
|
+
if save: write_nb(nb, fn)
|
|
64
|
+
res = True
|
|
65
|
+
except:
|
|
66
|
+
if showerr: sys.stderr.write(k.prettytb(fname=fn)+'\n')
|
|
67
|
+
res=False
|
|
68
|
+
if do_print: print(f'- Completed {fn}')
|
|
69
|
+
return res,time.time()-start
|
|
70
|
+
finally:
|
|
71
|
+
if prev_test is None: os.environ.pop('IN_TEST', None)
|
|
72
|
+
else: os.environ['IN_TEST'] = prev_test
|
|
73
|
+
|
|
65
74
|
|
|
66
75
|
# %% ../nbs/api/12_test.ipynb #d8bf1f1b-935d-4b69-ba96-827c5d7213f0
|
|
67
76
|
def _keep_file(p:Path, # filename for which to check for `indicator_fname`
|
|
@@ -86,7 +95,8 @@ def nbdev_test(
|
|
|
86
95
|
save:bool=False, # Write outputs back to notebooks on success?
|
|
87
96
|
**kwargs):
|
|
88
97
|
"Test in parallel notebooks matching `path`, passing along `flags`"
|
|
89
|
-
|
|
98
|
+
cfg = get_config(Path(path).resolve() if path else None)
|
|
99
|
+
skip_flags = cfg.tst_flags
|
|
90
100
|
if isinstance(skip_flags, str): skip_flags = skip_flags.split()
|
|
91
101
|
force_flags = flags.split()
|
|
92
102
|
files = nbglob(path, as_path=True, **kwargs)
|
|
@@ -96,10 +106,10 @@ def nbdev_test(
|
|
|
96
106
|
if n_workers is None: n_workers = 0 if len(files)==1 else min(num_cpus(), 8)
|
|
97
107
|
if IN_NOTEBOOK: kw = {'method':'spawn'} if os.name=='nt' else {'method':'forkserver'}
|
|
98
108
|
else: kw = {'method':'forkserver'} if sys.platform=='darwin' else {}
|
|
99
|
-
wd_pth =
|
|
109
|
+
wd_pth = cfg.nbs_path
|
|
100
110
|
with working_directory(wd_pth if (wd_pth and wd_pth.exists()) else os.getcwd()):
|
|
101
111
|
results = parallel(test_nb, files, skip_flags=skip_flags, force_flags=force_flags, n_workers=n_workers,
|
|
102
|
-
basepath=
|
|
112
|
+
basepath=cfg.config_path, pause=pause, do_print=do_print, verbose=verbose, save=save, **kw)
|
|
103
113
|
passed,times = zip(*results)
|
|
104
114
|
if all(passed): print("Success.")
|
|
105
115
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nbdev
|
|
3
|
-
Version: 3.0
|
|
3
|
+
Version: 3.1.0
|
|
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,10 +16,10 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
16
16
|
Requires-Python: >=3.9
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: fastcore>=1.
|
|
20
|
-
Requires-Dist: execnb>=0.
|
|
19
|
+
Requires-Dist: fastcore>=1.14.1
|
|
20
|
+
Requires-Dist: execnb>=0.2.4
|
|
21
21
|
Requires-Dist: astunparse
|
|
22
|
-
Requires-Dist: ghapi>=
|
|
22
|
+
Requires-Dist: ghapi>=2.0.0
|
|
23
23
|
Requires-Dist: watchdog
|
|
24
24
|
Requires-Dist: asttokens
|
|
25
25
|
Requires-Dist: setuptools
|
|
@@ -19,7 +19,7 @@ classifiers = [
|
|
|
19
19
|
"License :: OSI Approved :: Apache Software License",
|
|
20
20
|
]
|
|
21
21
|
dynamic = ["version"]
|
|
22
|
-
dependencies = [ "fastcore>=1.
|
|
22
|
+
dependencies = [ "fastcore>=1.14.1", "execnb>=0.2.4", "astunparse", "ghapi>=2.0.0", "watchdog", "asttokens",
|
|
23
23
|
"setuptools", "build", "fastgit", "pyyaml", "tomli; python_version < '3.11'", ]
|
|
24
24
|
|
|
25
25
|
[project.optional-dependencies]
|
|
@@ -82,3 +82,6 @@ readme_nb = "getting_started.ipynb"
|
|
|
82
82
|
jupyter_hooks = true
|
|
83
83
|
allowed_cell_metadata_keys = ['solveit_ai']
|
|
84
84
|
|
|
85
|
+
|
|
86
|
+
[tool.chkstyle]
|
|
87
|
+
skip_paths = ["_modidx.py"]
|
|
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
|