samchun-gemini / utils /env_validator.py
JHyeok5's picture
Upload folder using huggingface_hub
01057f3 verified
"""
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)