aiteamutils 0.2.51__py3-none-any.whl → 0.2.52__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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
- self.db.add(entity)
769
- await self.db.flush()
770
-
771
- return entity
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
 
@@ -1,135 +1,213 @@
1
- from typing import Type, Dict, Tuple, Any, Callable
2
- from fastapi import Depends, status
3
- from fastapi.security import OAuth2PasswordBearer
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
- class ServiceRegistry:
13
- """서비스 레지스트리 클래스"""
14
- def __init__(self):
15
- self._services: Dict[str, Tuple[Type[Any], Type[Any]]] = {}
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
- def register(self, name: str, repository_class: Type[Any], service_class: Type[Any]) -> None:
18
- """서비스를 등록합니다.
22
+ Yields:
23
+ AsyncSession: 데이터베이스 세션
19
24
 
20
- Args:
21
- name (str): 서비스 이름
22
- repository_class (Type[Any]): 레포지토리 클래스
23
- service_class (Type[Any]): 서비스 클래스
24
- """
25
- self._services[name] = (repository_class, service_class)
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
- def get(self, name: str) -> Tuple[Type[Any], Type[Any]]:
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
- Args:
31
- name (str): 서비스 이름
32
-
33
- Returns:
34
- Tuple[Type[Any], Type[Any]]: (레포지토리 클래스, 서비스 클래스)
35
-
36
- Raises:
37
- CustomException: 등록되지 않은 서비스인 경우
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.NOT_FOUND,
42
- detail=f"Service '{name}' is not registered",
43
- source_function="ServiceRegistry.get"
80
+ ErrorCode.SERVICE_NOT_FOUND,
81
+ detail=service_name,
82
+ source_function="dependencies._get_service"
44
83
  )
45
- return self._services[name]
46
-
47
- # ServiceRegistry 초기화
48
- service_registry = ServiceRegistry()
49
-
50
- def get_service(name: str):
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
- name (str): 서비스 이름
55
-
118
+ service_name: 서비스 이름
119
+
56
120
  Returns:
57
- Callable: 서비스 인스턴스를 반환하는 의존성 함수
121
+ Callable: 서비스 의존성 함수
58
122
  """
59
- def _get_service(
60
- db_service: DatabaseService = Depends(get_database_service),
123
+ async def _get_service_dependency(
124
+ request: Request,
61
125
  session: AsyncSession = Depends(get_db)
62
- ):
63
- try:
64
- repository_class, service_class = service_registry.get(name)
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
- token: str = Depends(oauth2_scheme),
83
- db_service: DatabaseServiceManager = Depends(get_database_service)
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
- token (str): OAuth2 토큰
89
- db_service (DatabaseServiceManager): DatabaseServiceManager 객체
90
-
138
+ request: FastAPI 요청 객체
139
+ session: 데이터베이스 세션
140
+ auth_service: 인증 서비스
141
+
91
142
  Returns:
92
- User: 현재 사용자
93
-
143
+ Dict[str, Any]: 사용자 정보
144
+
94
145
  Raises:
95
- CustomException: 인증 실패 시 예외
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
- settings = get_settings()
162
+ # JWT 토큰 디코딩
99
163
  payload = jwt.decode(
100
164
  token,
101
- settings.JWT_SECRET,
102
- algorithms=[settings.JWT_ALGORITHM],
103
- audience=settings.TOKEN_AUDIENCE
165
+ settings.jwt_secret,
166
+ algorithms=[settings.jwt_algorithm],
167
+ issuer=settings.token_issuer,
168
+ audience=settings.token_audience
104
169
  )
105
- user_ulid = payload.get("sub")
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 CustomException(
108
- ErrorCode.INVALID_TOKEN,
109
- source_function="dependencies.py / get_current_user"
183
+ raise HTTPException(
184
+ status_code=status.HTTP_401_UNAUTHORIZED,
185
+ detail="Invalid token payload",
186
+ headers={"WWW-Authenticate": "Bearer"}
110
187
  )
111
- except JWTError:
112
- raise CustomException(
113
- ErrorCode.INVALID_TOKEN,
114
- detail=token[:10] + "...",
115
- source_function="dependencies.py / get_current_user"
116
- )
117
-
118
- try:
119
- repository_class, _ = service_registry.get("user")
120
- user_repo = repository_class(db_service)
121
- user = await user_repo.get_user(user_ulid, by="ulid")
122
- except ValueError:
123
- raise CustomException(
124
- ErrorCode.SERVICE_NOT_REGISTERED,
125
- detail="User service is not registered",
126
- source_function="dependencies.py / get_current_user"
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
- return user
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
+ )