aiteamutils 0.2.80__py3-none-any.whl → 0.2.82__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.
@@ -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/enums.py CHANGED
@@ -1,6 +1,11 @@
1
1
  """시스템 전체에서 사용되는 열거형 정의."""
2
2
  from enum import Enum
3
3
 
4
+ def hyphen_to_camel_case(text: str) -> str:
5
+ """하이픈을 카멜케이스로 변환하는 함수"""
6
+ words = text.split('-')
7
+ return words[0].lower() + ''.join(word.capitalize() for word in words[1:])
8
+
4
9
  class UserStatus(str, Enum):
5
10
  """사용자 상태."""
6
11
  PENDING = "PENDING"
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.82"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.80
3
+ Version: 0.2.82
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=8WC7vcrxr95-ijjzbSgyvbt4jVBzetwqX5w4-yoBzvg,846
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=65qdn7CP4FsVew4EvQmWjoVZtTO2CKLFKe2gm9jpk4k,42
13
+ aiteamutils-0.2.82.dist-info/METADATA,sha256=G62Nb2s5xCKIP4ooXT3Q6JAL_zAJBkkbIVKglGaRx5I,1718
14
+ aiteamutils-0.2.82.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ aiteamutils-0.2.82.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,,