import os import re import urllib.parse from dotenv import load_dotenv class Config: """Singleton configuration class with thread-safe implementation""" _instance = None _initialized = False def __new__(cls): if cls._instance is None: cls._instance = super(Config, cls).__new__(cls) return cls._instance def __init__(self): if not self._initialized: load_dotenv() # Detect if running on HF Spaces self.is_hf_space = bool(os.getenv("SPACE_ID")) # API tokens self.hf_token = os.getenv("HF_TOKEN") self.openai_api_key = os.getenv("OPENAI_API_KEY") self.nasa_api_key = os.getenv("NASA_API_KEY") # API endpoints self.hf_api_url = self._sanitize_url(os.getenv("HF_API_ENDPOINT_URL", "https://api-inference.huggingface.co/v1/")) # Fallback settings self.use_fallback = os.getenv("USE_FALLBACK", "true").lower() == "true" # Local model configuration self.local_model_name = os.getenv("LOCAL_MODEL_NAME", "mistral:latest") self.ollama_host = self._sanitize_url(os.getenv("OLLAMA_HOST", "")) # OpenWeather API self.openweather_api_key = os.getenv("OPENWEATHER_API_KEY") self._initialized = True def _sanitize_url(self, url: str) -> str: """Sanitize URL by removing whitespace and control characters""" if not url: return "" # Remove leading/trailing whitespace and control characters url = url.strip() # Remove any newlines, carriage returns, tabs, and null bytes url = re.sub(r'[\r\n\t\0\x0b\x0c]+', '', url) # Remove any trailing URL encoding artifacts url = url.rstrip('%0a').rstrip('%0d') # Handle the specific case where environment variable name is included if 'hf_api_endpoint_url=' in url.lower(): # Extract the actual URL part url = url.split('=')[-1] # Remove any protocol prefix issues url = url.replace('hf_api_endpoint_url=', '') url = url.replace('HF_API_ENDPOINT_URL=', '') # Ensure proper URL format if url and not url.startswith(('http://', 'https://')): # Check if it looks like a domain if re.match(r'^[a-zA-Z0-9.-]+(?:\.[a-zA-Z]{2,})+', url) or 'huggingface.cloud' in url: url = 'https://' + url else: url = 'https://' + url # Remove double slashes (but keep ://) if '://' in url: protocol, rest = url.split('://', 1) rest = re.sub(r'//+', '/', rest) url = protocol + '://' + rest return url def _sanitize_host(self, host: str) -> str: """Sanitize host by removing whitespace and control characters""" if not host: return "" # Remove leading/trailing whitespace and control characters host = host.strip() # Remove any newlines, carriage returns, tabs, and null bytes host = re.sub(r'[\r\n\t\0\x0b\x0c]+', '', host) # Remove any trailing URL encoding artifacts host = host.rstrip('%0a').rstrip('%0d') return host # Global config instance config = Config()