"""Application configuration using pydantic-settings.""" from __future__ import annotations import os from enum import Enum from pathlib import Path from typing import Optional from pydantic import Field, field_validator from pydantic_settings import BaseSettings class EnvType(str, Enum): CONDA = "conda" VENV = "venv" VENV_UV = "venv-uv" class Settings(BaseSettings): """Application settings loaded from environment variables.""" # Server settings app_name: str = "MCP Code Executor" debug: bool = False host: str = "0.0.0.0" port: int = 7860 # Code storage code_storage_dir: str = Field( default="/app/code_storage", description="Directory where generated code files are stored", ) # Environment configuration env_type: EnvType = Field(default=EnvType.VENV_UV, description="Python environment type") conda_env_name: Optional[str] = Field(default=None, description="Conda environment name") venv_path: Optional[str] = Field(default=None, description="Path to virtualenv") uv_venv_path: Optional[str] = Field( default="/app/executor_venv", description="Path to UV virtualenv" ) # Execution settings execution_timeout: int = Field( default=120, description="Maximum execution time in seconds", ge=5, le=600 ) max_concurrent_executions: int = Field( default=20, description="Maximum concurrent code executions", ge=1, le=100 ) max_output_size: int = Field( default=1_048_576, description="Maximum output size in bytes (1MB)", ge=1024 ) max_file_size: int = Field( default=10_485_760, description="Maximum file size in bytes (10MB)", ge=1024 ) # HF Space hf_space: bool = Field(default=False, description="Running on HuggingFace Space") @field_validator("code_storage_dir") @classmethod def ensure_storage_dir_exists(cls, v: str) -> str: path = Path(v) path.mkdir(parents=True, exist_ok=True) return str(path.resolve()) def get_python_executable(self) -> str: """Get the Python executable path for the configured environment.""" if self.env_type == EnvType.CONDA: if not self.conda_env_name: raise ValueError("CONDA_ENV_NAME must be set when ENV_TYPE is conda") return f"conda run -n {self.conda_env_name} python" elif self.env_type == EnvType.VENV: if not self.venv_path: raise ValueError("VENV_PATH must be set when ENV_TYPE is venv") return str(Path(self.venv_path) / "bin" / "python") elif self.env_type == EnvType.VENV_UV: if not self.uv_venv_path: raise ValueError("UV_VENV_PATH must be set when ENV_TYPE is venv-uv") return str(Path(self.uv_venv_path) / "bin" / "python") raise ValueError(f"Unknown environment type: {self.env_type}") def get_pip_command(self) -> list[str]: """Get the pip install command for the configured environment.""" if self.env_type == EnvType.CONDA: return ["conda", "install", "-n", self.conda_env_name, "-y"] elif self.env_type == EnvType.VENV: pip_path = str(Path(self.venv_path) / "bin" / "pip") return [pip_path, "install"] elif self.env_type == EnvType.VENV_UV: return ["uv", "pip", "install", "--python", str(Path(self.uv_venv_path) / "bin" / "python")] raise ValueError(f"Unknown environment type: {self.env_type}") model_config = { "env_prefix": "", "case_sensitive": False, "env_file": ".env", "env_file_encoding": "utf-8", } # Singleton settings instance _settings: Optional[Settings] = None def get_settings() -> Settings: """Get or create the settings singleton.""" global _settings if _settings is None: _settings = Settings() return _settings def update_settings(**kwargs) -> Settings: """Update settings with new values (for dynamic configuration).""" global _settings current = get_settings() new_data = current.model_dump() new_data.update(kwargs) _settings = Settings(**new_data) return _settings