diff --git a/nbdev/config.py b/nbdev/config.py index dcce9eb9a..09f5e939d 100644 --- a/nbdev/config.py +++ b/nbdev/config.py @@ -171,7 +171,7 @@ def _find_nbdev_pyproject(path=None): # %% ../nbs/api/01_config.ipynb #3dac70e0 nbdev_defaults = dict(nbs_path='nbs', doc_path='_docs', tst_flags='notest', recursive=True, readme_nb='index.ipynb', clean_ids=True, clear_all=False, put_version_in_init=True, jupyter_hooks=False, custom_sidebar=False, branch='main', - doc_procs=[], export_procs=[], exec_profile=True, cell_timeout=600) + doc_procs=[], export_procs=[], exec_profile=True, exec_all=False, cell_timeout=600) _path_keys = 'lib_path', 'nbs_path', 'doc_path' diff --git a/nbdev/processors.py b/nbdev/processors.py index 222e8564f..1892d6bb8 100644 --- a/nbdev/processors.py +++ b/nbdev/processors.py @@ -228,11 +228,15 @@ def _ast_contains(trees, types): def _tl_contains(trees, types): return any(isinstance(t, types) for t in trees) -def _do_eval(cell): +def _do_eval(cell, all_cells=False, dflt=True, flags=frozenset()): if cell_lang(cell) != 'python': return if not cell.source or 'nbdev_export'+'()' in cell.source: return trees = cell.parsed_() if cell.cell_type != 'code' or not trees: return + if all_cells: + v = cell.directives_.get('eval') + run = dflt if v is None else v.lower() != 'false' + return run and not (cell.directives_.keys() & flags) and 'skip-execution' not in cell.metadata.get('tags', []) if cell.directives_.get('eval', '').lower() == 'false': return _show_dirs = {'export','exports','exporti','exec_doc'} @@ -245,17 +249,22 @@ def _do_eval(cell): # %% ../nbs/api/10_processors.ipynb #867cf721 class exec_show_docs(Processor): - "Execute cells needed for `show_docs` output, including exported cells and imports" + "Execute cells needed for `show_docs` output, including exported cells and imports, or every cell under `exec_all`" def begin(self): if nb_lang(self.nb) != 'python': return - self.k = CaptureShell(profile=bool(get_config().exec_profile)) + cfg,fm = get_config(),getattr(self.nb, 'frontmatter_', {}) + self.all_cells = str2bool(fm.get('exec_all', cfg.exec_all)) + self.dflt = fm_default_eval(fm) + flags = cfg.tst_flags + self.flags = set(flags.split() if isinstance(flags, str) else flags) + self.k = CaptureShell(profile=bool(cfg.exec_profile)) self.k.run_cell('from nbdev.showdoc import show_doc') def __call__(self, cell): - if not self.nb.has_docs_ or not hasattr(self, 'k'): return + if not hasattr(self, 'k') or not (self.nb.has_docs_ or self.all_cells): return fm = getattr(self.nb, 'frontmatter_', {}) if str2bool(fm.get('skip_showdoc', False)): return - if _do_eval(cell): self.k.cell(cell) + if _do_eval(cell, self.all_cells, self.dflt, self.flags): self.k.cell(cell) title = fm.get('title', '') if (e := self.k.exc): raise Exception(f"Error{' in notebook: '+title if title else ''} in cell {cell.idx_} :\n{cell.source}\n{type(e).__name__}: {e}") from e diff --git a/nbs/api/01_config.ipynb b/nbs/api/01_config.ipynb index 4b4e3ed62..b103e0385 100644 --- a/nbs/api/01_config.ipynb +++ b/nbs/api/01_config.ipynb @@ -356,7 +356,7 @@ "#| export\n", "nbdev_defaults = dict(nbs_path='nbs', doc_path='_docs', tst_flags='notest', recursive=True, readme_nb='index.ipynb',\n", " clean_ids=True, clear_all=False, put_version_in_init=True, jupyter_hooks=False, custom_sidebar=False, branch='main',\n", - " doc_procs=[], export_procs=[], exec_profile=True, cell_timeout=600)\n", + " doc_procs=[], export_procs=[], exec_profile=True, exec_all=False, cell_timeout=600)\n", "\n", "_path_keys = 'lib_path', 'nbs_path', 'doc_path'" ] @@ -1287,10 +1287,13 @@ "solveit": { "default_code": true, "mode": "learning", + "reasoning_effort": "h", + "reasoning_model": "anthropic/claude-opus-5-5", + "standard_effort": "m", + "standard_model": "openai/gpt-6-luna", "use_fence": false, "use_thinking": false, - "use_tools": true, - "ver": 2 + "use_tools": true } }, "nbformat": 4, diff --git a/nbs/api/10_processors.ipynb b/nbs/api/10_processors.ipynb index d79d3dd38..26ad9a9f9 100644 --- a/nbs/api/10_processors.ipynb +++ b/nbs/api/10_processors.ipynb @@ -704,11 +704,15 @@ "\n", "def _tl_contains(trees, types): return any(isinstance(t, types) for t in trees)\n", "\n", - "def _do_eval(cell):\n", + "def _do_eval(cell, all_cells=False, dflt=True, flags=frozenset()):\n", " if cell_lang(cell) != 'python': return\n", " if not cell.source or 'nbdev_export'+'()' in cell.source: return\n", " trees = cell.parsed_()\n", " if cell.cell_type != 'code' or not trees: return\n", + " if all_cells:\n", + " v = cell.directives_.get('eval')\n", + " run = dflt if v is None else v.lower() != 'false'\n", + " return run and not (cell.directives_.keys() & flags) and 'skip-execution' not in cell.metadata.get('tags', [])\n", " if cell.directives_.get('eval', '').lower() == 'false': return\n", "\n", " _show_dirs = {'export','exports','exporti','exec_doc'}\n", @@ -753,17 +757,22 @@ "source": [ "#| export\n", "class exec_show_docs(Processor):\n", - " \"Execute cells needed for `show_docs` output, including exported cells and imports\"\n", + " \"Execute cells needed for `show_docs` output, including exported cells and imports, or every cell under `exec_all`\"\n", " def begin(self):\n", " if nb_lang(self.nb) != 'python': return\n", - " self.k = CaptureShell(profile=bool(get_config().exec_profile))\n", + " cfg,fm = get_config(),getattr(self.nb, 'frontmatter_', {})\n", + " self.all_cells = str2bool(fm.get('exec_all', cfg.exec_all))\n", + " self.dflt = fm_default_eval(fm)\n", + " flags = cfg.tst_flags\n", + " self.flags = set(flags.split() if isinstance(flags, str) else flags)\n", + " self.k = CaptureShell(profile=bool(cfg.exec_profile))\n", " self.k.run_cell('from nbdev.showdoc import show_doc')\n", "\n", " def __call__(self, cell):\n", - " if not self.nb.has_docs_ or not hasattr(self, 'k'): return\n", + " if not hasattr(self, 'k') or not (self.nb.has_docs_ or self.all_cells): return\n", " fm = getattr(self.nb, 'frontmatter_', {})\n", " if str2bool(fm.get('skip_showdoc', False)): return\n", - " if _do_eval(cell): self.k.cell(cell)\n", + " if _do_eval(cell, self.all_cells, self.dflt, self.flags): self.k.cell(cell)\n", " title = fm.get('title', '')\n", " if (e := self.k.exc):\n", " raise Exception(f\"Error{' in notebook: '+title if title else ''} in cell {cell.idx_} :\\n{cell.source}\\n{type(e).__name__}: {e}\") from e\n", @@ -784,7 +793,7 @@ "id": "ded579a9", "metadata": {}, "source": [ - "`exec_show_docs` runs the cells the docs need: exports, imports, `show_doc` calls, and anything marked `exec_doc`. A notebook whose frontmatter sets `skip_showdoc: true` is not executed at all during docs rendering, so its stored outputs are used as-is." + "`exec_show_docs` runs the cells the docs need: exports, imports, `show_doc` calls, and anything marked `exec_doc`. With `exec_all` set it runs every cell instead. A notebook whose frontmatter sets `skip_showdoc: true` is not executed at all during docs rendering, whatever else is set, and its stored outputs are used as-is." ] }, { @@ -798,6 +807,29 @@ "assert res" ] }, + { + "cell_type": "markdown", + "id": "e42c2a07", + "metadata": {}, + "source": [ + "`exec_all: true` in a notebook's frontmatter, or `exec_all = true` under `[tool.nbdev]` for a whole project, runs every code cell the test runner would run, so a notebook with no exports or `show_doc` calls is executed too. Cells that `nbdev-test` skips, through `eval: false` or a `tst_flags` flag such as `notest`, are skipped here as well:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d58ac3b", + "metadata": {}, + "outputs": [], + "source": [ + "cells = [mk_cell('---\\nexec_all: true\\n---', 'raw'), mk_cell('x = 1'), mk_cell('x + 1'),\n", + " mk_cell('#| notest\\nraise Exception(\"skipped by tst_flags\")'), mk_cell('#| eval: false\\nraise Exception(\"skipped by eval\")')]\n", + "nb = dict2nb(dict(cells=cells, metadata={}, nbformat=4, nbformat_minor=5))\n", + "nb.path_ = 'exec_all.ipynb'\n", + "NBProcessor(nb=nb, procs=[FrontmatterProc, add_show_docs, exec_show_docs]).process()\n", + "test_eq(nb.cells[2].outputs[0].data['text/plain'], ['2'])" + ] + }, { "cell_type": "markdown", "id": "b1a63b85", @@ -878,7 +910,16 @@ "source": [] } ], - "metadata": {}, + "metadata": { + "solveit": { + "mode": "learning", + "reasoning_effort": "h", + "reasoning_model": "anthropic/claude-opus-5-5", + "standard_effort": "m", + "standard_model": "openai/gpt-6-luna", + "use_thinking": false + } + }, "nbformat": 4, "nbformat_minor": 5 } diff --git a/nbs/explanations/directives.ipynb b/nbs/explanations/directives.ipynb index 6493cc005..bc1b60baa 100644 --- a/nbs/explanations/directives.ipynb +++ b/nbs/explanations/directives.ipynb @@ -800,6 +800,7 @@ "\n", "- `skip_exec: true` makes `nbdev-test` skip the notebook and report it as passing. Use it when the notebook needs credentials or live services unavailable in the test environment.\n", "- `skip_showdoc: true` makes docs rendering use stored outputs without executing cells.\n", + "- `exec_all: true` makes docs rendering execute every cell, not only exports, imports and `show_doc` calls. `exec_all = true` under `[tool.nbdev]` does the same for every notebook in the project.\n", "\n", "For example, nbdev's own `api/19_diff.ipynb` sets `skip_exec: true`, and `tutorials/modular_nbdev.ipynb` sets `skip_showdoc: true`." ] @@ -829,7 +830,16 @@ "source": [] } ], - "metadata": {}, + "metadata": { + "solveit": { + "mode": "learning", + "reasoning_effort": "h", + "reasoning_model": "anthropic/claude-opus-5-5", + "standard_effort": "m", + "standard_model": "openai/gpt-6-luna", + "use_thinking": false + } + }, "nbformat": 4, "nbformat_minor": 5 } diff --git a/nbs/explanations/docs.ipynb b/nbs/explanations/docs.ipynb index 907622df9..8ede17319 100644 --- a/nbs/explanations/docs.ipynb +++ b/nbs/explanations/docs.ipynb @@ -400,6 +400,8 @@ "---\n", "```\n", "\n", + "To do that for every notebook in a project, set `exec_all = true` under `[tool.nbdev]` in `pyproject.toml`. Cells that `nbdev-test` skips, through `eval: false` or a `tst_flags` flag, are skipped here as well.\n", + "\n", "#### Skipping Execution\n", "\n", "Likewise, you can instruct nbdev to not execute any cells when rendering your docs with the following front matter:\n", @@ -593,7 +595,16 @@ "source": [] } ], - "metadata": {}, + "metadata": { + "solveit": { + "mode": "learning", + "reasoning_effort": "h", + "reasoning_model": "anthropic/claude-opus-5-5", + "standard_effort": "m", + "standard_model": "openai/gpt-6-luna", + "use_thinking": false + } + }, "nbformat": 4, "nbformat_minor": 5 }