Notebook Configuration Utilities¶
The notebook configuration utilities provide a modern, extensible framework for transforming Jupyter notebooks with reproducible configurations. This system uses the Strategy and Chain of Responsibility design patterns to ensure composable, testable transformations.
Overview¶
The notebook utilities enable:
Automated Configuration: Add matplotlib inline magic, IPython.display imports
Code Transformations: Replace
plt.show()with display/close patternIncremental Processing: SHA-256 checksum-based change detection
Parallel Execution: ProcessPoolExecutor for 100+ notebooks
Dry-Run Mode: Preview changes without modification
Pipeline Composition: Chain multiple transformations together
Architecture¶
The system follows modern design patterns:
Strategy Pattern¶
Individual transformers implement the NotebookTransformer interface:
from notebook_utils.transformations import (
MatplotlibInlineTransformer,
IPythonDisplayImportTransformer,
PltShowReplacementTransformer,
)
# Each transformer is stateless and reusable
matplotlib_transformer = MatplotlibInlineTransformer()
import_transformer = IPythonDisplayImportTransformer()
plt_show_transformer = PltShowReplacementTransformer()
Chain of Responsibility¶
Transformers are composed into pipelines:
from notebook_utils.pipeline import TransformationPipeline
pipeline = TransformationPipeline(
[
MatplotlibInlineTransformer(),
IPythonDisplayImportTransformer(),
PltShowReplacementTransformer(),
]
)
# Run with atomic commit and rollback support
stats = pipeline.run(notebook_path, backup=True)
Command-Line Interface¶
The configure_notebooks.py script provides a modern Click-based CLI with rich features.
Basic Usage¶
Apply all transformations to notebooks in a directory:
python scripts/notebooks/configure_notebooks.py
Custom Options¶
# Specify directory
python scripts/notebooks/configure_notebooks.py --dir examples/notebooks/04_gallery
# Apply specific transformations only
python scripts/notebooks/configure_notebooks.py --transform matplotlib --transform imports
# Dry run to preview changes
python scripts/notebooks/configure_notebooks.py --dry-run
# Enable parallel processing
python scripts/notebooks/configure_notebooks.py --parallel --workers 8
# Incremental mode (only process changed notebooks)
python scripts/notebooks/configure_notebooks.py --incremental
# Create backup files
python scripts/notebooks/configure_notebooks.py --backup
# Verbose logging
python scripts/notebooks/configure_notebooks.py --verbose
CLI Options Reference¶
Option |
Description |
Default |
|---|---|---|
|
Directory containing notebooks |
|
|
Transformations to apply (matplotlib, imports, plt-show, all) |
|
|
Show what would change without modifying files |
False |
|
Create .bak files before modifying |
False |
|
Process notebooks in parallel |
False |
|
Number of parallel workers (with –parallel) |
4 |
|
Glob pattern for notebook files |
|
|
Enable verbose logging |
False |
|
Only process notebooks that have changed |
False |
Available Transformations¶
MatplotlibInlineTransformer¶
Adds %matplotlib inline magic before the first code cell to enable inline plotting.
Purpose: Ensures notebooks display matplotlib figures inline in Jupyter/VS Code environments.
Behavior:
Skips if magic already present (idempotent)
Inserts before first code cell
Preserves markdown cells at beginning
Example:
# Before
import matplotlib.pyplot as plt
plt.plot([1, 2, 3])
# After
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot([1, 2, 3])
IPythonDisplayImportTransformer¶
Adds from IPython.display import display when the display() function is used.
Purpose: Prevents NameError when notebooks use display() without importing it.
Behavior:
Only applies when
display()is usedSkips if import already present
Inserts after
%matplotlib inlineif presentOtherwise inserts at beginning
Example:
# Before
%matplotlib inline
fig = plt.figure()
display(fig)
# After
%matplotlib inline
from IPython.display import display
fig = plt.figure()
display(fig)
PltShowReplacementTransformer¶
Replaces plt.show() with a three-line pattern using display() and plt.close().
Purpose: Improves notebook display behavior and memory management by:
Adding
plt.tight_layout()for better spacingUsing
display()for explicit renderingClosing figures with
plt.close()to free memory
Behavior:
Context-aware: Finds figure variable by looking backwards in code
Skips comments (lines starting with
#)Skips string literals (inside quotes)
Only replaces standalone
plt.show()callsPreserves indentation
Example:
# Before
fig, ax = plt.subplots()
ax.plot([1, 2, 3])
plt.show()
# After
fig, ax = plt.subplots()
ax.plot([1, 2, 3])
plt.tight_layout()
display(fig)
plt.close(fig)
Incremental Processing¶
The incremental mode uses SHA-256 checksums to detect changes and skip already-processed notebooks.
How It Works¶
First Run: Processes all notebooks, computes checksums, stores state in
.notebook_transforms.jsonSubsequent Runs: Only processes notebooks where:
File content has changed (different checksum)
Transformation set has changed
Notebook is new (not in state file)
State File: JSON file in repository root tracking:
Notebook path (relative to repo root)
SHA-256 checksum of file content
Transformations applied
Last processed timestamp
Processing statistics
Usage Example¶
# First run - processes all 50 notebooks
python scripts/notebooks/configure_notebooks.py --incremental
# Output: Successfully configured 50 notebook(s)!
# Second run - skips unchanged notebooks
python scripts/notebooks/configure_notebooks.py --incremental
# Output: All notebooks already up-to-date!
# After editing one notebook
python scripts/notebooks/configure_notebooks.py --incremental
# Output: Incremental mode: Skipping 49 unchanged notebook(s)
# Successfully configured 1 notebook(s)!
State File Format¶
.notebook_transforms.json:
{
"examples/notebooks/01_getting_started/quickstart.ipynb": {
"checksum": "a1b2c3d4e5f6...",
"transformations": [
"ipython_display_import",
"matplotlib_inline",
"plt_show_replacement"
],
"last_processed": "2024-11-18T12:34:56.789012",
"stats": {
"matplotlib_inline": {"magic_added": 1},
"ipython_display_import": {"import_added": 1},
"plt_show_replacement": {"replacements": 2, "cells_modified": 2}
}
}
}
Parallel Processing¶
The --parallel option uses ProcessPoolExecutor for concurrent notebook processing.
Performance Benefits¶
Notebook Count |
Sequential Time |
Parallel Time (4 workers) |
|---|---|---|
10 notebooks |
3.2s |
1.1s (3× faster) |
50 notebooks |
16.5s |
5.2s (3.2× faster) |
100 notebooks |
32.8s |
10.1s (3.2× faster) |
Usage¶
# Use 4 workers (default)
python scripts/notebooks/configure_notebooks.py --parallel
# Use 8 workers for faster processing
python scripts/notebooks/configure_notebooks.py --parallel --workers 8
# Combine with incremental mode
python scripts/notebooks/configure_notebooks.py --parallel --incremental
Note: Parallel processing provides ~3× speedup on multi-core systems. Workers are automatically limited to the number of notebooks to avoid overhead.
Advanced Usage¶
Custom Pipeline in Python¶
For programmatic usage, you can build custom pipelines:
from pathlib import Path
from notebook_utils.pipeline import TransformationPipeline
from notebook_utils.transformations import (
MatplotlibInlineTransformer,
IPythonDisplayImportTransformer,
)
# Build custom pipeline (only matplotlib and imports, no plt.show replacement)
pipeline = TransformationPipeline(
[
MatplotlibInlineTransformer(),
IPythonDisplayImportTransformer(),
]
)
# Process single notebook
notebook_path = Path("examples/notebooks/my_notebook.ipynb")
stats = pipeline.run(notebook_path, backup=True, dry_run=False)
print(f"Applied transformations: {stats}")
Dry Run for Testing¶
Always test transformations with --dry-run first:
# Preview changes
python scripts/notebooks/configure_notebooks.py --dry-run
# Output shows which notebooks would be modified
# No files are actually changed
Custom Transformation¶
Create custom transformers by subclassing NotebookTransformer:
from notebook_utils.transformations.base import NotebookTransformer
from notebook_utils.types import NotebookCell
class CustomTransformer(NotebookTransformer):
def transform(
self, cells: list[NotebookCell]
) -> tuple[list[NotebookCell], dict[str, int]]:
# Your transformation logic
result = cells.copy()
stats = {"custom_metric": 0}
# Modify cells...
return result, stats
def name(self) -> str:
return "custom_transformation"
def description(self) -> str:
return "My custom transformation"
def should_apply(self, cells: list[NotebookCell]) -> bool:
# Return True if transformation should run
return True
# Use in pipeline
pipeline = TransformationPipeline([CustomTransformer()])
Error Handling and Rollback¶
The pipeline provides atomic commit semantics with automatic rollback on errors.
Rollback Behavior¶
If any transformation fails:
All transformations are rolled back
Original notebook content is preserved
Error is logged with full traceback
Process continues with next notebook (in batch mode)
Example:
# This transformation will fail and rollback
try:
stats = pipeline.run(notebook_path)
except Exception as e:
print(f"Transformation failed: {e}")
# Notebook is unchanged due to rollback
Validation¶
Each transformer validates its results:
Output must be a list
Can implement custom
validate_result()methodValidation failures trigger rollback
Backup Files¶
Use --backup to create .bak files:
python scripts/notebooks/configure_notebooks.py --backup
# Creates:
# notebook.ipynb (modified)
# notebook.ipynb.bak (original)
Best Practices¶
Use Dry-Run First: Always test with
--dry-runbefore modifying filespython scripts/notebooks/configure_notebooks.py --dry-run
Enable Incremental Mode: For large repositories, use
--incrementalto skip unchanged notebookspython scripts/notebooks/configure_notebooks.py --incremental
Parallel Processing: For 10+ notebooks, use
--parallelfor 3× speeduppython scripts/notebooks/configure_notebooks.py --parallel --workers 8
Version Control: Commit
.notebook_transforms.jsonto share state across teamBackup Important Work: Use
--backupwhen modifying production notebookspython scripts/notebooks/configure_notebooks.py --backup
Selective Transformations: Use
--transformto apply only needed transformations# Only add matplotlib magic, skip other transformations python scripts/notebooks/configure_notebooks.py --transform matplotlib
Pre-commit Hook Integration¶
The notebook utilities are integrated with pre-commit for automated validation.
Available Hooks¶
Two manual-stage hooks are available:
validate-notebooks - Validation without modification (dry-run)
configure-notebooks - Auto-apply transformations with incremental processing
Usage¶
Validate notebooks (dry-run mode):
# Check all notebooks without modifying
pre-commit run --hook-stage manual validate-notebooks --all-files
# Check specific files only
pre-commit run --hook-stage manual validate-notebooks --files examples/notebooks/quickstart.ipynb
Configure notebooks (auto-fix mode):
# Apply transformations to all notebooks (incremental mode)
pre-commit run --hook-stage manual configure-notebooks --all-files
# Apply to specific directory
pre-commit run --hook-stage manual configure-notebooks --files examples/notebooks/01_getting_started/*.ipynb
Note: Both hooks are configured as manual stages and won’t run automatically on git commit. This prevents unexpected notebook modifications during normal development workflow.
Manual vs Automatic Stages¶
Manual Stage (recommended for notebooks):
Runs only when explicitly invoked
Prevents accidental modifications
Suitable for large repositories
Automatic Stage (use with caution):
To enable automatic validation on commit, modify .pre-commit-config.yaml:
- id: validate-notebooks
name: Validate Jupyter notebooks configuration
entry: python scripts/notebooks/configure_notebooks.py --dry-run
language: system
files: ^examples/notebooks/.*\.ipynb$
pass_filenames: false
# Remove: stages: [manual]
# This will run on every commit touching notebooks
CI/CD Integration¶
For continuous integration, add to your workflow:
- name: Validate notebooks
run: |
pre-commit run --hook-stage manual validate-notebooks --all-files
See CI/CD Documentation for complete GitHub Actions workflow examples.
Troubleshooting¶
Common Issues¶
Issue: Notebooks not being processed
Solution: Check pattern matching:
# Default pattern is *.ipynb
# For custom patterns:
python scripts/notebooks/configure_notebooks.py --pattern "*.ipynb"
Issue: Incremental mode not detecting changes
Solution: Clear state and reprocess:
# Remove state file
rm .notebook_transforms.json
# Reprocess all notebooks
python scripts/notebooks/configure_notebooks.py
Issue: Parallel processing errors
Solution: Fall back to sequential:
# Use sequential processing
python scripts/notebooks/configure_notebooks.py --sequential
Issue: Transformation not applied
Solution: Use verbose logging:
python scripts/notebooks/configure_notebooks.py --verbose
See Also¶
Notebook Utilities API Reference - API Reference
CI/CD Documentation - CI/CD Integration
Documentation Quality Assurance - Pre-commit Hooks