aiteamutils 0.2.57__py3-none-any.whl → 0.2.59__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
aiteamutils/__init__.py CHANGED
@@ -21,6 +21,7 @@ from .base_repository import BaseRepository
21
21
  from .validators import validate_with
22
22
  from .enums import ActivityType
23
23
  from .version import __version__
24
+ from .dependencies import setup_dependencies, register_service
24
25
 
25
26
  __all__ = [
26
27
  # Base Models
@@ -31,6 +32,10 @@ __all__ = [
31
32
  # Database
32
33
  "DatabaseService",
33
34
 
35
+ # Dependencies
36
+ "setup_dependencies",
37
+ "register_service",
38
+
34
39
  # Exceptions
35
40
  "CustomException",
36
41
  "ErrorCode",
@@ -1,393 +1,109 @@
1
1
  """기본 서비스 모듈."""
2
2
  from datetime import datetime
3
- from typing import TypeVar, Generic, Dict, Any, List, Optional, Type, Union
4
- from sqlalchemy.orm import DeclarativeBase, Load
5
- from sqlalchemy.exc import IntegrityError, SQLAlchemyError
3
+ from typing import TypeVar, Generic, Dict, Any, List, Optional, Type
4
+ from sqlalchemy.ext.asyncio import AsyncSession
5
+ from sqlalchemy.orm import DeclarativeBase
6
+ from fastapi import Request
7
+
6
8
  from .database import DatabaseService
7
9
  from .exceptions import CustomException, ErrorCode
8
10
  from .base_repository import BaseRepository
9
- from .security import hash_password
10
- from fastapi import Request
11
- from ulid import ULID
12
- from sqlalchemy import select
13
11
 
14
12
  ModelType = TypeVar("ModelType", bound=DeclarativeBase)
15
13
 
16
14
  class BaseService(Generic[ModelType]):
17
-
18
- ##################
19
- # 1. 초기화 영역 #
20
- ##################
21
15
  def __init__(
22
16
  self,
23
- repository: BaseRepository[ModelType],
24
- additional_models: Dict[str, Type[DeclarativeBase]] = None
17
+ db: DatabaseService,
18
+ repository: Optional[BaseRepository] = None,
19
+ request: Optional[Request] = None
25
20
  ):
26
- """
27
- Args:
28
- repository (BaseRepository[ModelType]): 레포지토리 인스턴스
29
- additional_models (Dict[str, Type[DeclarativeBase]], optional): 추가 모델 매핑. Defaults to None.
30
- """
31
- self.repository = repository
32
- self.model = repository.model
33
- self.additional_models = additional_models or {}
34
- self._session = None
35
- self.searchable_fields = {
36
- "name": {"type": "text", "description": "이름"},
37
- "organization_ulid": {"type": "exact", "description": "조직 ID"}
38
- }
39
-
40
- @property
41
- def session(self):
42
- """현재 세션을 반환합니다."""
43
- if self._session is None:
44
- raise CustomException(
45
- ErrorCode.DB_CONNECTION_ERROR,
46
- detail="Database session is not set",
47
- source_function=f"{self.__class__.__name__}.session"
48
- )
49
- return self._session
50
-
51
- @session.setter
52
- def session(self, value):
53
- """세션을 설정합니다."""
54
- self._session = value
55
- if hasattr(self.repository, 'session'):
56
- self.repository.session = value
57
-
58
- #########################
59
- # 2. 이벤트 처리 메서드 #
60
- #########################
61
- async def pre_save(self, data: Dict[str, Any]) -> Dict[str, Any]:
62
- """저장 전 처리를 수행합니다.
63
-
64
- Args:
65
- data (Dict[str, Any]): 저장할 데이터
66
-
67
- Returns:
68
- Dict[str, Any]: 처리된 데이터
69
- """
70
- return data
71
-
72
- async def post_save(self, entity: ModelType) -> None:
73
- """저장 후 처리를 수행합니다.
74
-
75
- Args:
76
- entity (ModelType): 저장된 엔티티
77
- """
78
- pass
79
-
80
- async def pre_delete(self, ulid: str) -> None:
81
- """삭제 전 처리를 수행합니다.
82
-
83
- Args:
84
- ulid (str): 삭제할 엔티티의 ULID
85
- """
86
- pass
87
-
88
- async def post_delete(self, ulid: str) -> None:
89
- """삭제 후 처리를 수행합니다.
90
-
91
- Args:
92
- ulid (str): 삭제된 엔티티의 ULID
93
- """
94
- pass
95
-
96
- ######################
97
- # 3. 캐시 관리 메서드 #
98
- ######################
99
- async def get_from_cache(self, key: str) -> Optional[Any]:
100
- """캐시에서 데이터를 조회합니다.
101
-
102
- Args:
103
- key (str): 캐시 키
104
-
105
- Returns:
106
- Optional[Any]: 캐시된 데이터 또는 None
107
- """
108
- return None
109
-
110
- async def set_to_cache(self, key: str, value: Any, ttl: int = 3600) -> None:
111
- """데이터를 캐시에 저장합니다.
112
-
113
- Args:
114
- key (str): 캐시 키
115
- value (Any): 저장할 값
116
- ttl (int, optional): 캐시 유효 시간(초). Defaults to 3600.
117
- """
118
- pass
119
-
120
- async def invalidate_cache(self, key: str) -> None:
121
- """캐시를 무효화합니다.
122
-
123
- Args:
124
- key (str): 캐시 키
125
- """
126
- pass
127
-
128
- ##########################
129
- # 4. 비즈니스 검증 메서드 #
130
- ##########################
131
- def _validate_business_rules(self, data: Dict[str, Any]) -> None:
132
- """비즈니스 규칙을 검증합니다.
133
-
134
- Args:
135
- data (Dict[str, Any]): 검증할 데이터
136
-
137
- Raises:
138
- CustomException: 비즈니스 규칙 위반 시
139
- """
140
- pass
141
-
142
- def _validate_permissions(self, request: Request, action: str) -> None:
143
- """권한을 검증합니다.
144
-
145
- Args:
146
- request (Request): FastAPI 요청 객체
147
- action (str): 수행할 작업
148
-
149
- Raises:
150
- CustomException: 권한이 없는 경우
151
- """
152
- pass
153
-
154
- ########################
155
- # 5. 응답 처리 메서드 #
156
- ########################
157
- def _handle_response_model(self, entity: ModelType, response_model: Any) -> Dict[str, Any]:
158
- """응답 모델에 맞게 데이터를 처리합니다.
159
-
160
- Args:
161
- entity (ModelType): 처리할 엔티티
162
- response_model (Any): 응답 모델
163
-
164
- Returns:
165
- Dict[str, Any]: 처리된 데이터
166
- """
167
- if not response_model:
168
- return self._process_response(entity)
169
-
170
- result = self._process_response(entity)
21
+ """BaseService 초기화
171
22
 
172
- # response_model에 없는 필드 제거
173
- keys_to_remove = [key for key in result if key not in response_model.model_fields]
174
- for key in keys_to_remove:
175
- result.pop(key)
176
-
177
- # 모델 검증
178
- return response_model(**result).model_dump()
179
-
180
- def _handle_exclude_fields(self, data: Dict[str, Any], exclude_fields: List[str]) -> Dict[str, Any]:
181
- """제외할 필드를 처리합니다.
182
-
183
23
  Args:
184
- data (Dict[str, Any]): 처리할 데이터
185
- exclude_fields (List[str]): 제외할 필드 목록
186
-
187
- Returns:
188
- Dict[str, Any]: 처리된 데이터
189
- """
190
- if not exclude_fields:
191
- return data
192
-
193
- return {k: v for k, v in data.items() if k not in exclude_fields}
194
-
195
- def _validate_ulid(self, ulid: str) -> bool:
196
- """ULID 형식을 검증합니다.
197
-
198
- Args:
199
- ulid (str): 검증할 ULID
200
-
201
- Returns:
202
- bool: 유효한 ULID 여부
24
+ db: 데이터베이스 서비스
25
+ repository: 레포지토리 인스턴스 (선택)
26
+ request: FastAPI 요청 객체 (선택)
203
27
  """
204
- try:
205
- ULID.from_str(ulid)
206
- return True
207
- except (ValueError, AttributeError):
208
- return False
209
-
210
- def _process_columns(self, entity: ModelType, exclude_extra_data: bool = True) -> Dict[str, Any]:
211
- """엔티티의 컬럼들을 처리합니다.
212
-
213
- Args:
214
- entity (ModelType): 처리할 엔티티
215
- exclude_extra_data (bool, optional): extra_data 컬럼 제외 여부. Defaults to True.
216
-
217
- Returns:
218
- Dict[str, Any]: 처리된 컬럼 데이터
219
- """
220
- result = {}
221
- for column in entity.__table__.columns:
222
- if exclude_extra_data and column.name == 'extra_data':
223
- continue
224
-
225
- # 필드 값 처리
226
- if hasattr(entity, column.name):
227
- value = getattr(entity, column.name)
228
- if isinstance(value, datetime):
229
- value = value.isoformat()
230
- result[column.name] = value
231
- elif hasattr(entity, 'extra_data') and isinstance(entity.extra_data, dict):
232
- result[column.name] = entity.extra_data.get(column.name)
233
- else:
234
- result[column.name] = None
235
-
236
- # extra_data의 내용을 최상위 레벨로 업데이트
237
- if hasattr(entity, 'extra_data') and isinstance(entity.extra_data, dict):
238
- result.update(entity.extra_data or {})
239
-
240
- return result
28
+ self.db = db
29
+ self.repository = repository
30
+ self.request = request
31
+ self.model = repository.model if repository else None
241
32
 
242
33
  def _process_response(self, entity: ModelType, response_model: Any = None) -> Dict[str, Any]:
243
34
  """응답 데이터를 처리합니다.
244
- extra_data의 내용을 최상위 레벨로 변환하고, 라우터에서 선언한 응답 스키마에 맞게 데이터를 변환합니다.
245
-
35
+
246
36
  Args:
247
- entity (ModelType): 처리할 엔티티
248
- response_model (Any, optional): 응답 스키마. Defaults to None.
249
-
37
+ entity: 처리할 엔티티
38
+ response_model: 응답 모델 클래스
39
+
250
40
  Returns:
251
- Dict[str, Any]: 처리된 엔티티 데이터
41
+ 처리된 데이터
252
42
  """
253
43
  if not entity:
254
44
  return None
255
-
256
- # 모든 필드 처리
257
- result = self._process_columns(entity)
45
+
46
+ # 기본 데이터 변환
47
+ result = {}
48
+
49
+ # 테이블 컬럼 처리
50
+ for column in entity.__table__.columns:
51
+ value = getattr(entity, column.name)
52
+ if isinstance(value, datetime):
53
+ value = value.isoformat()
54
+ result[column.name] = value
258
55
 
259
- # Relationship 처리 (이미 로드된 관계만 처리)
56
+ # Relationship 처리 (이미 로드된 관계만)
260
57
  for relationship in entity.__mapper__.relationships:
261
- if not relationship.key in entity.__dict__:
58
+ if relationship.key not in entity.__dict__:
262
59
  continue
263
60
 
264
61
  try:
265
62
  value = getattr(entity, relationship.key)
266
- # response_model이 있는 경우 해당 필드의 annotation type을 가져옴
267
- nested_response_model = None
268
- if response_model and relationship.key in response_model.model_fields:
269
- field_info = response_model.model_fields[relationship.key]
270
- nested_response_model = field_info.annotation
271
-
272
63
  if value is not None:
273
64
  if isinstance(value, list):
274
65
  result[relationship.key] = [
275
- self._process_response(item, nested_response_model)
66
+ self._process_response(item)
276
67
  for item in value
277
68
  ]
278
69
  else:
279
- result[relationship.key] = self._process_response(value, nested_response_model)
70
+ result[relationship.key] = self._process_response(value)
280
71
  else:
281
72
  result[relationship.key] = None
282
73
  except Exception:
283
74
  result[relationship.key] = None
284
-
75
+
285
76
  # response_model이 있는 경우 필터링
286
77
  if response_model:
287
- # 현재 목록을 저장
288
- current_keys = list(result.keys())
289
- # response_model에 없는 키 제거
290
- for key in current_keys:
291
- if key not in response_model.model_fields:
292
- result.pop(key)
293
- # 모델 검증 및 업데이트
294
- result.update(response_model(**result).model_dump())
295
-
78
+ # response_model에 없는 필드 제거
79
+ keys_to_remove = [key for key in result if key not in response_model.model_fields]
80
+ for key in keys_to_remove:
81
+ result.pop(key)
82
+ # 모델 검증
83
+ return response_model(**result).model_dump()
84
+
296
85
  return result
297
86
 
298
- def _process_basic_fields(self, entity: ModelType) -> Dict[str, Any]:
299
- """엔티티의 기본 필드만 처리합니다.
300
-
87
+ async def create(
88
+ self,
89
+ data: Dict[str, Any],
90
+ response_model: Any = None
91
+ ) -> Dict[str, Any]:
92
+ """엔티티를 생성합니다.
93
+
301
94
  Args:
302
- entity (ModelType): 처리할 엔티티
303
-
304
- Returns:
305
- Dict[str, Any]: 기본 필드만 포함된 딕셔너리
306
- """
307
- if not entity:
308
- return None
95
+ data: 생성할 데이터
96
+ response_model: 응답 모델 클래스
309
97
 
310
- return self._process_columns(entity)
311
-
312
- async def _create_for_model(self, model_name: str, data: Dict[str, Any], exclude_fields: List[str] = None) -> DeclarativeBase:
313
- """지정된 모델에 대해 새로운 엔티티를 생성합니다.
314
-
315
- Args:
316
- model_name (str): 생성할 모델 이름
317
- data (Dict[str, Any]): 생성할 엔티티 데이터
318
- exclude_fields (List[str], optional): 제외할 필드 목록. Defaults to None.
319
-
320
- Returns:
321
- DeclarativeBase: 생성된 엔티티
322
-
323
- Raises:
324
- CustomException: 데이터베이스 작업 중 오류 발생 시
325
- """
326
- if model_name not in self.additional_models:
327
- raise CustomException(
328
- ErrorCode.INVALID_REQUEST,
329
- detail=f"Model {model_name} not registered",
330
- source_function=f"{self.__class__.__name__}._create_for_model"
331
- )
332
-
333
- try:
334
- # 제외할 필드 처리
335
- if exclude_fields:
336
- data = {k: v for k, v in data.items() if k not in exclude_fields}
337
-
338
- return await self.db_service.create_entity(self.additional_models[model_name], data)
339
- except CustomException as e:
340
- raise e
341
- except Exception as e:
342
- raise CustomException(
343
- ErrorCode.DB_CREATE_ERROR,
344
- detail=str(e),
345
- source_function=f"{self.__class__.__name__}._create_for_model",
346
- original_error=e
347
- )
348
-
349
- def _process_password(self, data: Dict[str, Any]) -> Dict[str, Any]:
350
- """비밀번호 필드가 있는 경우 해시화합니다.
351
-
352
- Args:
353
- data (Dict[str, Any]): 처리할 데이터
354
-
355
- Returns:
356
- Dict[str, Any]: 처리된 데이터
357
- """
358
- if "password" in data:
359
- data["password"] = hash_password(data["password"])
360
- return data
361
-
362
- #######################
363
- # 6. CRUD 작업 메서드 #
364
- #######################
365
- async def create(self, data: Dict[str, Any], exclude_fields: List[str] = None, model_name: str = None) -> Union[ModelType, DeclarativeBase]:
366
- """새로운 엔티티를 생성합니다.
367
-
368
- Args:
369
- data (Dict[str, Any]): 생성할 엔티티 데이터
370
- exclude_fields (List[str], optional): 제외할 필드 목록. Defaults to None.
371
- model_name (str, optional): 생성할 모델 이름. Defaults to None.
372
-
373
98
  Returns:
374
- Union[ModelType, DeclarativeBase]: 생성된 엔티티
375
-
99
+ 생성된 엔티티
100
+
376
101
  Raises:
377
- CustomException: 데이터베이스 작업 중 오류 발생
102
+ CustomException: 생성 실패
378
103
  """
379
104
  try:
380
- # 비밀번호 해시화
381
- data = self._process_password(data)
382
-
383
- # 제외할 필드 처리
384
- if exclude_fields:
385
- data = {k: v for k, v in data.items() if k not in exclude_fields}
386
-
387
- if model_name:
388
- return await self._create_for_model(model_name, data)
389
-
390
- return await self.repository.create(data)
105
+ entity = await self.db.create_entity(self.model, data)
106
+ return self._process_response(entity, response_model)
391
107
  except CustomException as e:
392
108
  raise e
393
109
  except Exception as e:
@@ -398,274 +114,147 @@ class BaseService(Generic[ModelType]):
398
114
  original_error=e
399
115
  )
400
116
 
401
- async def update(
117
+ async def get(
402
118
  self,
403
119
  ulid: str,
404
- data: Dict[str, Any],
405
- exclude_fields: List[str] = None,
406
- model_name: str = None
407
- ) -> Optional[ModelType]:
408
- """기존 엔티티를 수정합니다.
409
-
120
+ response_model: Any = None
121
+ ) -> Dict[str, Any]:
122
+ """엔티티를 조회합니다.
123
+
410
124
  Args:
411
- ulid (str): 수정할 엔티티의 ULID
412
- data (Dict[str, Any]): 수정할 데이터
413
- exclude_fields (List[str], optional): 제외할 필드 목록. Defaults to None.
414
- model_name (str, optional): 수정할 모델 이름. Defaults to None.
415
-
416
- Returns:
417
- Optional[ModelType]: 수정된 엔티티, 없으면 None
418
-
419
- Raises:
420
- CustomException: 데이터베이스 작업 중 오류 발생 시
421
- """
422
- try:
423
- # 비밀번호 해시화
424
- data = self._process_password(data)
425
-
426
- # 제외할 필드 처리
427
- if exclude_fields:
428
- data = {k: v for k, v in data.items() if k not in exclude_fields}
429
-
430
- async with self.db_service.transaction():
431
- if model_name:
432
- if model_name not in self.additional_models:
433
- raise CustomException(
434
- ErrorCode.INVALID_REQUEST,
435
- detail=f"Model {model_name} not registered",
436
- source_function=f"{self.__class__.__name__}.update"
437
- )
438
- entity = await self.db_service.update_entity(
439
- self.additional_models[model_name],
440
- {"ulid": ulid},
441
- data
442
- )
443
- if not entity:
444
- raise CustomException(
445
- ErrorCode.NOT_FOUND,
446
- detail=f"{self.additional_models[model_name].__tablename__}|ulid|{ulid}",
447
- source_function=f"{self.__class__.__name__}.update"
448
- )
449
- return entity
450
-
451
- entity = await self.repository.update(ulid, data)
452
- if not entity:
453
- raise CustomException(
454
- ErrorCode.NOT_FOUND,
455
- detail=f"{self.model.__tablename__}|ulid|{ulid}",
456
- source_function=f"{self.__class__.__name__}.update"
457
- )
458
- return entity
459
- except CustomException as e:
460
- raise e
461
- except Exception as e:
462
- raise CustomException(
463
- ErrorCode.DB_UPDATE_ERROR,
464
- detail=str(e),
465
- source_function=f"{self.__class__.__name__}.update",
466
- original_error=e
467
- )
468
-
469
- async def delete(self, ulid: str, model_name: str = None) -> bool:
470
- """엔티티를 소프트 삭제합니다 (is_deleted = True)."""
471
- try:
472
- if model_name:
473
- if model_name not in self.additional_models:
474
- raise CustomException(
475
- ErrorCode.INVALID_REQUEST,
476
- detail=f"Model {model_name} not registered",
477
- source_function=f"{self.__class__.__name__}.delete"
478
- )
479
-
480
- stmt = select(self.additional_models[model_name]).filter_by(ulid=ulid, is_deleted=False)
481
- result = await self.session.execute(stmt)
482
- entity = result.scalars().first()
483
-
484
- if not entity:
485
- raise CustomException(
486
- ErrorCode.NOT_FOUND,
487
- detail=f"{self.additional_models[model_name].__tablename__}|ulid|{ulid}",
488
- source_function=f"{self.__class__.__name__}.delete"
489
- )
490
-
491
- entity.is_deleted = True
492
- await self.session.flush()
493
- return True
494
-
495
- return await self.repository.delete(ulid)
125
+ ulid: 조회할 엔티티의 ULID
126
+ response_model: 응답 모델 클래스
496
127
 
497
- except CustomException as e:
498
- raise e
499
- except Exception as e:
500
- raise CustomException(
501
- ErrorCode.DB_DELETE_ERROR,
502
- detail=str(e),
503
- source_function=f"{self.__class__.__name__}.delete",
504
- original_error=e
505
- )
506
-
507
- async def real_row_delete(self, ulid: str, model_name: str = None) -> bool:
508
- """엔티티를 실제로 삭제합니다.
509
-
510
- Args:
511
- ulid (str): 삭제할 엔티티의 ULID
512
- model_name (str, optional): 삭제할 모델 이름. Defaults to None.
513
-
514
128
  Returns:
515
- bool: 삭제 성공 여부
516
-
129
+ 조회된 엔티티
130
+
517
131
  Raises:
518
- CustomException: 데이터베이스 작업 중 오류 발생
132
+ CustomException: 조회 실패
519
133
  """
520
134
  try:
521
- if model_name:
522
- if model_name not in self.additional_models:
523
- raise CustomException(
524
- ErrorCode.INVALID_REQUEST,
525
- detail=f"Model {model_name} not registered",
526
- source_function=f"{self.__class__.__name__}.real_row_delete"
527
- )
528
- entity = await self.db_service.retrieve_entity(
529
- self.additional_models[model_name],
530
- {"ulid": ulid}
135
+ entity = await self.db.get_entity(self.model, {"ulid": ulid, "is_deleted": False})
136
+ if not entity:
137
+ raise CustomException(
138
+ ErrorCode.NOT_FOUND,
139
+ detail=f"{self.model.__tablename__}|ulid|{ulid}",
140
+ source_function=f"{self.__class__.__name__}.get"
531
141
  )
532
- if entity:
533
- await self.db_service.delete_entity(entity)
534
- return True
535
- return False
536
-
537
- return await self.repository.real_row_delete(ulid)
142
+ return self._process_response(entity, response_model)
538
143
  except CustomException as e:
539
144
  raise e
540
145
  except Exception as e:
541
146
  raise CustomException(
542
- ErrorCode.DB_DELETE_ERROR,
147
+ ErrorCode.DB_QUERY_ERROR,
543
148
  detail=str(e),
544
- source_function=f"{self.__class__.__name__}.real_row_delete",
149
+ source_function=f"{self.__class__.__name__}.get",
545
150
  original_error=e
546
151
  )
547
152
 
548
- #########################
549
- # 7. 조회 및 검색 메서드 #
550
- #########################
551
153
  async def list(
552
154
  self,
553
155
  skip: int = 0,
554
156
  limit: int = 100,
555
- filters: Dict[str, Any] | None = None,
556
- search_params: Dict[str, Any] | None = None,
557
- model_name: str | None = None,
558
- request: Request | None = None,
157
+ filters: Optional[Dict[str, Any]] = None,
559
158
  response_model: Any = None
560
159
  ) -> List[Dict[str, Any]]:
561
- """엔티티 목록을 조회합니다."""
160
+ """엔티티 목록을 조회합니다.
161
+
162
+ Args:
163
+ skip: 건너뛸 레코드 수
164
+ limit: 조회할 최대 레코드 수
165
+ filters: 필터 조건
166
+ response_model: 응답 모델 클래스
167
+
168
+ Returns:
169
+ 엔티티 목록
170
+
171
+ Raises:
172
+ CustomException: 조회 실패 시
173
+ """
562
174
  try:
563
- if model_name:
564
- if model_name not in self.additional_models:
565
- raise CustomException(
566
- ErrorCode.INVALID_REQUEST,
567
- detail=f"Model {model_name} not registered",
568
- source_function=f"{self.__class__.__name__}.list"
569
- )
570
-
571
- stmt = select(self.additional_models[model_name]).where(
572
- self.additional_models[model_name].is_deleted == False
573
- )
574
-
575
- if filters:
576
- for key, value in filters.items():
577
- if value is not None:
578
- stmt = stmt.where(getattr(self.additional_models[model_name], key) == value)
579
-
580
- stmt = stmt.offset(skip).limit(limit)
581
- result = await self.session.execute(stmt)
582
- entities = result.scalars().all()
583
-
584
- return [self._process_response(entity, response_model) for entity in entities]
585
-
586
- return await self.repository.list(
587
- skip=skip,
588
- limit=limit,
175
+ entities = await self.db.list_entities(
176
+ self.model,
589
177
  filters=filters,
590
- search_params=search_params
178
+ skip=skip,
179
+ limit=limit
591
180
  )
592
-
181
+ return [self._process_response(entity, response_model) for entity in entities]
593
182
  except CustomException as e:
594
- e.detail = f"Service list error for {self.repository.model.__tablename__}: {e.detail}"
595
- e.source_function = f"{self.__class__.__name__}.list -> {e.source_function}"
596
183
  raise e
597
184
  except Exception as e:
598
185
  raise CustomException(
599
- ErrorCode.INTERNAL_ERROR,
186
+ ErrorCode.DB_QUERY_ERROR,
600
187
  detail=str(e),
601
188
  source_function=f"{self.__class__.__name__}.list",
602
189
  original_error=e
603
190
  )
604
191
 
605
- async def get(
192
+ async def update(
606
193
  self,
607
194
  ulid: str,
608
- model_name: str | None = None,
609
- request: Request | None = None,
195
+ data: Dict[str, Any],
610
196
  response_model: Any = None
611
- ) -> Optional[Dict[str, Any]]:
612
- """특정 엔티티를 조회합니다.
613
-
197
+ ) -> Dict[str, Any]:
198
+ """엔티티를 수정합니다.
199
+
614
200
  Args:
615
- ulid (str): 조회할 엔티티의 ULID
616
- model_name (str | None, optional): 조회할 모델 이름. Defaults to None.
617
- request (Request | None, optional): 요청 객체. Defaults to None.
618
- response_model (Any, optional): 응답 스키마. Defaults to None.
619
-
201
+ ulid: 수정할 엔티티의 ULID
202
+ data: 수정할 데이터
203
+ response_model: 응답 모델 클래스
204
+
620
205
  Returns:
621
- Optional[Dict[str, Any]]: 조회된 엔티티, 없으면 None
206
+ 수정된 엔티티
622
207
 
623
208
  Raises:
624
- CustomException: 데이터베이스 작업 중 오류 발생
209
+ CustomException: 수정 실패
625
210
  """
626
211
  try:
627
- # ULID 검증
628
- if not self._validate_ulid(ulid):
212
+ entity = await self.db.get_entity(self.model, {"ulid": ulid, "is_deleted": False})
213
+ if not entity:
629
214
  raise CustomException(
630
- ErrorCode.VALIDATION_ERROR,
631
- detail=f"Invalid ULID format: {ulid}",
632
- source_function=f"{self.__class__.__name__}.get"
633
- )
634
-
635
- if model_name:
636
- if model_name not in self.additional_models:
637
- raise CustomException(
638
- ErrorCode.INVALID_REQUEST,
639
- detail=f"Model {model_name} not registered",
640
- source_function=f"{self.__class__.__name__}.get"
641
- )
642
- entity = await self.db_service.retrieve_entity(
643
- self.additional_models[model_name],
644
- {"ulid": ulid, "is_deleted": False}
215
+ ErrorCode.NOT_FOUND,
216
+ detail=f"{self.model.__tablename__}|ulid|{ulid}",
217
+ source_function=f"{self.__class__.__name__}.update"
645
218
  )
646
- if not entity:
647
- raise CustomException(
648
- ErrorCode.NOT_FOUND,
649
- detail=f"{self.additional_models[model_name].__tablename__}|ulid|{ulid}",
650
- source_function=f"{self.__class__.__name__}.get"
651
- )
652
- return self._process_response(entity, response_model)
219
+ updated = await self.db.update_entity(entity, data)
220
+ return self._process_response(updated, response_model)
221
+ except CustomException as e:
222
+ raise e
223
+ except Exception as e:
224
+ raise CustomException(
225
+ ErrorCode.DB_UPDATE_ERROR,
226
+ detail=str(e),
227
+ source_function=f"{self.__class__.__name__}.update",
228
+ original_error=e
229
+ )
653
230
 
654
- entity = await self.repository.get(ulid)
231
+ async def delete(self, ulid: str) -> bool:
232
+ """엔티티를 삭제합니다.
233
+
234
+ Args:
235
+ ulid: 삭제할 엔티티의 ULID
236
+
237
+ Returns:
238
+ 삭제 성공 여부
239
+
240
+ Raises:
241
+ CustomException: 삭제 실패 시
242
+ """
243
+ try:
244
+ entity = await self.db.get_entity(self.model, {"ulid": ulid, "is_deleted": False})
655
245
  if not entity:
656
246
  raise CustomException(
657
247
  ErrorCode.NOT_FOUND,
658
248
  detail=f"{self.model.__tablename__}|ulid|{ulid}",
659
- source_function=f"{self.__class__.__name__}.get"
249
+ source_function=f"{self.__class__.__name__}.delete"
660
250
  )
661
-
662
- return self._process_response(entity, response_model)
251
+ return await self.db.delete_entity(entity)
663
252
  except CustomException as e:
664
253
  raise e
665
254
  except Exception as e:
666
255
  raise CustomException(
667
- ErrorCode.INTERNAL_ERROR,
256
+ ErrorCode.DB_DELETE_ERROR,
668
257
  detail=str(e),
669
- source_function=f"{self.__class__.__name__}.get",
258
+ source_function=f"{self.__class__.__name__}.delete",
670
259
  original_error=e
671
260
  )
@@ -1,12 +1,9 @@
1
1
  """의존성 관리 모듈."""
2
2
  from typing import Type, TypeVar, Dict, Any, Optional, Callable, List, AsyncGenerator
3
- from fastapi import Request, Depends, HTTPException, status
3
+ from fastapi import Request, Depends
4
4
  from sqlalchemy.ext.asyncio import AsyncSession
5
- from jose import jwt, JWTError
6
- import logging
7
5
 
8
6
  from .exceptions import CustomException, ErrorCode
9
- from .config import get_settings
10
7
  from .base_service import BaseService
11
8
  from .base_repository import BaseRepository
12
9
  from .database import DatabaseService
@@ -17,6 +14,12 @@ R = TypeVar("R", bound=BaseRepository)
17
14
  _service_registry: Dict[str, Dict[str, Any]] = {}
18
15
  _session_provider = None
19
16
 
17
+ __all__ = [
18
+ "setup_dependencies",
19
+ "register_service",
20
+ "get_service"
21
+ ]
22
+
20
23
  def setup_dependencies(session_provider: Callable[[], AsyncGenerator[AsyncSession, None]]) -> None:
21
24
  """의존성 설정을 초기화합니다.
22
25
 
@@ -132,89 +135,4 @@ def get_service(service_name: str) -> Callable:
132
135
  session: AsyncSession = Depends(_session_provider)
133
136
  ) -> BaseService:
134
137
  return await _get_service(service_name, session, request)
135
- return _get_service_dependency
136
-
137
- async def get_current_user(
138
- request: Request,
139
- session: AsyncSession,
140
- auth_service: BaseService = Depends(get_service("AuthService"))
141
- ) -> Dict[str, Any]:
142
- """현재 사용자 정보를 반환합니다.
143
-
144
- Args:
145
- request: FastAPI 요청 객체
146
- session: 데이터베이스 세션
147
- auth_service: 인증 서비스
148
-
149
- Returns:
150
- Dict[str, Any]: 사용자 정보
151
-
152
- Raises:
153
- HTTPException: 인증 실패 시
154
- """
155
- settings = get_settings()
156
-
157
- # Authorization 헤더 검증
158
- authorization = request.headers.get("Authorization")
159
- if not authorization or not authorization.startswith("Bearer "):
160
- raise HTTPException(
161
- status_code=status.HTTP_401_UNAUTHORIZED,
162
- detail="Not authenticated",
163
- headers={"WWW-Authenticate": "Bearer"}
164
- )
165
-
166
- token = authorization.split(" ")[1]
167
-
168
- try:
169
- # JWT 토큰 디코딩
170
- payload = jwt.decode(
171
- token,
172
- settings.jwt_secret,
173
- algorithms=[settings.jwt_algorithm],
174
- issuer=settings.token_issuer,
175
- audience=settings.token_audience
176
- )
177
-
178
- # 토큰 타입 검증
179
- token_type = payload.get("token_type")
180
- if token_type != "access":
181
- raise HTTPException(
182
- status_code=status.HTTP_401_UNAUTHORIZED,
183
- detail="Invalid token type",
184
- headers={"WWW-Authenticate": "Bearer"}
185
- )
186
-
187
- # 사용자 조회
188
- user_ulid = payload.get("user_ulid")
189
- if not user_ulid:
190
- raise HTTPException(
191
- status_code=status.HTTP_401_UNAUTHORIZED,
192
- detail="Invalid token payload",
193
- headers={"WWW-Authenticate": "Bearer"}
194
- )
195
-
196
- user = await auth_service.get_by_ulid(user_ulid)
197
- if not user:
198
- raise HTTPException(
199
- status_code=status.HTTP_401_UNAUTHORIZED,
200
- detail="User not found",
201
- headers={"WWW-Authenticate": "Bearer"}
202
- )
203
-
204
- return user
205
-
206
- except JWTError as e:
207
- raise HTTPException(
208
- status_code=status.HTTP_401_UNAUTHORIZED,
209
- detail=str(e),
210
- headers={"WWW-Authenticate": "Bearer"}
211
- )
212
- except HTTPException as e:
213
- raise e
214
- except Exception as e:
215
- logging.error(f"Error in get_current_user: {str(e)}")
216
- raise HTTPException(
217
- status_code=status.HTTP_401_UNAUTHORIZED,
218
- detail="Authentication failed",
219
- headers={"WWW-Authenticate": "Bearer"}
220
- )
138
+ return _get_service_dependency
aiteamutils/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  """버전 정보"""
2
- __version__ = "0.2.57"
2
+ __version__ = "0.2.59"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.57
3
+ Version: 0.2.59
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
@@ -1,16 +1,16 @@
1
- aiteamutils/__init__.py,sha256=h7iFifWvRlaComsk4VPapRr16YhD19Kp1LuBzLHqdgQ,1170
1
+ aiteamutils/__init__.py,sha256=U9UEyU0AqpLZzbPJiBvwDEh-s2405y1IeWZ0zCqFSl0,1307
2
2
  aiteamutils/base_model.py,sha256=ODEnjvUVoxQ1RPCfq8-uZTfTADIA4c7Z3E6G4EVsSX0,2708
3
3
  aiteamutils/base_repository.py,sha256=vqsundoN0h7FVvgqTBEnnJNMcFpvMK0s_nxBWdIYg-U,7846
4
- aiteamutils/base_service.py,sha256=s2AcA-6_ogOQKgt2xf_3AG2s6tqBceU4nJoXO1II7S8,24588
4
+ aiteamutils/base_service.py,sha256=o4oeX__RmUFp5kV-fb4VwBhF8nTAZNMjoWJvK7G1Wmk,8615
5
5
  aiteamutils/cache.py,sha256=07xBGlgAwOTAdY5mnMOQJ5EBxVwe8glVD7DkGEkxCtw,1373
6
6
  aiteamutils/config.py,sha256=YdalpJb70-txhGJAS4aaKglEZAFVWgfzw5BXSWpkUz4,3232
7
7
  aiteamutils/database.py,sha256=x0x5gnSyGfwo_klL9O65RnGOQID6c9tH2miwFveVyoE,6326
8
- aiteamutils/dependencies.py,sha256=vkSEiXYsgeTEh8mEVtI_sMBjCmcUhxLyqFflAR5MpPU,6792
8
+ aiteamutils/dependencies.py,sha256=Qoy_M7r65Fv3Y8RMVBi81QLdfjfWIk-pm7O5_Przsa4,4084
9
9
  aiteamutils/enums.py,sha256=ipZi6k_QD5-3QV7Yzv7bnL0MjDz-vqfO9I5L77biMKs,632
10
10
  aiteamutils/exceptions.py,sha256=_lKWXq_ujNj41xN6LDE149PwsecAP7lgYWbOBbLOntg,15368
11
11
  aiteamutils/security.py,sha256=xFVrjttxwXB1TTjqgRQQgQJQohQBT28vuW8FVLjvi-M,10103
12
12
  aiteamutils/validators.py,sha256=3N245cZFjgwtW_KzjESkizx5BBUDaJLbbxfNO4WOFZ0,7764
13
- aiteamutils/version.py,sha256=iEd2OwHHOAuMG_ICyOZTObkqFKxGQjbDcLvqr9IZKXk,42
14
- aiteamutils-0.2.57.dist-info/METADATA,sha256=y8dfe__6SqRo2LLuki2isEhePbvNg_sGNJiIzMm3U2s,1718
15
- aiteamutils-0.2.57.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- aiteamutils-0.2.57.dist-info/RECORD,,
13
+ aiteamutils/version.py,sha256=_ql6JzqtHA14js-9_8pUBJDK_7BZm5uHqA1j4jfryCs,42
14
+ aiteamutils-0.2.59.dist-info/METADATA,sha256=OOT0-1lTWlUQv7fL2MtELHAUPSj18nqiaiXRyFc8QoQ,1718
15
+ aiteamutils-0.2.59.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ aiteamutils-0.2.59.dist-info/RECORD,,