diff --git a/.envrc b/.envrc index d6495f35..d46b61f2 100644 --- a/.envrc +++ b/.envrc @@ -1,20 +1,24 @@ # Python Virtual Environment Auto-Activation Template # Copy this file as .envrc to any Python project directory +# Default usage: # Then run: direnv allow +VENV_PATH=$(python -c "import json; print(json.load(open('.tinyrc')).get('venv_path', '.venv'))") +echo "Using virtual env in ${VENV_PATH}" +export VENV_PATH # Check if .venv exists, create if it doesn't -if [[ ! -d ".venv" ]]; then +if [[ ! -d "$VENV_PATH" ]]; then echo "🔧 Creating Python virtual environment..." - python3 -m venv .venv + python3 -m venv "$VENV_PATH" echo "📦 Installing basic dependencies..." - source .venv/bin/activate + source "$VENV_PATH/bin/activate" pip install --upgrade pip # Uncomment the next line if you have a requirements.txt # pip install -r requirements.txt fi # Activate the virtual environment -source .venv/bin/activate +source "$VENV_PATH/bin/activate" # Set common Python environment variables export PYTHONPATH="${PWD}:${PYTHONPATH}" diff --git a/.tinyrc b/.tinyrc new file mode 100644 index 00000000..04bb46c9 --- /dev/null +++ b/.tinyrc @@ -0,0 +1,3 @@ +{ + "venv_path": ".venv" +} diff --git a/bin/activate-tinytorch.sh b/bin/activate-tinytorch.sh index 34a783b7..24cbc0be 100755 --- a/bin/activate-tinytorch.sh +++ b/bin/activate-tinytorch.sh @@ -1,10 +1,14 @@ #!/bin/bash # Tiny🔥Torch Environment Activation & Setup +# Allow users to pass a path to existing virtual env +VENV_PATH=${1:-".venv"} +export VENV_PATH + # Check if virtual environment exists, create if not -if [ ! -d ".venv" ]; then +if [ ! -d "$VENV_PATH" ]; then echo "🆕 First time setup - creating environment..." - python3 -m venv .venv || { + python3 -m venv "$VENV_PATH" || { echo "❌ Failed to create virtual environment" exit 1 } @@ -17,7 +21,7 @@ if [ ! -d ".venv" ]; then fi echo "🔥 Activating Tiny🔥Torch environment..." -source .venv/bin/activate +source "$VENV_PATH/bin/activate" # Create tito alias for convenience alias tito="python3 bin/tito" diff --git a/tito/commands/base.py b/tito/commands/base.py index 1c4aeef7..e4bcbb1f 100644 --- a/tito/commands/base.py +++ b/tito/commands/base.py @@ -5,9 +5,11 @@ Base command class for TinyTorch CLI. from abc import ABC, abstractmethod from argparse import ArgumentParser, Namespace from typing import Optional +from pathlib import Path import logging from ..core.config import CLIConfig +from ..core.virtual_env_manager import get_venv_path from ..core.console import get_console from ..core.exceptions import TinyTorchCLIError @@ -26,6 +28,11 @@ class BaseCommand(ABC): def name(self) -> str: """Return the command name.""" pass + + @property + def venv_path(self) -> Path: + """Return the command name.""" + return get_venv_path() @property @abstractmethod diff --git a/tito/commands/doctor.py b/tito/commands/doctor.py index 5b18e0ee..fe7e1048 100644 --- a/tito/commands/doctor.py +++ b/tito/commands/doctor.py @@ -41,8 +41,7 @@ class DoctorCommand(BaseCommand): env_table.add_row("Python", "[green]✅ OK[/green]", f"{sys.version.split()[0]} ({sys.platform})") # Virtual environment - check if it exists and if we're using it - venv_path = Path(".venv") - venv_exists = venv_path.exists() + venv_exists = self.venv_path.exists() in_venv = ( # Method 1: Check VIRTUAL_ENV environment variable (most reliable for activation) os.environ.get('VIRTUAL_ENV') is not None or @@ -58,7 +57,7 @@ class DoctorCommand(BaseCommand): venv_status = "[yellow]✅ Ready (Not Active)[/yellow]" else: venv_status = "[red]❌ Not Found[/red]" - env_table.add_row("Virtual Environment", venv_status, ".venv") + env_table.add_row("Virtual Environment", venv_status, f"{self.venv_path}") # Dependencies dependencies = [ diff --git a/tito/commands/export.py b/tito/commands/export.py index 3d2fd459..7d96de13 100644 --- a/tito/commands/export.py +++ b/tito/commands/export.py @@ -478,7 +478,7 @@ class ExportCommand(BaseCommand): # Get the project root directory (where .venv should be) project_root = Path(__file__).parent.parent.parent - venv_jupytext = project_root / ".venv" / "bin" / "jupytext" + venv_jupytext = self.venv_path / "bin" / "jupytext" if venv_jupytext.exists(): # Test venv jupytext first diff --git a/tito/commands/help.py b/tito/commands/help.py index cba8d062..33b4e67a 100644 --- a/tito/commands/help.py +++ b/tito/commands/help.py @@ -409,8 +409,8 @@ class HelpCommand(BaseCommand): "```bash\n" "git clone https://github.com/mlsysbook/TinyTorch.git\n" "cd TinyTorch\n" - "python -m venv .venv\n" - "source .venv/bin/activate # Windows: .venv\\Scripts\\activate\n" + f"python -m venv {self.venv_path}\n" + f"source {self.venv_path}/bin/activate # Windows: .venv\\Scripts\\activate\n" "pip install -r requirements.txt\n" "pip install -e .\n" "```", diff --git a/tito/commands/info.py b/tito/commands/info.py index 74e53434..23a84ca5 100644 --- a/tito/commands/info.py +++ b/tito/commands/info.py @@ -3,7 +3,6 @@ Info command for TinyTorch CLI: shows system information and course navigation. """ from argparse import ArgumentParser, Namespace -from pathlib import Path import sys import os from rich.console import Console @@ -39,8 +38,7 @@ class InfoCommand(BaseCommand): info_text.append(f"Working Directory: {os.getcwd()}\n", style="cyan") # Virtual environment check - venv_path = Path(".venv") - venv_exists = venv_path.exists() + 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 diff --git a/tito/commands/protect.py b/tito/commands/protect.py index b47f6809..ed3d5898 100644 --- a/tito/commands/protect.py +++ b/tito/commands/protect.py @@ -194,6 +194,7 @@ echo "✅ No auto-generated files being committed" vscode_dir = Path(".vscode") vscode_dir.mkdir(exist_ok=True) + python_default_interpreter = str(self.venv_path) + "/bin/python" vscode_settings = { "_comment_protection": "🛡️ TinyTorch Student Protection", "files.readonlyInclude": { @@ -204,7 +205,7 @@ echo "✅ No auto-generated files being committed" "files.decorations.badges": True, "explorer.decorations.colors": True, "explorer.decorations.badges": True, - "python.defaultInterpreterPath": "./.venv/bin/python", + "python.defaultInterpreterPath": python_default_interpreter, "python.terminal.activateEnvironment": True } diff --git a/tito/core/config.py b/tito/core/config.py index bba78bc6..91783f60 100644 --- a/tito/core/config.py +++ b/tito/core/config.py @@ -8,6 +8,7 @@ from pathlib import Path from typing import Dict, Any, Optional, List from dataclasses import dataclass + @dataclass class CLIConfig: """Configuration for TinyTorch CLI.""" @@ -55,7 +56,7 @@ class CLIConfig: bin_dir=project_root / 'bin' ) - def validate(self) -> List[str]: + def validate(self, venv_path=Optional[Path]) -> List[str]: """Validate the configuration and return any issues.""" issues = [] @@ -73,10 +74,10 @@ class CLIConfig: # Method 3: Check for sys.real_prefix (older Python versions) hasattr(sys, 'real_prefix') or # Method 4: Check if .venv directory exists and packages are available - (Path('.venv').exists() and self._packages_available()) + (venv_path.exists() and self._packages_available()) ) if not in_venv: - issues.append("Virtual environment not activated. Run: source .venv/bin/activate") + issues.append(f"Virtual environment not activated. Run: source {venv_path}/bin/activate") # Check required directories if not self.assignments_dir.exists(): diff --git a/tito/core/virtual_env_manager.py b/tito/core/virtual_env_manager.py new file mode 100644 index 00000000..c706a591 --- /dev/null +++ b/tito/core/virtual_env_manager.py @@ -0,0 +1,23 @@ +import os, json +from pathlib import Path + +DEFAULT_VENV = ".venv" +CONFIG_FILE = ".tinyrc" + + +def get_venv_path() -> Path: + """ + Fetch venv in case users have a custom path + """ + print(f"running this from {os.getcwd()}") + if "VENV_PATH" in os.environ: + return Path(os.environ["VENV_PATH"]).expanduser().resolve() + + if Path(CONFIG_FILE).exists(): + try: + cfg = json.load(open(CONFIG_FILE)) + return Path(cfg.get("venv_path", DEFAULT_VENV)).expanduser().resolve() + except Exception: + pass + + return Path(DEFAULT_VENV).resolve() \ No newline at end of file diff --git a/tito/main.py b/tito/main.py index 081ea38f..63a1e631 100644 --- a/tito/main.py +++ b/tito/main.py @@ -17,6 +17,7 @@ from pathlib import Path from typing import Dict, Type, Optional, List from .core.config import CLIConfig +from .core.virtual_env_manager import get_venv_path from .core.console import get_console, print_banner, print_error, print_ascii_logo from .core.exceptions import TinyTorchCLIError from rich.panel import Panel @@ -158,7 +159,7 @@ Examples: def validate_environment(self) -> bool: """Validate the environment and show issues if any.""" - issues = self.config.validate() + issues = self.config.validate(get_venv_path()) if issues: print_error(