aiteamutils 0.2.51__py3-none-any.whl → 0.2.53__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_repository.py +68 -364
- aiteamutils/base_service.py +46 -43
- aiteamutils/database.py +24 -5
- aiteamutils/dependencies.py +183 -105
- aiteamutils/security.py +93 -281
- aiteamutils/version.py +1 -1
- {aiteamutils-0.2.51.dist-info → aiteamutils-0.2.53.dist-info}/METADATA +1 -1
- aiteamutils-0.2.53.dist-info/RECORD +16 -0
- aiteamutils-0.2.51.dist-info/RECORD +0 -16
- {aiteamutils-0.2.51.dist-info → aiteamutils-0.2.53.dist-info}/WHEEL +0 -0
    
        aiteamutils/database.py
    CHANGED
    
    | @@ -764,11 +764,12 @@ class DatabaseService: | |
| 764 764 | 
             
                        processed_data = self.preprocess_data(model, log_data)
         | 
| 765 765 | 
             
                        entity = model(**processed_data)
         | 
| 766 766 |  | 
| 767 | 
            -
                         | 
| 768 | 
            -
             | 
| 769 | 
            -
             | 
| 770 | 
            -
             | 
| 771 | 
            -
             | 
| 767 | 
            +
                        async with self.get_session() as session:
         | 
| 768 | 
            +
                            # 로그 엔티티 저장
         | 
| 769 | 
            +
                            session.add(entity)
         | 
| 770 | 
            +
                            await session.flush()
         | 
| 771 | 
            +
                            await session.commit()
         | 
| 772 | 
            +
                            return entity
         | 
| 772 773 |  | 
| 773 774 | 
             
                    except Exception as e:
         | 
| 774 775 | 
             
                        logging.error(f"Failed to create log: {str(e)}")
         | 
| @@ -996,6 +997,24 @@ class DatabaseService: | |
| 996 997 | 
             
                            original_error=e
         | 
| 997 998 | 
             
                        )
         | 
| 998 999 |  | 
| 1000 | 
            +
                async def create_session(self) -> AsyncSession:
         | 
| 1001 | 
            +
                    """새로운 데이터베이스 세션을 생성합니다.
         | 
| 1002 | 
            +
             | 
| 1003 | 
            +
                    Returns:
         | 
| 1004 | 
            +
                        AsyncSession: 생성된 세션
         | 
| 1005 | 
            +
             | 
| 1006 | 
            +
                    Raises:
         | 
| 1007 | 
            +
                        CustomException: 세션 생성 실패 시
         | 
| 1008 | 
            +
                    """
         | 
| 1009 | 
            +
                    if self.session_factory is None:
         | 
| 1010 | 
            +
                        raise CustomException(
         | 
| 1011 | 
            +
                            ErrorCode.DB_CONNECTION_ERROR,
         | 
| 1012 | 
            +
                            detail="session_factory is not initialized",
         | 
| 1013 | 
            +
                            source_function="DatabaseService.create_session"
         | 
| 1014 | 
            +
                        )
         | 
| 1015 | 
            +
                    
         | 
| 1016 | 
            +
                    return self.session_factory()
         | 
| 1017 | 
            +
             | 
| 999 1018 | 
             
            async def get_db() -> AsyncGenerator[AsyncSession, None]:
         | 
| 1000 1019 | 
             
                """데이터베이스 세션 의존성
         | 
| 1001 1020 |  | 
    
        aiteamutils/dependencies.py
    CHANGED
    
    | @@ -1,135 +1,213 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            from  | 
| 3 | 
            -
            from fastapi | 
| 4 | 
            -
            from jose import JWTError, jwt
         | 
| 1 | 
            +
            """의존성 관리 모듈."""
         | 
| 2 | 
            +
            from typing import Type, TypeVar, Dict, Any, Optional, Callable, List, AsyncGenerator
         | 
| 3 | 
            +
            from fastapi import Request, Depends, HTTPException, status
         | 
| 5 4 | 
             
            from sqlalchemy.ext.asyncio import AsyncSession
         | 
| 5 | 
            +
            from jose import jwt, JWTError
         | 
| 6 6 | 
             
            import logging
         | 
| 7 7 |  | 
| 8 | 
            -
            from .database import DatabaseServiceManager, DatabaseService, get_db, get_database_service
         | 
| 9 8 | 
             
            from .exceptions import CustomException, ErrorCode
         | 
| 10 9 | 
             
            from .config import get_settings
         | 
| 10 | 
            +
            from .base_service import BaseService
         | 
| 11 | 
            +
            from .base_repository import BaseRepository
         | 
| 12 | 
            +
            from .database import db_manager
         | 
| 11 13 |  | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 14 | 
            +
            T = TypeVar("T", bound=BaseService)
         | 
| 15 | 
            +
            R = TypeVar("R", bound=BaseRepository)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            _service_registry: Dict[str, Dict[str, Any]] = {}
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            async def get_db() -> AsyncGenerator[AsyncSession, None]:
         | 
| 20 | 
            +
                """데이터베이스 세션을 반환합니다.
         | 
| 16 21 |  | 
| 17 | 
            -
                 | 
| 18 | 
            -
                     | 
| 22 | 
            +
                Yields:
         | 
| 23 | 
            +
                    AsyncSession: 데이터베이스 세션
         | 
| 19 24 |  | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
                     | 
| 25 | 
            -
             | 
| 25 | 
            +
                Raises:
         | 
| 26 | 
            +
                    CustomException: 세션 생성 실패 시
         | 
| 27 | 
            +
                """
         | 
| 28 | 
            +
                try:
         | 
| 29 | 
            +
                    async with db_manager.get_session() as session:
         | 
| 30 | 
            +
                        yield session
         | 
| 31 | 
            +
                except Exception as e:
         | 
| 32 | 
            +
                    raise CustomException(
         | 
| 33 | 
            +
                        ErrorCode.DATABASE_ERROR,
         | 
| 34 | 
            +
                        detail=str(e),
         | 
| 35 | 
            +
                        source_function="dependencies.get_db",
         | 
| 36 | 
            +
                        original_error=e
         | 
| 37 | 
            +
                    )
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            def register_service(
         | 
| 40 | 
            +
                service_class: Type[T],
         | 
| 41 | 
            +
                repository_class: Optional[Type[R]] = None,
         | 
| 42 | 
            +
                **kwargs
         | 
| 43 | 
            +
            ) -> None:
         | 
| 44 | 
            +
                """서비스를 등록합니다.
         | 
| 26 45 |  | 
| 27 | 
            -
                 | 
| 28 | 
            -
                     | 
| 46 | 
            +
                Args:
         | 
| 47 | 
            +
                    service_class: 서비스 클래스
         | 
| 48 | 
            +
                    repository_class: 저장소 클래스 (선택)
         | 
| 49 | 
            +
                    **kwargs: 추가 의존성
         | 
| 50 | 
            +
                """
         | 
| 51 | 
            +
                service_name = service_class.__name__
         | 
| 52 | 
            +
                _service_registry[service_name] = {
         | 
| 53 | 
            +
                    "service_class": service_class,
         | 
| 54 | 
            +
                    "repository_class": repository_class,
         | 
| 55 | 
            +
                    "dependencies": kwargs
         | 
| 56 | 
            +
                }
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            async def _get_service(
         | 
| 59 | 
            +
                service_name: str,
         | 
| 60 | 
            +
                session: AsyncSession,
         | 
| 61 | 
            +
                request: Request
         | 
| 62 | 
            +
            ) -> BaseService:
         | 
| 63 | 
            +
                """서비스 인스턴스를 생성합니다.
         | 
| 64 | 
            +
                
         | 
| 65 | 
            +
                Args:
         | 
| 66 | 
            +
                    service_name: 서비스 이름
         | 
| 67 | 
            +
                    session: 데이터베이스 세션
         | 
| 68 | 
            +
                    request: FastAPI 요청 객체
         | 
| 29 69 |  | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
                     | 
| 39 | 
            -
                    if name not in self._services:
         | 
| 70 | 
            +
                Returns:
         | 
| 71 | 
            +
                    BaseService: 서비스 인스턴스
         | 
| 72 | 
            +
                    
         | 
| 73 | 
            +
                Raises:
         | 
| 74 | 
            +
                    CustomException: 서비스 생성 실패 시
         | 
| 75 | 
            +
                """
         | 
| 76 | 
            +
                try:
         | 
| 77 | 
            +
                    service_info = _service_registry.get(service_name)
         | 
| 78 | 
            +
                    if not service_info:
         | 
| 40 79 | 
             
                        raise CustomException(
         | 
| 41 | 
            -
                            ErrorCode. | 
| 42 | 
            -
                            detail= | 
| 43 | 
            -
                            source_function=" | 
| 80 | 
            +
                            ErrorCode.SERVICE_NOT_FOUND,
         | 
| 81 | 
            +
                            detail=service_name,
         | 
| 82 | 
            +
                            source_function="dependencies._get_service"
         | 
| 44 83 | 
             
                        )
         | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 84 | 
            +
                        
         | 
| 85 | 
            +
                    service_class = service_info["service_class"]
         | 
| 86 | 
            +
                    repository_class = service_info["repository_class"]
         | 
| 87 | 
            +
                    dependencies = service_info["dependencies"]
         | 
| 88 | 
            +
                    
         | 
| 89 | 
            +
                    # 저장소 인스턴스 생성
         | 
| 90 | 
            +
                    repository = None
         | 
| 91 | 
            +
                    if repository_class:
         | 
| 92 | 
            +
                        repository = repository_class(session=session)
         | 
| 93 | 
            +
                        
         | 
| 94 | 
            +
                    # 서비스 인스턴스 생성
         | 
| 95 | 
            +
                    service = service_class(
         | 
| 96 | 
            +
                        repository=repository,
         | 
| 97 | 
            +
                        session=session,
         | 
| 98 | 
            +
                        request=request,
         | 
| 99 | 
            +
                        **dependencies
         | 
| 100 | 
            +
                    )
         | 
| 101 | 
            +
                    
         | 
| 102 | 
            +
                    return service
         | 
| 103 | 
            +
                    
         | 
| 104 | 
            +
                except CustomException as e:
         | 
| 105 | 
            +
                    raise e
         | 
| 106 | 
            +
                except Exception as e:
         | 
| 107 | 
            +
                    raise CustomException(
         | 
| 108 | 
            +
                        ErrorCode.INTERNAL_ERROR,
         | 
| 109 | 
            +
                        detail=str(e),
         | 
| 110 | 
            +
                        source_function="dependencies._get_service",
         | 
| 111 | 
            +
                        original_error=e
         | 
| 112 | 
            +
                    )
         | 
| 52 113 |  | 
| 114 | 
            +
            def get_service(service_name: str) -> Callable:
         | 
| 115 | 
            +
                """서비스 의존성을 반환합니다.
         | 
| 116 | 
            +
                
         | 
| 53 117 | 
             
                Args:
         | 
| 54 | 
            -
                     | 
| 55 | 
            -
             | 
| 118 | 
            +
                    service_name: 서비스 이름
         | 
| 119 | 
            +
                    
         | 
| 56 120 | 
             
                Returns:
         | 
| 57 | 
            -
                    Callable: 서비스  | 
| 121 | 
            +
                    Callable: 서비스 의존성 함수
         | 
| 58 122 | 
             
                """
         | 
| 59 | 
            -
                def  | 
| 60 | 
            -
                     | 
| 123 | 
            +
                async def _get_service_dependency(
         | 
| 124 | 
            +
                    request: Request,
         | 
| 61 125 | 
             
                    session: AsyncSession = Depends(get_db)
         | 
| 62 | 
            -
                ):
         | 
| 63 | 
            -
                     | 
| 64 | 
            -
             | 
| 65 | 
            -
                        repository = repository_class(db_service)
         | 
| 66 | 
            -
                        service = service_class(repository, db_service)
         | 
| 67 | 
            -
                        service.session = session
         | 
| 68 | 
            -
                        return service
         | 
| 69 | 
            -
                    except CustomException as e:
         | 
| 70 | 
            -
                        raise e
         | 
| 71 | 
            -
                    except Exception as e:
         | 
| 72 | 
            -
                        raise CustomException(
         | 
| 73 | 
            -
                            ErrorCode.INTERNAL_ERROR,
         | 
| 74 | 
            -
                            detail=f"Failed to create service '{name}': {str(e)}",
         | 
| 75 | 
            -
                            source_function="dependencies.get_service",
         | 
| 76 | 
            -
                            original_error=e
         | 
| 77 | 
            -
                        )
         | 
| 78 | 
            -
                return _get_service
         | 
| 126 | 
            +
                ) -> BaseService:
         | 
| 127 | 
            +
                    return await _get_service(service_name, session, request)
         | 
| 128 | 
            +
                return _get_service_dependency
         | 
| 79 129 |  | 
| 80 | 
            -
            oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/users/token")
         | 
| 81 130 | 
             
            async def get_current_user(
         | 
| 82 | 
            -
                 | 
| 83 | 
            -
                 | 
| 84 | 
            -
            ) | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 131 | 
            +
                request: Request,
         | 
| 132 | 
            +
                session: AsyncSession = Depends(get_db),
         | 
| 133 | 
            +
                auth_service: BaseService = Depends(get_service("AuthService"))
         | 
| 134 | 
            +
            ) -> Dict[str, Any]:
         | 
| 135 | 
            +
                """현재 사용자 정보를 반환합니다.
         | 
| 136 | 
            +
                
         | 
| 87 137 | 
             
                Args:
         | 
| 88 | 
            -
                     | 
| 89 | 
            -
                     | 
| 90 | 
            -
             | 
| 138 | 
            +
                    request: FastAPI 요청 객체
         | 
| 139 | 
            +
                    session: 데이터베이스 세션
         | 
| 140 | 
            +
                    auth_service: 인증 서비스
         | 
| 141 | 
            +
                    
         | 
| 91 142 | 
             
                Returns:
         | 
| 92 | 
            -
                     | 
| 93 | 
            -
             | 
| 143 | 
            +
                    Dict[str, Any]: 사용자 정보
         | 
| 144 | 
            +
                    
         | 
| 94 145 | 
             
                Raises:
         | 
| 95 | 
            -
                     | 
| 146 | 
            +
                    HTTPException: 인증 실패 시
         | 
| 96 147 | 
             
                """
         | 
| 148 | 
            +
                settings = get_settings()
         | 
| 149 | 
            +
                
         | 
| 150 | 
            +
                # Authorization 헤더 검증
         | 
| 151 | 
            +
                authorization = request.headers.get("Authorization")
         | 
| 152 | 
            +
                if not authorization or not authorization.startswith("Bearer "):
         | 
| 153 | 
            +
                    raise HTTPException(
         | 
| 154 | 
            +
                        status_code=status.HTTP_401_UNAUTHORIZED,
         | 
| 155 | 
            +
                        detail="Not authenticated",
         | 
| 156 | 
            +
                        headers={"WWW-Authenticate": "Bearer"}
         | 
| 157 | 
            +
                    )
         | 
| 158 | 
            +
                    
         | 
| 159 | 
            +
                token = authorization.split(" ")[1]
         | 
| 160 | 
            +
                
         | 
| 97 161 | 
             
                try:
         | 
| 98 | 
            -
                     | 
| 162 | 
            +
                    # JWT 토큰 디코딩
         | 
| 99 163 | 
             
                    payload = jwt.decode(
         | 
| 100 164 | 
             
                        token,
         | 
| 101 | 
            -
                        settings. | 
| 102 | 
            -
                        algorithms=[settings. | 
| 103 | 
            -
                         | 
| 165 | 
            +
                        settings.jwt_secret,
         | 
| 166 | 
            +
                        algorithms=[settings.jwt_algorithm],
         | 
| 167 | 
            +
                        issuer=settings.token_issuer,
         | 
| 168 | 
            +
                        audience=settings.token_audience
         | 
| 104 169 | 
             
                    )
         | 
| 105 | 
            -
                     | 
| 170 | 
            +
                    
         | 
| 171 | 
            +
                    # 토큰 타입 검증
         | 
| 172 | 
            +
                    token_type = payload.get("token_type")
         | 
| 173 | 
            +
                    if token_type != "access":
         | 
| 174 | 
            +
                        raise HTTPException(
         | 
| 175 | 
            +
                            status_code=status.HTTP_401_UNAUTHORIZED,
         | 
| 176 | 
            +
                            detail="Invalid token type",
         | 
| 177 | 
            +
                            headers={"WWW-Authenticate": "Bearer"}
         | 
| 178 | 
            +
                        )
         | 
| 179 | 
            +
                        
         | 
| 180 | 
            +
                    # 사용자 조회
         | 
| 181 | 
            +
                    user_ulid = payload.get("user_ulid")
         | 
| 106 182 | 
             
                    if not user_ulid:
         | 
| 107 | 
            -
                        raise  | 
| 108 | 
            -
                             | 
| 109 | 
            -
                             | 
| 183 | 
            +
                        raise HTTPException(
         | 
| 184 | 
            +
                            status_code=status.HTTP_401_UNAUTHORIZED,
         | 
| 185 | 
            +
                            detail="Invalid token payload",
         | 
| 186 | 
            +
                            headers={"WWW-Authenticate": "Bearer"}
         | 
| 110 187 | 
             
                        )
         | 
| 111 | 
            -
             | 
| 112 | 
            -
                     | 
| 113 | 
            -
             | 
| 114 | 
            -
                         | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 120 | 
            -
                     | 
| 121 | 
            -
                     | 
| 122 | 
            -
                except  | 
| 123 | 
            -
                    raise  | 
| 124 | 
            -
                         | 
| 125 | 
            -
                        detail= | 
| 126 | 
            -
                         | 
| 127 | 
            -
                    )
         | 
| 128 | 
            -
             | 
| 129 | 
            -
                if not user:
         | 
| 130 | 
            -
                    raise CustomException(
         | 
| 131 | 
            -
                        ErrorCode.USER_NOT_FOUND,
         | 
| 132 | 
            -
                        source_function="dependencies.py / get_current_user"
         | 
| 188 | 
            +
                        
         | 
| 189 | 
            +
                    user = await auth_service.get_by_ulid(user_ulid)
         | 
| 190 | 
            +
                    if not user:
         | 
| 191 | 
            +
                        raise HTTPException(
         | 
| 192 | 
            +
                            status_code=status.HTTP_401_UNAUTHORIZED,
         | 
| 193 | 
            +
                            detail="User not found",
         | 
| 194 | 
            +
                            headers={"WWW-Authenticate": "Bearer"}
         | 
| 195 | 
            +
                        )
         | 
| 196 | 
            +
                        
         | 
| 197 | 
            +
                    return user
         | 
| 198 | 
            +
                    
         | 
| 199 | 
            +
                except JWTError as e:
         | 
| 200 | 
            +
                    raise HTTPException(
         | 
| 201 | 
            +
                        status_code=status.HTTP_401_UNAUTHORIZED,
         | 
| 202 | 
            +
                        detail=str(e),
         | 
| 203 | 
            +
                        headers={"WWW-Authenticate": "Bearer"}
         | 
| 133 204 | 
             
                    )
         | 
| 134 | 
            -
             | 
| 135 | 
            -
             | 
| 205 | 
            +
                except HTTPException as e:
         | 
| 206 | 
            +
                    raise e
         | 
| 207 | 
            +
                except Exception as e:
         | 
| 208 | 
            +
                    logging.error(f"Error in get_current_user: {str(e)}")
         | 
| 209 | 
            +
                    raise HTTPException(
         | 
| 210 | 
            +
                        status_code=status.HTTP_401_UNAUTHORIZED,
         | 
| 211 | 
            +
                        detail="Authentication failed",
         | 
| 212 | 
            +
                        headers={"WWW-Authenticate": "Bearer"}
         | 
| 213 | 
            +
                    )
         |