Files
TinyTorch/tito/commands/notebooks.py
T
Vijay Janapa Reddi a92a5530ef MAJOR: Separate CLI from framework - proper architectural separation
BREAKING CHANGE: CLI moved from tinytorch/cli/ to tito/

Perfect Senior Engineer Architecture:
- tinytorch/ = Pure ML framework (production)
- tito/ = Development/management CLI tool
- modules/ = Educational content

Benefits:
 Clean separation of concerns
 Framework stays lightweight (no CLI dependencies)
 Clear mental model for users
 Professional project organization
 Proper dependency management

Structure:
tinytorch/          # 🧠 Core ML Framework
├── core/          # Tensors, layers, operations
├── training/      # Training loops, optimizers
├── models/        # Model architectures
└── ...           # Pure ML functionality

tito/              # 🔧 Development CLI Tool
├── main.py        # CLI entry point
├── core/          # CLI configuration & console
├── commands/      # Command implementations
└── tools/         # CLI utilities

Key Changes:
- Moved all CLI code from tinytorch/cli/ to tito/
- Updated imports and entry points
- Separated dependencies (Rich only for dev tools)
- Updated documentation to reflect proper separation
- Maintained backward compatibility with bin/tito wrapper

This demonstrates how senior engineers separate:
- Production code (framework) from development tools (CLI)
- Core functionality from management utilities
- User-facing APIs from internal tooling

Educational Value:
- Shows proper software architecture
- Teaches separation of concerns
- Demonstrates dependency management
- Models real-world project organization
2025-07-10 22:08:56 -04:00

160 lines
6.1 KiB
Python

"""
Notebooks command for building Jupyter notebooks from Python files.
"""
import subprocess
import sys
from argparse import ArgumentParser, Namespace
from pathlib import Path
from typing import List, Tuple
from rich.panel import Panel
from rich.text import Text
from .base import BaseCommand
from ..core.exceptions import ExecutionError, ModuleNotFoundError
class NotebooksCommand(BaseCommand):
"""Command to build Jupyter notebooks from Python files."""
@property
def name(self) -> str:
return "notebooks"
@property
def description(self) -> str:
return "Build notebooks from Python files"
def add_arguments(self, parser: ArgumentParser) -> None:
"""Add notebooks command arguments."""
parser.add_argument(
'--module',
help='Build notebook for specific module'
)
parser.add_argument(
'--force',
action='store_true',
help='Force rebuild even if notebook exists'
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be built without actually building'
)
def validate_args(self, args: Namespace) -> None:
"""Validate notebooks command arguments."""
if args.module:
module_file = self.config.modules_dir / args.module / f"{args.module}_dev.py"
if not module_file.exists():
raise ModuleNotFoundError(
f"Module '{args.module}' not found or no {args.module}_dev.py file"
)
def _find_dev_files(self) -> List[Path]:
"""Find all *_dev.py files in modules directory."""
dev_files = []
for module_dir in self.config.modules_dir.iterdir():
if module_dir.is_dir():
dev_py = module_dir / f"{module_dir.name}_dev.py"
if dev_py.exists():
dev_files.append(dev_py)
return dev_files
def _convert_file(self, dev_file: Path) -> Tuple[bool, str]:
"""Convert a single Python file to notebook."""
try:
py_to_notebook_tool = self.config.bin_dir / "py_to_notebook.py"
result = subprocess.run([
sys.executable, str(py_to_notebook_tool), str(dev_file)
], capture_output=True, text=True, timeout=30)
if result.returncode == 0:
# Extract success message from the tool output
output_lines = result.stdout.strip().split('\n')
success_msg = output_lines[-1] if output_lines else f"{dev_file.name}{dev_file.with_suffix('.ipynb').name}"
# Clean up the message
clean_msg = success_msg.replace('✅ ', '').replace('Converted ', '')
return True, clean_msg
else:
error_msg = result.stderr.strip() if result.stderr.strip() else "Conversion failed"
return False, error_msg
except subprocess.TimeoutExpired:
return False, "Conversion timed out"
except Exception as e:
return False, f"Error: {str(e)}"
def run(self, args: Namespace) -> int:
"""Execute the notebooks command."""
self.console.print(Panel(
"📓 Building Notebooks from Python Files",
title="Notebook Generation",
border_style="bright_cyan"
))
# Find files to convert
if args.module:
dev_files = [self.config.modules_dir / args.module / f"{args.module}_dev.py"]
self.console.print(f"🔄 Building notebook for module: {args.module}")
else:
dev_files = self._find_dev_files()
if not dev_files:
self.console.print(Panel(
"[yellow]⚠️ No *_dev.py files found in modules/[/yellow]",
title="Nothing to Convert",
border_style="yellow"
))
return 0
self.console.print(f"🔄 Building notebooks for {len(dev_files)} modules...")
# Dry run mode
if args.dry_run:
self.console.print("\n[cyan]Dry run mode - would convert:[/cyan]")
for dev_file in dev_files:
module_name = dev_file.parent.name
self.console.print(f" • {module_name}: {dev_file.name}")
return 0
# Convert files
success_count = 0
error_count = 0
for dev_file in dev_files:
success, message = self._convert_file(dev_file)
module_name = dev_file.parent.name
if success:
success_count += 1
self.console.print(f" ✅ {module_name}: {message}")
else:
error_count += 1
self.console.print(f" ❌ {module_name}: {message}")
# Summary
self._print_summary(success_count, error_count)
return 0 if error_count == 0 else 1
def _print_summary(self, success_count: int, error_count: int) -> None:
"""Print command execution summary."""
summary_text = Text()
if success_count > 0:
summary_text.append(f"✅ Successfully built {success_count} notebook(s)\n", style="bold green")
if error_count > 0:
summary_text.append(f"❌ Failed to build {error_count} notebook(s)\n", style="bold red")
if success_count > 0:
summary_text.append("\n💡 Next steps:\n", style="bold yellow")
summary_text.append(" • Open notebooks with: jupyter lab\n", style="white")
summary_text.append(" • Work interactively in the notebooks\n", style="white")
summary_text.append(" • Export code with: tito sync\n", style="white")
summary_text.append(" • Run tests with: tito test\n", style="white")
border_style = "green" if error_count == 0 else "yellow"
self.console.print(Panel(
summary_text,
title="Notebook Generation Complete",
border_style=border_style
))