aiteamutils 0.2.50__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/base_repository.py +68 -364
- aiteamutils/base_service.py +46 -43
- aiteamutils/database.py +35 -48
- aiteamutils/dependencies.py +183 -105
- aiteamutils/security.py +52 -289
- aiteamutils/version.py +1 -1
- {aiteamutils-0.2.50.dist-info → aiteamutils-0.2.52.dist-info}/METADATA +1 -1
- aiteamutils-0.2.52.dist-info/RECORD +16 -0
- aiteamutils-0.2.50.dist-info/RECORD +0 -16
- {aiteamutils-0.2.50.dist-info → aiteamutils-0.2.52.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,63 +997,49 @@ 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
|
-
"""데이터베이스
|
1001
|
-
|
1019
|
+
"""데이터베이스 세션 의존성
|
1020
|
+
|
1002
1021
|
Yields:
|
1003
1022
|
AsyncSession: 데이터베이스 세션
|
1004
|
-
|
1023
|
+
|
1005
1024
|
Raises:
|
1006
|
-
CustomException:
|
1025
|
+
CustomException: 데이터베이스 연결 오류
|
1007
1026
|
"""
|
1008
|
-
|
1009
|
-
raise CustomException(
|
1010
|
-
ErrorCode.DB_CONNECTION_ERROR,
|
1011
|
-
detail="Database service is not initialized",
|
1012
|
-
source_function="get_db"
|
1013
|
-
)
|
1027
|
+
db_service = await get_database_service()
|
1014
1028
|
|
1015
|
-
|
1016
|
-
if not db_service or not db_service.engine:
|
1017
|
-
raise CustomException(
|
1018
|
-
ErrorCode.DB_CONNECTION_ERROR,
|
1019
|
-
detail="Database service or engine is not properly initialized",
|
1020
|
-
source_function="get_db"
|
1021
|
-
)
|
1022
|
-
|
1023
|
-
try:
|
1024
|
-
session = db_service.get_session()
|
1025
|
-
if session is None:
|
1026
|
-
raise CustomException(
|
1027
|
-
ErrorCode.DB_CONNECTION_ERROR,
|
1028
|
-
detail="Failed to create database session",
|
1029
|
-
source_function="get_db"
|
1030
|
-
)
|
1031
|
-
|
1032
|
-
# 세션이 유효한지 확인
|
1029
|
+
async with db_service.get_session() as session:
|
1033
1030
|
try:
|
1031
|
+
# 세션이 유효한지 확인
|
1034
1032
|
await session.execute(select(1))
|
1033
|
+
yield session
|
1035
1034
|
except Exception as e:
|
1036
|
-
|
1035
|
+
if isinstance(e, CustomException):
|
1036
|
+
raise e
|
1037
1037
|
raise CustomException(
|
1038
1038
|
ErrorCode.DB_CONNECTION_ERROR,
|
1039
|
-
detail="
|
1039
|
+
detail=f"Failed to get database session: {str(e)}",
|
1040
1040
|
source_function="get_db",
|
1041
1041
|
original_error=e
|
1042
1042
|
)
|
1043
|
-
|
1044
|
-
yield session
|
1045
|
-
except Exception as e:
|
1046
|
-
if isinstance(e, CustomException):
|
1047
|
-
raise e
|
1048
|
-
raise CustomException(
|
1049
|
-
ErrorCode.DB_CONNECTION_ERROR,
|
1050
|
-
detail=f"Failed to get database session: {str(e)}",
|
1051
|
-
source_function="get_db",
|
1052
|
-
original_error=e
|
1053
|
-
)
|
1054
|
-
finally:
|
1055
|
-
await session.close()
|
1056
1043
|
|
1057
1044
|
async def get_database_service() -> DatabaseService:
|
1058
1045
|
"""DatabaseService 의존성
|
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
|
+
)
|