MINDI-1.5-Vision-Coder / src /utils /env_loader.py
Faaz
feat: initial project scaffold for MINDI 1.5 Vision-Coder
553fbf7
"""
MINDI 1.5 Vision-Coder β€” Environment Variable Loader
Loads secrets from .env, validates required keys, and provides
typed access to environment configuration.
"""
from __future__ import annotations
import os
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Optional
from dotenv import load_dotenv
@dataclass
class EnvValidationResult:
"""Result of environment variable validation."""
valid: bool
missing: list[str]
warnings: list[str]
class EnvLoader:
"""
Loads and validates environment variables from .env files.
Usage:
env = EnvLoader()
env.load()
env.validate()
key = env.get("TAVILY_API_KEY")
"""
REQUIRED_KEYS = [
"HUGGINGFACE_TOKEN",
"TAVILY_API_KEY",
"WANDB_API_KEY",
"E2B_API_KEY",
]
OPTIONAL_KEYS = [
"HUGGINGFACE_REPO",
"WANDB_PROJECT",
"WANDB_ENTITY",
"MODEL_NAME",
"BASE_MODEL_PATH",
"FINETUNED_MODEL_PATH",
"API_HOST",
"API_PORT",
"API_WORKERS",
"DEVICE",
"MIXED_PRECISION",
"MAX_SEQ_LENGTH",
"TRAINING_OUTPUT_DIR",
"LOG_DIR",
"DATA_DIR",
"CHECKPOINT_DIR",
"SANDBOX_TYPE",
"MAX_SEARCH_RESULTS",
"SEARCH_TIMEOUT",
"CLOUD_GPU_HOST",
"CLOUD_GPU_USER",
"CLOUD_GPU_SSH_KEY",
"PROJECT_NAME",
"STARTUP_NAME",
"HF_USERNAME",
]
KEY_PREFIXES = {
"HUGGINGFACE_TOKEN": "hf_",
"TAVILY_API_KEY": "tvly-",
"E2B_API_KEY": "e2b_",
}
def __init__(self, env_path: Optional[Path] = None) -> None:
self.env_path = env_path or Path(__file__).resolve().parents[2] / ".env"
self._loaded = False
def load(self, override: bool = False) -> None:
"""Load environment variables from .env file."""
if not self.env_path.exists():
raise FileNotFoundError(
f".env file not found at {self.env_path}\n"
f"Copy .env.example to .env and fill in your API keys."
)
load_dotenv(self.env_path, override=override)
self._loaded = True
def validate(self) -> EnvValidationResult:
"""Validate that all required environment variables are set and well-formed."""
if not self._loaded:
self.load()
missing: list[str] = []
warnings: list[str] = []
for key in self.REQUIRED_KEYS:
value = os.environ.get(key, "").strip()
if not value:
missing.append(key)
continue
# Check prefix format
expected_prefix = self.KEY_PREFIXES.get(key)
if expected_prefix and not value.startswith(expected_prefix):
warnings.append(
f"{key} doesn't start with expected prefix '{expected_prefix}'"
)
return EnvValidationResult(
valid=len(missing) == 0,
missing=missing,
warnings=warnings,
)
def get(self, key: str, default: Optional[str] = None) -> str:
"""Get an environment variable with optional default."""
if not self._loaded:
self.load()
return os.environ.get(key, default or "")
def get_int(self, key: str, default: int = 0) -> int:
"""Get an environment variable as an integer."""
value = self.get(key)
if not value:
return default
return int(value)
def get_path(self, key: str, default: str = ".") -> Path:
"""Get an environment variable as a Path."""
return Path(self.get(key, default))
# ── Convenience properties ──
@property
def huggingface_token(self) -> str:
return self.get("HUGGINGFACE_TOKEN")
@property
def tavily_api_key(self) -> str:
return self.get("TAVILY_API_KEY")
@property
def wandb_api_key(self) -> str:
return self.get("WANDB_API_KEY")
@property
def e2b_api_key(self) -> str:
return self.get("E2B_API_KEY")
@property
def model_name(self) -> str:
return self.get("MODEL_NAME", "deepseek-ai/DeepSeek-Coder-V2-Lite-Instruct")
@property
def device(self) -> str:
return self.get("DEVICE", "cuda")
@property
def mixed_precision(self) -> str:
return self.get("MIXED_PRECISION", "bf16")
@property
def sandbox_type(self) -> str:
return self.get("SANDBOX_TYPE", "e2b")
def print_status(self) -> None:
"""Print a summary of environment variable status."""
result = self.validate()
print("\n╔══════════════════════════════════════════╗")
print("β•‘ MINDI 1.5 β€” Environment Status β•‘")
print("╠══════════════════════════════════════════╣")
for key in self.REQUIRED_KEYS:
value = os.environ.get(key, "")
if value:
masked = value[:8] + "..." + value[-4:]
print(f" βœ… {key:<25} = {masked}")
else:
print(f" ❌ {key:<25} = NOT SET")
print("╠──────────────────────────────────────────╣")
for key in self.OPTIONAL_KEYS:
value = os.environ.get(key, "")
if value:
display = value if len(value) <= 40 else value[:37] + "..."
print(f" βœ… {key:<25} = {display}")
else:
print(f" βšͺ {key:<25} = (not set)")
print("╠══════════════════════════════════════════╣")
if result.valid:
print(" βœ… All required keys are set!")
else:
print(f" ❌ Missing {len(result.missing)} required key(s): {', '.join(result.missing)}")
for w in result.warnings:
print(f" ⚠️ {w}")
print("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n")
if __name__ == "__main__":
env = EnvLoader()
env.load()
env.print_status()
result = env.validate()
sys.exit(0 if result.valid else 1)