aiteamutils 0.2.80__py3-none-any.whl → 0.2.81__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -95,7 +97,8 @@ class BaseRepository(Generic[ModelType]):
95
97
  limit: int = 100,
96
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:
@@ -170,15 +177,23 @@ class BaseService(Generic[ModelType]):
170
177
  filters: List[Dict[str, Any]] | None = None,
171
178
  response_model: Any = None,
172
179
  explicit_joins: Optional[List[Any]] = None,
173
- loading_joins: Optional[List[Any]] = None
180
+ loading_joins: Optional[List[Any]] = None,
181
+ order: Optional[str] = None
174
182
  ) -> List[Dict[str, Any]]:
175
183
  try:
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]}]
189
+
176
190
  entities = await self.repository.list(
177
191
  skip=skip,
178
192
  limit=limit,
179
193
  filters=filters,
180
194
  explicit_joins=explicit_joins,
181
- loading_joins=loading_joins
195
+ loading_joins=loading_joins,
196
+ order=order
182
197
  )
183
198
  return [process_response(entity, response_model) for entity in entities]
184
199
 
aiteamutils/database.py CHANGED
@@ -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():
@@ -412,7 +418,8 @@ async def list_entities(
412
418
  limit: int = 100,
413
419
  filters: Optional[List[Dict[str, Any]]] = None,
414
420
  explicit_joins: Optional[List[Any]] = None,
415
- loading_joins: Optional[List[Any]] = None
421
+ loading_joins: Optional[List[Any]] = None,
422
+ order: Optional[List[Dict[str, str]]] = None
416
423
  ) -> List[Dict[str, Any]]:
417
424
  """
418
425
  엔터티 리스트를 필터 및 조건에 따라 가져오는 함수.
@@ -458,6 +465,11 @@ async def list_entities(
458
465
  conditions = build_conditions(filters, model)
459
466
  query = query.where(and_(*conditions))
460
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
+
461
473
  # 페이지네이션 적용
462
474
  query = query.limit(limit).offset(skip)
463
475
 
aiteamutils/security.py CHANGED
@@ -162,10 +162,10 @@ async def create_jwt_token(
162
162
  "token_type": token_type,
163
163
 
164
164
  # 조직 관련 클레임
165
- "organization_ulid": user_data.role.team.organization.ulid,
166
- "organization_id": user_data.role.team.organization.id,
167
- "organization_name": user_data.role.team.organization.name,
168
- "company_name": user_data.role.team.organization.company.name
165
+ "organization_ulid": user_data.role.organization.ulid,
166
+ "organization_id": user_data.role.organization.id,
167
+ "organization_name": user_data.role.organization.name,
168
+ "company_name": user_data.role.organization.company.name
169
169
  }
170
170
  else: # refresh token
171
171
  expires_at = datetime.now(timezone.utc) + timedelta(days=14)
@@ -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),
aiteamutils/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  """버전 정보"""
2
- __version__ = "0.2.80"
2
+ __version__ = "0.2.81"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.80
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
@@ -0,0 +1,15 @@
1
+ aiteamutils/__init__.py,sha256=kRBpRjark0M8ZwFfmKiMFol6CbIILN3WE4f6_P6iIq0,1089
2
+ aiteamutils/base_model.py,sha256=bnRJJaGXGS3TKxfCWWV3arFjdG0qLsPFDXuguYsDyVM,3008
3
+ aiteamutils/base_repository.py,sha256=HKcgYyEb0JypojoXBFcIT39hPC5CqnjBkHT__GV-lfQ,4615
4
+ aiteamutils/base_service.py,sha256=7DXjEn3e_dve_ZfNwUP1_WD7QnucWRXBq-bYuxVmyXI,8694
5
+ aiteamutils/cache.py,sha256=07xBGlgAwOTAdY5mnMOQJ5EBxVwe8glVD7DkGEkxCtw,1373
6
+ aiteamutils/config.py,sha256=YdalpJb70-txhGJAS4aaKglEZAFVWgfzw5BXSWpkUz4,3232
7
+ aiteamutils/database.py,sha256=b4fN0XHNWxMJeS5M95JcJ7tujAJ1x3SPTAfDvJdB4sE,19696
8
+ aiteamutils/enums.py,sha256=ipZi6k_QD5-3QV7Yzv7bnL0MjDz-vqfO9I5L77biMKs,632
9
+ aiteamutils/exceptions.py,sha256=5yREcEUbJPzq2404pFJ4J9047I9_qx2QG5iBND1PjDI,15901
10
+ aiteamutils/security.py,sha256=tS7gdkCvL0hfgC_FWDSWaCVrzy3zUFpw-PyiFn9yXMM,10839
11
+ aiteamutils/validators.py,sha256=PvI9hbMEAqTawgxPbiWRyx2r9yTUrpNBQs1AD3w4F2U,7726
12
+ aiteamutils/version.py,sha256=w_eEoCaGdfblH2R8pC54VN-bdUU1cxm6KxYvvUVm6P0,42
13
+ aiteamutils-0.2.81.dist-info/METADATA,sha256=hETQTyF40i30aB1erNKSaMsPQm5MajSGP4vwUKMnACM,1718
14
+ aiteamutils-0.2.81.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ aiteamutils-0.2.81.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- aiteamutils/__init__.py,sha256=kRBpRjark0M8ZwFfmKiMFol6CbIILN3WE4f6_P6iIq0,1089
2
- aiteamutils/base_model.py,sha256=bnRJJaGXGS3TKxfCWWV3arFjdG0qLsPFDXuguYsDyVM,3008
3
- aiteamutils/base_repository.py,sha256=-5kPNsB82ILj_Z9Hdf2tVxSb7owars1KBgDubIRXA7I,4430
4
- aiteamutils/base_service.py,sha256=6wCEbxPj9idhvMJbE3EK1uhXf5On97JVz2IFF-MILrw,8330
5
- aiteamutils/cache.py,sha256=07xBGlgAwOTAdY5mnMOQJ5EBxVwe8glVD7DkGEkxCtw,1373
6
- aiteamutils/config.py,sha256=YdalpJb70-txhGJAS4aaKglEZAFVWgfzw5BXSWpkUz4,3232
7
- aiteamutils/database.py,sha256=HezCvPVVFV2qyZOkuyy92z1g3bKBgwAnksZQVHuwYyw,19220
8
- aiteamutils/enums.py,sha256=ipZi6k_QD5-3QV7Yzv7bnL0MjDz-vqfO9I5L77biMKs,632
9
- aiteamutils/exceptions.py,sha256=5yREcEUbJPzq2404pFJ4J9047I9_qx2QG5iBND1PjDI,15901
10
- aiteamutils/security.py,sha256=JA7QGO07Jfmiwu3u429gIX6f8ESc3wQ4GeyOetLbHEo,9681
11
- aiteamutils/validators.py,sha256=PvI9hbMEAqTawgxPbiWRyx2r9yTUrpNBQs1AD3w4F2U,7726
12
- aiteamutils/version.py,sha256=x9TYUs6I03uAB9NABtV3lwAjzbxNdHWdUxFrj6Xcfgg,42
13
- aiteamutils-0.2.80.dist-info/METADATA,sha256=ueFgVZRnNCHfe4Db8g0Is9NYOuAU44NUmlcRQa7aqz8,1718
14
- aiteamutils-0.2.80.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- aiteamutils-0.2.80.dist-info/RECORD,,