aiteamutils 0.2.79__tar.gz → 0.2.81__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.79
3
+ Version: 0.2.81
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
@@ -64,14 +64,16 @@ class BaseRepository(Generic[ModelType]):
64
64
  async def update(
65
65
  self,
66
66
  update_data: Dict[str, Any],
67
- conditions: Dict[str, Any]
67
+ conditions: Dict[str, Any],
68
+ exclude_entities: List[str] | None = None
68
69
  ) -> ModelType:
69
70
  try:
70
71
  return await update_entity(
71
72
  session=self.session,
72
73
  model=self.model,
73
74
  update_data=update_data,
74
- conditions=conditions
75
+ conditions=conditions,
76
+ exclude_entities=exclude_entities
75
77
  )
76
78
  except CustomException as e:
77
79
  raise e
@@ -79,13 +81,13 @@ class BaseRepository(Generic[ModelType]):
79
81
  async def delete(
80
82
  self,
81
83
  conditions: Dict[str, Any]
82
- ) -> None:
84
+ ) -> bool:
83
85
  await delete_entity(
84
86
  session=self.session,
85
87
  model=self.model,
86
88
  conditions=conditions
87
89
  )
88
-
90
+ return True
89
91
  #########################
90
92
  # 조회 및 검색 메서드 #
91
93
  #########################
@@ -93,9 +95,10 @@ class BaseRepository(Generic[ModelType]):
93
95
  self,
94
96
  skip: int = 0,
95
97
  limit: int = 100,
96
- filters: Optional[Dict[str, Any]] = None,
98
+ filters: Optional[List[Dict[str, Any]]] = None,
97
99
  explicit_joins: Optional[List[Any]] = None,
98
- loading_joins: Optional[List[Any]] = None
100
+ loading_joins: Optional[List[Any]] = None,
101
+ order: Optional[List[Dict[str, str]]] = None
99
102
  ) -> List[ModelType]:
100
103
  """
101
104
  엔티티 목록 조회.
@@ -109,7 +112,8 @@ class BaseRepository(Generic[ModelType]):
109
112
  limit=limit,
110
113
  filters=filters,
111
114
  explicit_joins=explicit_joins,
112
- loading_joins=loading_joins
115
+ loading_joins=loading_joins,
116
+ order=order
113
117
  )
114
118
  except CustomException as e:
115
119
  raise e
@@ -38,7 +38,6 @@ class BaseService(Generic[ModelType]):
38
38
  async def create(
39
39
  self,
40
40
  entity_data: Dict[str, Any],
41
- model_name: str | None = None,
42
41
  response_model: Any = None,
43
42
  exclude_entities: List[str] | None = None,
44
43
  unique_check: List[Dict[str, Any]] | None = None,
@@ -54,11 +53,17 @@ class BaseService(Generic[ModelType]):
54
53
  if fk_check:
55
54
  await validate_unique_fields(self.db_session, fk_check, find_value=False)
56
55
 
57
- # repositorycreate 메서드를 트랜잭션 내에서 실행
58
- return await self.repository.create(
56
+ result = await self.repository.create(
59
57
  entity_data=entity_data,
60
58
  exclude_entities=exclude_entities
61
59
  )
60
+
61
+ # 결과 반환
62
+ if response_model:
63
+ return process_response(result, response_model)
64
+ else:
65
+ return result
66
+
62
67
  except CustomException as e:
63
68
  raise e
64
69
  except Exception as e:
@@ -76,41 +81,43 @@ class BaseService(Generic[ModelType]):
76
81
  update_data: Dict[str, Any] | None = None,
77
82
  conditions: Dict[str, Any] | None = None,
78
83
  unique_check: List[Dict[str, Any]] | None = None,
79
- exclude_entities: List[str] | None = None
84
+ exclude_entities: List[str] | None = None,
85
+ response_model: Any = None
80
86
  ) -> ModelType:
81
87
  try:
82
- # 고유 검사 수행
83
- if unique_check:
84
- await validate_unique_fields(self.db_session, unique_check, find_value=True)
85
- # 비밀번호가 있으면 해시 처리
86
- if "password" in update_data:
87
- update_data["password"] = hash_password(update_data["password"])
88
- # 제외할 엔티티가 있으면 제외
89
- if exclude_entities:
90
- update_data = {k: v for k, v in update_data.items() if k not in exclude_entities}
91
-
92
- if not ulid and not conditions:
93
- raise CustomException(
94
- ErrorCode.INVALID_INPUT,
95
- detail="Either 'ulid' or 'conditions' must be provided.",
96
- source_function="database.update_entity"
97
- )
88
+ async with self.db_session.begin():
89
+ # 고유 검사 수행
90
+ if unique_check:
91
+ await validate_unique_fields(self.db_session, unique_check, find_value=True)
98
92
 
99
- # ulid 조건 생성
100
- if ulid:
101
- if not ULID.from_str(ulid):
93
+ if not ulid and not conditions:
102
94
  raise CustomException(
103
- ErrorCode.VALIDATION_ERROR,
104
- detail=ulid,
105
- source_function=f"{self.__class__.__name__}.update"
95
+ ErrorCode.INVALID_INPUT,
96
+ detail="Either 'ulid' or 'conditions' must be provided.",
97
+ source_function="database.update_entity"
106
98
  )
107
-
108
- conditions = {"ulid": ulid}
109
99
 
110
- return await self.repository.update(
111
- update_data=update_data,
112
- conditions=conditions
113
- )
100
+ # ulid로 조건 생성
101
+ if ulid:
102
+ if not ULID.from_str(ulid):
103
+ raise CustomException(
104
+ ErrorCode.VALIDATION_ERROR,
105
+ detail=ulid,
106
+ source_function=f"{self.__class__.__name__}.update"
107
+ )
108
+
109
+ conditions = {"ulid": ulid}
110
+
111
+ result = await self.repository.update(
112
+ update_data=update_data,
113
+ conditions=conditions,
114
+ exclude_entities=exclude_entities
115
+ )
116
+
117
+ if response_model:
118
+ return process_response(result, response_model)
119
+ else:
120
+ return result
114
121
  except CustomException as e:
115
122
  raise e
116
123
  except Exception as e:
@@ -125,7 +132,7 @@ class BaseService(Generic[ModelType]):
125
132
  self,
126
133
  ulid: str | None = None,
127
134
  conditions: Dict[str, Any] | None = None
128
- ) -> None:
135
+ ) -> bool:
129
136
  try:
130
137
  if not ULID.from_str(ulid):
131
138
  raise CustomException(
@@ -168,35 +175,25 @@ class BaseService(Generic[ModelType]):
168
175
  skip: int = 0,
169
176
  limit: int = 100,
170
177
  filters: List[Dict[str, Any]] | None = None,
171
- model_name: str | None = None,
172
178
  response_model: Any = None,
173
179
  explicit_joins: Optional[List[Any]] = None,
174
- loading_joins: Optional[List[Any]] = None
180
+ loading_joins: Optional[List[Any]] = None,
181
+ order: Optional[str] = None
175
182
  ) -> List[Dict[str, Any]]:
176
183
  try:
177
- # 모델 이름을 통한 동적 처리
178
- if model_name:
179
- if model_name not in self.additional_models:
180
- raise CustomException(
181
- ErrorCode.INVALID_REQUEST,
182
- detail=f"Model {model_name} not registered",
183
- source_function=f"{self.__class__.__name__}.list"
184
- )
185
- model = self.additional_models[model_name]
186
- entities = await self.repository.list(
187
- skip=skip,
188
- limit=limit,
189
- filters=filters,
190
- model=model
191
- )
192
- return [process_response(entity, response_model) for entity in entities]
184
+ if order is None:
185
+ order = "created_at|desc"
186
+
187
+ order_by = order.split("|")
188
+ order = [{"field": order_by[0], "direction": order_by[1]}]
193
189
 
194
190
  entities = await self.repository.list(
195
191
  skip=skip,
196
192
  limit=limit,
197
193
  filters=filters,
198
194
  explicit_joins=explicit_joins,
199
- loading_joins=loading_joins
195
+ loading_joins=loading_joins,
196
+ order=order
200
197
  )
201
198
  return [process_response(entity, response_model) for entity in entities]
202
199
 
@@ -298,7 +298,8 @@ async def update_entity(
298
298
  session: AsyncSession,
299
299
  model: Type[ModelType],
300
300
  conditions: Dict[str, Any],
301
- update_data: Dict[str, Any]
301
+ update_data: Dict[str, Any],
302
+ exclude_entities: List[str] | None = None
302
303
  ) -> ModelType:
303
304
  """
304
305
  조건을 기반으로 엔티티를 조회하고 업데이트합니다.
@@ -339,7 +340,12 @@ async def update_entity(
339
340
  }
340
341
 
341
342
  # 데이터 병합 및 전처리
342
- processed_data = process_entity_data(model, update_data, existing_data)
343
+ processed_data = process_entity_data(
344
+ model=model,
345
+ entity_data=update_data,
346
+ existing_data=existing_data,
347
+ exclude_entities=exclude_entities
348
+ )
343
349
 
344
350
  # 엔티티 데이터 업데이트
345
351
  for key, value in processed_data.items():
@@ -364,7 +370,7 @@ async def delete_entity(
364
370
  session: AsyncSession,
365
371
  model: Type[ModelType],
366
372
  conditions: Dict[str, Any]
367
- ) -> None:
373
+ ) -> bool:
368
374
  try:
369
375
  stmt = select(model)
370
376
  for key, value in conditions.items():
@@ -386,6 +392,7 @@ async def delete_entity(
386
392
  await session.flush()
387
393
  await session.refresh(entity)
388
394
 
395
+ return True
389
396
  except SQLAlchemyError as e:
390
397
  raise CustomException(
391
398
  ErrorCode.DB_DELETE_ERROR,
@@ -398,19 +405,21 @@ async def purge_entity(
398
405
  session: AsyncSession,
399
406
  model: Type[ModelType],
400
407
  entity: ModelType
401
- ) -> None:
408
+ ) -> bool:
402
409
  # 엔티티를 영구 삭제합니다.
403
410
  await session.delete(entity)
404
- await session.commit()
411
+
412
+ return True
405
413
 
406
414
  async def list_entities(
407
415
  session: AsyncSession,
408
416
  model: Type[ModelType],
409
417
  skip: int = 0,
410
418
  limit: int = 100,
411
- filters: Optional[Dict[str, Any]] = None,
419
+ filters: Optional[List[Dict[str, Any]]] = None,
412
420
  explicit_joins: Optional[List[Any]] = None,
413
- loading_joins: Optional[List[Any]] = None
421
+ loading_joins: Optional[List[Any]] = None,
422
+ order: Optional[List[Dict[str, str]]] = None
414
423
  ) -> List[Dict[str, Any]]:
415
424
  """
416
425
  엔터티 리스트를 필터 및 조건에 따라 가져오는 함수.
@@ -456,6 +465,11 @@ async def list_entities(
456
465
  conditions = build_conditions(filters, model)
457
466
  query = query.where(and_(*conditions))
458
467
 
468
+ # 정렬 조건 적용
469
+ if order:
470
+ for order_item in order:
471
+ query = query.order_by(getattr(model, order_item["field"]).desc() if order_item["direction"] == "desc" else getattr(model, order_item["field"]).asc())
472
+
459
473
  # 페이지네이션 적용
460
474
  query = query.limit(limit).offset(skip)
461
475
 
@@ -74,6 +74,8 @@ class ErrorCode(Enum):
74
74
  SERVICE_NOT_REGISTERED = ErrorResponse(5003, "GENERAL_SERVICE_UNAVAILABLE", 503, "서비스를 사용할 수 없습니다")
75
75
  LOGIN_ERROR = ErrorResponse(5004, "LOGIN_ERROR", 401, "로그인 오류")
76
76
  TOKEN_ERROR = ErrorResponse(5005, "TOKEN_ERROR", 401, "토큰 오류")
77
+ DELETE_ERROR = ErrorResponse(5006, "DELETE_ERROR", 400, "삭제 오류")
78
+
77
79
 
78
80
  class CustomException(Exception):
79
81
  """사용자 정의 예외 클래스"""
@@ -211,14 +211,47 @@ async def verify_jwt_token(
211
211
  ) -> Dict[str, Any]:
212
212
  """JWT 토큰을 검증합니다."""
213
213
  try:
214
+ # token_settings가 None인지 검증
215
+ if not token_settings:
216
+ raise CustomException(
217
+ ErrorCode.CONFIGURATION_ERROR,
218
+ detail="token_settings",
219
+ source_function="security.verify_jwt_token"
220
+ )
221
+
222
+ required_settings = ["JWT_SECRET", "JWT_ALGORITHM", "TOKEN_AUDIENCE", "TOKEN_ISSUER"]
223
+ missing_settings = [key for key in required_settings if key not in token_settings]
224
+ if missing_settings:
225
+ raise CustomException(
226
+ ErrorCode.CONFIGURATION_ERROR,
227
+ detail=f"token_settings|{'|'.join(missing_settings)}",
228
+ source_function="security.verify_jwt_token"
229
+ )
230
+
214
231
  # 토큰 디코딩
215
- payload = jwt.decode(
216
- token,
217
- token_settings["JWT_SECRET"],
218
- algorithms=[token_settings["JWT_ALGORITHM"]],
219
- audience=token_settings["TOKEN_AUDIENCE"],
220
- issuer=token_settings["TOKEN_ISSUER"]
221
- )
232
+ try:
233
+ payload = jwt.decode(
234
+ token,
235
+ token_settings["JWT_SECRET"],
236
+ algorithms=[token_settings["JWT_ALGORITHM"]],
237
+ audience=token_settings["TOKEN_AUDIENCE"],
238
+ issuer=token_settings["TOKEN_ISSUER"]
239
+ )
240
+ except JWTError as e:
241
+ raise CustomException(
242
+ ErrorCode.INVALID_TOKEN,
243
+ detail=token,
244
+ source_function="security.verify_jwt_token",
245
+ original_error=e
246
+ )
247
+
248
+ # payload가 None인지 확인
249
+ if not payload:
250
+ raise CustomException(
251
+ ErrorCode.INVALID_TOKEN,
252
+ detail=token,
253
+ source_function="security.verify_jwt_token"
254
+ )
222
255
 
223
256
  # 토큰 타입 검증
224
257
  token_type = payload.get("token_type")
@@ -232,22 +265,17 @@ async def verify_jwt_token(
232
265
  if expected_type and token_type != expected_type:
233
266
  raise CustomException(
234
267
  ErrorCode.INVALID_TOKEN,
235
- detail=token,
268
+ detail=f"token|{token_type}|{expected_type}",
236
269
  source_function="security.verify_jwt_token"
237
270
  )
238
271
 
239
272
  return payload
240
273
 
241
- except JWTError as e:
242
- raise CustomException(
243
- ErrorCode.INVALID_TOKEN,
244
- detail=token,
245
- source_function="security.verify_jwt_token",
246
- original_error=e
247
- )
248
274
  except CustomException as e:
275
+ # CustomException은 그대로 전달
249
276
  raise e
250
277
  except Exception as e:
278
+ # 예상치 못한 에러만 INTERNAL_ERROR로 변환
251
279
  raise CustomException(
252
280
  ErrorCode.INTERNAL_ERROR,
253
281
  detail=str(e),
@@ -0,0 +1,2 @@
1
+ """버전 정보"""
2
+ __version__ = "0.2.81"
@@ -1,2 +0,0 @@
1
- """버전 정보"""
2
- __version__ = "0.2.79"
File without changes
File without changes
File without changes
File without changes