Files
TinyTorch/tito/commands/view.py
Vijay Janapa Reddi 25f71041d4 Update Module Developer agent and add Module 02 restructure
- Enhanced Module Developer agent with balance philosophy
  - Preserve educational content while adding structure
  - Keep Build→Use→Understand flow
  - Maintain verbose but valuable explanations

- Created restructured Module 02 (Tensor)
  - Added 5 C's framework as enhancement not replacement
  - Preserved ALL educational content
  - Separated implementation from testing
  - Added comparison report showing 100% content preservation

- Added TITO CLI Developer agent for CLI enhancements
- Added CLAUDE.md with git best practices
- Added tito module view command (in progress)
- Generated setup_dev notebook
2025-09-15 19:03:09 -04:00

207 lines
8.4 KiB
Python

"""
View command for TinyTorch CLI: generates notebooks and opens Jupyter Lab.
"""
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 ViewCommand(BaseCommand):
"""Command to generate notebooks and open Jupyter Lab."""
@property
def name(self) -> str:
return "view"
@property
def description(self) -> str:
return "Generate notebooks and open Jupyter Lab"
def add_arguments(self, parser: ArgumentParser) -> None:
"""Add view command arguments."""
parser.add_argument(
'module',
nargs='?',
help='View specific module (optional)'
)
parser.add_argument(
'--force',
action='store_true',
help='Force rebuild even if notebook exists'
)
def validate_args(self, args: Namespace) -> None:
"""Validate view command arguments."""
if args.module:
module_dir = self.config.modules_dir / args.module
if not module_dir.exists():
raise ModuleNotFoundError(f"Module directory '{args.module}' not found")
# Look for any *_dev.py file in the module directory
dev_files = list(module_dir.glob("*_dev.py"))
if not dev_files:
raise ModuleNotFoundError(
f"No *_dev.py files found in module '{args.module}'"
)
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():
# Look for any *_dev.py file in the directory
for dev_py in module_dir.glob("*_dev.py"):
dev_files.append(dev_py)
return dev_files
def _convert_file(self, dev_file: Path, force: bool = False) -> Tuple[bool, str]:
"""Convert a single Python file to notebook using Jupytext."""
try:
notebook_file = dev_file.with_suffix('.ipynb')
# Check if notebook exists and we're not forcing
if notebook_file.exists() and not force:
return True, f"{dev_file.name}{notebook_file.name} (already exists)"
# Use Jupytext to convert Python file to notebook
result = subprocess.run([
"jupytext", "--to", "notebook", str(dev_file)
], capture_output=True, text=True, timeout=30, cwd=dev_file.parent)
if result.returncode == 0:
return True, f"{dev_file.name}{notebook_file.name}"
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 FileNotFoundError:
return False, "Jupytext not found. Install with: pip install jupytext"
except Exception as e:
return False, f"Error: {str(e)}"
def _launch_jupyter_lab(self, target_dir: Path) -> bool:
"""Launch Jupyter Lab in the specified directory."""
try:
# Change to target directory and launch Jupyter Lab
subprocess.Popen([
"jupyter", "lab", "--no-browser"
], cwd=target_dir)
return True
except FileNotFoundError:
self.console.print(Panel(
"[red]❌ Jupyter Lab not found. Install with: pip install jupyterlab[/red]",
title="Error",
border_style="red"
))
return False
except Exception as e:
self.console.print(Panel(
f"[red]❌ Failed to launch Jupyter Lab: {e}[/red]",
title="Error",
border_style="red"
))
return False
def run(self, args: Namespace) -> int:
"""Execute the view command."""
self.console.print(Panel(
"📓 View: Generate Notebooks and Open Jupyter Lab",
title="Interactive Development",
border_style="bright_cyan"
))
# Determine target directory for Jupyter Lab
if args.module:
target_dir = self.config.modules_dir / args.module
# Find the actual dev file(s) in this module directory
dev_files = list(target_dir.glob("*_dev.py"))
self.console.print(f"🔄 Generating notebook for module: {args.module}")
else:
target_dir = self.config.modules_dir
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"
))
# Still launch Jupyter Lab even if no notebooks to generate
self.console.print("🚀 Opening Jupyter Lab anyway...")
if self._launch_jupyter_lab(target_dir):
self._print_launch_info(target_dir)
return 0
self.console.print(f"🔄 Generating notebooks for {len(dev_files)} modules...")
# Generate notebooks
success_count = 0
error_count = 0
for dev_file in dev_files:
success, message = self._convert_file(dev_file, args.force)
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}")
# Launch Jupyter Lab
self.console.print("\n🚀 Opening Jupyter Lab...")
if not self._launch_jupyter_lab(target_dir):
return 1
# Print summary and instructions
self._print_summary(success_count, error_count, target_dir)
return 0 if error_count == 0 else 1
def _print_launch_info(self, target_dir: Path) -> None:
"""Print Jupyter Lab launch information."""
info_text = Text()
info_text.append("🌟 Jupyter Lab launched successfully!\n\n", style="bold green")
info_text.append("📍 Working directory: ", style="white")
info_text.append(f"{target_dir}\n", style="cyan")
info_text.append("🌐 Open your browser and navigate to the URL shown in the terminal\n", style="white")
info_text.append("📁 Your notebooks will be available in the file browser\n", style="white")
info_text.append("🔄 Press Ctrl+C in the terminal to stop Jupyter Lab", style="white")
self.console.print(Panel(
info_text,
title="Jupyter Lab Ready",
border_style="green"
))
def _print_summary(self, success_count: int, error_count: int, target_dir: Path) -> None:
"""Print command execution summary."""
summary_text = Text()
if success_count > 0:
summary_text.append(f"✅ Successfully generated {success_count} notebook(s)\n", style="bold green")
if error_count > 0:
summary_text.append(f"❌ Failed to generate {error_count} notebook(s)\n", style="bold red")
summary_text.append("\n🌟 Jupyter Lab launched successfully!\n\n", style="bold green")
summary_text.append("📍 Working directory: ", style="white")
summary_text.append(f"{target_dir}\n", style="cyan")
summary_text.append("🌐 Open your browser and navigate to the URL shown above\n", style="white")
summary_text.append("📁 Your notebooks are ready for interactive development\n", style="white")
summary_text.append("🔄 Press Ctrl+C in the terminal to stop Jupyter Lab", style="white")
border_style = "green" if error_count == 0 else "yellow"
self.console.print(Panel(
summary_text,
title="View Command Complete",
border_style=border_style
))