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
|
+
)
|