aiteamutils 0.2.58__py3-none-any.whl → 0.2.59__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aiteamutils/base_service.py +142 -553
- aiteamutils/dependencies.py +3 -92
- aiteamutils/version.py +1 -1
- {aiteamutils-0.2.58.dist-info → aiteamutils-0.2.59.dist-info}/METADATA +1 -1
- {aiteamutils-0.2.58.dist-info → aiteamutils-0.2.59.dist-info}/RECORD +6 -6
- {aiteamutils-0.2.58.dist-info → aiteamutils-0.2.59.dist-info}/WHEEL +0 -0
    
        aiteamutils/base_service.py
    CHANGED
    
    | @@ -1,393 +1,109 @@ | |
| 1 1 | 
             
            """기본 서비스 모듈."""
         | 
| 2 2 | 
             
            from datetime import datetime
         | 
| 3 | 
            -
            from typing import TypeVar, Generic, Dict, Any, List, Optional, Type | 
| 4 | 
            -
            from sqlalchemy. | 
| 5 | 
            -
            from sqlalchemy. | 
| 3 | 
            +
            from typing import TypeVar, Generic, Dict, Any, List, Optional, Type
         | 
| 4 | 
            +
            from sqlalchemy.ext.asyncio import AsyncSession
         | 
| 5 | 
            +
            from sqlalchemy.orm import DeclarativeBase
         | 
| 6 | 
            +
            from fastapi import Request
         | 
| 7 | 
            +
             | 
| 6 8 | 
             
            from .database import DatabaseService
         | 
| 7 9 | 
             
            from .exceptions import CustomException, ErrorCode
         | 
| 8 10 | 
             
            from .base_repository import BaseRepository
         | 
| 9 | 
            -
            from .security import hash_password
         | 
| 10 | 
            -
            from fastapi import Request
         | 
| 11 | 
            -
            from ulid import ULID
         | 
| 12 | 
            -
            from sqlalchemy import select
         | 
| 13 11 |  | 
| 14 12 | 
             
            ModelType = TypeVar("ModelType", bound=DeclarativeBase)
         | 
| 15 13 |  | 
| 16 14 | 
             
            class BaseService(Generic[ModelType]):
         | 
| 17 | 
            -
             | 
| 18 | 
            -
                ##################
         | 
| 19 | 
            -
                # 1. 초기화 영역 #
         | 
| 20 | 
            -
                ##################
         | 
| 21 15 | 
             
                def __init__(
         | 
| 22 16 | 
             
                    self,
         | 
| 23 | 
            -
                     | 
| 24 | 
            -
                     | 
| 17 | 
            +
                    db: DatabaseService,
         | 
| 18 | 
            +
                    repository: Optional[BaseRepository] = None,
         | 
| 19 | 
            +
                    request: Optional[Request] = None
         | 
| 25 20 | 
             
                ):
         | 
| 26 | 
            -
                    """
         | 
| 27 | 
            -
                    Args:
         | 
| 28 | 
            -
                        repository (BaseRepository[ModelType]): 레포지토리 인스턴스
         | 
| 29 | 
            -
                        additional_models (Dict[str, Type[DeclarativeBase]], optional): 추가 모델 매핑. Defaults to None.
         | 
| 30 | 
            -
                    """
         | 
| 31 | 
            -
                    self.repository = repository
         | 
| 32 | 
            -
                    self.model = repository.model
         | 
| 33 | 
            -
                    self.additional_models = additional_models or {}
         | 
| 34 | 
            -
                    self._session = None
         | 
| 35 | 
            -
                    self.searchable_fields = {
         | 
| 36 | 
            -
                        "name": {"type": "text", "description": "이름"},
         | 
| 37 | 
            -
                        "organization_ulid": {"type": "exact", "description": "조직 ID"}
         | 
| 38 | 
            -
                    }
         | 
| 39 | 
            -
                
         | 
| 40 | 
            -
                @property
         | 
| 41 | 
            -
                def session(self):
         | 
| 42 | 
            -
                    """현재 세션을 반환합니다."""
         | 
| 43 | 
            -
                    if self._session is None:
         | 
| 44 | 
            -
                        raise CustomException(
         | 
| 45 | 
            -
                            ErrorCode.DB_CONNECTION_ERROR,
         | 
| 46 | 
            -
                            detail="Database session is not set",
         | 
| 47 | 
            -
                            source_function=f"{self.__class__.__name__}.session"
         | 
| 48 | 
            -
                        )
         | 
| 49 | 
            -
                    return self._session
         | 
| 50 | 
            -
                
         | 
| 51 | 
            -
                @session.setter
         | 
| 52 | 
            -
                def session(self, value):
         | 
| 53 | 
            -
                    """세션을 설정합니다."""
         | 
| 54 | 
            -
                    self._session = value
         | 
| 55 | 
            -
                    if hasattr(self.repository, 'session'):
         | 
| 56 | 
            -
                        self.repository.session = value
         | 
| 57 | 
            -
             | 
| 58 | 
            -
                #########################
         | 
| 59 | 
            -
                # 2. 이벤트 처리 메서드 #
         | 
| 60 | 
            -
                #########################
         | 
| 61 | 
            -
                async def pre_save(self, data: Dict[str, Any]) -> Dict[str, Any]:
         | 
| 62 | 
            -
                    """저장 전 처리를 수행합니다.
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                    Args:
         | 
| 65 | 
            -
                        data (Dict[str, Any]): 저장할 데이터
         | 
| 66 | 
            -
             | 
| 67 | 
            -
                    Returns:
         | 
| 68 | 
            -
                        Dict[str, Any]: 처리된 데이터
         | 
| 69 | 
            -
                    """
         | 
| 70 | 
            -
                    return data
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                async def post_save(self, entity: ModelType) -> None:
         | 
| 73 | 
            -
                    """저장 후 처리를 수행합니다.
         | 
| 74 | 
            -
             | 
| 75 | 
            -
                    Args:
         | 
| 76 | 
            -
                        entity (ModelType): 저장된 엔티티
         | 
| 77 | 
            -
                    """
         | 
| 78 | 
            -
                    pass
         | 
| 79 | 
            -
             | 
| 80 | 
            -
                async def pre_delete(self, ulid: str) -> None:
         | 
| 81 | 
            -
                    """삭제 전 처리를 수행합니다.
         | 
| 82 | 
            -
             | 
| 83 | 
            -
                    Args:
         | 
| 84 | 
            -
                        ulid (str): 삭제할 엔티티의 ULID
         | 
| 85 | 
            -
                    """
         | 
| 86 | 
            -
                    pass
         | 
| 87 | 
            -
             | 
| 88 | 
            -
                async def post_delete(self, ulid: str) -> None:
         | 
| 89 | 
            -
                    """삭제 후 처리를 수행합니다.
         | 
| 90 | 
            -
             | 
| 91 | 
            -
                    Args:
         | 
| 92 | 
            -
                        ulid (str): 삭제된 엔티티의 ULID
         | 
| 93 | 
            -
                    """
         | 
| 94 | 
            -
                    pass
         | 
| 95 | 
            -
             | 
| 96 | 
            -
                ######################
         | 
| 97 | 
            -
                # 3. 캐시 관리 메서드 #
         | 
| 98 | 
            -
                ######################
         | 
| 99 | 
            -
                async def get_from_cache(self, key: str) -> Optional[Any]:
         | 
| 100 | 
            -
                    """캐시에서 데이터를 조회합니다.
         | 
| 101 | 
            -
             | 
| 102 | 
            -
                    Args:
         | 
| 103 | 
            -
                        key (str): 캐시 키
         | 
| 104 | 
            -
             | 
| 105 | 
            -
                    Returns:
         | 
| 106 | 
            -
                        Optional[Any]: 캐시된 데이터 또는 None
         | 
| 107 | 
            -
                    """
         | 
| 108 | 
            -
                    return None
         | 
| 109 | 
            -
             | 
| 110 | 
            -
                async def set_to_cache(self, key: str, value: Any, ttl: int = 3600) -> None:
         | 
| 111 | 
            -
                    """데이터를 캐시에 저장합니다.
         | 
| 112 | 
            -
             | 
| 113 | 
            -
                    Args:
         | 
| 114 | 
            -
                        key (str): 캐시 키
         | 
| 115 | 
            -
                        value (Any): 저장할 값
         | 
| 116 | 
            -
                        ttl (int, optional): 캐시 유효 시간(초). Defaults to 3600.
         | 
| 117 | 
            -
                    """
         | 
| 118 | 
            -
                    pass
         | 
| 119 | 
            -
             | 
| 120 | 
            -
                async def invalidate_cache(self, key: str) -> None:
         | 
| 121 | 
            -
                    """캐시를 무효화합니다.
         | 
| 122 | 
            -
             | 
| 123 | 
            -
                    Args:
         | 
| 124 | 
            -
                        key (str): 캐시 키
         | 
| 125 | 
            -
                    """
         | 
| 126 | 
            -
                    pass
         | 
| 127 | 
            -
                
         | 
| 128 | 
            -
                ##########################
         | 
| 129 | 
            -
                # 4. 비즈니스 검증 메서드 #
         | 
| 130 | 
            -
                ##########################
         | 
| 131 | 
            -
                def _validate_business_rules(self, data: Dict[str, Any]) -> None:
         | 
| 132 | 
            -
                    """비즈니스 규칙을 검증합니다.
         | 
| 133 | 
            -
             | 
| 134 | 
            -
                    Args:
         | 
| 135 | 
            -
                        data (Dict[str, Any]): 검증할 데이터
         | 
| 136 | 
            -
             | 
| 137 | 
            -
                    Raises:
         | 
| 138 | 
            -
                        CustomException: 비즈니스 규칙 위반 시
         | 
| 139 | 
            -
                    """
         | 
| 140 | 
            -
                    pass
         | 
| 141 | 
            -
             | 
| 142 | 
            -
                def _validate_permissions(self, request: Request, action: str) -> None:
         | 
| 143 | 
            -
                    """권한을 검증합니다.
         | 
| 144 | 
            -
             | 
| 145 | 
            -
                    Args:
         | 
| 146 | 
            -
                        request (Request): FastAPI 요청 객체
         | 
| 147 | 
            -
                        action (str): 수행할 작업
         | 
| 148 | 
            -
             | 
| 149 | 
            -
                    Raises:
         | 
| 150 | 
            -
                        CustomException: 권한이 없는 경우
         | 
| 151 | 
            -
                    """
         | 
| 152 | 
            -
                    pass
         | 
| 153 | 
            -
             | 
| 154 | 
            -
                ########################
         | 
| 155 | 
            -
                # 5. 응답 처리 메서드   #
         | 
| 156 | 
            -
                ########################
         | 
| 157 | 
            -
                def _handle_response_model(self, entity: ModelType, response_model: Any) -> Dict[str, Any]:
         | 
| 158 | 
            -
                    """응답 모델에 맞게 데이터를 처리합니다.
         | 
| 159 | 
            -
             | 
| 160 | 
            -
                    Args:
         | 
| 161 | 
            -
                        entity (ModelType): 처리할 엔티티
         | 
| 162 | 
            -
                        response_model (Any): 응답 모델
         | 
| 163 | 
            -
             | 
| 164 | 
            -
                    Returns:
         | 
| 165 | 
            -
                        Dict[str, Any]: 처리된 데이터
         | 
| 166 | 
            -
                    """
         | 
| 167 | 
            -
                    if not response_model:
         | 
| 168 | 
            -
                        return self._process_response(entity)
         | 
| 169 | 
            -
                        
         | 
| 170 | 
            -
                    result = self._process_response(entity)
         | 
| 21 | 
            +
                    """BaseService 초기화
         | 
| 171 22 |  | 
| 172 | 
            -
                    # response_model에 없는 필드 제거
         | 
| 173 | 
            -
                    keys_to_remove = [key for key in result if key not in response_model.model_fields]
         | 
| 174 | 
            -
                    for key in keys_to_remove:
         | 
| 175 | 
            -
                        result.pop(key)
         | 
| 176 | 
            -
                        
         | 
| 177 | 
            -
                    # 모델 검증
         | 
| 178 | 
            -
                    return response_model(**result).model_dump()
         | 
| 179 | 
            -
             | 
| 180 | 
            -
                def _handle_exclude_fields(self, data: Dict[str, Any], exclude_fields: List[str]) -> Dict[str, Any]:
         | 
| 181 | 
            -
                    """제외할 필드를 처리합니다.
         | 
| 182 | 
            -
             | 
| 183 23 | 
             
                    Args:
         | 
| 184 | 
            -
                         | 
| 185 | 
            -
                         | 
| 186 | 
            -
             | 
| 187 | 
            -
                    Returns:
         | 
| 188 | 
            -
                        Dict[str, Any]: 처리된 데이터
         | 
| 189 | 
            -
                    """
         | 
| 190 | 
            -
                    if not exclude_fields:
         | 
| 191 | 
            -
                        return data
         | 
| 192 | 
            -
                        
         | 
| 193 | 
            -
                    return {k: v for k, v in data.items() if k not in exclude_fields}
         | 
| 194 | 
            -
             | 
| 195 | 
            -
                def _validate_ulid(self, ulid: str) -> bool:
         | 
| 196 | 
            -
                    """ULID 형식을 검증합니다.
         | 
| 197 | 
            -
             | 
| 198 | 
            -
                    Args:
         | 
| 199 | 
            -
                        ulid (str): 검증할 ULID
         | 
| 200 | 
            -
             | 
| 201 | 
            -
                    Returns:
         | 
| 202 | 
            -
                        bool: 유효한 ULID 여부
         | 
| 24 | 
            +
                        db: 데이터베이스 서비스
         | 
| 25 | 
            +
                        repository: 레포지토리 인스턴스 (선택)
         | 
| 26 | 
            +
                        request: FastAPI 요청 객체 (선택)
         | 
| 203 27 | 
             
                    """
         | 
| 204 | 
            -
                     | 
| 205 | 
            -
             | 
| 206 | 
            -
             | 
| 207 | 
            -
                     | 
| 208 | 
            -
                        return False
         | 
| 209 | 
            -
             | 
| 210 | 
            -
                def _process_columns(self, entity: ModelType, exclude_extra_data: bool = True) -> Dict[str, Any]:
         | 
| 211 | 
            -
                    """엔티티의 컬럼들을 처리합니다.
         | 
| 212 | 
            -
             | 
| 213 | 
            -
                    Args:
         | 
| 214 | 
            -
                        entity (ModelType): 처리할 엔티티
         | 
| 215 | 
            -
                        exclude_extra_data (bool, optional): extra_data 컬럼 제외 여부. Defaults to True.
         | 
| 216 | 
            -
             | 
| 217 | 
            -
                    Returns:
         | 
| 218 | 
            -
                        Dict[str, Any]: 처리된 컬럼 데이터
         | 
| 219 | 
            -
                    """
         | 
| 220 | 
            -
                    result = {}
         | 
| 221 | 
            -
                    for column in entity.__table__.columns:
         | 
| 222 | 
            -
                        if exclude_extra_data and column.name == 'extra_data':
         | 
| 223 | 
            -
                            continue
         | 
| 224 | 
            -
                            
         | 
| 225 | 
            -
                        # 필드 값 처리
         | 
| 226 | 
            -
                        if hasattr(entity, column.name):
         | 
| 227 | 
            -
                            value = getattr(entity, column.name)
         | 
| 228 | 
            -
                            if isinstance(value, datetime):
         | 
| 229 | 
            -
                                value = value.isoformat()
         | 
| 230 | 
            -
                            result[column.name] = value
         | 
| 231 | 
            -
                        elif hasattr(entity, 'extra_data') and isinstance(entity.extra_data, dict):
         | 
| 232 | 
            -
                            result[column.name] = entity.extra_data.get(column.name)
         | 
| 233 | 
            -
                        else:
         | 
| 234 | 
            -
                            result[column.name] = None
         | 
| 235 | 
            -
                    
         | 
| 236 | 
            -
                    # extra_data의 내용을 최상위 레벨로 업데이트
         | 
| 237 | 
            -
                    if hasattr(entity, 'extra_data') and isinstance(entity.extra_data, dict):
         | 
| 238 | 
            -
                        result.update(entity.extra_data or {})
         | 
| 239 | 
            -
                            
         | 
| 240 | 
            -
                    return result
         | 
| 28 | 
            +
                    self.db = db
         | 
| 29 | 
            +
                    self.repository = repository
         | 
| 30 | 
            +
                    self.request = request
         | 
| 31 | 
            +
                    self.model = repository.model if repository else None
         | 
| 241 32 |  | 
| 242 33 | 
             
                def _process_response(self, entity: ModelType, response_model: Any = None) -> Dict[str, Any]:
         | 
| 243 34 | 
             
                    """응답 데이터를 처리합니다.
         | 
| 244 | 
            -
                     | 
| 245 | 
            -
             | 
| 35 | 
            +
                    
         | 
| 246 36 | 
             
                    Args:
         | 
| 247 | 
            -
                        entity | 
| 248 | 
            -
                        response_model | 
| 249 | 
            -
             | 
| 37 | 
            +
                        entity: 처리할 엔티티
         | 
| 38 | 
            +
                        response_model: 응답 모델 클래스
         | 
| 39 | 
            +
                        
         | 
| 250 40 | 
             
                    Returns:
         | 
| 251 | 
            -
                         | 
| 41 | 
            +
                        처리된 데이터
         | 
| 252 42 | 
             
                    """
         | 
| 253 43 | 
             
                    if not entity:
         | 
| 254 44 | 
             
                        return None
         | 
| 255 | 
            -
             | 
| 256 | 
            -
                    #  | 
| 257 | 
            -
                    result =  | 
| 45 | 
            +
                        
         | 
| 46 | 
            +
                    # 기본 데이터 변환
         | 
| 47 | 
            +
                    result = {}
         | 
| 48 | 
            +
                    
         | 
| 49 | 
            +
                    # 테이블 컬럼 처리
         | 
| 50 | 
            +
                    for column in entity.__table__.columns:
         | 
| 51 | 
            +
                        value = getattr(entity, column.name)
         | 
| 52 | 
            +
                        if isinstance(value, datetime):
         | 
| 53 | 
            +
                            value = value.isoformat()
         | 
| 54 | 
            +
                        result[column.name] = value
         | 
| 258 55 |  | 
| 259 | 
            -
                    # Relationship 처리 (이미 로드된 관계만 | 
| 56 | 
            +
                    # Relationship 처리 (이미 로드된 관계만)
         | 
| 260 57 | 
             
                    for relationship in entity.__mapper__.relationships:
         | 
| 261 | 
            -
                        if  | 
| 58 | 
            +
                        if relationship.key not in entity.__dict__:
         | 
| 262 59 | 
             
                            continue
         | 
| 263 60 |  | 
| 264 61 | 
             
                        try:
         | 
| 265 62 | 
             
                            value = getattr(entity, relationship.key)
         | 
| 266 | 
            -
                            # response_model이 있는 경우 해당 필드의 annotation type을 가져옴
         | 
| 267 | 
            -
                            nested_response_model = None
         | 
| 268 | 
            -
                            if response_model and relationship.key in response_model.model_fields:
         | 
| 269 | 
            -
                                field_info = response_model.model_fields[relationship.key]
         | 
| 270 | 
            -
                                nested_response_model = field_info.annotation
         | 
| 271 | 
            -
                            
         | 
| 272 63 | 
             
                            if value is not None:
         | 
| 273 64 | 
             
                                if isinstance(value, list):
         | 
| 274 65 | 
             
                                    result[relationship.key] = [
         | 
| 275 | 
            -
                                        self._process_response(item | 
| 66 | 
            +
                                        self._process_response(item)
         | 
| 276 67 | 
             
                                        for item in value
         | 
| 277 68 | 
             
                                    ]
         | 
| 278 69 | 
             
                                else:
         | 
| 279 | 
            -
                                    result[relationship.key] = self._process_response(value | 
| 70 | 
            +
                                    result[relationship.key] = self._process_response(value)
         | 
| 280 71 | 
             
                            else:
         | 
| 281 72 | 
             
                                result[relationship.key] = None
         | 
| 282 73 | 
             
                        except Exception:
         | 
| 283 74 | 
             
                            result[relationship.key] = None
         | 
| 284 | 
            -
             | 
| 75 | 
            +
                    
         | 
| 285 76 | 
             
                    # response_model이 있는 경우 필터링
         | 
| 286 77 | 
             
                    if response_model:
         | 
| 287 | 
            -
                        #  | 
| 288 | 
            -
                         | 
| 289 | 
            -
                         | 
| 290 | 
            -
             | 
| 291 | 
            -
             | 
| 292 | 
            -
             | 
| 293 | 
            -
                         | 
| 294 | 
            -
                        result.update(response_model(**result).model_dump())
         | 
| 295 | 
            -
                            
         | 
| 78 | 
            +
                        # response_model에 없는 필드 제거
         | 
| 79 | 
            +
                        keys_to_remove = [key for key in result if key not in response_model.model_fields]
         | 
| 80 | 
            +
                        for key in keys_to_remove:
         | 
| 81 | 
            +
                            result.pop(key)
         | 
| 82 | 
            +
                        # 모델 검증
         | 
| 83 | 
            +
                        return response_model(**result).model_dump()
         | 
| 84 | 
            +
                        
         | 
| 296 85 | 
             
                    return result
         | 
| 297 86 |  | 
| 298 | 
            -
                def  | 
| 299 | 
            -
                     | 
| 300 | 
            -
             | 
| 87 | 
            +
                async def create(
         | 
| 88 | 
            +
                    self,
         | 
| 89 | 
            +
                    data: Dict[str, Any],
         | 
| 90 | 
            +
                    response_model: Any = None
         | 
| 91 | 
            +
                ) -> Dict[str, Any]:
         | 
| 92 | 
            +
                    """엔티티를 생성합니다.
         | 
| 93 | 
            +
                    
         | 
| 301 94 | 
             
                    Args:
         | 
| 302 | 
            -
                         | 
| 303 | 
            -
             | 
| 304 | 
            -
                    Returns:
         | 
| 305 | 
            -
                        Dict[str, Any]: 기본 필드만 포함된 딕셔너리
         | 
| 306 | 
            -
                    """
         | 
| 307 | 
            -
                    if not entity:
         | 
| 308 | 
            -
                        return None
         | 
| 95 | 
            +
                        data: 생성할 데이터
         | 
| 96 | 
            +
                        response_model: 응답 모델 클래스
         | 
| 309 97 |  | 
| 310 | 
            -
                    return self._process_columns(entity)
         | 
| 311 | 
            -
             | 
| 312 | 
            -
                async def _create_for_model(self, model_name: str, data: Dict[str, Any], exclude_fields: List[str] = None) -> DeclarativeBase:
         | 
| 313 | 
            -
                    """지정된 모델에 대해 새로운 엔티티를 생성합니다.
         | 
| 314 | 
            -
             | 
| 315 | 
            -
                    Args:
         | 
| 316 | 
            -
                        model_name (str): 생성할 모델 이름
         | 
| 317 | 
            -
                        data (Dict[str, Any]): 생성할 엔티티 데이터
         | 
| 318 | 
            -
                        exclude_fields (List[str], optional): 제외할 필드 목록. Defaults to None.
         | 
| 319 | 
            -
             | 
| 320 | 
            -
                    Returns:
         | 
| 321 | 
            -
                        DeclarativeBase: 생성된 엔티티
         | 
| 322 | 
            -
             | 
| 323 | 
            -
                    Raises:
         | 
| 324 | 
            -
                        CustomException: 데이터베이스 작업 중 오류 발생 시
         | 
| 325 | 
            -
                    """
         | 
| 326 | 
            -
                    if model_name not in self.additional_models:
         | 
| 327 | 
            -
                        raise CustomException(
         | 
| 328 | 
            -
                            ErrorCode.INVALID_REQUEST,
         | 
| 329 | 
            -
                            detail=f"Model {model_name} not registered",
         | 
| 330 | 
            -
                            source_function=f"{self.__class__.__name__}._create_for_model"
         | 
| 331 | 
            -
                        )
         | 
| 332 | 
            -
             | 
| 333 | 
            -
                    try:
         | 
| 334 | 
            -
                        # 제외할 필드 처리
         | 
| 335 | 
            -
                        if exclude_fields:
         | 
| 336 | 
            -
                            data = {k: v for k, v in data.items() if k not in exclude_fields}
         | 
| 337 | 
            -
             | 
| 338 | 
            -
                        return await self.db_service.create_entity(self.additional_models[model_name], data)
         | 
| 339 | 
            -
                    except CustomException as e:
         | 
| 340 | 
            -
                        raise e
         | 
| 341 | 
            -
                    except Exception as e:
         | 
| 342 | 
            -
                        raise CustomException(
         | 
| 343 | 
            -
                            ErrorCode.DB_CREATE_ERROR,
         | 
| 344 | 
            -
                            detail=str(e),
         | 
| 345 | 
            -
                            source_function=f"{self.__class__.__name__}._create_for_model",
         | 
| 346 | 
            -
                            original_error=e
         | 
| 347 | 
            -
                        )
         | 
| 348 | 
            -
             | 
| 349 | 
            -
                def _process_password(self, data: Dict[str, Any]) -> Dict[str, Any]:
         | 
| 350 | 
            -
                    """비밀번호 필드가 있는 경우 해시화합니다.
         | 
| 351 | 
            -
             | 
| 352 | 
            -
                    Args:
         | 
| 353 | 
            -
                        data (Dict[str, Any]): 처리할 데이터
         | 
| 354 | 
            -
             | 
| 355 | 
            -
                    Returns:
         | 
| 356 | 
            -
                        Dict[str, Any]: 처리된 데이터
         | 
| 357 | 
            -
                    """
         | 
| 358 | 
            -
                    if "password" in data:
         | 
| 359 | 
            -
                        data["password"] = hash_password(data["password"])
         | 
| 360 | 
            -
                    return data
         | 
| 361 | 
            -
             | 
| 362 | 
            -
                #######################
         | 
| 363 | 
            -
                # 6. CRUD 작업 메서드 #
         | 
| 364 | 
            -
                #######################
         | 
| 365 | 
            -
                async def create(self, data: Dict[str, Any], exclude_fields: List[str] = None, model_name: str = None) -> Union[ModelType, DeclarativeBase]:
         | 
| 366 | 
            -
                    """새로운 엔티티를 생성합니다.
         | 
| 367 | 
            -
             | 
| 368 | 
            -
                    Args:
         | 
| 369 | 
            -
                        data (Dict[str, Any]): 생성할 엔티티 데이터
         | 
| 370 | 
            -
                        exclude_fields (List[str], optional): 제외할 필드 목록. Defaults to None.
         | 
| 371 | 
            -
                        model_name (str, optional): 생성할 모델 이름. Defaults to None.
         | 
| 372 | 
            -
             | 
| 373 98 | 
             
                    Returns:
         | 
| 374 | 
            -
                         | 
| 375 | 
            -
             | 
| 99 | 
            +
                        생성된 엔티티
         | 
| 100 | 
            +
                        
         | 
| 376 101 | 
             
                    Raises:
         | 
| 377 | 
            -
                        CustomException:  | 
| 102 | 
            +
                        CustomException: 생성 실패 시
         | 
| 378 103 | 
             
                    """
         | 
| 379 104 | 
             
                    try:
         | 
| 380 | 
            -
                         | 
| 381 | 
            -
                         | 
| 382 | 
            -
             | 
| 383 | 
            -
                        # 제외할 필드 처리
         | 
| 384 | 
            -
                        if exclude_fields:
         | 
| 385 | 
            -
                            data = {k: v for k, v in data.items() if k not in exclude_fields}
         | 
| 386 | 
            -
             | 
| 387 | 
            -
                        if model_name:
         | 
| 388 | 
            -
                            return await self._create_for_model(model_name, data)
         | 
| 389 | 
            -
             | 
| 390 | 
            -
                        return await self.repository.create(data)
         | 
| 105 | 
            +
                        entity = await self.db.create_entity(self.model, data)
         | 
| 106 | 
            +
                        return self._process_response(entity, response_model)
         | 
| 391 107 | 
             
                    except CustomException as e:
         | 
| 392 108 | 
             
                        raise e
         | 
| 393 109 | 
             
                    except Exception as e:
         | 
| @@ -398,274 +114,147 @@ class BaseService(Generic[ModelType]): | |
| 398 114 | 
             
                            original_error=e
         | 
| 399 115 | 
             
                        )
         | 
| 400 116 |  | 
| 401 | 
            -
                async def  | 
| 117 | 
            +
                async def get(
         | 
| 402 118 | 
             
                    self,
         | 
| 403 119 | 
             
                    ulid: str,
         | 
| 404 | 
            -
                     | 
| 405 | 
            -
             | 
| 406 | 
            -
                     | 
| 407 | 
            -
             | 
| 408 | 
            -
                    """기존 엔티티를 수정합니다.
         | 
| 409 | 
            -
             | 
| 120 | 
            +
                    response_model: Any = None
         | 
| 121 | 
            +
                ) -> Dict[str, Any]:
         | 
| 122 | 
            +
                    """엔티티를 조회합니다.
         | 
| 123 | 
            +
                    
         | 
| 410 124 | 
             
                    Args:
         | 
| 411 | 
            -
                        ulid | 
| 412 | 
            -
                         | 
| 413 | 
            -
                        exclude_fields (List[str], optional): 제외할 필드 목록. Defaults to None.
         | 
| 414 | 
            -
                        model_name (str, optional): 수정할 모델 이름. Defaults to None.
         | 
| 415 | 
            -
             | 
| 416 | 
            -
                    Returns:
         | 
| 417 | 
            -
                        Optional[ModelType]: 수정된 엔티티, 없으면 None
         | 
| 418 | 
            -
             | 
| 419 | 
            -
                    Raises:
         | 
| 420 | 
            -
                        CustomException: 데이터베이스 작업 중 오류 발생 시
         | 
| 421 | 
            -
                    """
         | 
| 422 | 
            -
                    try:
         | 
| 423 | 
            -
                        # 비밀번호 해시화
         | 
| 424 | 
            -
                        data = self._process_password(data)
         | 
| 425 | 
            -
             | 
| 426 | 
            -
                        # 제외할 필드 처리
         | 
| 427 | 
            -
                        if exclude_fields:
         | 
| 428 | 
            -
                            data = {k: v for k, v in data.items() if k not in exclude_fields}
         | 
| 429 | 
            -
             | 
| 430 | 
            -
                        async with self.db_service.transaction():
         | 
| 431 | 
            -
                            if model_name:
         | 
| 432 | 
            -
                                if model_name not in self.additional_models:
         | 
| 433 | 
            -
                                    raise CustomException(
         | 
| 434 | 
            -
                                        ErrorCode.INVALID_REQUEST,
         | 
| 435 | 
            -
                                        detail=f"Model {model_name} not registered",
         | 
| 436 | 
            -
                                        source_function=f"{self.__class__.__name__}.update"
         | 
| 437 | 
            -
                                    )
         | 
| 438 | 
            -
                                entity = await self.db_service.update_entity(
         | 
| 439 | 
            -
                                    self.additional_models[model_name],
         | 
| 440 | 
            -
                                    {"ulid": ulid},
         | 
| 441 | 
            -
                                    data
         | 
| 442 | 
            -
                                )
         | 
| 443 | 
            -
                                if not entity:
         | 
| 444 | 
            -
                                    raise CustomException(
         | 
| 445 | 
            -
                                        ErrorCode.NOT_FOUND,
         | 
| 446 | 
            -
                                        detail=f"{self.additional_models[model_name].__tablename__}|ulid|{ulid}",
         | 
| 447 | 
            -
                                        source_function=f"{self.__class__.__name__}.update"
         | 
| 448 | 
            -
                                    )
         | 
| 449 | 
            -
                                return entity
         | 
| 450 | 
            -
             | 
| 451 | 
            -
                            entity = await self.repository.update(ulid, data)
         | 
| 452 | 
            -
                            if not entity:
         | 
| 453 | 
            -
                                raise CustomException(
         | 
| 454 | 
            -
                                    ErrorCode.NOT_FOUND,
         | 
| 455 | 
            -
                                    detail=f"{self.model.__tablename__}|ulid|{ulid}",
         | 
| 456 | 
            -
                                    source_function=f"{self.__class__.__name__}.update"
         | 
| 457 | 
            -
                                )
         | 
| 458 | 
            -
                            return entity
         | 
| 459 | 
            -
                    except CustomException as e:
         | 
| 460 | 
            -
                        raise e
         | 
| 461 | 
            -
                    except Exception as e:
         | 
| 462 | 
            -
                        raise CustomException(
         | 
| 463 | 
            -
                            ErrorCode.DB_UPDATE_ERROR,
         | 
| 464 | 
            -
                            detail=str(e),
         | 
| 465 | 
            -
                            source_function=f"{self.__class__.__name__}.update",
         | 
| 466 | 
            -
                            original_error=e
         | 
| 467 | 
            -
                        )
         | 
| 468 | 
            -
             | 
| 469 | 
            -
                async def delete(self, ulid: str, model_name: str = None) -> bool:
         | 
| 470 | 
            -
                    """엔티티를 소프트 삭제합니다 (is_deleted = True)."""
         | 
| 471 | 
            -
                    try:
         | 
| 472 | 
            -
                        if model_name:
         | 
| 473 | 
            -
                            if model_name not in self.additional_models:
         | 
| 474 | 
            -
                                raise CustomException(
         | 
| 475 | 
            -
                                    ErrorCode.INVALID_REQUEST,
         | 
| 476 | 
            -
                                    detail=f"Model {model_name} not registered",
         | 
| 477 | 
            -
                                    source_function=f"{self.__class__.__name__}.delete"
         | 
| 478 | 
            -
                                )
         | 
| 479 | 
            -
                            
         | 
| 480 | 
            -
                            stmt = select(self.additional_models[model_name]).filter_by(ulid=ulid, is_deleted=False)
         | 
| 481 | 
            -
                            result = await self.session.execute(stmt)
         | 
| 482 | 
            -
                            entity = result.scalars().first()
         | 
| 483 | 
            -
                            
         | 
| 484 | 
            -
                            if not entity:
         | 
| 485 | 
            -
                                raise CustomException(
         | 
| 486 | 
            -
                                    ErrorCode.NOT_FOUND,
         | 
| 487 | 
            -
                                    detail=f"{self.additional_models[model_name].__tablename__}|ulid|{ulid}",
         | 
| 488 | 
            -
                                    source_function=f"{self.__class__.__name__}.delete"
         | 
| 489 | 
            -
                                )
         | 
| 490 | 
            -
                            
         | 
| 491 | 
            -
                            entity.is_deleted = True
         | 
| 492 | 
            -
                            await self.session.flush()
         | 
| 493 | 
            -
                            return True
         | 
| 494 | 
            -
             | 
| 495 | 
            -
                        return await self.repository.delete(ulid)
         | 
| 125 | 
            +
                        ulid: 조회할 엔티티의 ULID
         | 
| 126 | 
            +
                        response_model: 응답 모델 클래스
         | 
| 496 127 |  | 
| 497 | 
            -
                    except CustomException as e:
         | 
| 498 | 
            -
                        raise e
         | 
| 499 | 
            -
                    except Exception as e:
         | 
| 500 | 
            -
                        raise CustomException(
         | 
| 501 | 
            -
                            ErrorCode.DB_DELETE_ERROR,
         | 
| 502 | 
            -
                            detail=str(e),
         | 
| 503 | 
            -
                            source_function=f"{self.__class__.__name__}.delete",
         | 
| 504 | 
            -
                            original_error=e
         | 
| 505 | 
            -
                        )
         | 
| 506 | 
            -
             | 
| 507 | 
            -
                async def real_row_delete(self, ulid: str, model_name: str = None) -> bool:
         | 
| 508 | 
            -
                    """엔티티를 실제로 삭제합니다.
         | 
| 509 | 
            -
             | 
| 510 | 
            -
                    Args:
         | 
| 511 | 
            -
                        ulid (str): 삭제할 엔티티의 ULID
         | 
| 512 | 
            -
                        model_name (str, optional): 삭제할 모델 이름. Defaults to None.
         | 
| 513 | 
            -
             | 
| 514 128 | 
             
                    Returns:
         | 
| 515 | 
            -
                         | 
| 516 | 
            -
             | 
| 129 | 
            +
                        조회된 엔티티
         | 
| 130 | 
            +
                        
         | 
| 517 131 | 
             
                    Raises:
         | 
| 518 | 
            -
                        CustomException:  | 
| 132 | 
            +
                        CustomException: 조회 실패 시
         | 
| 519 133 | 
             
                    """
         | 
| 520 134 | 
             
                    try:
         | 
| 521 | 
            -
                         | 
| 522 | 
            -
             | 
| 523 | 
            -
             | 
| 524 | 
            -
             | 
| 525 | 
            -
             | 
| 526 | 
            -
             | 
| 527 | 
            -
                                )
         | 
| 528 | 
            -
                            entity = await self.db_service.retrieve_entity(
         | 
| 529 | 
            -
                                self.additional_models[model_name],
         | 
| 530 | 
            -
                                {"ulid": ulid}
         | 
| 135 | 
            +
                        entity = await self.db.get_entity(self.model, {"ulid": ulid, "is_deleted": False})
         | 
| 136 | 
            +
                        if not entity:
         | 
| 137 | 
            +
                            raise CustomException(
         | 
| 138 | 
            +
                                ErrorCode.NOT_FOUND,
         | 
| 139 | 
            +
                                detail=f"{self.model.__tablename__}|ulid|{ulid}",
         | 
| 140 | 
            +
                                source_function=f"{self.__class__.__name__}.get"
         | 
| 531 141 | 
             
                            )
         | 
| 532 | 
            -
             | 
| 533 | 
            -
                                await self.db_service.delete_entity(entity)
         | 
| 534 | 
            -
                                return True
         | 
| 535 | 
            -
                            return False
         | 
| 536 | 
            -
             | 
| 537 | 
            -
                        return await self.repository.real_row_delete(ulid)
         | 
| 142 | 
            +
                        return self._process_response(entity, response_model)
         | 
| 538 143 | 
             
                    except CustomException as e:
         | 
| 539 144 | 
             
                        raise e
         | 
| 540 145 | 
             
                    except Exception as e:
         | 
| 541 146 | 
             
                        raise CustomException(
         | 
| 542 | 
            -
                            ErrorCode. | 
| 147 | 
            +
                            ErrorCode.DB_QUERY_ERROR,
         | 
| 543 148 | 
             
                            detail=str(e),
         | 
| 544 | 
            -
                            source_function=f"{self.__class__.__name__}. | 
| 149 | 
            +
                            source_function=f"{self.__class__.__name__}.get",
         | 
| 545 150 | 
             
                            original_error=e
         | 
| 546 151 | 
             
                        )
         | 
| 547 152 |  | 
| 548 | 
            -
                #########################
         | 
| 549 | 
            -
                # 7. 조회 및 검색 메서드 #
         | 
| 550 | 
            -
                #########################
         | 
| 551 153 | 
             
                async def list(
         | 
| 552 154 | 
             
                    self,
         | 
| 553 155 | 
             
                    skip: int = 0,
         | 
| 554 156 | 
             
                    limit: int = 100,
         | 
| 555 | 
            -
                    filters: Dict[str, Any]  | 
| 556 | 
            -
                    search_params: Dict[str, Any] | None = None,
         | 
| 557 | 
            -
                    model_name: str | None = None,
         | 
| 558 | 
            -
                    request: Request | None = None,
         | 
| 157 | 
            +
                    filters: Optional[Dict[str, Any]] = None,
         | 
| 559 158 | 
             
                    response_model: Any = None
         | 
| 560 159 | 
             
                ) -> List[Dict[str, Any]]:
         | 
| 561 | 
            -
                    """엔티티 목록을 조회합니다. | 
| 160 | 
            +
                    """엔티티 목록을 조회합니다.
         | 
| 161 | 
            +
                    
         | 
| 162 | 
            +
                    Args:
         | 
| 163 | 
            +
                        skip: 건너뛸 레코드 수
         | 
| 164 | 
            +
                        limit: 조회할 최대 레코드 수
         | 
| 165 | 
            +
                        filters: 필터 조건
         | 
| 166 | 
            +
                        response_model: 응답 모델 클래스
         | 
| 167 | 
            +
                        
         | 
| 168 | 
            +
                    Returns:
         | 
| 169 | 
            +
                        엔티티 목록
         | 
| 170 | 
            +
                        
         | 
| 171 | 
            +
                    Raises:
         | 
| 172 | 
            +
                        CustomException: 조회 실패 시
         | 
| 173 | 
            +
                    """
         | 
| 562 174 | 
             
                    try:
         | 
| 563 | 
            -
                         | 
| 564 | 
            -
                             | 
| 565 | 
            -
                                raise CustomException(
         | 
| 566 | 
            -
                                    ErrorCode.INVALID_REQUEST,
         | 
| 567 | 
            -
                                    detail=f"Model {model_name} not registered",
         | 
| 568 | 
            -
                                    source_function=f"{self.__class__.__name__}.list"
         | 
| 569 | 
            -
                                )
         | 
| 570 | 
            -
                            
         | 
| 571 | 
            -
                            stmt = select(self.additional_models[model_name]).where(
         | 
| 572 | 
            -
                                self.additional_models[model_name].is_deleted == False
         | 
| 573 | 
            -
                            )
         | 
| 574 | 
            -
                            
         | 
| 575 | 
            -
                            if filters:
         | 
| 576 | 
            -
                                for key, value in filters.items():
         | 
| 577 | 
            -
                                    if value is not None:
         | 
| 578 | 
            -
                                        stmt = stmt.where(getattr(self.additional_models[model_name], key) == value)
         | 
| 579 | 
            -
                            
         | 
| 580 | 
            -
                            stmt = stmt.offset(skip).limit(limit)
         | 
| 581 | 
            -
                            result = await self.session.execute(stmt)
         | 
| 582 | 
            -
                            entities = result.scalars().all()
         | 
| 583 | 
            -
                            
         | 
| 584 | 
            -
                            return [self._process_response(entity, response_model) for entity in entities]
         | 
| 585 | 
            -
             | 
| 586 | 
            -
                        return await self.repository.list(
         | 
| 587 | 
            -
                            skip=skip,
         | 
| 588 | 
            -
                            limit=limit,
         | 
| 175 | 
            +
                        entities = await self.db.list_entities(
         | 
| 176 | 
            +
                            self.model,
         | 
| 589 177 | 
             
                            filters=filters,
         | 
| 590 | 
            -
                             | 
| 178 | 
            +
                            skip=skip,
         | 
| 179 | 
            +
                            limit=limit
         | 
| 591 180 | 
             
                        )
         | 
| 592 | 
            -
                        
         | 
| 181 | 
            +
                        return [self._process_response(entity, response_model) for entity in entities]
         | 
| 593 182 | 
             
                    except CustomException as e:
         | 
| 594 | 
            -
                        e.detail = f"Service list error for {self.repository.model.__tablename__}: {e.detail}"
         | 
| 595 | 
            -
                        e.source_function = f"{self.__class__.__name__}.list -> {e.source_function}"
         | 
| 596 183 | 
             
                        raise e
         | 
| 597 184 | 
             
                    except Exception as e:
         | 
| 598 185 | 
             
                        raise CustomException(
         | 
| 599 | 
            -
                            ErrorCode. | 
| 186 | 
            +
                            ErrorCode.DB_QUERY_ERROR,
         | 
| 600 187 | 
             
                            detail=str(e),
         | 
| 601 188 | 
             
                            source_function=f"{self.__class__.__name__}.list",
         | 
| 602 189 | 
             
                            original_error=e
         | 
| 603 190 | 
             
                        )
         | 
| 604 191 |  | 
| 605 | 
            -
                async def  | 
| 192 | 
            +
                async def update(
         | 
| 606 193 | 
             
                    self,
         | 
| 607 194 | 
             
                    ulid: str,
         | 
| 608 | 
            -
                     | 
| 609 | 
            -
                    request: Request | None = None,
         | 
| 195 | 
            +
                    data: Dict[str, Any],
         | 
| 610 196 | 
             
                    response_model: Any = None
         | 
| 611 | 
            -
                ) ->  | 
| 612 | 
            -
                    """ | 
| 613 | 
            -
             | 
| 197 | 
            +
                ) -> Dict[str, Any]:
         | 
| 198 | 
            +
                    """엔티티를 수정합니다.
         | 
| 199 | 
            +
                    
         | 
| 614 200 | 
             
                    Args:
         | 
| 615 | 
            -
                        ulid | 
| 616 | 
            -
                         | 
| 617 | 
            -
                         | 
| 618 | 
            -
                         | 
| 619 | 
            -
             | 
| 201 | 
            +
                        ulid: 수정할 엔티티의 ULID
         | 
| 202 | 
            +
                        data: 수정할 데이터
         | 
| 203 | 
            +
                        response_model: 응답 모델 클래스
         | 
| 204 | 
            +
                        
         | 
| 620 205 | 
             
                    Returns:
         | 
| 621 | 
            -
                         | 
| 206 | 
            +
                        수정된 엔티티
         | 
| 622 207 |  | 
| 623 208 | 
             
                    Raises:
         | 
| 624 | 
            -
                        CustomException:  | 
| 209 | 
            +
                        CustomException: 수정 실패 시
         | 
| 625 210 | 
             
                    """
         | 
| 626 211 | 
             
                    try:
         | 
| 627 | 
            -
                         | 
| 628 | 
            -
                        if not  | 
| 212 | 
            +
                        entity = await self.db.get_entity(self.model, {"ulid": ulid, "is_deleted": False})
         | 
| 213 | 
            +
                        if not entity:
         | 
| 629 214 | 
             
                            raise CustomException(
         | 
| 630 | 
            -
                                ErrorCode. | 
| 631 | 
            -
                                detail=f" | 
| 632 | 
            -
                                source_function=f"{self.__class__.__name__}. | 
| 633 | 
            -
                            )
         | 
| 634 | 
            -
             | 
| 635 | 
            -
                        if model_name:
         | 
| 636 | 
            -
                            if model_name not in self.additional_models:
         | 
| 637 | 
            -
                                raise CustomException(
         | 
| 638 | 
            -
                                    ErrorCode.INVALID_REQUEST,
         | 
| 639 | 
            -
                                    detail=f"Model {model_name} not registered",
         | 
| 640 | 
            -
                                    source_function=f"{self.__class__.__name__}.get"
         | 
| 641 | 
            -
                                )
         | 
| 642 | 
            -
                            entity = await self.db_service.retrieve_entity(
         | 
| 643 | 
            -
                                self.additional_models[model_name],
         | 
| 644 | 
            -
                                {"ulid": ulid, "is_deleted": False}
         | 
| 215 | 
            +
                                ErrorCode.NOT_FOUND,
         | 
| 216 | 
            +
                                detail=f"{self.model.__tablename__}|ulid|{ulid}",
         | 
| 217 | 
            +
                                source_function=f"{self.__class__.__name__}.update"
         | 
| 645 218 | 
             
                            )
         | 
| 646 | 
            -
             | 
| 647 | 
            -
             | 
| 648 | 
            -
             | 
| 649 | 
            -
             | 
| 650 | 
            -
             | 
| 651 | 
            -
             | 
| 652 | 
            -
                             | 
| 219 | 
            +
                        updated = await self.db.update_entity(entity, data)
         | 
| 220 | 
            +
                        return self._process_response(updated, response_model)
         | 
| 221 | 
            +
                    except CustomException as e:
         | 
| 222 | 
            +
                        raise e
         | 
| 223 | 
            +
                    except Exception as e:
         | 
| 224 | 
            +
                        raise CustomException(
         | 
| 225 | 
            +
                            ErrorCode.DB_UPDATE_ERROR,
         | 
| 226 | 
            +
                            detail=str(e),
         | 
| 227 | 
            +
                            source_function=f"{self.__class__.__name__}.update",
         | 
| 228 | 
            +
                            original_error=e
         | 
| 229 | 
            +
                        )
         | 
| 653 230 |  | 
| 654 | 
            -
             | 
| 231 | 
            +
                async def delete(self, ulid: str) -> bool:
         | 
| 232 | 
            +
                    """엔티티를 삭제합니다.
         | 
| 233 | 
            +
                    
         | 
| 234 | 
            +
                    Args:
         | 
| 235 | 
            +
                        ulid: 삭제할 엔티티의 ULID
         | 
| 236 | 
            +
                        
         | 
| 237 | 
            +
                    Returns:
         | 
| 238 | 
            +
                        삭제 성공 여부
         | 
| 239 | 
            +
                        
         | 
| 240 | 
            +
                    Raises:
         | 
| 241 | 
            +
                        CustomException: 삭제 실패 시
         | 
| 242 | 
            +
                    """
         | 
| 243 | 
            +
                    try:
         | 
| 244 | 
            +
                        entity = await self.db.get_entity(self.model, {"ulid": ulid, "is_deleted": False})
         | 
| 655 245 | 
             
                        if not entity:
         | 
| 656 246 | 
             
                            raise CustomException(
         | 
| 657 247 | 
             
                                ErrorCode.NOT_FOUND,
         | 
| 658 248 | 
             
                                detail=f"{self.model.__tablename__}|ulid|{ulid}",
         | 
| 659 | 
            -
                                source_function=f"{self.__class__.__name__}. | 
| 249 | 
            +
                                source_function=f"{self.__class__.__name__}.delete"
         | 
| 660 250 | 
             
                            )
         | 
| 661 | 
            -
             | 
| 662 | 
            -
                        return self._process_response(entity, response_model)
         | 
| 251 | 
            +
                        return await self.db.delete_entity(entity)
         | 
| 663 252 | 
             
                    except CustomException as e:
         | 
| 664 253 | 
             
                        raise e
         | 
| 665 254 | 
             
                    except Exception as e:
         | 
| 666 255 | 
             
                        raise CustomException(
         | 
| 667 | 
            -
                            ErrorCode. | 
| 256 | 
            +
                            ErrorCode.DB_DELETE_ERROR,
         | 
| 668 257 | 
             
                            detail=str(e),
         | 
| 669 | 
            -
                            source_function=f"{self.__class__.__name__}. | 
| 258 | 
            +
                            source_function=f"{self.__class__.__name__}.delete",
         | 
| 670 259 | 
             
                            original_error=e
         | 
| 671 260 | 
             
                        ) 
         | 
    
        aiteamutils/dependencies.py
    CHANGED
    
    | @@ -1,12 +1,9 @@ | |
| 1 1 | 
             
            """의존성 관리 모듈."""
         | 
| 2 2 | 
             
            from typing import Type, TypeVar, Dict, Any, Optional, Callable, List, AsyncGenerator
         | 
| 3 | 
            -
            from fastapi import Request, Depends | 
| 3 | 
            +
            from fastapi import Request, Depends
         | 
| 4 4 | 
             
            from sqlalchemy.ext.asyncio import AsyncSession
         | 
| 5 | 
            -
            from jose import jwt, JWTError
         | 
| 6 | 
            -
            import logging
         | 
| 7 5 |  | 
| 8 6 | 
             
            from .exceptions import CustomException, ErrorCode
         | 
| 9 | 
            -
            from .config import get_settings
         | 
| 10 7 | 
             
            from .base_service import BaseService
         | 
| 11 8 | 
             
            from .base_repository import BaseRepository
         | 
| 12 9 | 
             
            from .database import DatabaseService
         | 
| @@ -20,8 +17,7 @@ _session_provider = None | |
| 20 17 | 
             
            __all__ = [
         | 
| 21 18 | 
             
                "setup_dependencies",
         | 
| 22 19 | 
             
                "register_service",
         | 
| 23 | 
            -
                "get_service" | 
| 24 | 
            -
                "get_current_user"
         | 
| 20 | 
            +
                "get_service"
         | 
| 25 21 | 
             
            ]
         | 
| 26 22 |  | 
| 27 23 | 
             
            def setup_dependencies(session_provider: Callable[[], AsyncGenerator[AsyncSession, None]]) -> None:
         | 
| @@ -139,89 +135,4 @@ def get_service(service_name: str) -> Callable: | |
| 139 135 | 
             
                    session: AsyncSession = Depends(_session_provider)
         | 
| 140 136 | 
             
                ) -> BaseService:
         | 
| 141 137 | 
             
                    return await _get_service(service_name, session, request)
         | 
| 142 | 
            -
                return _get_service_dependency
         | 
| 143 | 
            -
             | 
| 144 | 
            -
            async def get_current_user(
         | 
| 145 | 
            -
                request: Request,
         | 
| 146 | 
            -
                session: AsyncSession,
         | 
| 147 | 
            -
                auth_service: BaseService = Depends(get_service("AuthService"))
         | 
| 148 | 
            -
            ) -> Dict[str, Any]:
         | 
| 149 | 
            -
                """현재 사용자 정보를 반환합니다.
         | 
| 150 | 
            -
                
         | 
| 151 | 
            -
                Args:
         | 
| 152 | 
            -
                    request: FastAPI 요청 객체
         | 
| 153 | 
            -
                    session: 데이터베이스 세션
         | 
| 154 | 
            -
                    auth_service: 인증 서비스
         | 
| 155 | 
            -
                    
         | 
| 156 | 
            -
                Returns:
         | 
| 157 | 
            -
                    Dict[str, Any]: 사용자 정보
         | 
| 158 | 
            -
                    
         | 
| 159 | 
            -
                Raises:
         | 
| 160 | 
            -
                    HTTPException: 인증 실패 시
         | 
| 161 | 
            -
                """
         | 
| 162 | 
            -
                settings = get_settings()
         | 
| 163 | 
            -
                
         | 
| 164 | 
            -
                # Authorization 헤더 검증
         | 
| 165 | 
            -
                authorization = request.headers.get("Authorization")
         | 
| 166 | 
            -
                if not authorization or not authorization.startswith("Bearer "):
         | 
| 167 | 
            -
                    raise HTTPException(
         | 
| 168 | 
            -
                        status_code=status.HTTP_401_UNAUTHORIZED,
         | 
| 169 | 
            -
                        detail="Not authenticated",
         | 
| 170 | 
            -
                        headers={"WWW-Authenticate": "Bearer"}
         | 
| 171 | 
            -
                    )
         | 
| 172 | 
            -
                    
         | 
| 173 | 
            -
                token = authorization.split(" ")[1]
         | 
| 174 | 
            -
                
         | 
| 175 | 
            -
                try:
         | 
| 176 | 
            -
                    # JWT 토큰 디코딩
         | 
| 177 | 
            -
                    payload = jwt.decode(
         | 
| 178 | 
            -
                        token,
         | 
| 179 | 
            -
                        settings.jwt_secret,
         | 
| 180 | 
            -
                        algorithms=[settings.jwt_algorithm],
         | 
| 181 | 
            -
                        issuer=settings.token_issuer,
         | 
| 182 | 
            -
                        audience=settings.token_audience
         | 
| 183 | 
            -
                    )
         | 
| 184 | 
            -
                    
         | 
| 185 | 
            -
                    # 토큰 타입 검증
         | 
| 186 | 
            -
                    token_type = payload.get("token_type")
         | 
| 187 | 
            -
                    if token_type != "access":
         | 
| 188 | 
            -
                        raise HTTPException(
         | 
| 189 | 
            -
                            status_code=status.HTTP_401_UNAUTHORIZED,
         | 
| 190 | 
            -
                            detail="Invalid token type",
         | 
| 191 | 
            -
                            headers={"WWW-Authenticate": "Bearer"}
         | 
| 192 | 
            -
                        )
         | 
| 193 | 
            -
                        
         | 
| 194 | 
            -
                    # 사용자 조회
         | 
| 195 | 
            -
                    user_ulid = payload.get("user_ulid")
         | 
| 196 | 
            -
                    if not user_ulid:
         | 
| 197 | 
            -
                        raise HTTPException(
         | 
| 198 | 
            -
                            status_code=status.HTTP_401_UNAUTHORIZED,
         | 
| 199 | 
            -
                            detail="Invalid token payload",
         | 
| 200 | 
            -
                            headers={"WWW-Authenticate": "Bearer"}
         | 
| 201 | 
            -
                        )
         | 
| 202 | 
            -
                        
         | 
| 203 | 
            -
                    user = await auth_service.get_by_ulid(user_ulid)
         | 
| 204 | 
            -
                    if not user:
         | 
| 205 | 
            -
                        raise HTTPException(
         | 
| 206 | 
            -
                            status_code=status.HTTP_401_UNAUTHORIZED,
         | 
| 207 | 
            -
                            detail="User not found",
         | 
| 208 | 
            -
                            headers={"WWW-Authenticate": "Bearer"}
         | 
| 209 | 
            -
                        )
         | 
| 210 | 
            -
                        
         | 
| 211 | 
            -
                    return user
         | 
| 212 | 
            -
                    
         | 
| 213 | 
            -
                except JWTError as e:
         | 
| 214 | 
            -
                    raise HTTPException(
         | 
| 215 | 
            -
                        status_code=status.HTTP_401_UNAUTHORIZED,
         | 
| 216 | 
            -
                        detail=str(e),
         | 
| 217 | 
            -
                        headers={"WWW-Authenticate": "Bearer"}
         | 
| 218 | 
            -
                    )
         | 
| 219 | 
            -
                except HTTPException as e:
         | 
| 220 | 
            -
                    raise e
         | 
| 221 | 
            -
                except Exception as e:
         | 
| 222 | 
            -
                    logging.error(f"Error in get_current_user: {str(e)}")
         | 
| 223 | 
            -
                    raise HTTPException(
         | 
| 224 | 
            -
                        status_code=status.HTTP_401_UNAUTHORIZED,
         | 
| 225 | 
            -
                        detail="Authentication failed",
         | 
| 226 | 
            -
                        headers={"WWW-Authenticate": "Bearer"}
         | 
| 227 | 
            -
                    )
         | 
| 138 | 
            +
                return _get_service_dependency
         | 
    
        aiteamutils/version.py
    CHANGED
    
    | @@ -1,2 +1,2 @@ | |
| 1 1 | 
             
            """버전 정보"""
         | 
| 2 | 
            -
            __version__ = "0.2. | 
| 2 | 
            +
            __version__ = "0.2.59"
         | 
| @@ -1,16 +1,16 @@ | |
| 1 1 | 
             
            aiteamutils/__init__.py,sha256=U9UEyU0AqpLZzbPJiBvwDEh-s2405y1IeWZ0zCqFSl0,1307
         | 
| 2 2 | 
             
            aiteamutils/base_model.py,sha256=ODEnjvUVoxQ1RPCfq8-uZTfTADIA4c7Z3E6G4EVsSX0,2708
         | 
| 3 3 | 
             
            aiteamutils/base_repository.py,sha256=vqsundoN0h7FVvgqTBEnnJNMcFpvMK0s_nxBWdIYg-U,7846
         | 
| 4 | 
            -
            aiteamutils/base_service.py,sha256= | 
| 4 | 
            +
            aiteamutils/base_service.py,sha256=o4oeX__RmUFp5kV-fb4VwBhF8nTAZNMjoWJvK7G1Wmk,8615
         | 
| 5 5 | 
             
            aiteamutils/cache.py,sha256=07xBGlgAwOTAdY5mnMOQJ5EBxVwe8glVD7DkGEkxCtw,1373
         | 
| 6 6 | 
             
            aiteamutils/config.py,sha256=YdalpJb70-txhGJAS4aaKglEZAFVWgfzw5BXSWpkUz4,3232
         | 
| 7 7 | 
             
            aiteamutils/database.py,sha256=x0x5gnSyGfwo_klL9O65RnGOQID6c9tH2miwFveVyoE,6326
         | 
| 8 | 
            -
            aiteamutils/dependencies.py,sha256= | 
| 8 | 
            +
            aiteamutils/dependencies.py,sha256=Qoy_M7r65Fv3Y8RMVBi81QLdfjfWIk-pm7O5_Przsa4,4084
         | 
| 9 9 | 
             
            aiteamutils/enums.py,sha256=ipZi6k_QD5-3QV7Yzv7bnL0MjDz-vqfO9I5L77biMKs,632
         | 
| 10 10 | 
             
            aiteamutils/exceptions.py,sha256=_lKWXq_ujNj41xN6LDE149PwsecAP7lgYWbOBbLOntg,15368
         | 
| 11 11 | 
             
            aiteamutils/security.py,sha256=xFVrjttxwXB1TTjqgRQQgQJQohQBT28vuW8FVLjvi-M,10103
         | 
| 12 12 | 
             
            aiteamutils/validators.py,sha256=3N245cZFjgwtW_KzjESkizx5BBUDaJLbbxfNO4WOFZ0,7764
         | 
| 13 | 
            -
            aiteamutils/version.py,sha256= | 
| 14 | 
            -
            aiteamutils-0.2. | 
| 15 | 
            -
            aiteamutils-0.2. | 
| 16 | 
            -
            aiteamutils-0.2. | 
| 13 | 
            +
            aiteamutils/version.py,sha256=_ql6JzqtHA14js-9_8pUBJDK_7BZm5uHqA1j4jfryCs,42
         | 
| 14 | 
            +
            aiteamutils-0.2.59.dist-info/METADATA,sha256=OOT0-1lTWlUQv7fL2MtELHAUPSj18nqiaiXRyFc8QoQ,1718
         | 
| 15 | 
            +
            aiteamutils-0.2.59.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
         | 
| 16 | 
            +
            aiteamutils-0.2.59.dist-info/RECORD,,
         | 
| 
            File without changes
         |