nbdev 2.4.3__py3-none-any.whl → 2.4.11__py3-none-any.whl

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