- Improved RevisionComparator with fuzzy matching (RapidFuzz) and dynamic DB material loading - Enhanced regex patterns for better size/material extraction - Initialized Alembic for schema migrations and created baseline migration - Added entrypoint.sh for automated migrations in Docker - Fixed SyntaxError in fitting_classifier.py - Updated test suite with new functionality tests
288 lines
11 KiB
Python
288 lines
11 KiB
Python
"""
|
|
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", "PATCH", "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
|
|
extra = "ignore"
|
|
|
|
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
|