Spaces:
Running
Running
| """ | |
| Environment Variable Validator | |
| νκ²½ λ³μ κ²μ¦ λ° μ€μ νμΈ | |
| μλ² μμ μ νμ νκ²½ λ³μ κ²μ¦μΌλ‘ λ°νμ μλ¬ λ°©μ§ | |
| @changelog | |
| - v1.1.0 (2026-01-25): νκ²½λ³μ κ²μ¦ κ°ν | |
| - νλ μ΄μ€νλ κ° κ°μ§ (your_, placeholder, xxx λ±) | |
| - μ΅μ κΈΈμ΄ κ²μ¦ (API ν€λ 20μ μ΄μ) | |
| - ν¨ν΄ κΈ°λ° κ²μ¦ μΆκ° | |
| """ | |
| import os | |
| import re | |
| import logging | |
| from typing import List, Dict, Any, Optional | |
| logger = logging.getLogger(__name__) | |
| class EnvValidationError(Exception): | |
| """νκ²½ λ³μ κ²μ¦ μ€ν¨ μμΈ""" | |
| pass | |
| # νλ μ΄μ€νλ ν¨ν΄ (μ΄ ν¨ν΄μΌλ‘ μμνλ©΄ μλ¬) | |
| PLACEHOLDER_PATTERNS = [ | |
| r'^your_', # your_api_key_here | |
| r'^placeholder', # placeholder | |
| r'^xxx', # xxx | |
| r'^example', # example_key | |
| r'^test_', # test_key (ν μ€νΈμ©) | |
| r'^sk-xxx', # sk-xxx... | |
| r'^AIza.*xxx', # AIzaxxxxx (Google API νλ μ΄μ€νλ) | |
| ] | |
| # API ν€ μ΅μ κΈΈμ΄ | |
| MIN_API_KEY_LENGTH = 20 | |
| class EnvValidator: | |
| """νκ²½ λ³μ κ²μ¦κΈ°""" | |
| def __init__(self): | |
| """μ΄κΈ°ν""" | |
| self.errors: List[str] = [] | |
| self.warnings: List[str] = [] | |
| self.validated: Dict[str, Any] = {} | |
| def _is_placeholder(self, value: str) -> bool: | |
| """ | |
| κ°μ΄ νλ μ΄μ€νλμΈμ§ νμΈ | |
| Args: | |
| value: κ²μ¬ν κ° | |
| Returns: | |
| νλ μ΄μ€νλ μ¬λΆ | |
| """ | |
| value_lower = value.lower().strip() | |
| for pattern in PLACEHOLDER_PATTERNS: | |
| if re.match(pattern, value_lower, re.IGNORECASE): | |
| return True | |
| return False | |
| def _check_min_length(self, key: str, value: str, min_length: int = MIN_API_KEY_LENGTH) -> bool: | |
| """ | |
| μ΅μ κΈΈμ΄ νμΈ (API ν€ κ²μ¦μ©) | |
| Args: | |
| key: νκ²½ λ³μ μ΄λ¦ | |
| value: κ° | |
| min_length: μ΅μ κΈΈμ΄ | |
| Returns: | |
| κΈΈμ΄ μΆ©μ‘± μ¬λΆ | |
| """ | |
| if len(value) < min_length: | |
| warning_msg = ( | |
| f"β οΈ '{key}' is shorter than expected ({len(value)} chars < {min_length}). " | |
| f"This may be an invalid or placeholder value." | |
| ) | |
| self.warnings.append(warning_msg) | |
| return False | |
| return True | |
| def require(self, key: str, description: str = "", is_api_key: bool = False) -> Optional[str]: | |
| """ | |
| νμ νκ²½ λ³μ νμΈ | |
| Args: | |
| key: νκ²½ λ³μ μ΄λ¦ | |
| description: μ€λͺ | |
| is_api_key: API ν€ μ¬λΆ (νλ μ΄μ€νλ λ° μ΅μ κΈΈμ΄ κ²μ¦) | |
| Returns: | |
| νκ²½ λ³μ κ° (μμΌλ©΄ None) | |
| """ | |
| value = os.getenv(key) | |
| if not value: | |
| error_msg = f"β Required environment variable '{key}' is not set" | |
| if description: | |
| error_msg += f" ({description})" | |
| self.errors.append(error_msg) | |
| return None | |
| # νλ μ΄μ€νλ κ²μ¦ (API ν€μΈ κ²½μ°) | |
| if is_api_key and self._is_placeholder(value): | |
| error_msg = ( | |
| f"β '{key}' contains a placeholder value (starts with 'your_', 'placeholder', etc.). " | |
| f"Please set the actual API key." | |
| ) | |
| self.errors.append(error_msg) | |
| return None | |
| # μ΅μ κΈΈμ΄ κ²μ¦ (API ν€μΈ κ²½μ°) | |
| if is_api_key: | |
| self._check_min_length(key, value) | |
| self.validated[key] = value | |
| logger.debug(f"β {key} is set") | |
| return value | |
| def optional( | |
| self, | |
| key: str, | |
| default: str = None, | |
| description: str = "" | |
| ) -> Optional[str]: | |
| """ | |
| μ νμ νκ²½ λ³μ νμΈ | |
| Args: | |
| key: νκ²½ λ³μ μ΄λ¦ | |
| default: κΈ°λ³Έκ° | |
| description: μ€λͺ | |
| Returns: | |
| νκ²½ λ³μ κ° λλ κΈ°λ³Έκ° | |
| """ | |
| value = os.getenv(key) | |
| if not value: | |
| if default: | |
| warning_msg = f"β οΈ Optional environment variable '{key}' not set, using default" | |
| if description: | |
| warning_msg += f" ({description})" | |
| self.warnings.append(warning_msg) | |
| self.validated[key] = default | |
| return default | |
| else: | |
| logger.debug(f"βΉοΈ Optional variable '{key}' not set") | |
| return None | |
| self.validated[key] = value | |
| logger.debug(f"β {key} is set") | |
| return value | |
| def validate_url(self, key: str, description: str = "") -> Optional[str]: | |
| """ | |
| URL νμ νκ²½ λ³μ κ²μ¦ | |
| Args: | |
| key: νκ²½ λ³μ μ΄λ¦ | |
| description: μ€λͺ | |
| Returns: | |
| URL κ° | |
| """ | |
| value = self.require(key, description) | |
| if value and not (value.startswith("http://") or value.startswith("https://")): | |
| error_msg = f"β '{key}' must be a valid URL (http:// or https://)" | |
| self.errors.append(error_msg) | |
| return None | |
| return value | |
| def validate_port(self, key: str, default: int = None) -> int: | |
| """ | |
| ν¬νΈ λ²νΈ κ²μ¦ | |
| Args: | |
| key: νκ²½ λ³μ μ΄λ¦ | |
| default: κΈ°λ³Έ ν¬νΈ | |
| Returns: | |
| ν¬νΈ λ²νΈ | |
| """ | |
| value = os.getenv(key) | |
| if not value: | |
| if default is not None: | |
| self.validated[key] = default | |
| return default | |
| else: | |
| self.errors.append(f"β Port '{key}' is not set") | |
| return None | |
| try: | |
| port = int(value) | |
| if not 1 <= port <= 65535: | |
| self.errors.append(f"β '{key}' must be between 1 and 65535") | |
| return None | |
| self.validated[key] = port | |
| return port | |
| except ValueError: | |
| self.errors.append(f"β '{key}' must be a valid number") | |
| return None | |
| def check(self) -> bool: | |
| """ | |
| κ²μ¦ κ²°κ³Ό νμΈ | |
| Returns: | |
| λͺ¨λ κ²μ¦ ν΅κ³Ό μ¬λΆ | |
| """ | |
| if self.warnings: | |
| logger.warning("Environment variable warnings:") | |
| for warning in self.warnings: | |
| logger.warning(f" {warning}") | |
| if self.errors: | |
| logger.error("Environment variable validation failed:") | |
| for error in self.errors: | |
| logger.error(f" {error}") | |
| return False | |
| logger.info("β All required environment variables are set") | |
| return True | |
| def raise_if_invalid(self): | |
| """κ²μ¦ μ€ν¨ μ μμΈ λ°μ""" | |
| if not self.check(): | |
| raise EnvValidationError( | |
| "Environment variable validation failed. " | |
| "Please check your .env file and environment settings." | |
| ) | |
| def validate_backend_env() -> Dict[str, Any]: | |
| """ | |
| λ°±μλ νμ νκ²½ λ³μ κ²μ¦ | |
| Returns: | |
| κ²μ¦λ νκ²½ λ³μ λμ λ리 | |
| Raises: | |
| EnvValidationError: νμ νκ²½ λ³μ λλ½ μ | |
| @changelog | |
| - v1.1.0: API ν€λ is_api_key=Trueλ‘ νλ μ΄μ€νλ λ° μ΅μ κΈΈμ΄ κ²μ¦ | |
| """ | |
| validator = EnvValidator() | |
| # ===== νμ νκ²½ λ³μ ===== | |
| # Gemini AI (is_api_key=Trueλ‘ νλ μ΄μ€νλ λ° κΈΈμ΄ κ²μ¦) | |
| validator.require("GEMINI_API_KEY", "Gemini AI API ν€", is_api_key=True) | |
| # Supabase URL (μλ²μ© SUPABASE_URL μ°μ , VITE_* ν΄λ°±) | |
| supabase_url = validator.validate_url("SUPABASE_URL", "Supabase νλ‘μ νΈ URL") | |
| if not supabase_url: | |
| # Fallback | |
| supabase_url = validator.validate_url("VITE_SUPABASE_URL", "Supabase νλ‘μ νΈ URL (VITE ν΄λ°±)") | |
| # Supabase Service Key (is_api_key=Trueλ‘ νλ μ΄μ€νλ λ° κΈΈμ΄ κ²μ¦) | |
| supabase_service_key = validator.require( | |
| "SUPABASE_SERVICE_ROLE_KEY", | |
| "Supabase μλΉμ€ μν ν€", | |
| is_api_key=True | |
| ) | |
| if not supabase_service_key: | |
| validator.errors.append("β 'SUPABASE_SERVICE_ROLE_KEY' is required but not set") | |
| # ===== μ νμ νκ²½ λ³μ ===== | |
| # μλ² μ€μ | |
| validator.optional("PORT", "7860", "μλ² ν¬νΈ") | |
| validator.optional("HOST", "0.0.0.0", "μλ² νΈμ€νΈ") | |
| validator.optional("LOG_LEVEL", "INFO", "λ‘κ·Έ λ 벨") | |
| validator.optional("LOG_FORMAT_JSON", "true", "JSON λ‘κ·Έ νμ μ¬μ©") | |
| # Rate Limiting | |
| validator.optional("RATE_LIMIT_PER_MINUTE", "60", "λΆλΉ μμ² νλ") | |
| validator.optional("RATE_LIMIT_PER_HOUR", "1000", "μκ°λΉ μμ² νλ") | |
| # HuggingFace (νλ‘ νΈμλμμ νμ, λ°±μλλ μ νμ ) | |
| validator.optional("HF_TOKEN", None, "HuggingFace μΈμ¦ ν ν°") | |
| # κ²μ¦ μ€ν¨ μ μμΈ λ°μ | |
| validator.raise_if_invalid() | |
| return validator.validated | |
| def print_env_summary(): | |
| """νκ²½ λ³μ μμ½ μΆλ ₯""" | |
| logger.info("=" * 60) | |
| logger.info("Environment Configuration Summary") | |
| logger.info("=" * 60) | |
| # Gemini | |
| gemini_key = os.getenv("GEMINI_API_KEY") | |
| logger.info(f"Gemini API: {'β Configured' if gemini_key else 'β Not configured'}") | |
| # Supabase | |
| supabase_url = os.getenv("SUPABASE_URL") or os.getenv("VITE_SUPABASE_URL") | |
| supabase_key = os.getenv("SUPABASE_SERVICE_ROLE_KEY") | |
| logger.info(f"Supabase URL: {'β Configured' if supabase_url else 'β Not configured'}") | |
| logger.info(f"Supabase Key: {'β Configured' if supabase_key else 'β Not configured'}") | |
| # Server | |
| port = os.getenv("PORT", "7860") | |
| log_level = os.getenv("LOG_LEVEL", "INFO") | |
| logger.info(f"Server Port: {port}") | |
| logger.info(f"Log Level: {log_level}") | |
| logger.info("=" * 60) | |