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.
Files changed (38) hide show
  1. {nbdev-3.0.18/nbdev.egg-info → nbdev-3.1.0}/PKG-INFO +4 -4
  2. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/__init__.py +1 -1
  3. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/_modidx.py +2 -0
  4. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/cli.py +12 -11
  5. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/config.py +5 -7
  6. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/doclinks.py +10 -8
  7. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/merge.py +2 -1
  8. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/processors.py +5 -4
  9. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/quarto.py +7 -0
  10. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/release.py +36 -24
  11. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/test.py +39 -29
  12. {nbdev-3.0.18 → nbdev-3.1.0/nbdev.egg-info}/PKG-INFO +4 -4
  13. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev.egg-info/requires.txt +3 -3
  14. {nbdev-3.0.18 → nbdev-3.1.0}/pyproject.toml +4 -1
  15. {nbdev-3.0.18 → nbdev-3.1.0}/CONTRIBUTING.md +0 -0
  16. {nbdev-3.0.18 → nbdev-3.1.0}/LICENSE +0 -0
  17. {nbdev-3.0.18 → nbdev-3.1.0}/MANIFEST.in +0 -0
  18. {nbdev-3.0.18 → nbdev-3.1.0}/README.md +0 -0
  19. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/clean.py +0 -0
  20. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/diff.py +0 -0
  21. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/export.py +0 -0
  22. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/extract_attachments.py +0 -0
  23. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/frontmatter.py +0 -0
  24. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/imports.py +0 -0
  25. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/maker.py +0 -0
  26. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/migrate.py +0 -0
  27. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/process.py +0 -0
  28. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/qmd.py +0 -0
  29. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/scrubmagics.py +0 -0
  30. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/serve.py +0 -0
  31. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/serve_drv.py +0 -0
  32. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/showdoc.py +0 -0
  33. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev/sync.py +0 -0
  34. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev.egg-info/SOURCES.txt +0 -0
  35. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev.egg-info/dependency_links.txt +0 -0
  36. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev.egg-info/entry_points.txt +0 -0
  37. {nbdev-3.0.18 → nbdev-3.1.0}/nbdev.egg-info/top_level.txt +0 -0
  38. {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.18
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.12.35
20
- Requires-Dist: execnb>=0.1.12
19
+ Requires-Dist: fastcore>=1.14.1
20
+ Requires-Dist: execnb>=0.2.4
21
21
  Requires-Dist: astunparse
22
- Requires-Dist: ghapi>=1.0.3
22
+ Requires-Dist: ghapi>=2.0.0
23
23
  Requires-Dist: watchdog
24
24
  Requires-Dist: asttokens
25
25
  Requires-Dist: setuptools
@@ -1,4 +1,4 @@
1
- __version__ = "3.0.18"
1
+ __version__ = "3.1.0"
2
2
 
3
3
  from .doclinks import nbdev_export
4
4
  from .showdoc import show_doc
@@ -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 HTTPError:print(f"Could not update the description & URL on the repo: {cfg.user}/{cfg.repo} using $GITHUB_TOKEN.\n"
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
- avail_lic = GhApi().licenses.get_all_commonly_used().map(lambda x: x['key'])
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 = GhApi().licenses.get(mapped)['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 GhApi
42
- api = GhApi(owner=owner, repo=repo, token=os.getenv('GITHUB_TOKEN'))
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 not is_nbdev(): raise Exception('`nbdev-export` must be called from a directory within a nbdev project.')
150
- cfg = get_config()
151
- procs = procs.split() if procs else cfg.get('export_procs', [])
152
- procs = [import_obj(p) for p in procs] if procs else None
153
- files = nbglob(path=path, as_path=True, **kwargs).sorted('name')
154
- for f in files: nb_export(f, procs=procs)
155
- add_init(cfg.lib_path)
156
- _build_modidx()
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
- matches = SequenceMatcher(None, a, b).get_matching_blocks()
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
- o.data['text/markdown'] = [nl.link_line(s) for s in o.data['text/markdown']]
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'] = [_re_ansi_escape.sub('', o) for o in 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'] = [l for l in outp.text if not re.search('|'.join(words), l)]
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', 'pypi_json',
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 parallel(self._issues, self.groups.keys(), progress=False, threadpool=True)
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 HTTP404NotFoundError: lr,self.commit_date = None,'2000-01-01T00:00:004Z'
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().replace(marker, marker+res+"\n")
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 `lib_name` from pyproject.toml
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): # write outputs back to notebook on success?
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
- if not IN_NOTEBOOK: os.environ["IN_TEST"] = '1'
40
- flags=set(L(skip_flags)) - set(L(force_flags))
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
- with working_directory(fn.parent):
57
- k.run_all(nb, exc_stop=True, preproc=_no_eval, verbose=verbose)
58
- if save: write_nb(nb, fn)
59
- res = True
60
- except:
61
- if showerr: sys.stderr.write(k.prettytb(fname=fn)+'\n')
62
- res=False
63
- if do_print: print(f'- Completed {fn}')
64
- return res,time.time()-start
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
- skip_flags = get_config().tst_flags
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 = get_config().nbs_path
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=get_config().config_path, pause=pause, do_print=do_print, verbose=verbose, save=save, **kw)
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.18
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.12.35
20
- Requires-Dist: execnb>=0.1.12
19
+ Requires-Dist: fastcore>=1.14.1
20
+ Requires-Dist: execnb>=0.2.4
21
21
  Requires-Dist: astunparse
22
- Requires-Dist: ghapi>=1.0.3
22
+ Requires-Dist: ghapi>=2.0.0
23
23
  Requires-Dist: watchdog
24
24
  Requires-Dist: asttokens
25
25
  Requires-Dist: setuptools
@@ -1,7 +1,7 @@
1
- fastcore>=1.12.35
2
- execnb>=0.1.12
1
+ fastcore>=1.14.1
2
+ execnb>=0.2.4
3
3
  astunparse
4
- ghapi>=1.0.3
4
+ ghapi>=2.0.0
5
5
  watchdog
6
6
  asttokens
7
7
  setuptools
@@ -19,7 +19,7 @@ classifiers = [
19
19
  "License :: OSI Approved :: Apache Software License",
20
20
  ]
21
21
  dynamic = ["version"]
22
- dependencies = [ "fastcore>=1.12.35", "execnb>=0.1.12", "astunparse", "ghapi>=1.0.3", "watchdog", "asttokens",
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