mirror of
https://github.com/MLSysBook/TinyTorch.git
synced 2026-03-11 20:03:49 -05:00
Add new system commands: check, version, clean, report
- check: Comprehensive environment validation (60+ tests) - version: Definitive version catalog for all dependencies - clean: Workspace cleanup (caches, temp files, build artifacts) - report: Generate diagnostic JSON for bug reports and support
This commit is contained in:
181
tito/commands/check.py
Normal file
181
tito/commands/check.py
Normal file
@@ -0,0 +1,181 @@
|
||||
"""
|
||||
Check command for TinyTorch CLI: comprehensive environment validation.
|
||||
|
||||
Runs 60+ automated tests to validate the entire TinyTorch environment.
|
||||
Perfect for students to share with TAs when something isn't working.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
from argparse import ArgumentParser, Namespace
|
||||
from pathlib import Path
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
|
||||
from .base import BaseCommand
|
||||
|
||||
class CheckCommand(BaseCommand):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "check"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "Run comprehensive environment validation (60+ tests)"
|
||||
|
||||
def add_arguments(self, parser: ArgumentParser) -> None:
|
||||
parser.add_argument(
|
||||
'--verbose',
|
||||
'-v',
|
||||
action='store_true',
|
||||
help='Show detailed test output'
|
||||
)
|
||||
|
||||
def run(self, args: Namespace) -> int:
|
||||
"""Run comprehensive validation tests with rich output."""
|
||||
console = self.console
|
||||
|
||||
console.print()
|
||||
console.print(Panel(
|
||||
"🧪 Running Comprehensive Environment Validation\n\n"
|
||||
"This will test 60+ aspects of your TinyTorch environment.\n"
|
||||
"Perfect for sharing with TAs if something isn't working!",
|
||||
title="TinyTorch Environment Check",
|
||||
border_style="bright_cyan"
|
||||
))
|
||||
console.print()
|
||||
|
||||
# Check if tests directory exists
|
||||
tests_dir = Path("tests/environment")
|
||||
if not tests_dir.exists():
|
||||
console.print(Panel(
|
||||
"[red]❌ Validation tests not found![/red]\n\n"
|
||||
f"Expected location: {tests_dir.absolute()}\n\n"
|
||||
"Please ensure you're running this from the TinyTorch root directory.",
|
||||
title="Error",
|
||||
border_style="red"
|
||||
))
|
||||
return 1
|
||||
|
||||
# Run the validation tests with pytest
|
||||
test_files = [
|
||||
"tests/environment/test_setup_validation.py",
|
||||
"tests/environment/test_all_requirements.py"
|
||||
]
|
||||
|
||||
console.print("[bold cyan]Running validation tests...[/bold cyan]")
|
||||
console.print()
|
||||
|
||||
# Build pytest command
|
||||
pytest_args = [
|
||||
sys.executable, "-m", "pytest"
|
||||
] + test_files + [
|
||||
"-v" if args.verbose else "-q",
|
||||
"--tb=short",
|
||||
"--color=yes",
|
||||
"-p", "no:warnings" # Suppress warnings for cleaner output
|
||||
]
|
||||
|
||||
# Run pytest and capture output
|
||||
result = subprocess.run(
|
||||
pytest_args,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
# Parse test results from output
|
||||
output_lines = result.stdout.split('\n')
|
||||
|
||||
# Count results
|
||||
passed = failed = skipped = 0
|
||||
|
||||
for line in output_lines:
|
||||
if 'passed' in line.lower():
|
||||
# Extract numbers from pytest summary
|
||||
import re
|
||||
match = re.search(r'(\d+) passed', line)
|
||||
if match:
|
||||
passed = int(match.group(1))
|
||||
match = re.search(r'(\d+) failed', line)
|
||||
if match:
|
||||
failed = int(match.group(1))
|
||||
match = re.search(r'(\d+) skipped', line)
|
||||
if match:
|
||||
skipped = int(match.group(1))
|
||||
|
||||
# Display results with rich formatting
|
||||
console.print()
|
||||
|
||||
# Summary table
|
||||
results_table = Table(title="Test Results Summary", show_header=True, header_style="bold magenta")
|
||||
results_table.add_column("Category", style="cyan", width=30)
|
||||
results_table.add_column("Count", justify="right", width=10)
|
||||
results_table.add_column("Status", width=20)
|
||||
|
||||
if passed > 0:
|
||||
results_table.add_row("Tests Passed", str(passed), "[green]✅ OK[/green]")
|
||||
if failed > 0:
|
||||
results_table.add_row("Tests Failed", str(failed), "[red]❌ Issues Found[/red]")
|
||||
if skipped > 0:
|
||||
results_table.add_row("Tests Skipped", str(skipped), "[yellow]⏭️ Optional[/yellow]")
|
||||
|
||||
console.print(results_table)
|
||||
console.print()
|
||||
|
||||
# Overall health status
|
||||
if failed == 0:
|
||||
status_panel = Panel(
|
||||
"[bold green]✅ Environment is HEALTHY![/bold green]\n\n"
|
||||
f"All {passed} required checks passed.\n"
|
||||
f"{skipped} optional checks skipped.\n\n"
|
||||
"Your TinyTorch environment is ready to use! 🎉\n\n"
|
||||
"[dim]Next: [/dim][cyan]tito module 01[/cyan]",
|
||||
title="Environment Status",
|
||||
border_style="green"
|
||||
)
|
||||
else:
|
||||
status_panel = Panel(
|
||||
f"[bold red]❌ Found {failed} issue(s)[/bold red]\n\n"
|
||||
f"{passed} checks passed, but some components need attention.\n\n"
|
||||
"[bold]What to share with your TA:[/bold]\n"
|
||||
"1. Copy the output above\n"
|
||||
"2. Include the error messages below\n"
|
||||
"3. Mention what you were trying to do\n\n"
|
||||
"[dim]Or try:[/dim] [cyan]tito setup[/cyan] [dim]to reinstall[/dim]",
|
||||
title="Environment Status",
|
||||
border_style="red"
|
||||
)
|
||||
|
||||
console.print(status_panel)
|
||||
|
||||
# Show detailed output if verbose or if there are failures
|
||||
if args.verbose or failed > 0:
|
||||
console.print()
|
||||
console.print(Panel("📋 Detailed Test Output", border_style="blue"))
|
||||
console.print()
|
||||
console.print(result.stdout)
|
||||
|
||||
if result.stderr:
|
||||
console.print()
|
||||
console.print(Panel("⚠️ Error Messages", border_style="yellow"))
|
||||
console.print()
|
||||
console.print(result.stderr)
|
||||
|
||||
# Add helpful hints for common failures
|
||||
if failed > 0:
|
||||
console.print()
|
||||
console.print(Panel(
|
||||
"[bold]Common Solutions:[/bold]\n\n"
|
||||
"• Missing packages: [cyan]pip install -r requirements.txt[/cyan]\n"
|
||||
"• Jupyter issues: [cyan]pip install --upgrade jupyterlab[/cyan]\n"
|
||||
"• Import errors: [cyan]pip install -e .[/cyan] [dim](reinstall TinyTorch)[/dim]\n"
|
||||
"• Still stuck: Run [cyan]tito system check --verbose[/cyan]\n\n"
|
||||
"[dim]Then share the full output with your TA[/dim]",
|
||||
title="💡 Quick Fixes",
|
||||
border_style="yellow"
|
||||
))
|
||||
|
||||
console.print()
|
||||
|
||||
# Return appropriate exit code
|
||||
return 0 if failed == 0 else 1
|
||||
232
tito/commands/clean_workspace.py
Normal file
232
tito/commands/clean_workspace.py
Normal file
@@ -0,0 +1,232 @@
|
||||
"""
|
||||
Clean command for TinyTorch CLI: clean up generated files and caches.
|
||||
"""
|
||||
|
||||
from argparse import ArgumentParser, Namespace
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich.prompt import Confirm
|
||||
|
||||
from .base import BaseCommand
|
||||
|
||||
class CleanWorkspaceCommand(BaseCommand):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "clean"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "Clean up generated files, caches, and temporary files"
|
||||
|
||||
def add_arguments(self, parser: ArgumentParser) -> None:
|
||||
parser.add_argument(
|
||||
'--all',
|
||||
action='store_true',
|
||||
help='Clean everything including build artifacts'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='Show what would be deleted without actually deleting'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-y', '--yes',
|
||||
action='store_true',
|
||||
help='Skip confirmation prompt'
|
||||
)
|
||||
|
||||
def run(self, args: Namespace) -> int:
|
||||
console = self.console
|
||||
|
||||
console.print()
|
||||
console.print(Panel(
|
||||
"🧹 Cleaning TinyTorch Workspace",
|
||||
title="Workspace Cleanup",
|
||||
border_style="bright_yellow"
|
||||
))
|
||||
console.print()
|
||||
|
||||
# Define patterns to clean
|
||||
patterns = {
|
||||
'__pycache__': ('__pycache__/', 'Python bytecode cache'),
|
||||
'.pytest_cache': ('.pytest_cache/', 'Pytest cache'),
|
||||
'.ipynb_checkpoints': ('.ipynb_checkpoints/', 'Jupyter checkpoints'),
|
||||
'*.pyc': ('*.pyc', 'Compiled Python files'),
|
||||
'*.pyo': ('*.pyo', 'Optimized Python files'),
|
||||
'*.pyd': ('*.pyd', 'Python extension modules'),
|
||||
}
|
||||
|
||||
if args.all:
|
||||
# Additional patterns for --all
|
||||
patterns.update({
|
||||
'.coverage': ('.coverage', 'Coverage data'),
|
||||
'htmlcov': ('htmlcov/', 'Coverage HTML report'),
|
||||
'.tox': ('.tox/', 'Tox environments'),
|
||||
'dist': ('dist/', 'Distribution files'),
|
||||
'build': ('build/', 'Build files'),
|
||||
'*.egg-info': ('*.egg-info/', 'Egg info directories'),
|
||||
})
|
||||
|
||||
# Scan for files to delete
|
||||
console.print("[bold cyan]🔍 Scanning for files to clean...[/bold cyan]")
|
||||
console.print()
|
||||
|
||||
files_to_delete = []
|
||||
total_size = 0
|
||||
|
||||
# Find __pycache__ directories
|
||||
for pycache_dir in Path.cwd().rglob('__pycache__'):
|
||||
for file in pycache_dir.iterdir():
|
||||
if file.is_file():
|
||||
files_to_delete.append(file)
|
||||
total_size += file.stat().st_size
|
||||
files_to_delete.append(pycache_dir)
|
||||
|
||||
# Find .pytest_cache directories
|
||||
for cache_dir in Path.cwd().rglob('.pytest_cache'):
|
||||
for file in cache_dir.rglob('*'):
|
||||
if file.is_file():
|
||||
files_to_delete.append(file)
|
||||
total_size += file.stat().st_size
|
||||
files_to_delete.append(cache_dir)
|
||||
|
||||
# Find .ipynb_checkpoints directories
|
||||
for checkpoint_dir in Path.cwd().rglob('.ipynb_checkpoints'):
|
||||
for file in checkpoint_dir.rglob('*'):
|
||||
if file.is_file():
|
||||
files_to_delete.append(file)
|
||||
total_size += file.stat().st_size
|
||||
files_to_delete.append(checkpoint_dir)
|
||||
|
||||
# Find .pyc, .pyo, .pyd files
|
||||
for ext in ['*.pyc', '*.pyo', '*.pyd']:
|
||||
for file in Path.cwd().rglob(ext):
|
||||
if file.is_file():
|
||||
files_to_delete.append(file)
|
||||
total_size += file.stat().st_size
|
||||
|
||||
if args.all:
|
||||
# Additional cleanups for --all flag
|
||||
for pattern in ['.coverage', 'htmlcov', '.tox', 'dist', 'build']:
|
||||
target = Path.cwd() / pattern
|
||||
if target.exists():
|
||||
if target.is_file():
|
||||
files_to_delete.append(target)
|
||||
total_size += target.stat().st_size
|
||||
elif target.is_dir():
|
||||
for file in target.rglob('*'):
|
||||
if file.is_file():
|
||||
files_to_delete.append(file)
|
||||
total_size += file.stat().st_size
|
||||
files_to_delete.append(target)
|
||||
|
||||
# Find .egg-info directories
|
||||
for egg_info in Path.cwd().rglob('*.egg-info'):
|
||||
if egg_info.is_dir():
|
||||
for file in egg_info.rglob('*'):
|
||||
if file.is_file():
|
||||
files_to_delete.append(file)
|
||||
total_size += file.stat().st_size
|
||||
files_to_delete.append(egg_info)
|
||||
|
||||
if not files_to_delete:
|
||||
console.print(Panel(
|
||||
"[green]✅ Workspace is already clean![/green]\n\n"
|
||||
"No temporary files or caches found.",
|
||||
title="Clean Workspace",
|
||||
border_style="green"
|
||||
))
|
||||
return 0
|
||||
|
||||
# Count file types
|
||||
file_count = len([f for f in files_to_delete if f.is_file()])
|
||||
dir_count = len([f for f in files_to_delete if f.is_dir()])
|
||||
|
||||
# Summary table
|
||||
summary_table = Table(title="Files Found", show_header=True, header_style="bold yellow")
|
||||
summary_table.add_column("Type", style="cyan", width=30)
|
||||
summary_table.add_column("Count", justify="right", width=15)
|
||||
summary_table.add_column("Size", width=20)
|
||||
|
||||
# Count by type
|
||||
pycache_count = len([f for f in files_to_delete if '__pycache__' in str(f)])
|
||||
pytest_count = len([f for f in files_to_delete if '.pytest_cache' in str(f)])
|
||||
checkpoint_count = len([f for f in files_to_delete if '.ipynb_checkpoints' in str(f)])
|
||||
pyc_count = len([f for f in files_to_delete if str(f).endswith(('.pyc', '.pyo', '.pyd'))])
|
||||
|
||||
if pycache_count > 0:
|
||||
summary_table.add_row("__pycache__/", str(pycache_count), "—")
|
||||
if pytest_count > 0:
|
||||
summary_table.add_row(".pytest_cache/", str(pytest_count), "—")
|
||||
if checkpoint_count > 0:
|
||||
summary_table.add_row(".ipynb_checkpoints/", str(checkpoint_count), "—")
|
||||
if pyc_count > 0:
|
||||
summary_table.add_row("*.pyc/*.pyo/*.pyd", str(pyc_count), f"{total_size / 1024 / 1024:.2f} MB")
|
||||
|
||||
if args.all:
|
||||
other_count = file_count - pycache_count - pytest_count - checkpoint_count - pyc_count
|
||||
if other_count > 0:
|
||||
summary_table.add_row("Build artifacts", str(other_count), "—")
|
||||
|
||||
summary_table.add_row("[bold]Total[/bold]", f"[bold]{file_count} files, {dir_count} dirs[/bold]", f"[bold]{total_size / 1024 / 1024:.2f} MB[/bold]")
|
||||
|
||||
console.print(summary_table)
|
||||
console.print()
|
||||
|
||||
# Dry run mode
|
||||
if args.dry_run:
|
||||
console.print(Panel(
|
||||
"[yellow]🔍 DRY RUN MODE[/yellow]\n\n"
|
||||
f"Would delete {file_count} files and {dir_count} directories ({total_size / 1024 / 1024:.2f} MB)\n\n"
|
||||
"[dim]Remove --dry-run flag to actually delete these files[/dim]",
|
||||
title="Dry Run",
|
||||
border_style="yellow"
|
||||
))
|
||||
return 0
|
||||
|
||||
# Confirm deletion
|
||||
if not args.yes:
|
||||
confirmed = Confirm.ask(
|
||||
f"\n[yellow]⚠️ Delete {file_count} files and {dir_count} directories ({total_size / 1024 / 1024:.2f} MB)?[/yellow]",
|
||||
default=False
|
||||
)
|
||||
if not confirmed:
|
||||
console.print("\n[dim]Cleanup cancelled.[/dim]")
|
||||
return 0
|
||||
|
||||
# Perform cleanup
|
||||
console.print()
|
||||
console.print("[bold cyan]🗑️ Cleaning workspace...[/bold cyan]")
|
||||
|
||||
deleted_files = 0
|
||||
deleted_dirs = 0
|
||||
freed_space = 0
|
||||
|
||||
# Delete files first, then directories
|
||||
for item in files_to_delete:
|
||||
try:
|
||||
if item.is_file():
|
||||
size = item.stat().st_size
|
||||
item.unlink()
|
||||
deleted_files += 1
|
||||
freed_space += size
|
||||
elif item.is_dir() and not any(item.iterdir()): # Only delete if empty
|
||||
shutil.rmtree(item, ignore_errors=True)
|
||||
deleted_dirs += 1
|
||||
except Exception as e:
|
||||
console.print(f"[dim red] ✗ Failed to delete {item}: {e}[/dim red]")
|
||||
|
||||
# Success message
|
||||
console.print()
|
||||
console.print(Panel(
|
||||
f"[bold green]✅ Workspace Cleaned![/bold green]\n\n"
|
||||
f"🗑️ Deleted: {deleted_files} files, {deleted_dirs} directories\n"
|
||||
f"💾 Freed: {freed_space / 1024 / 1024:.2f} MB\n"
|
||||
f"⏱️ Time: < 1 second",
|
||||
title="Cleanup Complete",
|
||||
border_style="green"
|
||||
))
|
||||
|
||||
return 0
|
||||
363
tito/commands/report.py
Normal file
363
tito/commands/report.py
Normal file
@@ -0,0 +1,363 @@
|
||||
"""
|
||||
Report command for TinyTorch CLI: generate comprehensive diagnostic report.
|
||||
|
||||
This command generates a JSON report containing all environment information,
|
||||
perfect for sharing with TAs, instructors, or when filing bug reports.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from argparse import ArgumentParser, Namespace
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
|
||||
from .base import BaseCommand
|
||||
|
||||
|
||||
class ReportCommand(BaseCommand):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "report"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "Generate comprehensive diagnostic report (JSON)"
|
||||
|
||||
def add_arguments(self, parser: ArgumentParser) -> None:
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
type=str,
|
||||
default=None,
|
||||
help='Output file path (default: tinytorch-report-TIMESTAMP.json)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--stdout',
|
||||
action='store_true',
|
||||
help='Print JSON to stdout instead of file'
|
||||
)
|
||||
|
||||
def run(self, args: Namespace) -> int:
|
||||
console = self.console
|
||||
|
||||
console.print()
|
||||
console.print(Panel(
|
||||
"📋 Generating TinyTorch Diagnostic Report\n\n"
|
||||
"[dim]This report contains all environment information\n"
|
||||
"needed for debugging and support.[/dim]",
|
||||
title="System Report",
|
||||
border_style="bright_yellow"
|
||||
))
|
||||
console.print()
|
||||
|
||||
# Collect all diagnostic information
|
||||
report = self._collect_report_data()
|
||||
|
||||
# Determine output path
|
||||
if args.stdout:
|
||||
# Print to stdout
|
||||
print(json.dumps(report, indent=2))
|
||||
return 0
|
||||
else:
|
||||
# Write to file
|
||||
if args.output:
|
||||
output_path = Path(args.output)
|
||||
else:
|
||||
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
output_path = Path.cwd() / f"tinytorch-report-{timestamp}.json"
|
||||
|
||||
try:
|
||||
with open(output_path, 'w') as f:
|
||||
json.dump(report, f, indent=2)
|
||||
|
||||
console.print(Panel(
|
||||
f"[bold green]✅ Report Generated Successfully![/bold green]\n\n"
|
||||
f"📄 File: [cyan]{output_path}[/cyan]\n"
|
||||
f"📦 Size: {output_path.stat().st_size} bytes\n\n"
|
||||
f"[dim]Share this file with your TA or instructor for support.[/dim]",
|
||||
title="Report Complete",
|
||||
border_style="green"
|
||||
))
|
||||
|
||||
return 0
|
||||
except Exception as e:
|
||||
console.print(Panel(
|
||||
f"[red]❌ Failed to write report:[/red]\n\n{str(e)}",
|
||||
title="Error",
|
||||
border_style="red"
|
||||
))
|
||||
return 1
|
||||
|
||||
def _collect_report_data(self) -> dict:
|
||||
"""Collect comprehensive diagnostic information."""
|
||||
report = {
|
||||
"report_metadata": {
|
||||
"generated_at": datetime.now().isoformat(),
|
||||
"report_version": "1.0",
|
||||
"tinytorch_cli_version": self._get_tinytorch_version()
|
||||
},
|
||||
"system": self._collect_system_info(),
|
||||
"python": self._collect_python_info(),
|
||||
"environment": self._collect_environment_info(),
|
||||
"dependencies": self._collect_dependencies_info(),
|
||||
"tinytorch": self._collect_tinytorch_info(),
|
||||
"modules": self._collect_modules_info(),
|
||||
"git": self._collect_git_info(),
|
||||
"disk_memory": self._collect_disk_memory_info()
|
||||
}
|
||||
return report
|
||||
|
||||
def _get_tinytorch_version(self) -> str:
|
||||
"""Get TinyTorch version."""
|
||||
try:
|
||||
import tinytorch
|
||||
return getattr(tinytorch, '__version__', 'unknown')
|
||||
except ImportError:
|
||||
return "not_installed"
|
||||
|
||||
def _collect_system_info(self) -> dict:
|
||||
"""Collect system information."""
|
||||
return {
|
||||
"os": platform.system(),
|
||||
"os_release": platform.release(),
|
||||
"os_version": platform.version(),
|
||||
"machine": platform.machine(),
|
||||
"processor": platform.processor(),
|
||||
"platform": platform.platform(),
|
||||
"node": platform.node()
|
||||
}
|
||||
|
||||
def _collect_python_info(self) -> dict:
|
||||
"""Collect Python interpreter information."""
|
||||
return {
|
||||
"version": sys.version,
|
||||
"version_info": {
|
||||
"major": sys.version_info.major,
|
||||
"minor": sys.version_info.minor,
|
||||
"micro": sys.version_info.micro,
|
||||
"releaselevel": sys.version_info.releaselevel,
|
||||
"serial": sys.version_info.serial
|
||||
},
|
||||
"implementation": platform.python_implementation(),
|
||||
"compiler": platform.python_compiler(),
|
||||
"executable": sys.executable,
|
||||
"prefix": sys.prefix,
|
||||
"base_prefix": getattr(sys, 'base_prefix', sys.prefix),
|
||||
"path": sys.path
|
||||
}
|
||||
|
||||
def _collect_environment_info(self) -> dict:
|
||||
"""Collect environment variables and paths."""
|
||||
venv_exists = self.venv_path.exists()
|
||||
in_venv = (
|
||||
os.environ.get('VIRTUAL_ENV') is not None or
|
||||
(hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix) or
|
||||
hasattr(sys, 'real_prefix')
|
||||
)
|
||||
|
||||
return {
|
||||
"working_directory": str(Path.cwd()),
|
||||
"virtual_environment": {
|
||||
"exists": venv_exists,
|
||||
"active": in_venv,
|
||||
"path": str(self.venv_path) if venv_exists else None,
|
||||
"VIRTUAL_ENV": os.environ.get('VIRTUAL_ENV'),
|
||||
"CONDA_DEFAULT_ENV": os.environ.get('CONDA_DEFAULT_ENV'),
|
||||
"CONDA_PREFIX": os.environ.get('CONDA_PREFIX')
|
||||
},
|
||||
"PATH": os.environ.get('PATH', '').split(os.pathsep),
|
||||
"PYTHONPATH": os.environ.get('PYTHONPATH', '').split(os.pathsep) if os.environ.get('PYTHONPATH') else []
|
||||
}
|
||||
|
||||
def _collect_dependencies_info(self) -> dict:
|
||||
"""Collect installed package information."""
|
||||
dependencies = {}
|
||||
|
||||
# Core dependencies
|
||||
packages = [
|
||||
('numpy', 'numpy'),
|
||||
('pytest', 'pytest'),
|
||||
('PyYAML', 'yaml'),
|
||||
('rich', 'rich'),
|
||||
('jupyterlab', 'jupyterlab'),
|
||||
('jupytext', 'jupytext'),
|
||||
('nbformat', 'nbformat'),
|
||||
('nbgrader', 'nbgrader'),
|
||||
('nbconvert', 'nbconvert'),
|
||||
('jupyter', 'jupyter'),
|
||||
('matplotlib', 'matplotlib'),
|
||||
('psutil', 'psutil'),
|
||||
('black', 'black'),
|
||||
('isort', 'isort'),
|
||||
('flake8', 'flake8')
|
||||
]
|
||||
|
||||
for display_name, import_name in packages:
|
||||
try:
|
||||
module = __import__(import_name)
|
||||
version = getattr(module, '__version__', 'unknown')
|
||||
location = getattr(module, '__file__', 'unknown')
|
||||
dependencies[display_name] = {
|
||||
"installed": True,
|
||||
"version": version,
|
||||
"location": location
|
||||
}
|
||||
except ImportError:
|
||||
dependencies[display_name] = {
|
||||
"installed": False,
|
||||
"version": None,
|
||||
"location": None
|
||||
}
|
||||
|
||||
return dependencies
|
||||
|
||||
def _collect_tinytorch_info(self) -> dict:
|
||||
"""Collect TinyTorch package information."""
|
||||
try:
|
||||
import tinytorch
|
||||
version = getattr(tinytorch, '__version__', 'unknown')
|
||||
location = Path(tinytorch.__file__).parent
|
||||
|
||||
# Check if in development mode
|
||||
is_dev = (location / '../setup.py').exists() or (location / '../pyproject.toml').exists()
|
||||
|
||||
return {
|
||||
"installed": True,
|
||||
"version": version,
|
||||
"location": str(location),
|
||||
"development_mode": is_dev,
|
||||
"package_structure": {
|
||||
"has_init": (location / '__init__.py').exists(),
|
||||
"has_core": (location / 'core').exists(),
|
||||
"has_ops": (location / 'ops').exists()
|
||||
}
|
||||
}
|
||||
except ImportError:
|
||||
return {
|
||||
"installed": False,
|
||||
"version": None,
|
||||
"location": None,
|
||||
"development_mode": False,
|
||||
"package_structure": {}
|
||||
}
|
||||
|
||||
def _collect_modules_info(self) -> dict:
|
||||
"""Collect TinyTorch modules information."""
|
||||
modules_dir = Path.cwd() / "modules"
|
||||
|
||||
if not modules_dir.exists():
|
||||
return {"exists": False, "modules": []}
|
||||
|
||||
modules = []
|
||||
for module_path in sorted(modules_dir.iterdir()):
|
||||
if module_path.is_dir() and module_path.name.startswith(('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')):
|
||||
module_info = {
|
||||
"name": module_path.name,
|
||||
"path": str(module_path),
|
||||
"has_notebook": any(module_path.glob("*.ipynb")),
|
||||
"has_dev_py": any(module_path.glob("*_dev.py")),
|
||||
"has_tests": (module_path / "tests").exists()
|
||||
}
|
||||
modules.append(module_info)
|
||||
|
||||
return {
|
||||
"exists": True,
|
||||
"count": len(modules),
|
||||
"modules": modules
|
||||
}
|
||||
|
||||
def _collect_git_info(self) -> dict:
|
||||
"""Collect git repository information."""
|
||||
git_dir = Path.cwd() / ".git"
|
||||
|
||||
if not git_dir.exists():
|
||||
return {"is_repo": False}
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
# Get current branch
|
||||
branch = subprocess.check_output(
|
||||
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True
|
||||
).strip()
|
||||
|
||||
# Get current commit
|
||||
commit = subprocess.check_output(
|
||||
['git', 'rev-parse', '--short', 'HEAD'],
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True
|
||||
).strip()
|
||||
|
||||
# Get remote URL
|
||||
try:
|
||||
remote = subprocess.check_output(
|
||||
['git', 'remote', 'get-url', 'origin'],
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True
|
||||
).strip()
|
||||
except:
|
||||
remote = None
|
||||
|
||||
# Check for uncommitted changes
|
||||
status = subprocess.check_output(
|
||||
['git', 'status', '--porcelain'],
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True
|
||||
).strip()
|
||||
has_changes = len(status) > 0
|
||||
|
||||
return {
|
||||
"is_repo": True,
|
||||
"branch": branch,
|
||||
"commit": commit,
|
||||
"remote": remote,
|
||||
"has_uncommitted_changes": has_changes,
|
||||
"status": status if has_changes else None
|
||||
}
|
||||
except:
|
||||
return {"is_repo": True, "error": "Failed to get git info"}
|
||||
|
||||
def _collect_disk_memory_info(self) -> dict:
|
||||
"""Collect disk space and memory information."""
|
||||
info = {}
|
||||
|
||||
# Disk space
|
||||
try:
|
||||
disk_usage = shutil.disk_usage(Path.cwd())
|
||||
info["disk"] = {
|
||||
"total_bytes": disk_usage.total,
|
||||
"used_bytes": disk_usage.used,
|
||||
"free_bytes": disk_usage.free,
|
||||
"total_gb": round(disk_usage.total / (1024**3), 2),
|
||||
"used_gb": round(disk_usage.used / (1024**3), 2),
|
||||
"free_gb": round(disk_usage.free / (1024**3), 2),
|
||||
"percent_used": round((disk_usage.used / disk_usage.total) * 100, 1)
|
||||
}
|
||||
except Exception as e:
|
||||
info["disk"] = {"error": str(e)}
|
||||
|
||||
# Memory
|
||||
try:
|
||||
import psutil
|
||||
mem = psutil.virtual_memory()
|
||||
info["memory"] = {
|
||||
"total_bytes": mem.total,
|
||||
"available_bytes": mem.available,
|
||||
"used_bytes": mem.used,
|
||||
"total_gb": round(mem.total / (1024**3), 2),
|
||||
"available_gb": round(mem.available / (1024**3), 2),
|
||||
"used_gb": round(mem.used / (1024**3), 2),
|
||||
"percent_used": mem.percent
|
||||
}
|
||||
except ImportError:
|
||||
info["memory"] = {"error": "psutil not installed"}
|
||||
except Exception as e:
|
||||
info["memory"] = {"error": str(e)}
|
||||
|
||||
return info
|
||||
127
tito/commands/version.py
Normal file
127
tito/commands/version.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
Version command for TinyTorch CLI: show version information for TinyTorch and dependencies.
|
||||
"""
|
||||
|
||||
from argparse import ArgumentParser, Namespace
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
|
||||
from .base import BaseCommand
|
||||
|
||||
class VersionCommand(BaseCommand):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "version"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "Show version information for TinyTorch and dependencies"
|
||||
|
||||
def add_arguments(self, parser: ArgumentParser) -> None:
|
||||
# No arguments needed
|
||||
pass
|
||||
|
||||
def run(self, args: Namespace) -> int:
|
||||
console = self.console
|
||||
|
||||
console.print()
|
||||
console.print(Panel(
|
||||
"📦 TinyTorch Version Information",
|
||||
title="Version Info",
|
||||
border_style="bright_magenta"
|
||||
))
|
||||
console.print()
|
||||
|
||||
# Main Version Table
|
||||
version_table = Table(title="TinyTorch", show_header=True, header_style="bold cyan")
|
||||
version_table.add_column("Component", style="yellow", width=25)
|
||||
version_table.add_column("Version", style="white", width=50)
|
||||
|
||||
# TinyTorch Version
|
||||
try:
|
||||
import tinytorch
|
||||
tinytorch_version = getattr(tinytorch, '__version__', '0.1.0-dev')
|
||||
tinytorch_path = Path(tinytorch.__file__).parent
|
||||
|
||||
version_table.add_row("TinyTorch", f"v{tinytorch_version}")
|
||||
version_table.add_row(" └─ Installation", "Development Mode")
|
||||
version_table.add_row(" └─ Location", str(tinytorch_path))
|
||||
|
||||
# Check if it's a git repo
|
||||
git_dir = Path.cwd() / ".git"
|
||||
if git_dir.exists():
|
||||
try:
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "--short", "HEAD"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
commit_hash = result.stdout.strip()
|
||||
version_table.add_row(" └─ Git Commit", commit_hash)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
except ImportError:
|
||||
version_table.add_row("TinyTorch", "[red]Not Installed[/red]")
|
||||
|
||||
# Python Version
|
||||
python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
||||
version_table.add_row("Python", python_version)
|
||||
|
||||
console.print(version_table)
|
||||
console.print()
|
||||
|
||||
# Dependencies Version Table
|
||||
deps_table = Table(title="Core Dependencies", show_header=True, header_style="bold magenta")
|
||||
deps_table.add_column("Package", style="cyan", width=20)
|
||||
deps_table.add_column("Version", style="white", width=20)
|
||||
deps_table.add_column("Status", width=30)
|
||||
|
||||
dependencies = [
|
||||
('numpy', 'NumPy'),
|
||||
('pytest', 'pytest'),
|
||||
('yaml', 'PyYAML'),
|
||||
('rich', 'Rich'),
|
||||
('jupyterlab', 'JupyterLab'),
|
||||
('jupytext', 'Jupytext'),
|
||||
('nbformat', 'nbformat'),
|
||||
]
|
||||
|
||||
for import_name, display_name in dependencies:
|
||||
try:
|
||||
module = __import__(import_name)
|
||||
version = getattr(module, '__version__', 'unknown')
|
||||
deps_table.add_row(display_name, version, "[green]✅ Installed[/green]")
|
||||
except ImportError:
|
||||
deps_table.add_row(display_name, "—", "[red]❌ Not Installed[/red]")
|
||||
|
||||
console.print(deps_table)
|
||||
console.print()
|
||||
|
||||
# System Info (brief)
|
||||
system_table = Table(title="System", show_header=True, header_style="bold blue")
|
||||
system_table.add_column("Component", style="yellow", width=20)
|
||||
system_table.add_column("Value", style="white", width=50)
|
||||
|
||||
import platform
|
||||
system_table.add_row("OS", f"{platform.system()} {platform.release()}")
|
||||
system_table.add_row("Architecture", platform.machine())
|
||||
system_table.add_row("Python Implementation", platform.python_implementation())
|
||||
|
||||
console.print(system_table)
|
||||
console.print()
|
||||
|
||||
# Helpful info panel
|
||||
console.print(Panel(
|
||||
"[dim]💡 For complete system information, run:[/dim] [cyan]tito system info[/cyan]\n"
|
||||
"[dim]💡 To check environment health, run:[/dim] [cyan]tito system health[/cyan]",
|
||||
border_style="blue"
|
||||
))
|
||||
|
||||
return 0
|
||||
Reference in New Issue
Block a user