aiteamutils 0.2.58__py3-none-any.whl → 0.2.59__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.
@@ -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
@@ -20,8 +17,7 @@ _session_provider = None
20
17
  __all__ = [
21
18
  "setup_dependencies",
22
19
  "register_service",
23
- "get_service",
24
- "get_current_user"
20
+ "get_service"
25
21
  ]
26
22
 
27
23
  def setup_dependencies(session_provider: Callable[[], AsyncGenerator[AsyncSession, None]]) -> None:
@@ -139,89 +135,4 @@ def get_service(service_name: str) -> Callable:
139
135
  session: AsyncSession = Depends(_session_provider)
140
136
  ) -> BaseService:
141
137
  return await _get_service(service_name, session, request)
142
- return _get_service_dependency
143
-
144
- async def get_current_user(
145
- request: Request,
146
- session: AsyncSession,
147
- auth_service: BaseService = Depends(get_service("AuthService"))
148
- ) -> Dict[str, Any]:
149
- """현재 사용자 정보를 반환합니다.
150
-
151
- Args:
152
- request: FastAPI 요청 객체
153
- session: 데이터베이스 세션
154
- auth_service: 인증 서비스
155
-
156
- Returns:
157
- Dict[str, Any]: 사용자 정보
158
-
159
- Raises:
160
- HTTPException: 인증 실패 시
161
- """
162
- settings = get_settings()
163
-
164
- # Authorization 헤더 검증
165
- authorization = request.headers.get("Authorization")
166
- if not authorization or not authorization.startswith("Bearer "):
167
- raise HTTPException(
168
- status_code=status.HTTP_401_UNAUTHORIZED,
169
- detail="Not authenticated",
170
- headers={"WWW-Authenticate": "Bearer"}
171
- )
172
-
173
- token = authorization.split(" ")[1]
174
-
175
- try:
176
- # JWT 토큰 디코딩
177
- payload = jwt.decode(
178
- token,
179
- settings.jwt_secret,
180
- algorithms=[settings.jwt_algorithm],
181
- issuer=settings.token_issuer,
182
- audience=settings.token_audience
183
- )
184
-
185
- # 토큰 타입 검증
186
- token_type = payload.get("token_type")
187
- if token_type != "access":
188
- raise HTTPException(
189
- status_code=status.HTTP_401_UNAUTHORIZED,
190
- detail="Invalid token type",
191
- headers={"WWW-Authenticate": "Bearer"}
192
- )
193
-
194
- # 사용자 조회
195
- user_ulid = payload.get("user_ulid")
196
- if not user_ulid:
197
- raise HTTPException(
198
- status_code=status.HTTP_401_UNAUTHORIZED,
199
- detail="Invalid token payload",
200
- headers={"WWW-Authenticate": "Bearer"}
201
- )
202
-
203
- user = await auth_service.get_by_ulid(user_ulid)
204
- if not user:
205
- raise HTTPException(
206
- status_code=status.HTTP_401_UNAUTHORIZED,
207
- detail="User not found",
208
- headers={"WWW-Authenticate": "Bearer"}
209
- )
210
-
211
- return user
212
-
213
- except JWTError as e:
214
- raise HTTPException(
215
- status_code=status.HTTP_401_UNAUTHORIZED,
216
- detail=str(e),
217
- headers={"WWW-Authenticate": "Bearer"}
218
- )
219
- except HTTPException as e:
220
- raise e
221
- except Exception as e:
222
- logging.error(f"Error in get_current_user: {str(e)}")
223
- raise HTTPException(
224
- status_code=status.HTTP_401_UNAUTHORIZED,
225
- detail="Authentication failed",
226
- headers={"WWW-Authenticate": "Bearer"}
227
- )
138
+ return _get_service_dependency
aiteamutils/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  """버전 정보"""
2
- __version__ = "0.2.58"
2
+ __version__ = "0.2.59"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.58
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
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=UjHGgee4qKxPwHJnfMZ3YKB7m7UQNIvKNr7IdOx590c,6899
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=57HkAV8pVrmV03eTVINw5qTQURkXwGPl2H--uJC4OWs,42
14
- aiteamutils-0.2.58.dist-info/METADATA,sha256=yULGE3mtpwvWXzHDczGJfqu52urFzknJhm5V9PgCOGc,1718
15
- aiteamutils-0.2.58.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- aiteamutils-0.2.58.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,,