nbdev 2.3.34__tar.gz → 2.4.9__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 (42) hide show
  1. {nbdev-2.3.34/nbdev.egg-info → nbdev-2.4.9}/PKG-INFO +22 -7
  2. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/__init__.py +1 -1
  3. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/_modidx.py +12 -30
  4. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/clean.py +1 -1
  5. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/cli.py +13 -5
  6. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/config.py +34 -6
  7. nbdev-2.4.9/nbdev/diff.py +92 -0
  8. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/doclinks.py +18 -8
  9. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/export.py +3 -2
  10. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/maker.py +7 -7
  11. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/process.py +1 -1
  12. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/quarto.py +39 -4
  13. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/release.py +15 -7
  14. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/serve.py +2 -1
  15. nbdev-2.4.9/nbdev/showdoc.py +113 -0
  16. {nbdev-2.3.34 → nbdev-2.4.9/nbdev.egg-info}/PKG-INFO +22 -7
  17. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev.egg-info/SOURCES.txt +1 -0
  18. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev.egg-info/entry_points.txt +1 -0
  19. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev.egg-info/requires.txt +5 -2
  20. nbdev-2.4.9/pyproject.toml +11 -0
  21. {nbdev-2.3.34 → nbdev-2.4.9}/settings.ini +7 -5
  22. {nbdev-2.3.34 → nbdev-2.4.9}/setup.py +0 -3
  23. nbdev-2.3.34/nbdev/showdoc.py +0 -255
  24. nbdev-2.3.34/pyproject.toml +0 -3
  25. {nbdev-2.3.34 → nbdev-2.4.9}/CONTRIBUTING.md +0 -0
  26. {nbdev-2.3.34 → nbdev-2.4.9}/LICENSE +0 -0
  27. {nbdev-2.3.34 → nbdev-2.4.9}/MANIFEST.in +0 -0
  28. {nbdev-2.3.34 → nbdev-2.4.9}/README.md +0 -0
  29. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/extract_attachments.py +0 -0
  30. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/frontmatter.py +0 -0
  31. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/imports.py +0 -0
  32. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/merge.py +0 -0
  33. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/migrate.py +0 -0
  34. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/processors.py +0 -0
  35. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/qmd.py +0 -0
  36. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/serve_drv.py +0 -0
  37. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/sync.py +0 -0
  38. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev/test.py +0 -0
  39. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev.egg-info/dependency_links.txt +0 -0
  40. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev.egg-info/not-zip-safe +0 -0
  41. {nbdev-2.3.34 → nbdev-2.4.9}/nbdev.egg-info/top_level.txt +0 -0
  42. {nbdev-2.3.34 → nbdev-2.4.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: nbdev
3
- Version: 2.3.34
3
+ Version: 2.4.9
4
4
  Summary: Create delightful software with Jupyter Notebooks
5
5
  Home-page: https://github.com/AnswerDotAI/nbdev
6
6
  Author: Jeremy Howard and Hamel Husain
@@ -11,24 +11,24 @@ Keywords: nbdev fastai jupyter notebook export
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Natural Language :: English
14
- Classifier: Programming Language :: Python :: 3.7
15
- Classifier: Programming Language :: Python :: 3.8
16
14
  Classifier: Programming Language :: Python :: 3.9
17
15
  Classifier: Programming Language :: Python :: 3.10
18
16
  Classifier: Programming Language :: Python :: 3.12
19
17
  Classifier: Programming Language :: Python :: 3.13
20
18
  Classifier: License :: OSI Approved :: Apache Software License
21
- Requires-Python: >=3.7
19
+ Requires-Python: >=3.9
22
20
  Description-Content-Type: text/markdown
23
21
  License-File: LICENSE
24
22
  Requires-Dist: packaging
25
- Requires-Dist: fastcore>=1.5.27
26
- Requires-Dist: execnb>=0.1.4
23
+ Requires-Dist: fastcore>=1.11.0
24
+ Requires-Dist: execnb>=0.1.12
27
25
  Requires-Dist: astunparse
28
26
  Requires-Dist: ghapi>=1.0.3
29
27
  Requires-Dist: watchdog
30
28
  Requires-Dist: asttokens
31
29
  Requires-Dist: setuptools
30
+ Requires-Dist: build
31
+ Requires-Dist: fastgit
32
32
  Requires-Dist: PyYAML
33
33
  Provides-Extra: dev
34
34
  Requires-Dist: ipywidgets; extra == "dev"
@@ -42,6 +42,21 @@ Requires-Dist: nbclassic; extra == "dev"
42
42
  Requires-Dist: pysymbol_llm; extra == "dev"
43
43
  Requires-Dist: llms-txt; extra == "dev"
44
44
  Requires-Dist: sphinx; extra == "dev"
45
+ Requires-Dist: plum-dispatch; extra == "dev"
46
+ Dynamic: author
47
+ Dynamic: author-email
48
+ Dynamic: classifier
49
+ Dynamic: description
50
+ Dynamic: description-content-type
51
+ Dynamic: home-page
52
+ Dynamic: keywords
53
+ Dynamic: license
54
+ Dynamic: license-file
55
+ Dynamic: project-url
56
+ Dynamic: provides-extra
57
+ Dynamic: requires-dist
58
+ Dynamic: requires-python
59
+ Dynamic: summary
45
60
 
46
61
  # Getting Started
47
62
 
@@ -1,4 +1,4 @@
1
- __version__ = "2.3.34"
1
+ __version__ = "2.4.9"
2
2
 
3
3
  from .doclinks import nbdev_export
4
4
  from .showdoc import show_doc
@@ -1,6 +1,6 @@
1
1
  # Autogenerated by nbdev
2
2
 
3
- d = { 'settings': { 'branch': 'master',
3
+ d = { 'settings': { 'branch': 'main',
4
4
  'doc_baseurl': '/',
5
5
  'doc_host': 'https://nbdev.fast.ai',
6
6
  'git_url': 'https://github.com/AnswerDotAI/nbdev',
@@ -46,8 +46,16 @@ d = { 'settings': { 'branch': 'master',
46
46
  'nbdev.config.is_nbdev': ('api/config.html#is_nbdev', 'nbdev/config.py'),
47
47
  'nbdev.config.nbdev_create_config': ('api/config.html#nbdev_create_config', 'nbdev/config.py'),
48
48
  'nbdev.config.show_src': ('api/config.html#show_src', 'nbdev/config.py'),
49
+ 'nbdev.config.update_proj': ('api/config.html#update_proj', 'nbdev/config.py'),
49
50
  'nbdev.config.update_version': ('api/config.html#update_version', 'nbdev/config.py'),
50
51
  'nbdev.config.write_cells': ('api/config.html#write_cells', 'nbdev/config.py')},
52
+ 'nbdev.diff': { 'nbdev.diff._cell_changes': ('api/diff.html#_cell_changes', 'nbdev/diff.py'),
53
+ 'nbdev.diff._nb_srcdict': ('api/diff.html#_nb_srcdict', 'nbdev/diff.py'),
54
+ 'nbdev.diff.cell_diffs': ('api/diff.html#cell_diffs', 'nbdev/diff.py'),
55
+ 'nbdev.diff.changed_cells': ('api/diff.html#changed_cells', 'nbdev/diff.py'),
56
+ 'nbdev.diff.nbs_pair': ('api/diff.html#nbs_pair', 'nbdev/diff.py'),
57
+ 'nbdev.diff.read_nb_from_git': ('api/diff.html#read_nb_from_git', 'nbdev/diff.py'),
58
+ 'nbdev.diff.source_diff': ('api/diff.html#source_diff', 'nbdev/diff.py')},
51
59
  'nbdev.doclinks': { 'nbdev.doclinks.NbdevLookup': ('api/doclinks.html#nbdevlookup', 'nbdev/doclinks.py'),
52
60
  'nbdev.doclinks.NbdevLookup.__getitem__': ( 'api/doclinks.html#nbdevlookup.__getitem__',
53
61
  'nbdev/doclinks.py'),
@@ -257,20 +265,22 @@ d = { 'settings': { 'branch': 'master',
257
265
  'nbdev.quarto._SidebarYmlRemoved.__init__': ( 'api/quarto.html#_sidebarymlremoved.__init__',
258
266
  'nbdev/quarto.py'),
259
267
  'nbdev.quarto._copytree': ('api/quarto.html#_copytree', 'nbdev/quarto.py'),
268
+ 'nbdev.quarto._doc_mtime_not_older': ('api/quarto.html#_doc_mtime_not_older', 'nbdev/quarto.py'),
260
269
  'nbdev.quarto._ensure_quarto': ('api/quarto.html#_ensure_quarto', 'nbdev/quarto.py'),
261
270
  'nbdev.quarto._install_linux': ('api/quarto.html#_install_linux', 'nbdev/quarto.py'),
262
271
  'nbdev.quarto._install_mac': ('api/quarto.html#_install_mac', 'nbdev/quarto.py'),
263
272
  'nbdev.quarto._nbglob_docs': ('api/quarto.html#_nbglob_docs', 'nbdev/quarto.py'),
264
273
  'nbdev.quarto._pre': ('api/quarto.html#_pre', 'nbdev/quarto.py'),
265
274
  'nbdev.quarto._pre_docs': ('api/quarto.html#_pre_docs', 'nbdev/quarto.py'),
266
- 'nbdev.quarto._readme_mtime_not_older': ('api/quarto.html#_readme_mtime_not_older', 'nbdev/quarto.py'),
267
275
  'nbdev.quarto._recursive_parser': ('api/quarto.html#_recursive_parser', 'nbdev/quarto.py'),
276
+ 'nbdev.quarto._save_cached_contributing': ('api/quarto.html#_save_cached_contributing', 'nbdev/quarto.py'),
268
277
  'nbdev.quarto._save_cached_readme': ('api/quarto.html#_save_cached_readme', 'nbdev/quarto.py'),
269
278
  'nbdev.quarto._sort': ('api/quarto.html#_sort', 'nbdev/quarto.py'),
270
279
  'nbdev.quarto._sprun': ('api/quarto.html#_sprun', 'nbdev/quarto.py'),
271
280
  'nbdev.quarto.fs_watchdog': ('api/quarto.html#fs_watchdog', 'nbdev/quarto.py'),
272
281
  'nbdev.quarto.install': ('api/quarto.html#install', 'nbdev/quarto.py'),
273
282
  'nbdev.quarto.install_quarto': ('api/quarto.html#install_quarto', 'nbdev/quarto.py'),
283
+ 'nbdev.quarto.nbdev_contributing': ('api/quarto.html#nbdev_contributing', 'nbdev/quarto.py'),
274
284
  'nbdev.quarto.nbdev_docs': ('api/quarto.html#nbdev_docs', 'nbdev/quarto.py'),
275
285
  'nbdev.quarto.nbdev_preview': ('api/quarto.html#nbdev_preview', 'nbdev/quarto.py'),
276
286
  'nbdev.quarto.nbdev_proc_nbs': ('api/quarto.html#nbdev_proc_nbs', 'nbdev/quarto.py'),
@@ -319,37 +329,9 @@ d = { 'settings': { 'branch': 'master',
319
329
  'nbdev.showdoc.BasicMarkdownRenderer': ('api/showdoc.html#basicmarkdownrenderer', 'nbdev/showdoc.py'),
320
330
  'nbdev.showdoc.BasicMarkdownRenderer._repr_markdown_': ( 'api/showdoc.html#basicmarkdownrenderer._repr_markdown_',
321
331
  'nbdev/showdoc.py'),
322
- 'nbdev.showdoc.DocmentTbl': ('api/showdoc.html#docmenttbl', 'nbdev/showdoc.py'),
323
- 'nbdev.showdoc.DocmentTbl.__eq__': ('api/showdoc.html#docmenttbl.__eq__', 'nbdev/showdoc.py'),
324
- 'nbdev.showdoc.DocmentTbl.__init__': ('api/showdoc.html#docmenttbl.__init__', 'nbdev/showdoc.py'),
325
- 'nbdev.showdoc.DocmentTbl._columns': ('api/showdoc.html#docmenttbl._columns', 'nbdev/showdoc.py'),
326
- 'nbdev.showdoc.DocmentTbl._hdr_list': ('api/showdoc.html#docmenttbl._hdr_list', 'nbdev/showdoc.py'),
327
- 'nbdev.showdoc.DocmentTbl._repr_markdown_': ( 'api/showdoc.html#docmenttbl._repr_markdown_',
328
- 'nbdev/showdoc.py'),
329
- 'nbdev.showdoc.DocmentTbl._row': ('api/showdoc.html#docmenttbl._row', 'nbdev/showdoc.py'),
330
- 'nbdev.showdoc.DocmentTbl._row_list': ('api/showdoc.html#docmenttbl._row_list', 'nbdev/showdoc.py'),
331
- 'nbdev.showdoc.DocmentTbl.has_docment': ('api/showdoc.html#docmenttbl.has_docment', 'nbdev/showdoc.py'),
332
- 'nbdev.showdoc.DocmentTbl.has_return': ('api/showdoc.html#docmenttbl.has_return', 'nbdev/showdoc.py'),
333
- 'nbdev.showdoc.DocmentTbl.hdr_str': ('api/showdoc.html#docmenttbl.hdr_str', 'nbdev/showdoc.py'),
334
- 'nbdev.showdoc.DocmentTbl.params_str': ('api/showdoc.html#docmenttbl.params_str', 'nbdev/showdoc.py'),
335
- 'nbdev.showdoc.DocmentTbl.return_str': ('api/showdoc.html#docmenttbl.return_str', 'nbdev/showdoc.py'),
336
- 'nbdev.showdoc.ShowDocRenderer': ('api/showdoc.html#showdocrenderer', 'nbdev/showdoc.py'),
337
- 'nbdev.showdoc.ShowDocRenderer.__init__': ('api/showdoc.html#showdocrenderer.__init__', 'nbdev/showdoc.py'),
338
- 'nbdev.showdoc._bold': ('api/showdoc.html#_bold', 'nbdev/showdoc.py'),
339
332
  'nbdev.showdoc._create_html_table': ('api/showdoc.html#_create_html_table', 'nbdev/showdoc.py'),
340
- 'nbdev.showdoc._docstring': ('api/showdoc.html#_docstring', 'nbdev/showdoc.py'),
341
- 'nbdev.showdoc._escape_markdown': ('api/showdoc.html#_escape_markdown', 'nbdev/showdoc.py'),
342
333
  'nbdev.showdoc._ext_link': ('api/showdoc.html#_ext_link', 'nbdev/showdoc.py'),
343
- 'nbdev.showdoc._f_name': ('api/showdoc.html#_f_name', 'nbdev/showdoc.py'),
344
- 'nbdev.showdoc._fmt_anno': ('api/showdoc.html#_fmt_anno', 'nbdev/showdoc.py'),
345
- 'nbdev.showdoc._fmt_sig': ('api/showdoc.html#_fmt_sig', 'nbdev/showdoc.py'),
346
- 'nbdev.showdoc._fullname': ('api/showdoc.html#_fullname', 'nbdev/showdoc.py'),
347
334
  'nbdev.showdoc._html_link': ('api/showdoc.html#_html_link', 'nbdev/showdoc.py'),
348
- 'nbdev.showdoc._list2row': ('api/showdoc.html#_list2row', 'nbdev/showdoc.py'),
349
- 'nbdev.showdoc._maybe_nm': ('api/showdoc.html#_maybe_nm', 'nbdev/showdoc.py'),
350
- 'nbdev.showdoc._non_empty_keys': ('api/showdoc.html#_non_empty_keys', 'nbdev/showdoc.py'),
351
- 'nbdev.showdoc._show_param': ('api/showdoc.html#_show_param', 'nbdev/showdoc.py'),
352
- 'nbdev.showdoc._wrap_sig': ('api/showdoc.html#_wrap_sig', 'nbdev/showdoc.py'),
353
335
  'nbdev.showdoc.colab_link': ('api/showdoc.html#colab_link', 'nbdev/showdoc.py'),
354
336
  'nbdev.showdoc.doc': ('api/showdoc.html#doc', 'nbdev/showdoc.py'),
355
337
  'nbdev.showdoc.show_doc': ('api/showdoc.html#show_doc', 'nbdev/showdoc.py'),
@@ -69,7 +69,7 @@ def _clean_cell_output(cell, clean_ids):
69
69
  if k.startswith('text') and clean_ids: data[k] = _clean_cell_output_id(data[k])
70
70
  if k.startswith('image') and "svg" not in k: data[k] = data[k].rstrip()
71
71
  if 'text' in o and clean_ids: o['text'] = _clean_cell_output_id(o['text'])
72
- o.get('metadata', {}).pop('tags', None)
72
+ # o.get('metadata', {}).pop('tags', None)
73
73
 
74
74
  # %% ../nbs/api/11_clean.ipynb
75
75
  def _clean_cell(cell, clear_all, allowed_metadata_keys, clean_ids):
@@ -13,7 +13,7 @@ from .processors import *
13
13
  from .doclinks import *
14
14
  from .test import *
15
15
  from .clean import *
16
- from .quarto import nbdev_readme, refresh_quarto_yml, fs_watchdog
16
+ from .quarto import nbdev_readme, nbdev_contributing, refresh_quarto_yml, fs_watchdog
17
17
  from .export import nb_export
18
18
  from .frontmatter import FrontmatterProc
19
19
 
@@ -91,7 +91,7 @@ def nbdev_new(**kwargs):
91
91
  _update_repo_meta(cfg)
92
92
  path = Path()
93
93
 
94
- _ORG_OR_USR,_REPOSITORY = 'fastai','nbdev-template'
94
+ _ORG_OR_USR,_REPOSITORY = 'answerdotai','nbdev-template'
95
95
  _TEMPLATE = f'{_ORG_OR_USR}/{_REPOSITORY}'
96
96
  template = kwargs.get('template', _TEMPLATE)
97
97
  try: org_or_usr, repo = template.split('/')
@@ -101,7 +101,8 @@ def nbdev_new(**kwargs):
101
101
  if tag is None:
102
102
  with warnings.catch_warnings():
103
103
  warnings.simplefilter('ignore', UserWarning)
104
- tag = GhApi(gh_host='https://api.github.com', authenticate=False).repos.get_latest_release(org_or_usr, repo).tag_name
104
+ tag = GhApi(gh_host='https://api.github.com', authenticate=False
105
+ ).repos.get_latest_release(org_or_usr, repo).tag_name
105
106
 
106
107
  url = f"https://github.com/{org_or_usr}/{repo}/archive/{tag}.tar.gz"
107
108
  extract_tgz(url)
@@ -121,6 +122,7 @@ def nbdev_new(**kwargs):
121
122
  refresh_quarto_yml()
122
123
  nbdev_export.__wrapped__()
123
124
  nbdev_readme.__wrapped__()
125
+ nbdev_contributing.__wrapped__()
124
126
 
125
127
  # %% ../nbs/api/13_cli.ipynb
126
128
  mapping = {
@@ -183,10 +185,16 @@ def watch_export(nbs:str=None, # Nb directory to watch for changes
183
185
  lib = lib or (cfg.lib_path if cfg else '.')
184
186
  if cfg and (nbs != cfg.nbs_path or lib != cfg.lib_path) and not force:
185
187
  raise ValueError("In nbdev project. Use --force to override config.")
188
+ run(f'nbdev_export')
186
189
  def _export(e,lib=lib):
187
190
  p = e.src_path
188
- if (not '.ipynb_checkpoints' in p and p.endswith('.ipynb') and not Path(p).name.startswith('.~')):
189
- if e.event_type == 'modified': run(f'nb_export --lib_path {lib} "{p}"')
191
+ if (not '.ipynb_checkpoints' in p and p.endswith('.ipynb') and not Path(p).name.startswith(('tmp','.~'))):
192
+ if e.event_type == 'modified':
193
+ time.sleep(0.1)
194
+ try: run(f'nb_export --lib_path {lib} "{p}"')
195
+ except IOError:
196
+ time.sleep(0.3)
197
+ run(f'nb_export --lib_path {lib} "{p}"')
190
198
  with fs_watchdog(_export, nbs):
191
199
  while True: time.sleep(1)
192
200
 
@@ -3,8 +3,8 @@
3
3
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/01_config.ipynb.
4
4
 
5
5
  # %% auto 0
6
- __all__ = ['nbdev_create_config', 'get_config', 'config_key', 'is_nbdev', 'create_output', 'show_src', 'update_version',
7
- 'add_init', 'write_cells']
6
+ __all__ = ['pyproj_tmpl', 'nbdev_create_config', 'get_config', 'config_key', 'is_nbdev', 'create_output', 'show_src',
7
+ 'update_version', 'update_proj', 'add_init', 'write_cells']
8
8
 
9
9
  # %% ../nbs/api/01_config.ipynb
10
10
  from datetime import datetime
@@ -49,7 +49,7 @@ def _apply_defaults(
49
49
  license='apache2', # License for the package
50
50
  copyright:str=None, # Copyright for the package, defaults to '`current_year` onwards, `author`'
51
51
  status='3', # Development status PyPI classifier
52
- min_python='3.7', # Minimum Python version PyPI classifier
52
+ min_python='3.9', # Minimum Python version PyPI classifier
53
53
  audience='Developers', # Intended audience PyPI classifier
54
54
  language='English', # Language PyPI classifier
55
55
  recursive:bool_arg=True, # Include subfolders in notebook globs?
@@ -63,6 +63,7 @@ def _apply_defaults(
63
63
  clear_all:bool_arg=False, # Remove all cell metadata and cell outputs?
64
64
  cell_number:bool_arg=True, # Add cell number to the exported file
65
65
  put_version_in_init:bool_arg=True, # Add the version to the main __init__.py in nbdev_export
66
+ update_pyproject:bool_arg=True, # Create/update pyproject.toml with correct project name
66
67
  skip_procs:str='', # A comma-separated list of processors that you want to skip
67
68
  ):
68
69
  "Apply default settings where missing in `cfg`."
@@ -142,7 +143,7 @@ _nbdev_cfg_head = '''# All sections below are required unless otherwise specifie
142
143
 
143
144
  '''
144
145
  _nbdev_cfg_sections = {'Python library': 'repo lib_name version min_python license black_formatting',
145
- 'nbdev': 'doc_path lib_path nbs_path recursive tst_flags put_version_in_init',
146
+ 'nbdev': 'doc_path lib_path nbs_path recursive tst_flags put_version_in_init update_pyproject',
146
147
  'Docs': 'branch custom_sidebar doc_host doc_baseurl git_url title',
147
148
  'PyPI': 'audience author author_email copyright description keywords language status user'}
148
149
  _nbdev_cfg_tail = '''### Optional ###
@@ -219,9 +220,26 @@ def create_output(txt, mime):
219
220
  # %% ../nbs/api/01_config.ipynb
220
221
  def show_src(src, lang='python'): return Markdown(f'```{lang}\n{src}\n```')
221
222
 
223
+ # %% ../nbs/api/01_config.ipynb
224
+ pyproj_tmpl = """[build-system]
225
+ requires = ["setuptools>=64.0"]
226
+ build-backend = "setuptools.build_meta"
227
+
228
+ [project]
229
+ name = "FILL_IN"
230
+ requires-python="FILL_IN"
231
+ dynamic = [ "keywords", "description", "version", "dependencies", "optional-dependencies", "readme", "license", "authors", "classifiers", "entry-points", "scripts", "urls"]
232
+
233
+ [tool.uv]
234
+ cache-keys = [{ file = "pyproject.toml" }, { file = "settings.ini" }, { file = "setup.py" }]
235
+ """
236
+
222
237
  # %% ../nbs/api/01_config.ipynb
223
238
  _re_version = re.compile(r'^__version__\s*=.*$', re.MULTILINE)
239
+ _re_proj = re.compile(r'^name\s*=\s*".*$', re.MULTILINE)
240
+ _re_reqpy = re.compile(r'^requires-python\s*=\s*".*$', re.MULTILINE)
224
241
  _init = '__init__.py'
242
+ _pyproj = 'pyproject.toml'
225
243
 
226
244
  def update_version(path=None):
227
245
  "Add or update `__version__` in the main `__init__.py` of the library."
@@ -236,6 +254,15 @@ def update_version(path=None):
236
254
 
237
255
  def _has_py(fs): return any(1 for f in fs if f.endswith('.py'))
238
256
 
257
+ def update_proj(path):
258
+ "Create or update `pyproject.toml` in the project root."
259
+ fname = path/_pyproj
260
+ if not fname.exists(): fname.write_text(pyproj_tmpl)
261
+ txt = fname.read_text()
262
+ txt = _re_proj.sub(f'name="{get_config().lib_name}"', txt)
263
+ txt = _re_reqpy.sub(f'requires-python=">={get_config().min_python}"', txt)
264
+ fname.write_text(txt)
265
+
239
266
  def add_init(path=None):
240
267
  "Add `__init__.py` in all subdirs of `path` containing python files if it's not there already."
241
268
  # we add the lowest-level `__init__.py` files first, which ensures _has_py succeeds for parent modules
@@ -247,14 +274,15 @@ def add_init(path=None):
247
274
  subds = (os.listdir(r/d) for d in ds)
248
275
  if _has_py(fs) or any(filter(_has_py, subds)) and not (r/_init).exists(): (r/_init).touch()
249
276
  if get_config().get('put_version_in_init', True): update_version(path)
277
+ if get_config().get('update_pyproject', True): update_proj(path.parent)
250
278
 
251
279
  # %% ../nbs/api/01_config.ipynb
252
- def write_cells(cells, hdr, file, offset=0, cell_number=True):
280
+ def write_cells(cells, hdr, file, offset=0, cell_number=True, solo_nb=False):
253
281
  "Write `cells` to `file` along with header `hdr` starting at index `offset` (mainly for nbdev internal use)."
254
282
  for cell in cells:
255
283
  if cell.cell_type=='code' and cell.source.strip():
256
284
  idx = f" {cell.idx_+offset}" if cell_number else ""
257
- file.write(f'\n\n{hdr}{idx}\n{cell.source}')
285
+ file.write(f'\n\n{hdr}{idx}\n{cell.source}') if not solo_nb else file.write(f'\n\n{cell.source}')
258
286
 
259
287
  # %% ../nbs/api/01_config.ipynb
260
288
  def _basic_export_nb(fname, name, dest=None):
@@ -0,0 +1,92 @@
1
+ """Get ipynb diffs by cell"""
2
+
3
+ # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/19_diff.ipynb.
4
+
5
+ # %% auto 0
6
+ __all__ = ['read_nb_from_git', 'nbs_pair', 'changed_cells', 'source_diff', 'cell_diffs']
7
+
8
+ # %% ../nbs/api/19_diff.ipynb
9
+ import json
10
+ from fastcore.utils import *
11
+ from fastcore.meta import delegates
12
+ from difflib import unified_diff
13
+ from fastgit import Git
14
+ from execnb.nbio import *
15
+
16
+ # %% ../nbs/api/19_diff.ipynb
17
+ def read_nb_from_git(
18
+ g:Git, # The git object
19
+ path, # The path to the notebook (absolute or relative to git root)
20
+ ref=None # The git ref to read from (e.g. HEAD); None for working dir
21
+ )->AttrDict: # The notebook
22
+ "Read notebook from git ref (e.g. HEAD) at path, or working dir if ref is None"
23
+ path = Path(path)
24
+ if path.is_absolute(): path = path.relative_to(g.top())
25
+ if ref is None: return read_nb(g.top()/path)
26
+ raw = g.show(f'{ref}:{path}', split=False)
27
+ return dict2nb(json.loads(raw))
28
+
29
+ # %% ../nbs/api/19_diff.ipynb
30
+ def _nb_srcdict(g:Git, nb_path, ref=None, f=noop):
31
+ "Dict of id->source"
32
+ nb = read_nb_from_git(g, nb_path, ref)
33
+ return {c['id']: f(c) for c in nb.cells}
34
+
35
+ # %% ../nbs/api/19_diff.ipynb
36
+ def nbs_pair(
37
+ nb_path, # Path to the notebook
38
+ ref_a='HEAD', # First git ref (None for working dir)
39
+ ref_b=None, # Second git ref (None for working dir)
40
+ f=noop # Function to call on contents
41
+ ): # Tuple of two notebooks
42
+ "NBs at two refs; None means working dir. By default provides HEAD and working dir"
43
+ nb_path = Path(nb_path).resolve()
44
+ g = Git(nb_path.parent)
45
+ return _nb_srcdict(g, nb_path, ref_a, f), _nb_srcdict(g, nb_path, ref_b, f)
46
+
47
+ # %% ../nbs/api/19_diff.ipynb
48
+ def _cell_changes(
49
+ nb_path, # Path to the notebook
50
+ fn, # function to call to get dict values
51
+ ref_a='HEAD', # First git ref (None for working dir)
52
+ ref_b=None, # Second git ref (None for working dir)
53
+ adds=True, # Include cells in b but not in a
54
+ changes=True, # Include cells with different content
55
+ dels=False, # Include cells in a but not in b
56
+ metadata=False, # Consider cell metadata when comparing
57
+ outputs=False # Consider cell outputs when comparing
58
+ ): # Dict of results
59
+ "Apply fn(cell_id, old_content, new_content) to changed cells between two refs"
60
+ def cell_content(c):
61
+ res = c.get('source', '')
62
+ if metadata: res += '\n# metadata: ' + json.dumps(c.get('metadata', {}), sort_keys=True)
63
+ if outputs: res += '\n# outputs: ' + json.dumps(c.get('outputs', []), sort_keys=True)
64
+ return res
65
+ old,new = nbs_pair(nb_path, ref_a, ref_b, f=cell_content)
66
+ res = {}
67
+ if adds: res |= {cid: fn(cid, '', new[cid]) for cid in new if cid not in old}
68
+ if changes: res |= {cid: fn(cid, old[cid], new[cid]) for cid in new if cid in old and new[cid] != old[cid]}
69
+ if dels: res |= {cid: fn(cid, old[cid], '') for cid in old if cid not in new}
70
+ return res
71
+
72
+ # %% ../nbs/api/19_diff.ipynb
73
+ @delegates(_cell_changes)
74
+ def changed_cells(nb_path, **kwargs):
75
+ "Return set of cell IDs for changed/added/deleted cells between two refs"
76
+ def f(cid,o,n): return cid
77
+ return set(_cell_changes(nb_path, f, **kwargs).keys())
78
+
79
+ # %% ../nbs/api/19_diff.ipynb
80
+ def source_diff(
81
+ old_source, # Original source string
82
+ new_source # New source string
83
+ ): # Unified diff string
84
+ "Return unified diff string for source change"
85
+ return '\n'.join(unified_diff(old_source.splitlines(), new_source.splitlines(), lineterm=''))
86
+
87
+ # %% ../nbs/api/19_diff.ipynb
88
+ @delegates(_cell_changes)
89
+ def cell_diffs(nb_path, **kwargs):
90
+ "{cell_id:diff} for changed/added/deleted cells between two refs"
91
+ def f(cid,o,n): return source_diff(o,n)
92
+ return _cell_changes(nb_path, f, **kwargs)
@@ -17,7 +17,7 @@ from fastcore.meta import delegates
17
17
  from fastcore.net import urlread
18
18
 
19
19
  import ast,builtins,contextlib
20
- import pkg_resources,importlib
20
+ import importlib
21
21
 
22
22
  from astunparse import unparse
23
23
  from io import BytesIO
@@ -27,6 +27,8 @@ from urllib.parse import urljoin
27
27
  from functools import lru_cache
28
28
  from types import ModuleType
29
29
 
30
+ from importlib.metadata import entry_points
31
+
30
32
  # %% ../nbs/api/05_doclinks.ipynb
31
33
  def _sym_nm(klas, sym): return f'{unparse(klas).strip()}.{sym.name}'
32
34
 
@@ -111,7 +113,8 @@ def _build_modidx(dest=None, nbs_path=None, skip_exists=False):
111
113
  if k in ('doc_host','doc_baseurl','lib_path','git_url','branch')}
112
114
  code_root = dest.parent.resolve()
113
115
  for file in globtastic(dest, file_glob="*.py", skip_file_re='^_', skip_folder_re=r"\.ipynb_checkpoints"):
114
- res['syms'].update(_get_modidx((dest.parent/file).resolve(), code_root, nbs_path=nbs_path))
116
+ try: res['syms'].update(_get_modidx((dest.parent/file).resolve(), code_root, nbs_path=nbs_path))
117
+ except ValueError: pass
115
118
  idxfile.write_text("# Autogenerated by nbdev\n\nd = "+pformat(res, width=140, indent=2, compact=True)+'\n')
116
119
 
117
120
  # %% ../nbs/api/05_doclinks.ipynb
@@ -236,17 +239,24 @@ def _build_lookup_table(strip_libs=None, incl_libs=None, skip_mods=None):
236
239
  strip_libs = L(strip_libs)
237
240
  if incl_libs is not None: incl_libs = (L(incl_libs)+strip_libs).unique()
238
241
  entries = {}
239
- for o in pkg_resources.iter_entry_points(group='nbdev'):
240
- if incl_libs is not None and o.dist.key not in incl_libs: continue
241
- try: entries[o.name] = _qual_syms(o.resolve())
242
+ try: eps = entry_points(group='nbdev')
243
+ # Python 3.9 fallback - entry_points() doesn't accept group parameter
244
+ except TypeError: eps = entry_points().get('nbdev', [])
245
+
246
+ for o in eps:
247
+ if incl_libs is not None and o.dist.name not in incl_libs: continue
248
+ try: entries[o.name] = _qual_syms(o.load())
242
249
  except Exception: pass
243
250
  py_syms = merge(*L(o['syms'].values() for o in entries.values()).concat())
244
251
  for m in strip_libs:
245
252
  if m in entries:
246
253
  _d = entries[m]
247
- stripped = {remove_prefix(k,f"{mod}."):v
248
- for mod,dets in _d['syms'].items() if mod not in skip_mods
249
- for k,v in dets.items()}
254
+ stripped = {}
255
+ for mod, dets in _d['syms'].items():
256
+ if mod not in skip_mods:
257
+ for k,v in dets.items():
258
+ k = remove_prefix(k,f"{mod}.")
259
+ if k not in stripped: stripped[k] = v
250
260
  py_syms = merge(stripped, py_syms)
251
261
  return entries,py_syms
252
262
 
@@ -73,6 +73,7 @@ def nb_export(nbname:str, # Filename of notebook
73
73
  name:str=None, # Name of python script {name}.py to create.
74
74
  mod_maker=ModuleMaker,
75
75
  debug:bool=False, # Debug mode
76
+ solo_nb:bool=False # Export single notebook outside of an nbdev project.
76
77
  ):
77
78
  "Create module(s) from notebook"
78
79
  if lib_path is None: lib_path = get_config().lib_path if is_nbdev() else '.'
@@ -84,9 +85,9 @@ def nb_export(nbname:str, # Filename of notebook
84
85
  all_cells = exp.in_all[mod]
85
86
  nm = ifnone(name, getattr(exp, 'default_exp', None) if mod=='#' else mod)
86
87
  if not nm:
87
- warn(f"Notebook '{nbname}' uses `#|export` without `#|default_exp` cell.\n"
88
+ warn(f"Notebook '{nbname}' uses `#| export` without `#| default_exp` cell.\n"
88
89
  "Note nbdev2 no longer supports nbdev1 syntax. Run `nbdev_migrate` to upgrade.\n"
89
90
  "See https://nbdev.fast.ai/getting_started.html for more information.")
90
91
  return
91
- mm = mod_maker(dest=lib_path, name=nm, nb_path=nbname, is_new=bool(name) or mod=='#')
92
+ mm = mod_maker(dest=lib_path, name=nm, nb_path=nbname, is_new=bool(name) or mod=='#', solo_nb=solo_nb)
92
93
  mm.make(cells, all_cells, lib_path=lib_path)
@@ -63,7 +63,7 @@ def update_var(varname, func, fn=None, code=None):
63
63
  # %% ../nbs/api/02_maker.ipynb
64
64
  class ModuleMaker:
65
65
  "Helper class to create exported library from notebook source cells"
66
- def __init__(self, dest, name, nb_path, is_new=True, parse=True):
66
+ def __init__(self, dest, name, nb_path, is_new=True, parse=True, solo_nb=False):
67
67
  dest,nb_path = Path(dest),Path(nb_path)
68
68
  store_attr()
69
69
  self.fname = dest/(name.replace('.','/') + ".py")
@@ -170,10 +170,10 @@ def _last_future(self:ModuleMaker, cells):
170
170
  except ValueError: return 0
171
171
 
172
172
  # %% ../nbs/api/02_maker.ipynb
173
- def _import2relative(cells, lib_name=None):
174
- "Converts `cells` to use `import2relative` based on `lib_name`"
175
- if lib_name is None: lib_name = get_config().lib_name
176
- for cell in cells: cell.import2relative(lib_name)
173
+ def _import2relative(cells, lib_path=None):
174
+ "Converts `cells` to use `import2relative` based on `lib_path`"
175
+ if lib_path is None: lib_path = get_config().lib_path
176
+ for cell in cells: cell.import2relative(lib_path)
177
177
 
178
178
  # %% ../nbs/api/02_maker.ipynb
179
179
  def _retr_mdoc(cells):
@@ -208,8 +208,8 @@ def make(self:ModuleMaker, cells, all_cells=None, lib_path=None):
208
208
  f.write(_retr_mdoc(cells))
209
209
  f.write(f"# AUTOGENERATED! DO NOT EDIT! File to edit: {self.dest2nb}.")
210
210
  if last_future > 0: write_cells(cells[:last_future], self.hdr, f)
211
- if self.parse: f.write(f"\n\n# %% auto 0\n__all__ = {all_str}")
212
- write_cells(cells[last_future:], self.hdr, f, cell_number=get_config().cell_number)
211
+ if self.parse and not self.solo_nb: f.write(f"\n\n# %% auto 0\n__all__ = {all_str}")
212
+ write_cells(cells[last_future:], self.hdr, f, cell_number=get_config().cell_number, solo_nb=self.solo_nb)
213
213
  f.write('\n')
214
214
 
215
215
  # %% ../nbs/api/02_maker.ipynb
@@ -35,7 +35,7 @@ def _quarto_re(lang=None): return re.compile(_dir_pre(lang) + r'\s*[\w|-]+\s*:')
35
35
  # %% ../nbs/api/03_process.ipynb
36
36
  def _directive(s, lang='python'):
37
37
  s = re.sub('^'+_dir_pre(lang), f"{langs[lang]}|", s)
38
- if s.strip().endswith(':'): s = s.replace(':', '') # You can append colon at the end to be Quarto compliant. Ex: #|hide:
38
+ if s.strip().endswith(':'): s = s.replace(':', '') # You can append colon at the end to be Quarto compliant. Ex: #| hide:
39
39
  if ':' in s: s = s.replace(':', ': ')
40
40
  s = (s.strip()[2:]).strip().split()
41
41
  if not s: return None
@@ -21,7 +21,8 @@ import yaml
21
21
 
22
22
  # %% auto 0
23
23
  __all__ = ['BASE_QUARTO_URL', 'install_quarto', 'install', 'IndentDumper', 'nbdev_sidebar', 'refresh_quarto_yml',
24
- 'nbdev_proc_nbs', 'nbdev_readme', 'nbdev_docs', 'prepare', 'fs_watchdog', 'nbdev_preview']
24
+ 'nbdev_proc_nbs', 'nbdev_readme', 'nbdev_contributing', 'nbdev_docs', 'prepare', 'fs_watchdog',
25
+ 'nbdev_preview']
25
26
 
26
27
  # %% ../nbs/api/14_quarto.ipynb
27
28
  def _sprun(cmd):
@@ -69,7 +70,7 @@ def _sort(a):
69
70
  x,y = a
70
71
  if y.startswith('index.'): return x,'00'
71
72
  return a
72
- #|export
73
+ #| export
73
74
  _def_file_re = r'\.(?:ipynb|qmd|html)$'
74
75
 
75
76
  @delegates(nbglob_cli)
@@ -214,7 +215,7 @@ def nbdev_proc_nbs(**kwargs):
214
215
  _pre_docs(**kwargs)[0]
215
216
 
216
217
  # %% ../nbs/api/14_quarto.ipynb
217
- def _readme_mtime_not_older(readme_path, readme_nb_path):
218
+ def _doc_mtime_not_older(readme_path, readme_nb_path):
218
219
  if not readme_nb_path.exists():
219
220
  print(f"Could not find {readme_nb_path}")
220
221
  return True
@@ -259,7 +260,7 @@ def nbdev_readme(
259
260
  "Create README.md from readme_nb (index.ipynb by default)"
260
261
  cfg = get_config()
261
262
  path = Path(path) if path else cfg.nbs_path
262
- if chk_time and _readme_mtime_not_older(cfg.config_path/'README.md', path/cfg.readme_nb): return
263
+ if chk_time and _doc_mtime_not_older(cfg.config_path/'README.md', path/cfg.readme_nb): return
263
264
 
264
265
  with _SidebarYmlRemoved(path): # to avoid rendering whole website
265
266
  cache = proc_nbs(path)
@@ -267,6 +268,38 @@ def nbdev_readme(
267
268
 
268
269
  _save_cached_readme(cache, cfg)
269
270
 
271
+ # %% ../nbs/api/14_quarto.ipynb
272
+ def _save_cached_contributing(cache, cfg, contrib_nb):
273
+ "Move CONTRIBUTING.md (and any `_files` assets) from the Quarto build cache to the repo root."
274
+ tmp_doc_path = cache / cfg.doc_path.name
275
+ contrib_file = tmp_doc_path / 'CONTRIBUTING.md'
276
+ if contrib_file.exists():
277
+ final_path = cfg.config_path / 'CONTRIBUTING.md'
278
+ if final_path.exists(): final_path.unlink() # py37 doesn't have `missing_ok`
279
+ move(contrib_file, final_path)
280
+ assets_folder = tmp_doc_path / (Path(contrib_nb).stem + '_files') # Supporting files for CONTRIBUTING
281
+ if assets_folder.exists(): _copytree(assets_folder, cfg.config_path / assets_folder.name)
282
+
283
+ # %% ../nbs/api/14_quarto.ipynb
284
+ @call_parse
285
+ def nbdev_contributing(
286
+ path:str=None, # Path to notebooks
287
+ chk_time:bool=False # Only build if out-of-date
288
+ ):
289
+ """Create CONTRIBUTING.md from contributing_nb (defaults to 'contributing.ipynb' if present). Skips if the file doesn't exist."""
290
+ cfg = get_config()
291
+ path = Path(path) if path else cfg.nbs_path
292
+ contrib_nb_name = cfg.get('contributing_nb', 'contributing.ipynb')
293
+ contrib_nb_path = path / contrib_nb_name
294
+ if not contrib_nb_path.exists(): return
295
+ if chk_time and _doc_mtime_not_older(cfg.config_path / 'CONTRIBUTING.md' , contrib_nb_path): return
296
+
297
+ with _SidebarYmlRemoved(path): # to avoid rendering whole website
298
+ cache = proc_nbs(path)
299
+ _sprun(f'cd "{cache}" && quarto render "{cache/contrib_nb_name}" -o CONTRIBUTING.md -t gfm --no-execute')
300
+
301
+ _save_cached_contributing(cache, cfg, contrib_nb_name)
302
+
270
303
  # %% ../nbs/api/14_quarto.ipynb
271
304
  @call_parse
272
305
  @delegates(_nbglob_docs)
@@ -277,6 +310,7 @@ def nbdev_docs(
277
310
  "Create Quarto docs and README.md"
278
311
  cache,cfg,path = _pre_docs(path, n_workers=n_workers, **kwargs)
279
312
  nbdev_readme.__wrapped__(path=path, chk_time=True)
313
+ nbdev_contributing.__wrapped__(path=path, chk_time=True)
280
314
  _sprun(f'cd "{cache}" && quarto render --no-cache')
281
315
  shutil.rmtree(cfg.doc_path, ignore_errors=True)
282
316
  move(cache/cfg.doc_path.name, cfg.config_path)
@@ -291,6 +325,7 @@ def prepare():
291
325
  nbdev.clean.nbdev_clean.__wrapped__()
292
326
  refresh_quarto_yml()
293
327
  nbdev_readme.__wrapped__(chk_time=True)
328
+ nbdev_contributing.__wrapped__(chk_time=True)
294
329
 
295
330
  # %% ../nbs/api/14_quarto.ipynb
296
331
  @contextmanager