Scanner

Scan nbdev notebooks for exported functions and classes

get_export_cells


def get_export_cells(
    nb_path:Path, # Path to the notebook file
)->List: # List of cells with export directives

Extract all code cells from a notebook that have export directives


extract_definitions


def extract_definitions(
    source:str, # Python source code
)->List: # List of function/class definitions with metadata

Extract function and class definitions from source code


scan_notebook


def scan_notebook(
    nb_path:Path, # Path to the notebook to scan
    nbs_root:Optional=None, # Root notebooks directory (for relative paths)
)->List: # List of exported definitions with metadata

Scan a notebook and extract all exported function/class definitions


scan_project


def scan_project(
    nbs_path:Optional=None, # Path to notebooks directory (defaults to config.nbs_path)
    pattern:str='*.ipynb', # Pattern for notebook files to scan
)->List: # All exported definitions found in the project

Scan all notebooks in a project for exported definitions

# Test scanning this project
definitions = scan_project()
print(f"Found {len(definitions)} exported definitions")
for defn in definitions[:5]:  # Show first 5
    print(f"- {defn['type']}: {defn['name']} in {defn['notebook']}")
Found 59 exported definitions
- FunctionDef: create_parser in cli.ipynb
- FunctionDef: handle_autofix in cli.ipynb
- FunctionDef: generate_report in cli.ipynb
- FunctionDef: output_report in cli.ipynb
- FunctionDef: main in cli.ipynb
# Test if nested folders would be detected
import tempfile
import shutil
from pathlib import Path

# Create a temporary directory structure to test nested scanning
with tempfile.TemporaryDirectory() as tmpdir:
    tmp_path = Path(tmpdir)
    
    # Create nested structure
    (tmp_path / "actions").mkdir()
    (tmp_path / "data_display").mkdir()
    
    # Create dummy notebooks
    dummy_nb = {
        "cells": [
            {"cell_type": "code", "source": "#| export\ndef test_func(): pass", "id": "cell-1"}
        ],
        "metadata": {},
        "nbformat": 4,
        "nbformat_minor": 5
    }
    
    import json
    
    # Create notebooks in root
    with open(tmp_path / "index.ipynb", "w") as f:
        json.dump(dummy_nb, f)
    
    # Create notebooks in subdirectories
    with open(tmp_path / "actions" / "button.ipynb", "w") as f:
        json.dump(dummy_nb, f)
    
    with open(tmp_path / "data_display" / "table.ipynb", "w") as f:
        json.dump(dummy_nb, f)
    
    # Test scanning
    print("Testing nested folder scanning:")
    notebooks = list(tmp_path.rglob("*.ipynb"))
    print(f"Created {len(notebooks)} test notebooks:")
    for nb in sorted(notebooks):
        print(f"  - {nb.relative_to(tmp_path)}")
    
    # Test scan_project with nested folders
    definitions = scan_project(nbs_path=tmp_path)
    print(f"\nscan_project found {len(definitions)} definitions")
    for defn in definitions:
        print(f"  - {defn['name']} in {defn['notebook']}")
Testing nested folder scanning:
Created 3 test notebooks:
  - actions/button.ipynb
  - data_display/table.ipynb
  - index.ipynb

scan_project found 3 definitions
  - test_func in index.ipynb
  - test_func in actions/button.ipynb
  - test_func in data_display/table.ipynb
# Debug: Check what notebooks we're finding (including nested folders)
from nbdev.config import get_config
cfg = get_config()
nbs_path = Path(cfg.config_path) / cfg.nbs_path
notebooks = list(nbs_path.rglob("*.ipynb"))
print(f"Looking in: {nbs_path}")
print(f"Found {len(notebooks)} notebooks (including nested):")
for nb in sorted(notebooks):
    if not nb.name.startswith('_') and '.ipynb_checkpoints' not in str(nb):
        relative_path = nb.relative_to(nbs_path)
        print(f"  - {relative_path}")
        cells = get_export_cells(nb)
        if cells:
            print(f"    Export cells: {len(cells)}")
Looking in: /home/runner/work/cjm-nbdev-docments/cjm-nbdev-docments/nbs
Found 6 notebooks (including nested):
  - autofix.ipynb
    Export cells: 29
  - cli.ipynb
    Export cells: 6
  - core.ipynb
    Export cells: 15
  - index.ipynb
  - report.ipynb
    Export cells: 8
  - scanner.ipynb
    Export cells: 5