aiteamutils 0.2.32__py3-none-any.whl → 0.2.34__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/config.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """설정 모듈."""
2
2
  from typing import Union
3
3
  from .database import init_database_service
4
+ from .exceptions import CustomException, ErrorCode
4
5
 
5
6
  class Settings:
6
7
  """기본 설정 클래스"""
@@ -74,8 +75,12 @@ def get_settings() -> Settings:
74
75
  Settings: 설정 객체
75
76
 
76
77
  Raises:
77
- RuntimeError: 설정이 초기화되지 않은 경우
78
+ CustomException: 설정이 초기화되지 않은 경우
78
79
  """
79
80
  if _settings is None:
80
- raise RuntimeError("Settings not initialized. Call init_settings first.")
81
+ raise CustomException(
82
+ ErrorCode.INTERNAL_ERROR,
83
+ detail="settings",
84
+ source_function="get_settings"
85
+ )
81
86
  return _settings
aiteamutils/database.py CHANGED
@@ -26,10 +26,14 @@ def get_database_service() -> 'DatabaseService':
26
26
  DatabaseService: DatabaseService 인스턴스
27
27
 
28
28
  Raises:
29
- RuntimeError: DatabaseService가 초기화되지 않은 경우
29
+ CustomException: DatabaseService가 초기화되지 않은 경우
30
30
  """
31
31
  if _database_service is None:
32
- raise RuntimeError("DatabaseService not initialized. Call init_settings with db_url first.")
32
+ raise CustomException(
33
+ ErrorCode.DB_CONNECTION_ERROR,
34
+ detail="database_service",
35
+ source_function="get_database_service"
36
+ )
33
37
  return _database_service
34
38
 
35
39
  async def get_db() -> AsyncGenerator[AsyncSession, None]:
@@ -100,13 +104,21 @@ class DatabaseService:
100
104
  )
101
105
  self.db = session
102
106
  else:
103
- raise ValueError("Either db_url or session must be provided")
107
+ raise CustomException(
108
+ ErrorCode.DB_CONNECTION_ERROR,
109
+ detail="db_url|session",
110
+ source_function="DatabaseService.__init__"
111
+ )
104
112
 
105
113
  @asynccontextmanager
106
114
  async def get_session(self) -> AsyncGenerator[AsyncSession, None]:
107
115
  """데이터베이스 세션을 생성하고 반환하는 비동기 컨텍스트 매니저."""
108
116
  if self.session_factory is None:
109
- raise RuntimeError("Session factory not initialized")
117
+ raise CustomException(
118
+ ErrorCode.DB_CONNECTION_ERROR,
119
+ detail="session_factory",
120
+ source_function="DatabaseService.get_session"
121
+ )
110
122
 
111
123
  async with self.session_factory() as session:
112
124
  try:
@@ -26,10 +26,14 @@ class ServiceRegistry:
26
26
  service_class (Type): Service 클래스
27
27
 
28
28
  Raises:
29
- ValueError: 이미 등록된 서비스인 경우
29
+ CustomException: 이미 등록된 서비스인 경우
30
30
  """
31
31
  if name in self._services:
32
- raise ValueError(f"Service '{name}' is already registered.")
32
+ raise CustomException(
33
+ ErrorCode.INTERNAL_ERROR,
34
+ detail=f"service|{name}",
35
+ source_function="ServiceRegistry.register"
36
+ )
33
37
  self._services[name] = (repository_class, service_class)
34
38
 
35
39
  def get(self, name: str) -> Tuple[Type, Type]:
@@ -42,10 +46,14 @@ class ServiceRegistry:
42
46
  Tuple[Type, Type]: (Repository 클래스, Service 클래스) 튜플
43
47
 
44
48
  Raises:
45
- ValueError: 등록되지 않은 서비스인 경우
49
+ CustomException: 등록되지 않은 서비스인 경우
46
50
  """
47
51
  if name not in self._services:
48
- raise ValueError(f"Service '{name}' is not registered.")
52
+ raise CustomException(
53
+ ErrorCode.NOT_FOUND,
54
+ detail=f"service|{name}",
55
+ source_function="ServiceRegistry.get"
56
+ )
49
57
  return self._services[name]
50
58
 
51
59
  # ServiceRegistry 초기화
aiteamutils/security.py CHANGED
@@ -157,8 +157,19 @@ def verify_password(plain_password: str, hashed_password: str) -> bool:
157
157
 
158
158
  Returns:
159
159
  bool: 비밀번호 일치 여부
160
+
161
+ Raises:
162
+ CustomException: 비밀번호 검증 실패 시
160
163
  """
161
- return pwd_context.verify(plain_password, hashed_password)
164
+ try:
165
+ return pwd_context.verify(plain_password, hashed_password)
166
+ except Exception as e:
167
+ raise CustomException(
168
+ ErrorCode.INVALID_PASSWORD,
169
+ detail=plain_password,
170
+ source_function="security.verify_password",
171
+ original_error=e
172
+ )
162
173
 
163
174
  def hash_password(password: str) -> str:
164
175
  """비밀번호를 해시화합니다.
@@ -168,8 +179,19 @@ def hash_password(password: str) -> str:
168
179
 
169
180
  Returns:
170
181
  str: 해시된 비밀번호
182
+
183
+ Raises:
184
+ CustomException: 비밀번호 해시화 실패 시
171
185
  """
172
- return pwd_context.hash(password)
186
+ try:
187
+ return pwd_context.hash(password)
188
+ except Exception as e:
189
+ raise CustomException(
190
+ ErrorCode.INTERNAL_ERROR,
191
+ detail=password,
192
+ source_function="security.hash_password",
193
+ original_error=e
194
+ )
173
195
 
174
196
  def rate_limit(
175
197
  max_requests: int,
@@ -177,7 +199,7 @@ def rate_limit(
177
199
  key_func: Optional[Callable] = None
178
200
  ):
179
201
  """Rate limiting 데코레이터."""
180
- limiter = RateLimiter(max_requests, window_seconds)
202
+ rate_limits: Dict[str, Dict[str, Any]] = {}
181
203
 
182
204
  def decorator(func: Callable) -> Callable:
183
205
  @wraps(func)
@@ -194,39 +216,56 @@ def rate_limit(
194
216
  request = arg
195
217
  break
196
218
  if not request:
197
- raise SecurityError(
219
+ raise CustomException(
198
220
  ErrorCode.INTERNAL_ERROR,
199
221
  detail="Request object not found",
200
222
  source_function="rate_limit"
201
223
  )
202
224
 
203
- # Rate limit 키 생성
225
+ # 레이트 리밋 키 생성
204
226
  if key_func:
205
227
  rate_limit_key = f"rate_limit:{key_func(request)}"
206
228
  else:
207
229
  client_ip = request.client.host
208
230
  rate_limit_key = f"rate_limit:{client_ip}:{func.__name__}"
209
231
 
210
- try:
211
- if not limiter.is_allowed(rate_limit_key):
212
- remaining_time = limiter.get_remaining_time(rate_limit_key)
232
+ now = datetime.now(UTC)
233
+
234
+ # 현재 rate limit 정보 가져오기
235
+ rate_info = rate_limits.get(rate_limit_key)
236
+
237
+ if rate_info is None or (now - rate_info["start_time"]).total_seconds() >= window_seconds:
238
+ # 새로운 rate limit 설정
239
+ rate_limits[rate_limit_key] = {
240
+ "count": 1,
241
+ "start_time": now
242
+ }
243
+ else:
244
+ # 기존 rate limit 업데이트
245
+ if rate_info["count"] >= max_requests:
246
+ # rate limit 초과
247
+ remaining_seconds = window_seconds - (now - rate_info["start_time"]).total_seconds()
213
248
  raise RateLimitExceeded(
214
- detail=f"Rate limit exceeded. Try again in {int(remaining_time)} seconds",
249
+ detail=rate_limit_key,
215
250
  source_function=func.__name__,
216
- remaining_seconds=remaining_time,
251
+ remaining_seconds=remaining_seconds,
217
252
  max_requests=max_requests,
218
253
  window_seconds=window_seconds
219
254
  )
220
-
255
+ rate_info["count"] += 1
256
+
257
+ try:
258
+ # 원래 함수 실행
221
259
  return await func(*args, **kwargs)
222
-
223
- except (RateLimitExceeded, SecurityError) as e:
260
+ except CustomException as e:
261
+ # CustomException은 그대로 전파
224
262
  raise e
225
263
  except Exception as e:
226
- raise SecurityError(
264
+ # 다른 예외는 INTERNAL_ERROR로 래핑
265
+ raise CustomException(
227
266
  ErrorCode.INTERNAL_ERROR,
228
267
  detail=str(e),
229
- source_function="rate_limit",
268
+ source_function=func.__name__,
230
269
  original_error=e
231
270
  )
232
271
 
@@ -288,10 +327,10 @@ async def create_jwt_token(
288
327
  required_fields = {"username", "ulid"}
289
328
  missing_fields = required_fields - set(user_data.keys())
290
329
  if missing_fields:
291
- raise TokenCreationError(
292
- detail=f"Missing required fields: {', '.join(missing_fields)}",
293
- source_function="create_jwt_token",
294
- token_type=token_type
330
+ raise CustomException(
331
+ ErrorCode.REQUIRED_FIELD_MISSING,
332
+ detail="|".join(missing_fields),
333
+ source_function="security.create_jwt_token"
295
334
  )
296
335
 
297
336
  if token_type == "access":
@@ -337,10 +376,10 @@ async def create_jwt_token(
337
376
  algorithm=settings.JWT_ALGORITHM
338
377
  )
339
378
  except Exception as e:
340
- raise TokenCreationError(
341
- detail="Failed to encode JWT token",
342
- source_function="create_jwt_token",
343
- token_type=token_type,
379
+ raise CustomException(
380
+ ErrorCode.INTERNAL_ERROR,
381
+ detail=f"token|{token_type}",
382
+ source_function="security.create_jwt_token",
344
383
  original_error=e
345
384
  )
346
385
 
@@ -362,13 +401,13 @@ async def create_jwt_token(
362
401
 
363
402
  return token
364
403
 
365
- except (TokenCreationError, SecurityError) as e:
404
+ except CustomException as e:
366
405
  raise e
367
406
  except Exception as e:
368
- raise SecurityError(
407
+ raise CustomException(
369
408
  ErrorCode.INTERNAL_ERROR,
370
409
  detail=str(e),
371
- source_function="create_jwt_token",
410
+ source_function="security.create_jwt_token",
372
411
  original_error=e
373
412
  )
374
413
 
@@ -376,7 +415,18 @@ async def verify_jwt_token(
376
415
  token: str,
377
416
  expected_type: Optional[Literal["access", "refresh"]] = None
378
417
  ) -> Dict[str, Any]:
379
- """JWT 토큰을 검증합니다."""
418
+ """JWT 토큰을 검증합니다.
419
+
420
+ Args:
421
+ token: 검증할 JWT 토큰
422
+ expected_type: 예상되는 토큰 타입
423
+
424
+ Returns:
425
+ Dict[str, Any]: 토큰 페이로드
426
+
427
+ Raises:
428
+ CustomException: 토큰 검증 실패 시
429
+ """
380
430
  try:
381
431
  settings = get_settings()
382
432
  # 토큰 디코딩
@@ -390,25 +440,27 @@ async def verify_jwt_token(
390
440
 
391
441
  # 토큰 타입 검증 (expected_type이 주어진 경우에만)
392
442
  if expected_type and payload.get("token_type") != expected_type:
393
- raise TokenError(
394
- detail=f"Expected {expected_type} token but got {payload.get('token_type')}",
395
- source_function="verify_jwt_token"
443
+ raise CustomException(
444
+ ErrorCode.INVALID_TOKEN,
445
+ detail=f"token|{expected_type}|{payload.get('token_type')}",
446
+ source_function="security.verify_jwt_token"
396
447
  )
397
448
 
398
449
  return payload
399
450
 
400
451
  except JWTError as e:
401
- raise TokenError(
402
- detail=str(e),
403
- source_function="verify_jwt_token",
452
+ raise CustomException(
453
+ ErrorCode.INVALID_TOKEN,
454
+ detail=token[:10] + "...",
455
+ source_function="security.verify_jwt_token",
404
456
  original_error=e
405
457
  )
406
- except (TokenError, SecurityError) as e:
458
+ except CustomException as e:
407
459
  raise e
408
460
  except Exception as e:
409
- raise SecurityError(
461
+ raise CustomException(
410
462
  ErrorCode.INTERNAL_ERROR,
411
463
  detail=str(e),
412
- source_function="verify_jwt_token",
464
+ source_function="security.verify_jwt_token",
413
465
  original_error=e
414
466
  )
aiteamutils/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  """버전 정보"""
2
- __version__ = "0.2.32"
2
+ __version__ = "0.2.34"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.32
3
+ Version: 0.2.34
4
4
  Summary: AI Team Utilities
5
5
  Project-URL: Homepage, https://github.com/yourusername/aiteamutils
6
6
  Project-URL: Issues, https://github.com/yourusername/aiteamutils/issues
@@ -3,14 +3,14 @@ aiteamutils/base_model.py,sha256=ODEnjvUVoxQ1RPCfq8-uZTfTADIA4c7Z3E6G4EVsSX0,270
3
3
  aiteamutils/base_repository.py,sha256=qdwQ7Sj2fUqxpDg6cWM48n_QbwPK_VUlG9zTSem8iCk,18968
4
4
  aiteamutils/base_service.py,sha256=E4dHGE0DvhmRyFplh46SwKJOSF_nUL7OAsCkf_ZJF_8,24733
5
5
  aiteamutils/cache.py,sha256=tr0Yn8VPYA9QHiKCUzciVlQ2J1RAwNo2K9lGMH4rY3s,1334
6
- aiteamutils/config.py,sha256=7pJHBml8RRXC9FpoRBx0emofFkvtxsyk9_Rcur2F9hI,2724
7
- aiteamutils/database.py,sha256=raYKRZjEfefM3SnoPYozPu7OUGm62XWOUqZzN4_cayw,33479
8
- aiteamutils/dependencies.py,sha256=t4bYFu_u6_rhSHZH65MRmQoN74GC_1IqVK-l-wDy3Hw,3984
6
+ aiteamutils/config.py,sha256=kFKMeIx1KcuEwwx4VjZdCgoTOHCkG3ySYVJ0G6cvMoA,2849
7
+ aiteamutils/database.py,sha256=CaH73g8PPNcmLCz4Xr0DBtNSrLcpRlyt0F1zO5Tigfo,33811
8
+ aiteamutils/dependencies.py,sha256=GgN8sFM7qGNmOfpLaFrhuHOgPlBVXWrEgxv29jxjEgI,4226
9
9
  aiteamutils/enums.py,sha256=ipZi6k_QD5-3QV7Yzv7bnL0MjDz-vqfO9I5L77biMKs,632
10
10
  aiteamutils/exceptions.py,sha256=YV-ISya4wQlHk4twvGo16I5r8h22-tXpn9wa-b3WwDM,15231
11
- aiteamutils/security.py,sha256=jxJsWSjJOMVlNaBFxroIXrAgseJYKl_vQ_kfmHblPIA,13498
11
+ aiteamutils/security.py,sha256=NHiX0rIznYqMVqSIDFj1u2qjsJb6YFgC0GMjzVjnbWk,15135
12
12
  aiteamutils/validators.py,sha256=3N245cZFjgwtW_KzjESkizx5BBUDaJLbbxfNO4WOFZ0,7764
13
- aiteamutils/version.py,sha256=TS_f4LH3EJgdiSTWc718XoM6nVHoW3rx_v3DZlzWJyQ,42
14
- aiteamutils-0.2.32.dist-info/METADATA,sha256=giY2qyWUxWi5xCg_krbjwleHlrgmguEemTG6QNZ3a6E,1718
15
- aiteamutils-0.2.32.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- aiteamutils-0.2.32.dist-info/RECORD,,
13
+ aiteamutils/version.py,sha256=t69ncPrInx6hcKQn1rb-2H6AE0Aas3A-6K--9iJOFlg,42
14
+ aiteamutils-0.2.34.dist-info/METADATA,sha256=Nq_9Q5465EIAShF1fdxThWkhL5WpELqkea3ffyic28g,1718
15
+ aiteamutils-0.2.34.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ aiteamutils-0.2.34.dist-info/RECORD,,