nbdev 3.0.18__tar.gz → 3.2.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.2.0}/PKG-INFO +8 -5
- {nbdev-3.0.18 → nbdev-3.2.0}/README.md +4 -1
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/__init__.py +1 -1
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/_modidx.py +4 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/cli.py +22 -18
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/config.py +8 -9
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/doclinks.py +10 -8
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/export.py +9 -8
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/merge.py +9 -6
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/migrate.py +9 -7
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/processors.py +15 -8
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/qmd.py +34 -24
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/quarto.py +22 -8
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/release.py +36 -24
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/showdoc.py +6 -4
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/test.py +54 -40
- {nbdev-3.0.18 → nbdev-3.2.0/nbdev.egg-info}/PKG-INFO +8 -5
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev.egg-info/requires.txt +3 -3
- {nbdev-3.0.18 → nbdev-3.2.0}/pyproject.toml +4 -1
- {nbdev-3.0.18 → nbdev-3.2.0}/CONTRIBUTING.md +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/LICENSE +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/MANIFEST.in +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/clean.py +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/diff.py +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/extract_attachments.py +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/frontmatter.py +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/imports.py +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/maker.py +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/process.py +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/scrubmagics.py +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/serve.py +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/serve_drv.py +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev/sync.py +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev.egg-info/SOURCES.txt +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev.egg-info/dependency_links.txt +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev.egg-info/entry_points.txt +0 -0
- {nbdev-3.0.18 → nbdev-3.2.0}/nbdev.egg-info/top_level.txt +0 -0
- {nbdev-3.0.18 → nbdev-3.2.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.2.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.6
|
|
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
|
|
@@ -158,7 +158,7 @@ available commands:
|
|
|
158
158
|
nbdev-pypi Create and upload Python package to PyPI
|
|
159
159
|
nbdev-readme Create README.md from readme_nb (index.ipynb by default)
|
|
160
160
|
nbdev-release-both Release both conda and PyPI packages
|
|
161
|
-
nbdev-release-gh Calls `
|
|
161
|
+
nbdev-release-gh Calls `nbdev-changelog`, lets you edit the result, then pushes to git and calls `nbdev-release-git`
|
|
162
162
|
nbdev-release-git Tag and create a release in GitHub for the current version
|
|
163
163
|
nbdev-requirements Writes a `requirements.txt` file to `directory` based on pyproject.toml.
|
|
164
164
|
nbdev-sidebar Create sidebar.yml
|
|
@@ -184,6 +184,9 @@ some_module.something()
|
|
|
184
184
|
Instead, split this into two cells, one which does `import some_module`,
|
|
185
185
|
and the other which does `some_module.something()`.
|
|
186
186
|
|
|
187
|
+
Only top-level statements count: `try: import` blocks and imports inside
|
|
188
|
+
function definitions are fine.
|
|
189
|
+
|
|
187
190
|
The reason for this is that when we create your documentation website,
|
|
188
191
|
we ensure that all of the signatures for functions you document are up
|
|
189
192
|
to date, by running the imports, exported cells, and
|
|
@@ -114,7 +114,7 @@ available commands:
|
|
|
114
114
|
nbdev-pypi Create and upload Python package to PyPI
|
|
115
115
|
nbdev-readme Create README.md from readme_nb (index.ipynb by default)
|
|
116
116
|
nbdev-release-both Release both conda and PyPI packages
|
|
117
|
-
nbdev-release-gh Calls `
|
|
117
|
+
nbdev-release-gh Calls `nbdev-changelog`, lets you edit the result, then pushes to git and calls `nbdev-release-git`
|
|
118
118
|
nbdev-release-git Tag and create a release in GitHub for the current version
|
|
119
119
|
nbdev-requirements Writes a `requirements.txt` file to `directory` based on pyproject.toml.
|
|
120
120
|
nbdev-sidebar Create sidebar.yml
|
|
@@ -140,6 +140,9 @@ some_module.something()
|
|
|
140
140
|
Instead, split this into two cells, one which does `import some_module`,
|
|
141
141
|
and the other which does `some_module.something()`.
|
|
142
142
|
|
|
143
|
+
Only top-level statements count: `try: import` blocks and imports inside
|
|
144
|
+
function definitions are fine.
|
|
145
|
+
|
|
143
146
|
The reason for this is that when we create your documentation website,
|
|
144
147
|
we ensure that all of the signatures for functions you document are up
|
|
145
148
|
to date, by running the imports, exported cells, and
|
|
@@ -225,6 +225,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
225
225
|
'nbdev.processors._is_showdoc': ('api/processors.html#_is_showdoc', 'nbdev/processors.py'),
|
|
226
226
|
'nbdev.processors._re_hideline': ('api/processors.html#_re_hideline', 'nbdev/processors.py'),
|
|
227
227
|
'nbdev.processors._show_docs': ('api/processors.html#_show_docs', 'nbdev/processors.py'),
|
|
228
|
+
'nbdev.processors._tl_contains': ('api/processors.html#_tl_contains', 'nbdev/processors.py'),
|
|
228
229
|
'nbdev.processors._want_doc': ('api/processors.html#_want_doc', 'nbdev/processors.py'),
|
|
229
230
|
'nbdev.processors.add_fold': ('api/processors.html#add_fold', 'nbdev/processors.py'),
|
|
230
231
|
'nbdev.processors.add_links': ('api/processors.html#add_links', 'nbdev/processors.py'),
|
|
@@ -278,6 +279,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
278
279
|
'nbdev/quarto.py'),
|
|
279
280
|
'nbdev.quarto._SidebarYmlRemoved.__init__': ( 'api/quarto.html#_sidebarymlremoved.__init__',
|
|
280
281
|
'nbdev/quarto.py'),
|
|
282
|
+
'nbdev.quarto._chk_nbdev_yml': ('api/quarto.html#_chk_nbdev_yml', 'nbdev/quarto.py'),
|
|
281
283
|
'nbdev.quarto._copytree': ('api/quarto.html#_copytree', 'nbdev/quarto.py'),
|
|
282
284
|
'nbdev.quarto._doc_mtime_not_older': ('api/quarto.html#_doc_mtime_not_older', 'nbdev/quarto.py'),
|
|
283
285
|
'nbdev.quarto._ensure_quarto': ('api/quarto.html#_ensure_quarto', 'nbdev/quarto.py'),
|
|
@@ -286,6 +288,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
286
288
|
'nbdev.quarto._nbglob_docs': ('api/quarto.html#_nbglob_docs', 'nbdev/quarto.py'),
|
|
287
289
|
'nbdev.quarto._pre': ('api/quarto.html#_pre', 'nbdev/quarto.py'),
|
|
288
290
|
'nbdev.quarto._pre_docs': ('api/quarto.html#_pre_docs', 'nbdev/quarto.py'),
|
|
291
|
+
'nbdev.quarto._readme_cands': ('api/quarto.html#_readme_cands', 'nbdev/quarto.py'),
|
|
289
292
|
'nbdev.quarto._recursive_parser': ('api/quarto.html#_recursive_parser', 'nbdev/quarto.py'),
|
|
290
293
|
'nbdev.quarto._save_cached_contributing': ('api/quarto.html#_save_cached_contributing', 'nbdev/quarto.py'),
|
|
291
294
|
'nbdev.quarto._save_cached_readme': ('api/quarto.html#_save_cached_readme', 'nbdev/quarto.py'),
|
|
@@ -330,6 +333,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
330
333
|
'nbdev.release.release_gh': ('api/release.html#release_gh', 'nbdev/release.py'),
|
|
331
334
|
'nbdev.release.release_git': ('api/release.html#release_git', 'nbdev/release.py'),
|
|
332
335
|
'nbdev.release.release_pypi': ('api/release.html#release_pypi', 'nbdev/release.py'),
|
|
336
|
+
'nbdev.release.update_changelog': ('api/release.html#update_changelog', 'nbdev/release.py'),
|
|
333
337
|
'nbdev.release.write_conda_meta': ('api/release.html#write_conda_meta', 'nbdev/release.py'),
|
|
334
338
|
'nbdev.release.write_requirements': ('api/release.html#write_requirements', 'nbdev/release.py')},
|
|
335
339
|
'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)
|
|
@@ -168,18 +169,21 @@ def nbdev_update_license(
|
|
|
168
169
|
# %% ../nbs/api/13_cli.ipynb #412b4cd2
|
|
169
170
|
@call_parse
|
|
170
171
|
@delegates(nb_export, but=['procs', 'mod_maker'])
|
|
171
|
-
def nb_export_cli(
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
def nb_export_cli(
|
|
173
|
+
nbname,
|
|
174
|
+
debug:store_true=False, # Debug flag
|
|
175
|
+
**kwargs
|
|
176
|
+
):
|
|
174
177
|
"Export a single nbdev notebook to a python script."
|
|
175
178
|
return nb_export(nbname=nbname, debug=debug, **kwargs)
|
|
176
179
|
|
|
177
180
|
# %% ../nbs/api/13_cli.ipynb #aaa472e7
|
|
178
181
|
@call_parse
|
|
179
|
-
def watch_export(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
def watch_export(
|
|
183
|
+
nbs:str=None, # Nb directory to watch for changes
|
|
184
|
+
lib:str=None, # Export directory to write py files to
|
|
185
|
+
force:bool=False # Ignore nbdev config if in nbdev project
|
|
186
|
+
):
|
|
183
187
|
'''Use `nb_export` on ipynb files in `nbs` directory on changes using nbdev config if available'''
|
|
184
188
|
cfg = get_config() if is_nbdev() else None
|
|
185
189
|
nbs = nbs or (cfg.nbs_path if cfg else '.')
|
|
@@ -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
|
|
|
@@ -238,7 +236,7 @@ def get_config(path=None, also_settings=False):
|
|
|
238
236
|
nbdev = {**user, **d.get('tool', {}).get('nbdev', {})}
|
|
239
237
|
return ConfigToml(nbdev, d.get('project', {}), cfg_file)
|
|
240
238
|
if also_settings:
|
|
241
|
-
from fastcore.
|
|
239
|
+
from fastcore.xtras import Config
|
|
242
240
|
cfg = Config.find('settings.ini', path)
|
|
243
241
|
if cfg: return cfg
|
|
244
242
|
cfg_path = Path(path or Path.cwd()).expanduser().absolute()
|
|
@@ -267,7 +265,8 @@ def nbpath2docurl(nb_path):
|
|
|
267
265
|
"Hosted docs URL (with `.md` suffix) for notebook `nb_path`, or '' if no `doc_host`"
|
|
268
266
|
cfg = get_config()
|
|
269
267
|
if not cfg.doc_host: return ''
|
|
270
|
-
p = Path(nb_path).resolve().relative_to(cfg.path('nbs_path'))
|
|
268
|
+
try: p = Path(nb_path).resolve().relative_to(cfg.path('nbs_path'))
|
|
269
|
+
except ValueError: return ""
|
|
271
270
|
return f"{cfg.doc_host.rstrip('/')}{cfg.doc_baseurl.rstrip('/')}/{nbpath2html(p).as_posix()}.md"
|
|
272
271
|
|
|
273
272
|
# %% ../nbs/api/01_config.ipynb #163177f2
|
|
@@ -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'
|
|
@@ -36,14 +36,15 @@ class ExportModuleProc:
|
|
|
36
36
|
_exports_=_export_
|
|
37
37
|
|
|
38
38
|
# %% ../nbs/api/04_export.ipynb #76717e36
|
|
39
|
-
def nb_export(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
def nb_export(
|
|
40
|
+
nbname:str, # Filename of notebook
|
|
41
|
+
lib_path:str=None, # Path to destination library. If not in a nbdev project, defaults to current directory.
|
|
42
|
+
procs=None, # Processors to use
|
|
43
|
+
name:str=None, # Name of python script {name}.py to create.
|
|
44
|
+
mod_maker=ModuleMaker,
|
|
45
|
+
debug:bool=False, # Debug mode
|
|
46
|
+
solo_nb:bool=False # Export single notebook outside of an nbdev project.
|
|
47
|
+
):
|
|
47
48
|
"Create module(s) from notebook"
|
|
48
49
|
if lib_path is None: lib_path = get_config().lib_path if is_nbdev() else '.'
|
|
49
50
|
exp = ExportModuleProc()
|
|
@@ -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]
|
|
@@ -59,11 +60,13 @@ def _merge_cells(a, b, brancha, branchb, theirs):
|
|
|
59
60
|
|
|
60
61
|
# %% ../nbs/api/07_merge.ipynb #cc492d30
|
|
61
62
|
@call_parse
|
|
62
|
-
def nbdev_fix(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
def nbdev_fix(
|
|
64
|
+
nbname:str, # Notebook filename to fix
|
|
65
|
+
outname:str=None, # Filename of output notebook (defaults to `nbname`)
|
|
66
|
+
nobackup:bool_arg=True, # Do not backup `nbname` to `nbname`.bak if `outname` not provided
|
|
67
|
+
theirs:bool=False, # Use their outputs and metadata instead of ours
|
|
68
|
+
noprint:bool=False # Do not print info about whether conflicts are found
|
|
69
|
+
):
|
|
67
70
|
"Create working notebook from conflicted notebook `nbname`"
|
|
68
71
|
nbname = Path(nbname)
|
|
69
72
|
if not nobackup and not outname: shutil.copy(nbname, nbname.with_suffix('.ipynb.bak'))
|
|
@@ -32,11 +32,12 @@ def _file_slug(fname):
|
|
|
32
32
|
return dt + p.stem[11:]
|
|
33
33
|
|
|
34
34
|
# %% ../nbs/api/16_migrate.ipynb #689bf354
|
|
35
|
-
def _replace_fm(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
def _replace_fm(
|
|
36
|
+
d:dict, # dictionary you wish to conditionally change
|
|
37
|
+
k:str, # key to check
|
|
38
|
+
val:str,# value to check if d[k] == v
|
|
39
|
+
repl_dict:dict #dictionary that will be used as a replacement
|
|
40
|
+
):
|
|
40
41
|
"replace key `k` in dict `d` if d[k] == val with `repl_dict`"
|
|
41
42
|
if str(d.get(k, '')).lower().strip() == str(val.lower()).strip():
|
|
42
43
|
d.pop(k)
|
|
@@ -191,8 +192,9 @@ def _migrate_workflows(path):
|
|
|
191
192
|
wf_path = Path(path) / '.github/workflows'
|
|
192
193
|
if not wf_path.exists(): return
|
|
193
194
|
replacements = [
|
|
194
|
-
('fastai/workflows/quarto-ghp@', '
|
|
195
|
-
('fastai/workflows/nbdev-ci@', '
|
|
195
|
+
('fastai/workflows/quarto-ghp@', 'AnswerDotAI/workflows/quarto-ghp3@'),
|
|
196
|
+
('fastai/workflows/nbdev-ci@', 'AnswerDotAI/workflows/nbdev3-ci@'),
|
|
197
|
+
('fastai/workflows/', 'AnswerDotAI/workflows/'),
|
|
196
198
|
]
|
|
197
199
|
for f in (*wf_path.glob('*.yml'), *wf_path.glob('*.yaml')):
|
|
198
200
|
txt = f.read_text()
|
|
@@ -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)
|
|
@@ -181,11 +182,15 @@ def ai_magics(cell):
|
|
|
181
182
|
cell.source = '\n'.join(cell.source.splitlines()[1:])
|
|
182
183
|
|
|
183
184
|
# %% ../nbs/api/10_processors.ipynb #5a9c8fd4
|
|
184
|
-
|
|
185
|
+
_def_strip_magics = '%load_ext %autoreload %reload_ext %matplotlib %config'
|
|
185
186
|
|
|
186
187
|
def clean_magics(cell):
|
|
187
|
-
"
|
|
188
|
-
|
|
188
|
+
"Remove housekeeping magics: those named in the `strip_magics` config (space-separated, default `_def_strip_magics`)"
|
|
189
|
+
mm = str(get_config().get('strip_magics', _def_strip_magics)).split()
|
|
190
|
+
if not mm or cell.cell_type != 'code': return
|
|
191
|
+
pat = re.compile(rf"^\s*({'|'.join(re.escape(m) for m in mm)})\b.*", re.MULTILINE)
|
|
192
|
+
cell.source = pat.sub('', cell.source).strip()
|
|
193
|
+
|
|
189
194
|
|
|
190
195
|
# %% ../nbs/api/10_processors.ipynb #93e27a52
|
|
191
196
|
_re_hdr_dash = re.compile(r'^#+\s+.*\s+-\s*$', re.MULTILINE)
|
|
@@ -222,6 +227,8 @@ def _ast_contains(trees, types):
|
|
|
222
227
|
for node in ast.walk(tree):
|
|
223
228
|
if isinstance(node, types): return True
|
|
224
229
|
|
|
230
|
+
def _tl_contains(trees, types): return any(isinstance(t, types) for t in trees)
|
|
231
|
+
|
|
225
232
|
def _do_eval(cell):
|
|
226
233
|
if cell_lang(cell) != 'python': return
|
|
227
234
|
if not cell.source or 'nbdev_export'+'()' in cell.source: return
|
|
@@ -232,7 +239,7 @@ def _do_eval(cell):
|
|
|
232
239
|
_show_dirs = {'export','exports','exporti','exec_doc'}
|
|
233
240
|
if cell.directives_.keys() & _show_dirs: return True
|
|
234
241
|
if _ast_contains(trees, (ast.Import, ast.ImportFrom)):
|
|
235
|
-
if
|
|
242
|
+
if _tl_contains(trees, (ast.Import, ast.ImportFrom)) and _tl_contains(trees, (ast.Expr, ast.Assign)):
|
|
236
243
|
warn(f'Found cells containing imports and other code. See FAQ.\n---\n{cell.source}\n---\n')
|
|
237
244
|
return True
|
|
238
245
|
if _show_docs(trees): return True
|
|
@@ -242,7 +249,7 @@ class exec_show_docs(Processor):
|
|
|
242
249
|
"Execute cells needed for `show_docs` output, including exported cells and imports"
|
|
243
250
|
def begin(self):
|
|
244
251
|
if nb_lang(self.nb) != 'python': return
|
|
245
|
-
self.k = CaptureShell()
|
|
252
|
+
self.k = CaptureShell(profile=bool(get_config().exec_profile))
|
|
246
253
|
self.k.run_cell('from nbdev.showdoc import show_doc')
|
|
247
254
|
|
|
248
255
|
def __call__(self, cell):
|
|
@@ -14,10 +14,12 @@ from fastcore.utils import *
|
|
|
14
14
|
from fastcore.meta import delegates
|
|
15
15
|
|
|
16
16
|
# %% ../nbs/api/15_qmd.ipynb #5a64f1f4
|
|
17
|
-
def meta(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
def meta(
|
|
18
|
+
md, # Markdown to add meta to
|
|
19
|
+
classes=None, # List of CSS classes to add
|
|
20
|
+
style=None, # Dict of CSS styles to add
|
|
21
|
+
**kwargs # Additional attributes to add to meta
|
|
22
|
+
):
|
|
21
23
|
"A metadata section for qmd div in `{}`"
|
|
22
24
|
if style: kwargs['style'] = "; ".join(f'{k}: {v}' for k,v in style.items())
|
|
23
25
|
props = ' '.join(f'{k}="{v}"' for k,v in kwargs.items())
|
|
@@ -29,21 +31,25 @@ def meta(md, # Markdown to add meta to
|
|
|
29
31
|
return md + ("{" + meta + "}" if meta else "")
|
|
30
32
|
|
|
31
33
|
# %% ../nbs/api/15_qmd.ipynb #52637a70
|
|
32
|
-
def div(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
def div(
|
|
35
|
+
txt, # Markdown to add meta to
|
|
36
|
+
classes=None, # List of CSS classes to add
|
|
37
|
+
style=None, # Dict of CSS styles to add
|
|
38
|
+
**kwargs
|
|
39
|
+
):
|
|
36
40
|
"A qmd div with optional metadata section"
|
|
37
41
|
return meta("::: ", classes=classes, style=style, **kwargs) + f"\n\n{txt}\n\n:::\n\n"
|
|
38
42
|
|
|
39
43
|
# %% ../nbs/api/15_qmd.ipynb #f9f499f4
|
|
40
|
-
def img(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
def img(
|
|
45
|
+
fname, # Image to link to
|
|
46
|
+
classes=None, # List of CSS classes to add
|
|
47
|
+
style=None, # Dict of CSS styles to add
|
|
48
|
+
height=None, # Height attribute
|
|
49
|
+
relative=None, # Tuple of (position,px)
|
|
50
|
+
link=False, # Hyperlink to this image
|
|
51
|
+
**kwargs
|
|
52
|
+
):
|
|
47
53
|
"A qmd image"
|
|
48
54
|
kwargs,style = kwargs or {}, style or {}
|
|
49
55
|
if height: kwargs["height"]= f"{height}px"
|
|
@@ -55,23 +61,27 @@ def img(fname, # Image to link to
|
|
|
55
61
|
return f'[{res}]({fname})' if link else res
|
|
56
62
|
|
|
57
63
|
# %% ../nbs/api/15_qmd.ipynb #16d7aa5f
|
|
58
|
-
def btn(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
def btn(
|
|
65
|
+
txt, # Button text
|
|
66
|
+
link, # Button link URL
|
|
67
|
+
classes=None, # List of CSS classes to add
|
|
68
|
+
style=None, # Dict of CSS styles to add
|
|
69
|
+
**kwargs
|
|
70
|
+
):
|
|
63
71
|
"A qmd button"
|
|
64
72
|
return meta(f'[{txt}]({link})', classes=classes, style=style, role="button")
|
|
65
73
|
|
|
66
74
|
# %% ../nbs/api/15_qmd.ipynb #e414ed33
|
|
67
|
-
def tbl_row(
|
|
68
|
-
|
|
75
|
+
def tbl_row(
|
|
76
|
+
cols:list, # Auto-stringified columns to show in the row
|
|
77
|
+
):
|
|
69
78
|
"Create a markdown table row from `cols`"
|
|
70
79
|
return '|' + '|'.join(str(c or '') for c in cols) + '|'
|
|
71
80
|
|
|
72
81
|
# %% ../nbs/api/15_qmd.ipynb #38f0641b
|
|
73
|
-
def tbl_sep(
|
|
74
|
-
|
|
82
|
+
def tbl_sep(
|
|
83
|
+
sizes:int|list=3 # List of column sizes, or single `int` if all sizes the same
|
|
84
|
+
):
|
|
75
85
|
"Create a markdown table separator with relative column size `sizes`"
|
|
76
86
|
if isinstance(sizes,int): sizes = [3]*sizes
|
|
77
87
|
return tbl_row('-'*s for s in sizes)
|
|
@@ -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
|
|
@@ -243,15 +249,20 @@ def _copytree(a,b):
|
|
|
243
249
|
copy_tree(a, b)
|
|
244
250
|
|
|
245
251
|
# %% ../nbs/api/14_quarto.ipynb #caeaa153
|
|
252
|
+
def _readme_cands(cache, cfg):
|
|
253
|
+
"Locations where `quarto render -o README.md` may put its output, which differs by quarto version"
|
|
254
|
+
return cache/cfg.doc_path.name/'README.md', cache/'README.md'
|
|
255
|
+
|
|
246
256
|
def _save_cached_readme(cache, cfg):
|
|
247
|
-
|
|
248
|
-
readme =
|
|
249
|
-
if readme.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
257
|
+
cands = _readme_cands(cache, cfg)
|
|
258
|
+
readme = next((f for f in cands if f.exists()), None)
|
|
259
|
+
if not readme: raise FileNotFoundError(f"quarto render did not produce a README.md in any of: {cands}")
|
|
260
|
+
readme_path = cfg.config_path/'README.md'
|
|
261
|
+
if readme_path.exists(): readme_path.unlink() # py37 doesn't have `missing_ok`
|
|
262
|
+
move(readme, cfg.config_path)
|
|
263
|
+
_rdmi = readme.parent/((cache/cfg.readme_nb).stem + '_files') # Supporting files for README
|
|
264
|
+
if _rdmi.exists(): _copytree(_rdmi, cfg.config_path/_rdmi.name)
|
|
265
|
+
|
|
255
266
|
|
|
256
267
|
# %% ../nbs/api/14_quarto.ipynb #45d6bb5d
|
|
257
268
|
@call_parse
|
|
@@ -261,10 +272,13 @@ def nbdev_readme(
|
|
|
261
272
|
"Create README.md from readme_nb (index.ipynb by default)"
|
|
262
273
|
cfg = get_config()
|
|
263
274
|
path = Path(path) if path else cfg.nbs_path
|
|
275
|
+
_chk_nbdev_yml(path)
|
|
264
276
|
if chk_time and _doc_mtime_not_older(cfg.config_path/'README.md', path/cfg.readme_nb): return
|
|
265
277
|
|
|
266
278
|
with _SidebarYmlRemoved(path): # to avoid rendering whole website
|
|
267
279
|
cache = proc_nbs(path)
|
|
280
|
+
for f in _readme_cands(cache, cfg):
|
|
281
|
+
if f.exists(): f.unlink() # remove stale renders from either quarto layout
|
|
268
282
|
_sprun(f'cd "{cache}" && quarto render "{cache/cfg.readme_nb}" -o README.md -t gfm --no-execute')
|
|
269
283
|
|
|
270
284
|
_save_cached_readme(cache, cfg)
|
|
@@ -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 *
|
|
@@ -34,10 +34,12 @@ class BasicMarkdownRenderer(MarkdownRenderer):
|
|
|
34
34
|
return doc+super()._repr_markdown_()
|
|
35
35
|
|
|
36
36
|
# %% ../nbs/api/08_showdoc.ipynb #1256ef79
|
|
37
|
-
def show_doc(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
def show_doc(
|
|
38
|
+
sym, # Symbol to document
|
|
39
|
+
renderer=None, # Optional renderer (defaults to markdown)
|
|
40
|
+
name:str|None=None, # Optionally override displayed name of `sym`
|
|
41
|
+
title_level:int=3 # Heading level to use for symbol name
|
|
42
|
+
):
|
|
41
43
|
"Show signature and docstring for `sym`"
|
|
42
44
|
if renderer is None: renderer = get_config().get('renderer', None)
|
|
43
45
|
if renderer is None: renderer=BasicMarkdownRenderer
|
|
@@ -25,48 +25,60 @@ from fastcore.nbio import *
|
|
|
25
25
|
from execnb.shell import *
|
|
26
26
|
|
|
27
27
|
# %% ../nbs/api/12_test.ipynb #3f4fa1ad
|
|
28
|
-
def test_nb(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
def test_nb(
|
|
29
|
+
fn, # file name of notebook to test
|
|
30
|
+
skip_flags=None, # list of flags marking cells to skip
|
|
31
|
+
force_flags=None, # list of flags marking cells to always run
|
|
32
|
+
do_print=False, # print completion?
|
|
33
|
+
showerr=True, # print errors to stderr?
|
|
34
|
+
basepath=None, # path to add to sys.path
|
|
35
|
+
verbose=False, # stream stdout/stderr from cells to console?
|
|
36
|
+
save=False, # write outputs back to notebook on success?
|
|
37
|
+
profile:bool=None # load the IPython profile, as `ipykernel` does? (default: `exec_profile` config key)
|
|
38
|
+
):
|
|
36
39
|
"Execute tests in notebook in `fn` except those with `skip_flags`"
|
|
37
40
|
faulthandler.register(signal.SIGINT, file=sys.__stderr__, all_threads=True, chain=True)
|
|
41
|
+
fn = Path(fn)
|
|
38
42
|
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}')
|
|
43
|
+
prev_test = os.environ.get('IN_TEST')
|
|
44
|
+
if not IN_NOTEBOOK: os.environ['IN_TEST'] = '1'
|
|
55
45
|
try:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
46
|
+
flags=set(L(skip_flags)) - set(L(force_flags))
|
|
47
|
+
nb = NBProcessor(fn, rm_directives=False, process=True).nb
|
|
48
|
+
fm = nb_frontmatter(nb)
|
|
49
|
+
if str2bool(fm.get('skip_exec', False)) or nb_lang(nb) != 'python': return True, 0
|
|
50
|
+
|
|
51
|
+
def _no_eval(cell):
|
|
52
|
+
if cell.cell_type != 'code': return True
|
|
53
|
+
if 'nbdev_export'+'(' in cell.source: return True
|
|
54
|
+
direc = getattr(cell, 'directives_', {}) or {}
|
|
55
|
+
if direc.get('eval:', [''])[0].lower() == 'false': return True
|
|
56
|
+
return flags & direc.keys()
|
|
57
|
+
|
|
58
|
+
start = time.time()
|
|
59
|
+
if profile is None: profile = bool(get_config(fn.parent).exec_profile)
|
|
60
|
+
k = CaptureShell(fn, profile=profile)
|
|
61
|
+
if do_print: print(f'Starting {fn}')
|
|
62
|
+
try:
|
|
63
|
+
with working_directory(fn.parent):
|
|
64
|
+
k.run_all(nb, exc_stop=True, preproc=_no_eval, verbose=verbose)
|
|
65
|
+
if save: write_nb(nb, fn)
|
|
66
|
+
res = True
|
|
67
|
+
except:
|
|
68
|
+
if showerr: sys.stderr.write(k.prettytb(fname=fn)+'\n')
|
|
69
|
+
res=False
|
|
70
|
+
if do_print: print(f'- Completed {fn}')
|
|
71
|
+
return res,time.time()-start
|
|
72
|
+
finally:
|
|
73
|
+
if prev_test is None: os.environ.pop('IN_TEST', None)
|
|
74
|
+
else: os.environ['IN_TEST'] = prev_test
|
|
75
|
+
|
|
65
76
|
|
|
66
77
|
# %% ../nbs/api/12_test.ipynb #d8bf1f1b-935d-4b69-ba96-827c5d7213f0
|
|
67
|
-
def _keep_file(
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
def _keep_file(
|
|
79
|
+
p:Path, # filename for which to check for `indicator_fname`
|
|
80
|
+
ignore_fname:str # filename that will result in siblings being ignored
|
|
81
|
+
) -> bool:
|
|
70
82
|
"Returns False if `indicator_fname` is a sibling to `fname` else True"
|
|
71
83
|
if p.exists(): return not bool(p.parent.ls().attrgot('name').filter(lambda x: x == ignore_fname))
|
|
72
84
|
else: True
|
|
@@ -84,9 +96,11 @@ def nbdev_test(
|
|
|
84
96
|
ignore_fname:str='.notest', # Filename that will result in siblings being ignored
|
|
85
97
|
verbose:bool=False, # Print stdout/stderr from notebook cells?
|
|
86
98
|
save:bool=False, # Write outputs back to notebooks on success?
|
|
87
|
-
**kwargs
|
|
99
|
+
**kwargs
|
|
100
|
+
):
|
|
88
101
|
"Test in parallel notebooks matching `path`, passing along `flags`"
|
|
89
|
-
|
|
102
|
+
cfg = get_config(Path(path).resolve() if path else None)
|
|
103
|
+
skip_flags = cfg.tst_flags
|
|
90
104
|
if isinstance(skip_flags, str): skip_flags = skip_flags.split()
|
|
91
105
|
force_flags = flags.split()
|
|
92
106
|
files = nbglob(path, as_path=True, **kwargs)
|
|
@@ -96,10 +110,10 @@ def nbdev_test(
|
|
|
96
110
|
if n_workers is None: n_workers = 0 if len(files)==1 else min(num_cpus(), 8)
|
|
97
111
|
if IN_NOTEBOOK: kw = {'method':'spawn'} if os.name=='nt' else {'method':'forkserver'}
|
|
98
112
|
else: kw = {'method':'forkserver'} if sys.platform=='darwin' else {}
|
|
99
|
-
wd_pth =
|
|
113
|
+
wd_pth = cfg.nbs_path
|
|
100
114
|
with working_directory(wd_pth if (wd_pth and wd_pth.exists()) else os.getcwd()):
|
|
101
115
|
results = parallel(test_nb, files, skip_flags=skip_flags, force_flags=force_flags, n_workers=n_workers,
|
|
102
|
-
basepath=
|
|
116
|
+
basepath=cfg.config_path, pause=pause, do_print=do_print, verbose=verbose, save=save, **kw)
|
|
103
117
|
passed,times = zip(*results)
|
|
104
118
|
if all(passed): print("Success.")
|
|
105
119
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nbdev
|
|
3
|
-
Version: 3.0
|
|
3
|
+
Version: 3.2.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.6
|
|
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
|
|
@@ -158,7 +158,7 @@ available commands:
|
|
|
158
158
|
nbdev-pypi Create and upload Python package to PyPI
|
|
159
159
|
nbdev-readme Create README.md from readme_nb (index.ipynb by default)
|
|
160
160
|
nbdev-release-both Release both conda and PyPI packages
|
|
161
|
-
nbdev-release-gh Calls `
|
|
161
|
+
nbdev-release-gh Calls `nbdev-changelog`, lets you edit the result, then pushes to git and calls `nbdev-release-git`
|
|
162
162
|
nbdev-release-git Tag and create a release in GitHub for the current version
|
|
163
163
|
nbdev-requirements Writes a `requirements.txt` file to `directory` based on pyproject.toml.
|
|
164
164
|
nbdev-sidebar Create sidebar.yml
|
|
@@ -184,6 +184,9 @@ some_module.something()
|
|
|
184
184
|
Instead, split this into two cells, one which does `import some_module`,
|
|
185
185
|
and the other which does `some_module.something()`.
|
|
186
186
|
|
|
187
|
+
Only top-level statements count: `try: import` blocks and imports inside
|
|
188
|
+
function definitions are fine.
|
|
189
|
+
|
|
187
190
|
The reason for this is that when we create your documentation website,
|
|
188
191
|
we ensure that all of the signatures for functions you document are up
|
|
189
192
|
to date, by running the imports, exported cells, and
|
|
@@ -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.6", "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
|