""" TK-MP-Project 설정 관리 환경별 설정을 중앙화하여 관리 """ import os from typing import List, Optional, Dict, Any from pathlib import Path from pydantic_settings import BaseSettings from pydantic import Field, validator import json class DatabaseSettings(BaseSettings): """데이터베이스 설정""" url: str = Field( default="postgresql://tkmp_user:tkmp_password_2025@postgres:5432/tk_mp_bom", description="데이터베이스 연결 URL" ) pool_size: int = Field(default=10, description="연결 풀 크기") max_overflow: int = Field(default=20, description="최대 오버플로우") pool_timeout: int = Field(default=30, description="연결 타임아웃 (초)") pool_recycle: int = Field(default=3600, description="연결 재활용 시간 (초)") echo: bool = Field(default=False, description="SQL 로그 출력 여부") class Config: env_prefix = "DB_" class RedisSettings(BaseSettings): """Redis 설정""" url: str = Field(default="redis://redis:6379", description="Redis 연결 URL") max_connections: int = Field(default=20, description="최대 연결 수") socket_timeout: int = Field(default=5, description="소켓 타임아웃 (초)") socket_connect_timeout: int = Field(default=5, description="연결 타임아웃 (초)") retry_on_timeout: bool = Field(default=True, description="타임아웃 시 재시도") decode_responses: bool = Field(default=False, description="응답 디코딩 여부") class Config: env_prefix = "REDIS_" class SecuritySettings(BaseSettings): """보안 설정""" cors_origins: List[str] = Field(default=[], description="CORS 허용 도메인") cors_methods: List[str] = Field( default=["GET", "POST", "PUT", "DELETE", "OPTIONS"], description="CORS 허용 메서드" ) cors_headers: List[str] = Field( default=["*"], description="CORS 허용 헤더" ) cors_credentials: bool = Field(default=True, description="CORS 자격증명 허용") # 파일 업로드 보안 max_file_size: int = Field(default=50 * 1024 * 1024, description="최대 파일 크기 (bytes)") allowed_file_extensions: List[str] = Field( default=['.xlsx', '.xls', '.csv'], description="허용된 파일 확장자" ) upload_path: str = Field(default="uploads", description="업로드 경로") # API 보안 api_key_header: str = Field(default="X-API-Key", description="API 키 헤더명") rate_limit_per_minute: int = Field(default=100, description="분당 요청 제한") class Config: env_prefix = "SECURITY_" class LoggingSettings(BaseSettings): """로깅 설정""" level: str = Field(default="INFO", description="로그 레벨") file_path: str = Field(default="logs/app.log", description="로그 파일 경로") max_file_size: int = Field(default=10 * 1024 * 1024, description="로그 파일 최대 크기") backup_count: int = Field(default=5, description="백업 파일 수") format: str = Field( default="%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s", description="로그 포맷" ) date_format: str = Field(default="%Y-%m-%d %H:%M:%S", description="날짜 포맷") # 환경별 로그 레벨 development_level: str = Field(default="DEBUG", description="개발 환경 로그 레벨") production_level: str = Field(default="INFO", description="운영 환경 로그 레벨") test_level: str = Field(default="WARNING", description="테스트 환경 로그 레벨") class Config: env_prefix = "LOG_" class PerformanceSettings(BaseSettings): """성능 설정""" # 캐시 설정 cache_ttl_default: int = Field(default=3600, description="기본 캐시 TTL (초)") cache_ttl_files: int = Field(default=300, description="파일 목록 캐시 TTL") cache_ttl_materials: int = Field(default=600, description="자재 목록 캐시 TTL") cache_ttl_jobs: int = Field(default=1800, description="작업 목록 캐시 TTL") cache_ttl_classification: int = Field(default=3600, description="분류 결과 캐시 TTL") cache_ttl_statistics: int = Field(default=900, description="통계 데이터 캐시 TTL") # 파일 처리 설정 chunk_size: int = Field(default=1000, description="파일 처리 청크 크기") max_workers: int = Field(default=4, description="최대 워커 수") memory_limit_mb: int = Field(default=512, description="메모리 제한 (MB)") class Config: env_prefix = "PERF_" class Settings(BaseSettings): """메인 애플리케이션 설정""" # 기본 설정 app_name: str = Field(default="TK-MP BOM Management API", description="애플리케이션 이름") app_version: str = Field(default="1.0.0", description="애플리케이션 버전") app_description: str = Field( default="자재 분류 및 프로젝트 관리 시스템", description="애플리케이션 설명" ) debug: bool = Field(default=False, description="디버그 모드") # 환경 설정 environment: str = Field( default="development", description="실행 환경 (development, production, test, synology)" ) # 서버 설정 host: str = Field(default="0.0.0.0", description="서버 호스트") port: int = Field(default=8000, description="서버 포트") reload: bool = Field(default=False, description="자동 재로드") workers: int = Field(default=1, description="워커 프로세스 수") # 하위 설정들 database: DatabaseSettings = Field(default_factory=DatabaseSettings) redis: RedisSettings = Field(default_factory=RedisSettings) security: SecuritySettings = Field(default_factory=SecuritySettings) logging: LoggingSettings = Field(default_factory=LoggingSettings) performance: PerformanceSettings = Field(default_factory=PerformanceSettings) # 추가 설정 timezone: str = Field(default="Asia/Seoul", description="시간대") language: str = Field(default="ko", description="기본 언어") class Config: env_file = ".env" env_file_encoding = "utf-8" case_sensitive = False def __init__(self, **kwargs): super().__init__(**kwargs) self._setup_environment_specific_settings() self._setup_cors_origins() self._validate_settings() @validator('environment') def validate_environment(cls, v): """환경 값 검증""" allowed_environments = ['development', 'production', 'test', 'synology'] if v not in allowed_environments: raise ValueError(f'Environment must be one of: {allowed_environments}') return v @validator('port') def validate_port(cls, v): """포트 번호 검증""" if not 1 <= v <= 65535: raise ValueError('Port must be between 1 and 65535') return v def _setup_environment_specific_settings(self): """환경별 특정 설정 적용""" if self.environment == "development": self.debug = True self.reload = True self.database.echo = True self.logging.level = self.logging.development_level elif self.environment == "production": self.debug = False self.reload = False self.database.echo = False self.logging.level = self.logging.production_level self.workers = max(2, os.cpu_count() or 1) elif self.environment == "test": self.debug = False self.reload = False self.database.echo = False self.logging.level = self.logging.test_level # 테스트용 인메모리 데이터베이스 self.database.url = "sqlite:///:memory:" elif self.environment == "synology": self.debug = False self.reload = False self.host = "0.0.0.0" self.port = 10080 def _setup_cors_origins(self): """환경별 CORS origins 설정""" if not self.security.cors_origins: cors_config = { "development": [ "http://localhost:3000", "http://localhost:5173", "http://localhost:13000", "http://127.0.0.1:3000", "http://127.0.0.1:5173", "http://127.0.0.1:13000" ], "production": [ "https://your-domain.com", "https://api.your-domain.com" ], "synology": [ "http://192.168.0.3:10173", "http://localhost:10173" ], "test": [ "http://testserver" ] } self.security.cors_origins = cors_config.get( self.environment, cors_config["development"] ) def _validate_settings(self): """설정 검증""" # 로그 디렉토리 생성 log_dir = Path(self.logging.file_path).parent log_dir.mkdir(parents=True, exist_ok=True) # 업로드 디렉토리 생성 upload_dir = Path(self.security.upload_path) upload_dir.mkdir(parents=True, exist_ok=True) def get_database_url(self) -> str: """데이터베이스 URL 반환""" return self.database.url def get_redis_url(self) -> str: """Redis URL 반환""" return self.redis.url def is_development(self) -> bool: """개발 환경 여부""" return self.environment == "development" def is_production(self) -> bool: """운영 환경 여부""" return self.environment == "production" def is_test(self) -> bool: """테스트 환경 여부""" return self.environment == "test" def get_cors_config(self) -> Dict[str, Any]: """CORS 설정 반환""" return { "allow_origins": self.security.cors_origins, "allow_methods": self.security.cors_methods, "allow_headers": self.security.cors_headers, "allow_credentials": self.security.cors_credentials } def to_dict(self) -> Dict[str, Any]: """설정을 딕셔너리로 변환""" return self.dict() def save_to_file(self, file_path: str): """설정을 파일로 저장""" with open(file_path, 'w', encoding='utf-8') as f: json.dump(self.to_dict(), f, indent=2, ensure_ascii=False, default=str) # 전역 설정 인스턴스 settings = Settings() def get_settings() -> Settings: """설정 인스턴스 반환""" return settings