aiteamutils 0.2.50__py3-none-any.whl → 0.2.52__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 +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
|
+
)
|