import subprocess import json import os import platform from pathlib import Path from typing import List, Dict, Optional from app.models import CondaEnvironment from app.config.database import db class CondaService: """Service for managing conda environments.""" def __init__(self): self.conda_executable = self.find_conda_executable() self.system = platform.system().lower() def find_conda_executable(self) -> Optional[str]: """Find conda executable on Windows/Linux.""" possible_paths = [ "conda", "/opt/conda/bin/conda", "/usr/local/bin/conda", os.path.expanduser("~/miniconda3/bin/conda"), os.path.expanduser("~/anaconda3/bin/conda"), ] # Windows specific paths if platform.system().lower() == "windows": possible_paths.extend( [ r"C:\ProgramData\Miniconda3\Scripts\conda.exe", r"C:\ProgramData\Anaconda3\Scripts\conda.exe", os.path.expanduser(r"~\Miniconda3\Scripts\conda.exe"), os.path.expanduser(r"~\Anaconda3\Scripts\conda.exe"), r"C:\tools\miniconda3\Scripts\conda.exe", r"C:\Miniconda3\Scripts\conda.exe", r"C:\Anaconda3\Scripts\conda.exe", ] ) for path in possible_paths: try: result = subprocess.run( [path, "--version"], capture_output=True, text=True, timeout=10 ) if result.returncode == 0: return path except (FileNotFoundError, subprocess.TimeoutExpired, OSError): continue return None def is_available(self) -> bool: """Check if conda is available.""" return self.conda_executable is not None def list_environments(self) -> List[Dict]: """List all available conda environments.""" if not self.conda_executable: return [] try: result = subprocess.run( [self.conda_executable, "env", "list", "--json"], capture_output=True, text=True, timeout=30, ) if result.returncode == 0: env_data = json.loads(result.stdout) environments = [] for env_path in env_data.get("envs", []): env_name = Path(env_path).name if env_path == env_data.get("conda_default_env", ""): env_name = "base" python_version = self.get_python_version(env_path) environments.append( { "name": env_name, "path": env_path, "python_version": python_version, } ) return environments except Exception as e: print(f"Error listing conda environments: {e}") return [] def get_python_version(self, env_path: str) -> Optional[str]: """Get Python version for an environment.""" try: python_exe = os.path.join( env_path, "python.exe" if self.system == "windows" else "bin/python" ) if os.path.exists(python_exe): result = subprocess.run( [python_exe, "--version"], capture_output=True, text=True, timeout=10, ) if result.returncode == 0: version_output = result.stdout.strip() if version_output.startswith("Python "): return version_output.split()[1] except Exception: pass return None def refresh_environments(self) -> List[CondaEnvironment]: """Refresh conda environments in database.""" environments = self.list_environments() db_environments = [] for env_data in environments: # Check if environment already exists existing = CondaEnvironment.query.filter_by(name=env_data["name"]).first() if existing: # Update existing environment existing.path = env_data["path"] existing.python_version = env_data["python_version"] existing.is_available = True existing.last_verified = db.func.now() db_environments.append(existing) else: # Create new environment new_env = CondaEnvironment( name=env_data["name"], path=env_data["path"], python_version=env_data["python_version"], is_available=True, ) db.session.add(new_env) db_environments.append(new_env) # Mark missing environments as unavailable all_db_envs = CondaEnvironment.query.all() current_names = [env["name"] for env in environments] for db_env in all_db_envs: if db_env.name not in current_names: db_env.is_available = False db.session.commit() return db_environments def get_environment_info(self, env_name: str) -> Optional[Dict]: """Get detailed information about an environment.""" env = CondaEnvironment.query.filter_by(name=env_name).first() if env and env.is_available: return env.to_dict() return None def build_conda_command(self, env_name: str, command: List[str]) -> List[str]: """Build conda command for executing in specific environment.""" if not self.conda_executable: raise RuntimeError("Conda executable not found") # Check if we're already in the target environment current_env = os.environ.get("CONDA_DEFAULT_ENV", "") print(f"[CONDA_SERVICE] Current environment: {current_env}") print(f"[CONDA_SERVICE] Target environment: {env_name}") if current_env == env_name: print( f"[CONDA_SERVICE] Already in target environment {env_name}, " "using direct command" ) return command print("[CONDA_SERVICE] Different environment, using conda run") if self.system == "windows": # Windows conda activation return [ self.conda_executable, "run", "-n", env_name, "--no-capture-output", ] + command else: # Linux conda activation return [self.conda_executable, "run", "-n", env_name] + command def validate_environment(self, env_name: str) -> bool: """Validate that an environment exists and is functional.""" try: command = self.build_conda_command(env_name, ["python", "--version"]) result = subprocess.run(command, capture_output=True, text=True, timeout=15) return result.returncode == 0 except Exception: return False