From 76fdc2808547ec713d7ddbca91bcaec5ff129362 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Tue, 23 Dec 2025 18:50:15 +1100 Subject: [PATCH 1/5] initial working ruff per cell --- nbdev/_modidx.py | 2 + nbdev/config.py | 2 + nbdev/doclinks.py | 2 +- nbdev/export.py | 60 ++++++++++++++++++- nbs/api/01_config.ipynb | 2 + nbs/api/04_export.ipynb | 119 +++++++++++++++++++++++++++++++++++++- nbs/api/05_doclinks.ipynb | 4 +- pyproject.toml | 1 + settings.ini | 4 +- tests/export_procs.ipynb | 11 +++- 10 files changed, 198 insertions(+), 9 deletions(-) diff --git a/nbdev/_modidx.py b/nbdev/_modidx.py index 632ee6af8..9b71e73a6 100644 --- a/nbdev/_modidx.py +++ b/nbdev/_modidx.py @@ -93,6 +93,8 @@ 'nbdev.export.black_format': ('api/export.html#black_format', 'nbdev/export.py'), 'nbdev.export.nb_export': ('api/export.html#nb_export', 'nbdev/export.py'), 'nbdev.export.optional_procs': ('api/export.html#optional_procs', 'nbdev/export.py'), + 'nbdev.export.ruff_fix': ('api/export.html#ruff_fix', 'nbdev/export.py'), + 'nbdev.export.ruff_format': ('api/export.html#ruff_format', 'nbdev/export.py'), 'nbdev.export.scrub_magics': ('api/export.html#scrub_magics', 'nbdev/export.py')}, 'nbdev.extract_attachments': {}, 'nbdev.frontmatter': { 'nbdev.frontmatter.FrontmatterProc': ('api/frontmatter.html#frontmatterproc', 'nbdev/frontmatter.py'), diff --git a/nbdev/config.py b/nbdev/config.py index 991557e98..078b6a709 100644 --- a/nbdev/config.py +++ b/nbdev/config.py @@ -54,6 +54,8 @@ def _apply_defaults( language='English', # Language PyPI classifier recursive:bool_arg=True, # Include subfolders in notebook globs? black_formatting:bool_arg=False, # Format libraries with black? + ruff_formatting:bool_arg=False, # Format libraries with ruff format? + ruff_fixing:bool_arg=False, # Fix libraries with ruff check --fix? readme_nb='index.ipynb', # Notebook to export as repo readme title='%(lib_name)s', # Quarto website title allowed_metadata_keys='', # Preserve the list of keys in the main notebook metadata diff --git a/nbdev/doclinks.py b/nbdev/doclinks.py index dc59f67da..0991987f6 100644 --- a/nbdev/doclinks.py +++ b/nbdev/doclinks.py @@ -146,7 +146,7 @@ def nbglob_cli( @delegates(nbglob_cli) def nbdev_export( path:str=None, # Path or filename - procs:Param("tokens naming the export processors to use.", nargs="*", choices=optional_procs())="black_format", + procs:Param("tokens naming the export processors to use.", nargs="*", choices=optional_procs())=["black_format", "ruff_format", "ruff_fix"], **kwargs): "Export notebooks in `path` to Python modules" if os.environ.get('IN_TEST',0): return diff --git a/nbdev/export.py b/nbdev/export.py index c6e338111..b7da3d8fc 100644 --- a/nbdev/export.py +++ b/nbdev/export.py @@ -3,7 +3,7 @@ # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/04_export.ipynb. # %% auto 0 -__all__ = ['ExportModuleProc', 'black_format', 'scrub_magics', 'optional_procs', 'nb_export'] +__all__ = ['ExportModuleProc', 'black_format', 'ruff_format', 'ruff_fix', 'scrub_magics', 'optional_procs', 'nb_export'] # %% ../nbs/api/04_export.ipynb from .config import * @@ -47,6 +47,64 @@ def black_format(cell, # Cell to format try: cell.source = _format_str(cell.source).strip() except: pass +# %% ../nbs/api/04_export.ipynb +def ruff_format(cell, # Cell to format + force=False): # Turn ruff formatting on regardless of settings.ini + "Processor to format code with `ruff format`" + try: cfg = get_config() + except FileNotFoundError: return + if (not cfg.ruff_formatting and not force) or cell.cell_type != 'code': return + + try: import subprocess + except: raise ImportError("subprocess is required") + + try: + subprocess.run(['ruff', '--version'], capture_output=True, check=True) + except (subprocess.CalledProcessError, FileNotFoundError): + raise ImportError("You must install ruff: `pip install ruff` if you wish to use ruff formatting with nbdev") + else: + try: + result = subprocess.run( + ['ruff', 'format', '-'], + input=cell.source, + capture_output=True, + text=True, + check=False + ) + if result.returncode == 0: + cell.source = result.stdout.strip() + except: + pass + +# %% ../nbs/api/04_export.ipynb +def ruff_fix(cell, # Cell to lint and fix + force=False): # Turn ruff fixing on regardless of settings.ini + "Processor to lint and auto-fix code with `ruff check --fix`" + try: cfg = get_config() + except FileNotFoundError: return + if (not cfg.ruff_fixing and not force) or cell.cell_type != 'code': return + + try: import subprocess + except: raise ImportError("subprocess is required") + + try: + subprocess.run(['ruff', '--version'], capture_output=True, check=True) + except (subprocess.CalledProcessError, FileNotFoundError): + raise ImportError("You must install ruff: `pip install ruff` if you wish to use ruff formatting with nbdev") + else: + try: + result = subprocess.run( + ['ruff', 'check', '--fix', '-'], + input=cell.source, + capture_output=True, + text=True, + check=False + ) + if result.returncode == 0 or result.returncode == 1: # 1 means fixes were applied + cell.source = result.stdout.strip() if result.stdout else cell.source + except: + pass + # %% ../nbs/api/04_export.ipynb # includes the newline, because calling .strip() would affect all cells. _magics_pattern = re.compile(r'^\s*(%%|%).*\n?', re.MULTILINE) diff --git a/nbs/api/01_config.ipynb b/nbs/api/01_config.ipynb index 44d740639..7408fca04 100644 --- a/nbs/api/01_config.ipynb +++ b/nbs/api/01_config.ipynb @@ -144,6 +144,8 @@ " language='English', # Language PyPI classifier\n", " recursive:bool_arg=True, # Include subfolders in notebook globs?\n", " black_formatting:bool_arg=False, # Format libraries with black?\n", + " ruff_formatting:bool_arg=False, # Format libraries with ruff format?\n", + " ruff_fixing:bool_arg=False, # Fix libraries with ruff check --fix?\n", " readme_nb='index.ipynb', # Notebook to export as repo readme\n", " title='%(lib_name)s', # Quarto website title\n", " allowed_metadata_keys='', # Preserve the list of keys in the main notebook metadata\n", diff --git a/nbs/api/04_export.ipynb b/nbs/api/04_export.ipynb index 454457bc1..7305cb5ee 100644 --- a/nbs/api/04_export.ipynb +++ b/nbs/api/04_export.ipynb @@ -107,6 +107,27 @@ "### Optional export processors" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "False False False\n" + ] + } + ], + "source": [ + "print(\n", + " get_config().black_formatting,\n", + " get_config().ruff_formatting,\n", + " get_config().ruff_fixing,\n", + ")" + ] + }, { "cell_type": "code", "execution_count": null, @@ -136,7 +157,101 @@ "source": [ "_cell = read_nb('../../tests/export_procs.ipynb')['cells'][0]\n", "black_format(_cell, force=True)\n", - "test_eq(_cell.source, 'j = [1, 2, 3]')" + "test_eq(_cell.source, 'import ast\\n\\nj = [1, 2, 3]\\nfor i in j:\\n str(1)')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def ruff_format(cell, # Cell to format\n", + " force=False): # Turn ruff formatting on regardless of settings.ini\n", + " \"Processor to format code with `ruff format`\"\n", + " try: cfg = get_config()\n", + " except FileNotFoundError: return\n", + " if (not cfg.ruff_formatting and not force) or cell.cell_type != 'code': return\n", + " \n", + " try: import subprocess\n", + " except: raise ImportError(\"subprocess is required\")\n", + " \n", + " try:\n", + " subprocess.run(['ruff', '--version'], capture_output=True, check=True)\n", + " except (subprocess.CalledProcessError, FileNotFoundError):\n", + " raise ImportError(\"You must install ruff: `pip install ruff` if you wish to use ruff formatting with nbdev\")\n", + " else:\n", + " try:\n", + " result = subprocess.run(\n", + " ['ruff', 'format', '-'],\n", + " input=cell.source,\n", + " capture_output=True,\n", + " text=True,\n", + " check=False\n", + " )\n", + " if result.returncode == 0:\n", + " cell.source = result.stdout.strip()\n", + " except:\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "_cell = read_nb('../../tests/export_procs.ipynb')['cells'][0]\n", + "ruff_format(_cell, force=True)\n", + "test_eq(_cell.source, 'import ast\\n\\nj = [1, 2, 3]\\nfor i in j:\\n str(1)')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def ruff_fix(cell, # Cell to lint and fix\n", + " force=False): # Turn ruff fixing on regardless of settings.ini\n", + " \"Processor to lint and auto-fix code with `ruff check --fix`\"\n", + " try: cfg = get_config()\n", + " except FileNotFoundError: return\n", + " if (not cfg.ruff_fixing and not force) or cell.cell_type != 'code': return\n", + " \n", + " try: import subprocess\n", + " except: raise ImportError(\"subprocess is required\")\n", + "\n", + " try:\n", + " subprocess.run(['ruff', '--version'], capture_output=True, check=True)\n", + " except (subprocess.CalledProcessError, FileNotFoundError):\n", + " raise ImportError(\"You must install ruff: `pip install ruff` if you wish to use ruff formatting with nbdev\")\n", + " else:\n", + " try:\n", + " result = subprocess.run(\n", + " ['ruff', 'check', '--fix', '-'],\n", + " input=cell.source,\n", + " capture_output=True,\n", + " text=True,\n", + " check=False\n", + " )\n", + " if result.returncode == 0 or result.returncode == 1: # 1 means fixes were applied\n", + " cell.source = result.stdout.strip() if result.stdout else cell.source\n", + " except:\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "_cell = read_nb('../../tests/export_procs.ipynb')['cells'][0]\n", + "ruff_fix(_cell, force=True)\n", + "test_eq(_cell.source, 'j = [1,\\n 2,\\n 3\\n]\\nfor i in j:\\n str(1)')" ] }, { @@ -221,7 +336,7 @@ "outputs": [], "source": [ "# every optional processor should be explicitly listed here\n", - "test_eq(optional_procs(), ['black_format', 'scrub_magics'])" + "test_eq(optional_procs(), ['black_format', 'ruff_format', 'ruff_fix', 'scrub_magics'])" ] }, { diff --git a/nbs/api/05_doclinks.ipynb b/nbs/api/05_doclinks.ipynb index 4017e3a43..13ab9084b 100644 --- a/nbs/api/05_doclinks.ipynb +++ b/nbs/api/05_doclinks.ipynb @@ -388,7 +388,7 @@ "@delegates(nbglob_cli)\n", "def nbdev_export(\n", " path:str=None, # Path or filename\n", - " procs:Param(\"tokens naming the export processors to use.\", nargs=\"*\", choices=optional_procs())=\"black_format\",\n", + " procs:Param(\"tokens naming the export processors to use.\", nargs=\"*\", choices=optional_procs())=[\"black_format\", \"ruff_format\", \"ruff_fix\"],\n", " **kwargs):\n", " \"Export notebooks in `path` to Python modules\"\n", " if os.environ.get('IN_TEST',0): return\n", @@ -409,7 +409,7 @@ "source": [ "`procs` names the optional processors you wish to run on the exported cells of your notebook.\n", "\n", - "N.B.: the `black_format` processor is passed in by default. But it is a no-op, unless `black_formatting=True` is set in your `settings.ini` configuration. You can omit it from `nbdev_export` on the command line by passing in `--procs`." + "N.B.: the `[\"black_format\", \"ruff_format\", \"ruff_fix\"]` processor is passed in by default. But it is a no-op, unless `black_formatting=True`, `ruff_formatting=True`, or `ruff_fixing=True` is set in your `settings.ini` configuration. You can omit it from `nbdev_export` on the command line by passing in `--procs`." ] }, { diff --git a/pyproject.toml b/pyproject.toml index a4eca02de..fee87ac7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,3 +9,4 @@ dynamic = [ "keywords", "description", "version", "dependencies", "optional-depe [tool.uv] cache-keys = [{ file = "pyproject.toml" }, { file = "settings.ini" }, { file = "setup.py" }] + diff --git a/settings.ini b/settings.ini index f22bb0be5..264630fdd 100644 --- a/settings.ini +++ b/settings.ini @@ -19,7 +19,7 @@ requirements = fastcore>=1.11.0 execnb>=0.1.12 astunparse ghapi>=1.0.3 watchdog pip_requirements = PyYAML conda_requirements = pyyaml conda_user = fastai -dev_requirements = ipywidgets nbdev-numpy nbdev-stdlib pandas matplotlib black svg.py nbclassic pysymbol_llm llms-txt sphinx plum-dispatch +dev_requirements = ipywidgets nbdev-numpy nbdev-stdlib pandas matplotlib black ruff svg.py nbclassic pysymbol_llm llms-txt sphinx plum-dispatch console_scripts = nbdev_create_config=nbdev.config:nbdev_create_config nbdev_update=nbdev.sync:nbdev_update nbdev_update_license=nbdev.cli:nbdev_update_license @@ -63,6 +63,8 @@ git_url = https://github.com/AnswerDotAI/nbdev lib_path = nbdev title = nbdev black_formatting = False +ruff_formatting = False +ruff_fixing = False readme_nb = getting_started.ipynb allowed_metadata_keys = allowed_cell_metadata_keys = diff --git a/tests/export_procs.ipynb b/tests/export_procs.ipynb index c57cebf41..c634dffbd 100644 --- a/tests/export_procs.ipynb +++ b/tests/export_procs.ipynb @@ -7,10 +7,13 @@ "metadata": {}, "outputs": [], "source": [ + "import ast\n", "j = [1,\n", " 2,\n", " 3\n", - "]" + "]\n", + "for i in j:\n", + " str(1);" ] }, { @@ -36,9 +39,13 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "env", "language": "python", "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.13.3" } }, "nbformat": 4, From 411048742b3d11e2413ddf1603412ddd62644276 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Wed, 24 Dec 2025 11:41:18 +1100 Subject: [PATCH 2/5] add _cell to ruff to make it clear that it is for the cell basis --- nbs/api/04_export.ipynb | 10 +++++----- nbs/api/05_doclinks.ipynb | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/nbs/api/04_export.ipynb b/nbs/api/04_export.ipynb index 7305cb5ee..6ce011eb6 100644 --- a/nbs/api/04_export.ipynb +++ b/nbs/api/04_export.ipynb @@ -167,7 +167,7 @@ "outputs": [], "source": [ "#| export\n", - "def ruff_format(cell, # Cell to format\n", + "def ruff_format_cell(cell, # Cell to format\n", " force=False): # Turn ruff formatting on regardless of settings.ini\n", " \"Processor to format code with `ruff format`\"\n", " try: cfg = get_config()\n", @@ -203,7 +203,7 @@ "outputs": [], "source": [ "_cell = read_nb('../../tests/export_procs.ipynb')['cells'][0]\n", - "ruff_format(_cell, force=True)\n", + "ruff_format_cell(_cell, force=True)\n", "test_eq(_cell.source, 'import ast\\n\\nj = [1, 2, 3]\\nfor i in j:\\n str(1)')" ] }, @@ -214,7 +214,7 @@ "outputs": [], "source": [ "#| export\n", - "def ruff_fix(cell, # Cell to lint and fix\n", + "def ruff_fix_cell(cell, # Cell to lint and fix\n", " force=False): # Turn ruff fixing on regardless of settings.ini\n", " \"Processor to lint and auto-fix code with `ruff check --fix`\"\n", " try: cfg = get_config()\n", @@ -250,7 +250,7 @@ "outputs": [], "source": [ "_cell = read_nb('../../tests/export_procs.ipynb')['cells'][0]\n", - "ruff_fix(_cell, force=True)\n", + "ruff_fix_cell(_cell, force=True)\n", "test_eq(_cell.source, 'j = [1,\\n 2,\\n 3\\n]\\nfor i in j:\\n str(1)')" ] }, @@ -336,7 +336,7 @@ "outputs": [], "source": [ "# every optional processor should be explicitly listed here\n", - "test_eq(optional_procs(), ['black_format', 'ruff_format', 'ruff_fix', 'scrub_magics'])" + "test_eq(optional_procs(), ['black_format', 'ruff_format_cell', 'ruff_fix_cell', 'scrub_magics'])" ] }, { diff --git a/nbs/api/05_doclinks.ipynb b/nbs/api/05_doclinks.ipynb index 13ab9084b..4e58d5b1a 100644 --- a/nbs/api/05_doclinks.ipynb +++ b/nbs/api/05_doclinks.ipynb @@ -388,7 +388,7 @@ "@delegates(nbglob_cli)\n", "def nbdev_export(\n", " path:str=None, # Path or filename\n", - " procs:Param(\"tokens naming the export processors to use.\", nargs=\"*\", choices=optional_procs())=[\"black_format\", \"ruff_format\", \"ruff_fix\"],\n", + " procs:Param(\"tokens naming the export processors to use.\", nargs=\"*\", choices=optional_procs())=[\"black_format\"],\n", " **kwargs):\n", " \"Export notebooks in `path` to Python modules\"\n", " if os.environ.get('IN_TEST',0): return\n", @@ -409,7 +409,7 @@ "source": [ "`procs` names the optional processors you wish to run on the exported cells of your notebook.\n", "\n", - "N.B.: the `[\"black_format\", \"ruff_format\", \"ruff_fix\"]` processor is passed in by default. But it is a no-op, unless `black_formatting=True`, `ruff_formatting=True`, or `ruff_fixing=True` is set in your `settings.ini` configuration. You can omit it from `nbdev_export` on the command line by passing in `--procs`." + "N.B.: the `[\"black_format\", \"ruff_format_cell\", \"ruff_fix_cell\"]` processor is passed in by default. But it is a no-op, unless `black_formatting=True`, `ruff_formatting=True`, or `ruff_fixing=True` is set in your `settings.ini` configuration. You can omit it from `nbdev_export` on the command line by passing in `--procs`." ] }, { From 08867d941661272f004eda084de54fe600b91539 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Wed, 24 Dec 2025 13:11:19 +1100 Subject: [PATCH 3/5] run ruff on all files --- nbdev/_modidx.py | 6 +- nbdev/doclinks.py | 37 +++++++++- nbdev/export.py | 60 +--------------- nbs/api/04_export.ipynb | 117 +------------------------------ nbs/api/05_doclinks.ipynb | 141 +++++++++++++++++++++++++++++++++----- pyproject.toml | 1 - tests/export_procs.ipynb | 6 +- 7 files changed, 164 insertions(+), 204 deletions(-) diff --git a/nbdev/_modidx.py b/nbdev/_modidx.py index 9b71e73a6..19ecd46a6 100644 --- a/nbdev/_modidx.py +++ b/nbdev/_modidx.py @@ -82,7 +82,9 @@ 'nbdev.doclinks.nbdev_export': ('api/doclinks.html#nbdev_export', 'nbdev/doclinks.py'), 'nbdev.doclinks.nbglob': ('api/doclinks.html#nbglob', 'nbdev/doclinks.py'), 'nbdev.doclinks.nbglob_cli': ('api/doclinks.html#nbglob_cli', 'nbdev/doclinks.py'), - 'nbdev.doclinks.patch_name': ('api/doclinks.html#patch_name', 'nbdev/doclinks.py')}, + 'nbdev.doclinks.patch_name': ('api/doclinks.html#patch_name', 'nbdev/doclinks.py'), + 'nbdev.doclinks.ruff_fix': ('api/doclinks.html#ruff_fix', 'nbdev/doclinks.py'), + 'nbdev.doclinks.ruff_format': ('api/doclinks.html#ruff_format', 'nbdev/doclinks.py')}, 'nbdev.export': { 'nbdev.export.ExportModuleProc': ('api/export.html#exportmoduleproc', 'nbdev/export.py'), 'nbdev.export.ExportModuleProc.__call__': ('api/export.html#exportmoduleproc.__call__', 'nbdev/export.py'), 'nbdev.export.ExportModuleProc._default_exp_': ( 'api/export.html#exportmoduleproc._default_exp_', @@ -93,8 +95,6 @@ 'nbdev.export.black_format': ('api/export.html#black_format', 'nbdev/export.py'), 'nbdev.export.nb_export': ('api/export.html#nb_export', 'nbdev/export.py'), 'nbdev.export.optional_procs': ('api/export.html#optional_procs', 'nbdev/export.py'), - 'nbdev.export.ruff_fix': ('api/export.html#ruff_fix', 'nbdev/export.py'), - 'nbdev.export.ruff_format': ('api/export.html#ruff_format', 'nbdev/export.py'), 'nbdev.export.scrub_magics': ('api/export.html#scrub_magics', 'nbdev/export.py')}, 'nbdev.extract_attachments': {}, 'nbdev.frontmatter': { 'nbdev.frontmatter.FrontmatterProc': ('api/frontmatter.html#frontmatterproc', 'nbdev/frontmatter.py'), diff --git a/nbdev/doclinks.py b/nbdev/doclinks.py index 0991987f6..7eb09b51a 100644 --- a/nbdev/doclinks.py +++ b/nbdev/doclinks.py @@ -3,7 +3,8 @@ # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/05_doclinks.ipynb. # %% auto 0 -__all__ = ['typs', 'bset', 'patch_name', 'nbglob', 'nbglob_cli', 'nbdev_export', 'create_index', 'NbdevLookup'] +__all__ = ['typs', 'bset', 'patch_name', 'ruff_format', 'ruff_fix', 'nbglob', 'nbglob_cli', 'nbdev_export', 'create_index', + 'NbdevLookup'] # %% ../nbs/api/05_doclinks.ipynb from .config import * @@ -117,6 +118,32 @@ def _build_modidx(dest=None, nbs_path=None, skip_exists=False): except ValueError: pass idxfile.write_text("# Autogenerated by nbdev\n\nd = "+pformat(res, width=140, indent=2, compact=True)+'\n') +# %% ../nbs/api/05_doclinks.ipynb +def ruff_format(): + "Format code with `ruff format` on the entire library" + import subprocess + try: + subprocess.run( + ['ruff', 'format'], + capture_output=True, + check=False + ) + except FileNotFoundError: + raise ImportError("You must install ruff: `pip install ruff` if you wish to use ruff formatting with nbdev") + +# %% ../nbs/api/05_doclinks.ipynb +def ruff_fix(): + "Lint and auto-fix code with `ruff check --fix` on the entire library" + import subprocess + try: + subprocess.run( + ['ruff', 'check', '--fix'], + capture_output=True, + check=False + ) + except FileNotFoundError: + raise ImportError("You must install ruff: `pip install ruff` if you wish to use ruff formatting with nbdev") + # %% ../nbs/api/05_doclinks.ipynb @delegates(globtastic) def nbglob(path=None, skip_folder_re = '^[_.]', file_glob='*.ipynb', skip_file_re='^[_.]', key='nbs_path', as_path=False, **kwargs): @@ -146,7 +173,7 @@ def nbglob_cli( @delegates(nbglob_cli) def nbdev_export( path:str=None, # Path or filename - procs:Param("tokens naming the export processors to use.", nargs="*", choices=optional_procs())=["black_format", "ruff_format", "ruff_fix"], + procs:Param("tokens naming the export processors to use.", nargs="*", choices=optional_procs())="black_format", **kwargs): "Export notebooks in `path` to Python modules" if os.environ.get('IN_TEST',0): return @@ -156,9 +183,13 @@ def nbdev_export( procs = [getattr(nbdev.export, p) for p in L(procs)] files = nbglob(path=path, as_path=True, **kwargs).sorted('name') for f in files: nb_export(f, procs=procs) - add_init(get_config().lib_path) + cfg = get_config() + add_init(cfg.lib_path) _build_modidx() + if cfg.ruff_formatting: ruff_format() + if cfg.ruff_fixing: ruff_fix() + # %% ../nbs/api/05_doclinks.ipynb typs = 'module','class','method','function' bset = set(dir(builtins)) diff --git a/nbdev/export.py b/nbdev/export.py index b7da3d8fc..c6e338111 100644 --- a/nbdev/export.py +++ b/nbdev/export.py @@ -3,7 +3,7 @@ # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/04_export.ipynb. # %% auto 0 -__all__ = ['ExportModuleProc', 'black_format', 'ruff_format', 'ruff_fix', 'scrub_magics', 'optional_procs', 'nb_export'] +__all__ = ['ExportModuleProc', 'black_format', 'scrub_magics', 'optional_procs', 'nb_export'] # %% ../nbs/api/04_export.ipynb from .config import * @@ -47,64 +47,6 @@ def black_format(cell, # Cell to format try: cell.source = _format_str(cell.source).strip() except: pass -# %% ../nbs/api/04_export.ipynb -def ruff_format(cell, # Cell to format - force=False): # Turn ruff formatting on regardless of settings.ini - "Processor to format code with `ruff format`" - try: cfg = get_config() - except FileNotFoundError: return - if (not cfg.ruff_formatting and not force) or cell.cell_type != 'code': return - - try: import subprocess - except: raise ImportError("subprocess is required") - - try: - subprocess.run(['ruff', '--version'], capture_output=True, check=True) - except (subprocess.CalledProcessError, FileNotFoundError): - raise ImportError("You must install ruff: `pip install ruff` if you wish to use ruff formatting with nbdev") - else: - try: - result = subprocess.run( - ['ruff', 'format', '-'], - input=cell.source, - capture_output=True, - text=True, - check=False - ) - if result.returncode == 0: - cell.source = result.stdout.strip() - except: - pass - -# %% ../nbs/api/04_export.ipynb -def ruff_fix(cell, # Cell to lint and fix - force=False): # Turn ruff fixing on regardless of settings.ini - "Processor to lint and auto-fix code with `ruff check --fix`" - try: cfg = get_config() - except FileNotFoundError: return - if (not cfg.ruff_fixing and not force) or cell.cell_type != 'code': return - - try: import subprocess - except: raise ImportError("subprocess is required") - - try: - subprocess.run(['ruff', '--version'], capture_output=True, check=True) - except (subprocess.CalledProcessError, FileNotFoundError): - raise ImportError("You must install ruff: `pip install ruff` if you wish to use ruff formatting with nbdev") - else: - try: - result = subprocess.run( - ['ruff', 'check', '--fix', '-'], - input=cell.source, - capture_output=True, - text=True, - check=False - ) - if result.returncode == 0 or result.returncode == 1: # 1 means fixes were applied - cell.source = result.stdout.strip() if result.stdout else cell.source - except: - pass - # %% ../nbs/api/04_export.ipynb # includes the newline, because calling .strip() would affect all cells. _magics_pattern = re.compile(r'^\s*(%%|%).*\n?', re.MULTILINE) diff --git a/nbs/api/04_export.ipynb b/nbs/api/04_export.ipynb index 6ce011eb6..6cbd3520c 100644 --- a/nbs/api/04_export.ipynb +++ b/nbs/api/04_export.ipynb @@ -107,27 +107,6 @@ "### Optional export processors" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "False False False\n" - ] - } - ], - "source": [ - "print(\n", - " get_config().black_formatting,\n", - " get_config().ruff_formatting,\n", - " get_config().ruff_fixing,\n", - ")" - ] - }, { "cell_type": "code", "execution_count": null, @@ -160,100 +139,6 @@ "test_eq(_cell.source, 'import ast\\n\\nj = [1, 2, 3]\\nfor i in j:\\n str(1)')" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#| export\n", - "def ruff_format_cell(cell, # Cell to format\n", - " force=False): # Turn ruff formatting on regardless of settings.ini\n", - " \"Processor to format code with `ruff format`\"\n", - " try: cfg = get_config()\n", - " except FileNotFoundError: return\n", - " if (not cfg.ruff_formatting and not force) or cell.cell_type != 'code': return\n", - " \n", - " try: import subprocess\n", - " except: raise ImportError(\"subprocess is required\")\n", - " \n", - " try:\n", - " subprocess.run(['ruff', '--version'], capture_output=True, check=True)\n", - " except (subprocess.CalledProcessError, FileNotFoundError):\n", - " raise ImportError(\"You must install ruff: `pip install ruff` if you wish to use ruff formatting with nbdev\")\n", - " else:\n", - " try:\n", - " result = subprocess.run(\n", - " ['ruff', 'format', '-'],\n", - " input=cell.source,\n", - " capture_output=True,\n", - " text=True,\n", - " check=False\n", - " )\n", - " if result.returncode == 0:\n", - " cell.source = result.stdout.strip()\n", - " except:\n", - " pass" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "_cell = read_nb('../../tests/export_procs.ipynb')['cells'][0]\n", - "ruff_format_cell(_cell, force=True)\n", - "test_eq(_cell.source, 'import ast\\n\\nj = [1, 2, 3]\\nfor i in j:\\n str(1)')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#| export\n", - "def ruff_fix_cell(cell, # Cell to lint and fix\n", - " force=False): # Turn ruff fixing on regardless of settings.ini\n", - " \"Processor to lint and auto-fix code with `ruff check --fix`\"\n", - " try: cfg = get_config()\n", - " except FileNotFoundError: return\n", - " if (not cfg.ruff_fixing and not force) or cell.cell_type != 'code': return\n", - " \n", - " try: import subprocess\n", - " except: raise ImportError(\"subprocess is required\")\n", - "\n", - " try:\n", - " subprocess.run(['ruff', '--version'], capture_output=True, check=True)\n", - " except (subprocess.CalledProcessError, FileNotFoundError):\n", - " raise ImportError(\"You must install ruff: `pip install ruff` if you wish to use ruff formatting with nbdev\")\n", - " else:\n", - " try:\n", - " result = subprocess.run(\n", - " ['ruff', 'check', '--fix', '-'],\n", - " input=cell.source,\n", - " capture_output=True,\n", - " text=True,\n", - " check=False\n", - " )\n", - " if result.returncode == 0 or result.returncode == 1: # 1 means fixes were applied\n", - " cell.source = result.stdout.strip() if result.stdout else cell.source\n", - " except:\n", - " pass" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "_cell = read_nb('../../tests/export_procs.ipynb')['cells'][0]\n", - "ruff_fix_cell(_cell, force=True)\n", - "test_eq(_cell.source, 'j = [1,\\n 2,\\n 3\\n]\\nfor i in j:\\n str(1)')" - ] - }, { "cell_type": "code", "execution_count": null, @@ -336,7 +221,7 @@ "outputs": [], "source": [ "# every optional processor should be explicitly listed here\n", - "test_eq(optional_procs(), ['black_format', 'ruff_format_cell', 'ruff_fix_cell', 'scrub_magics'])" + "test_eq(optional_procs(), ['black_format', 'scrub_magics'])" ] }, { diff --git a/nbs/api/05_doclinks.ipynb b/nbs/api/05_doclinks.ipynb index 4e58d5b1a..3280699f7 100644 --- a/nbs/api/05_doclinks.ipynb +++ b/nbs/api/05_doclinks.ipynb @@ -60,12 +60,18 @@ "outputs": [], "source": [ "#| hide\n", + "import shutil\n", + "import tempfile\n", + "import os\n", + "\n", + "from pathlib import Path\n", "from IPython.display import Markdown,display\n", "from unittest.mock import patch as xpatch\n", "from fastcore.test import *\n", "from pdb import set_trace\n", "from importlib import reload\n", - "from nbdev.showdoc import show_doc" + "from nbdev.showdoc import show_doc\n", + "from execnb.nbio import read_nb" ] }, { @@ -284,17 +290,6 @@ "# _build_modidx()" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "96bef2c7", - "metadata": {}, - "outputs": [], - "source": [ - "#| hide\n", - "import shutil" - ] - }, { "cell_type": "code", "execution_count": null, @@ -330,7 +325,107 @@ }, { "cell_type": "markdown", - "id": "ad55d279", + "id": "c66f3b37", + "metadata": {}, + "source": [ + "## Ruff" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40a6b9f1", + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def ruff_format():\n", + " \"Format code with `ruff format` on the entire library\"\n", + " import subprocess\n", + " try:\n", + " subprocess.run(\n", + " ['ruff', 'format'],\n", + " capture_output=True,\n", + " check=False\n", + " )\n", + " except FileNotFoundError:\n", + " raise ImportError(\"You must install ruff: `pip install ruff` if you wish to use ruff formatting with nbdev\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b82ba02", + "metadata": {}, + "outputs": [], + "source": [ + "with tempfile.TemporaryDirectory() as tmpdir:\n", + " tmpdir = Path(tmpdir)\n", + " test_nb = Path('../../tests/export_procs.ipynb')\n", + " shutil.copy(test_nb, tmpdir / 'export_procs.ipynb')\n", + "\n", + " old_cwd = os.getcwd()\n", + " try:\n", + " os.chdir(tmpdir)\n", + " ruff_format()\n", + " assert (tmpdir / 'export_procs.ipynb').exists()\n", + " formatted_nb = read_nb(tmpdir / 'export_procs.ipynb')\n", + " formatted_source = formatted_nb['cells'][0]['source']\n", + " finally:\n", + " os.chdir(old_cwd)\n", + "\n", + "test_eq(formatted_source, 'import ast\\n\\nj = [1, 2, 3]\\nfor i in j:\\n str(1)')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "99e4ac75", + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def ruff_fix():\n", + " \"Lint and auto-fix code with `ruff check --fix` on the entire library\"\n", + " import subprocess\n", + " try:\n", + " subprocess.run(\n", + " ['ruff', 'check', '--fix'],\n", + " capture_output=True,\n", + " check=False\n", + " )\n", + " except FileNotFoundError:\n", + " raise ImportError(\"You must install ruff: `pip install ruff` if you wish to use ruff formatting with nbdev\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41aa4ab7", + "metadata": {}, + "outputs": [], + "source": [ + "with tempfile.TemporaryDirectory() as tmpdir:\n", + " tmpdir = Path(tmpdir)\n", + " test_nb = Path('../../tests/export_procs.ipynb')\n", + " shutil.copy(test_nb, tmpdir / 'export_procs.ipynb')\n", + "\n", + " old_cwd = os.getcwd()\n", + " try:\n", + " os.chdir(tmpdir)\n", + " ruff_fix()\n", + " assert (tmpdir / 'export_procs.ipynb').exists()\n", + " fixed_nb = read_nb(tmpdir / 'export_procs.ipynb')\n", + " fixed_source = fixed_nb['cells'][0]['source']\n", + " finally:\n", + " os.chdir(old_cwd)\n", + "\n", + "test_eq(fixed_source, 'j = [1,\\n 2,\\n 3\\n]\\nfor i in j:\\n str(1)')" + ] + }, + { + "cell_type": "markdown", + "id": "b1205ff6", "metadata": {}, "source": [ "## Export a notebook" @@ -388,7 +483,7 @@ "@delegates(nbglob_cli)\n", "def nbdev_export(\n", " path:str=None, # Path or filename\n", - " procs:Param(\"tokens naming the export processors to use.\", nargs=\"*\", choices=optional_procs())=[\"black_format\"],\n", + " procs:Param(\"tokens naming the export processors to use.\", nargs=\"*\", choices=optional_procs())=\"black_format\",\n", " **kwargs):\n", " \"Export notebooks in `path` to Python modules\"\n", " if os.environ.get('IN_TEST',0): return\n", @@ -398,8 +493,12 @@ " procs = [getattr(nbdev.export, p) for p in L(procs)]\n", " files = nbglob(path=path, as_path=True, **kwargs).sorted('name')\n", " for f in files: nb_export(f, procs=procs)\n", - " add_init(get_config().lib_path)\n", - " _build_modidx()" + " cfg = get_config()\n", + " add_init(cfg.lib_path)\n", + " _build_modidx()\n", + "\n", + " if cfg.ruff_formatting: ruff_format()\n", + " if cfg.ruff_fixing: ruff_fix()" ] }, { @@ -409,7 +508,7 @@ "source": [ "`procs` names the optional processors you wish to run on the exported cells of your notebook.\n", "\n", - "N.B.: the `[\"black_format\", \"ruff_format_cell\", \"ruff_fix_cell\"]` processor is passed in by default. But it is a no-op, unless `black_formatting=True`, `ruff_formatting=True`, or `ruff_fixing=True` is set in your `settings.ini` configuration. You can omit it from `nbdev_export` on the command line by passing in `--procs`." + "N.B.: the `black_format` processor is passed in by default. But it is a no-op, unless `black_formatting=True` is set in your `settings.ini` configuration. You can omit it from `nbdev_export` on the command line by passing in `--procs`." ] }, { @@ -1237,6 +1336,14 @@ "from nbdev._modidx import d\n", "assert d['syms']['nbdev.doclinks']['nbdev.doclinks.NbdevLookup'][0]" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d4e2d51e", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": {}, diff --git a/pyproject.toml b/pyproject.toml index fee87ac7d..a4eca02de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,4 +9,3 @@ dynamic = [ "keywords", "description", "version", "dependencies", "optional-depe [tool.uv] cache-keys = [{ file = "pyproject.toml" }, { file = "settings.ini" }, { file = "setup.py" }] - diff --git a/tests/export_procs.ipynb b/tests/export_procs.ipynb index c634dffbd..550468bf0 100644 --- a/tests/export_procs.ipynb +++ b/tests/export_procs.ipynb @@ -39,13 +39,9 @@ ], "metadata": { "kernelspec": { - "display_name": "env", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.13.3" } }, "nbformat": 4, From 6a6980281596d08127a4a0b71f92bdfde29ec965 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Wed, 24 Dec 2025 13:15:55 +1100 Subject: [PATCH 4/5] remove RemovedInSphinx10Warning --- nbs/api/05_doclinks.ipynb | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/nbs/api/05_doclinks.ipynb b/nbs/api/05_doclinks.ipynb index 3280699f7..d94f1c93f 100644 --- a/nbs/api/05_doclinks.ipynb +++ b/nbs/api/05_doclinks.ipynb @@ -562,27 +562,7 @@ "execution_count": null, "id": "9ed99190", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'builtins.bool': 'https://docs.python.org/3/library/functions.html#bool',\n", - " 'builtins.bytearray': 'https://docs.python.org/3/library/stdtypes.html#bytearray',\n", - " 'builtins.bytes': 'https://docs.python.org/3/library/stdtypes.html#bytes',\n", - " 'builtins.complex': 'https://docs.python.org/3/library/functions.html#complex',\n", - " 'builtins.dict': 'https://docs.python.org/3/library/stdtypes.html#dict',\n", - " 'builtins.float': 'https://docs.python.org/3/library/functions.html#float',\n", - " 'builtins.frozenset': 'https://docs.python.org/3/library/stdtypes.html#frozenset',\n", - " 'builtins.int': 'https://docs.python.org/3/library/functions.html#int',\n", - " 'builtins.list': 'https://docs.python.org/3/library/stdtypes.html#list',\n", - " 'builtins.memoryview': 'https://docs.python.org/3/library/stdtypes.html#memoryview'}" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "url = 'https://docs.python.org/3'\n", "syms = create_index(url)\n", From 696d55a09a664dedd294a91ff0d9dce81db769e6 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Thu, 1 Jan 2026 07:37:20 +1100 Subject: [PATCH 5/5] add nbdev_docs for pre-commit-hooks --- .pre-commit-hooks.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 719417315..cd43a1667 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -12,3 +12,9 @@ name: nbdev_export entry: nbdev_export description: "Export notebooks to modules and build modidx" + +- <<: *hook + id: nbdev_docs + name: nbdev_docs + entry: nbdev_docs + description: "Create Quarto docs and README.md"