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.
- aiteamutils/base_repository.py +8 -4
- aiteamutils/base_service.py +49 -34
- aiteamutils/database.py +15 -3
- aiteamutils/enums.py +5 -0
- aiteamutils/security.py +47 -19
- aiteamutils/version.py +1 -1
- {aiteamutils-0.2.80.dist-info → aiteamutils-0.2.82.dist-info}/METADATA +1 -1
- aiteamutils-0.2.82.dist-info/RECORD +15 -0
- aiteamutils-0.2.80.dist-info/RECORD +0 -15
- {aiteamutils-0.2.80.dist-info → aiteamutils-0.2.82.dist-info}/WHEEL +0 -0
aiteamutils/base_repository.py
CHANGED
@@ -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
|
aiteamutils/base_service.py
CHANGED
@@ -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
|
-
|
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
|
-
|
84
|
-
|
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
|
-
|
100
|
-
if ulid:
|
101
|
-
if not ULID.from_str(ulid):
|
93
|
+
if not ulid and not conditions:
|
102
94
|
raise CustomException(
|
103
|
-
ErrorCode.
|
104
|
-
detail=ulid,
|
105
|
-
source_function=
|
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
|
-
|
111
|
-
|
112
|
-
|
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(
|
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.
|
166
|
-
"organization_id": user_data.role.
|
167
|
-
"organization_name": user_data.role.
|
168
|
-
"company_name": user_data.role.
|
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
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
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.
|
2
|
+
__version__ = "0.2.82"
|
@@ -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,,
|
File without changes
|