aiteamutils 0.2.136__py3-none-any.whl → 0.2.138__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.
@@ -32,7 +32,7 @@ class BaseRepository(Generic[ModelType]):
32
32
  raise CustomException(
33
33
  ErrorCode.DB_CONNECTION_ERROR,
34
34
  detail="Session cannot be None",
35
- source_function=f"{self.__class__.__name__}.session"
35
+ source_function=f"base_repository.{self.__class__.__name__}.session"
36
36
  )
37
37
  self._session = value
38
38
 
@@ -57,7 +57,7 @@ class BaseRepository(Generic[ModelType]):
57
57
  raise CustomException(
58
58
  ErrorCode.INTERNAL_ERROR,
59
59
  detail=str(e),
60
- source_function=f"{self.__class__.__name__}.create",
60
+ source_function=f"base_repository.{self.__class__.__name__}.create",
61
61
  original_error=e
62
62
  )
63
63
 
@@ -61,10 +61,35 @@ class BaseService(Generic[ModelType]):
61
61
  raise CustomException(
62
62
  ErrorCode.FORBIDDEN,
63
63
  detail=f"{role_permission}",
64
- source_function=f"{self.__class__.__name__}.create"
64
+ source_function=f"base_service.{self.__class__.__name__}.create.permission_result"
65
65
  )
66
66
 
67
67
  try:
68
+ # 파일 데이터 분리
69
+ entity_data_copy = entity_data.copy()
70
+ separated_files = {}
71
+
72
+ # extra_data 내의 파일 필드 분리
73
+ if 'extra_data' in entity_data_copy and isinstance(entity_data_copy['extra_data'], dict):
74
+ extra_data = entity_data_copy['extra_data'].copy()
75
+ file_fields = {k: v for k, v in extra_data.items() if k.endswith('_files')}
76
+
77
+ if file_fields and not storage_dir:
78
+ raise CustomException(
79
+ ErrorCode.INVALID_INPUT,
80
+ detail="storage_dir is required for file upload",
81
+ source_function=f"base_service.{self.__class__.__name__}.create.file_fields"
82
+ )
83
+
84
+ # 파일 필드 분리 및 제거
85
+ for field_name, files in file_fields.items():
86
+ if files:
87
+ separated_files[field_name] = files
88
+ # extra_data에서 파일 필드 제거
89
+ del extra_data[field_name]
90
+
91
+ entity_data_copy['extra_data'] = extra_data
92
+
68
93
  async with self.db_session.begin():
69
94
  # 고유 검사 수행
70
95
  if unique_check:
@@ -73,35 +98,44 @@ class BaseService(Generic[ModelType]):
73
98
  if fk_check:
74
99
  await validate_unique_fields(self.db_session, fk_check, find_value=False)
75
100
 
76
- # _files로 끝나는 키 처리
77
- file_keys = [key for key in entity_data.keys() if key.endswith('_files')]
78
- file_infos = {}
79
-
80
- if file_keys and not storage_dir:
81
- raise CustomException(
82
- ErrorCode.INVALID_INPUT,
83
- detail="storage_dir is required for file upload",
84
- source_function=f"{self.__class__.__name__}.create"
85
- )
86
-
87
- # 먼저 엔티티를 생성하여 ULID를 얻음
101
+ # 엔티티 생성
88
102
  result = await self.repository.create(
89
- entity_data=entity_data,
103
+ entity_data=entity_data_copy,
90
104
  exclude_entities=exclude_entities
91
105
  )
92
106
 
93
- # 파일 처리
94
- for file_key in file_keys:
95
- files_data = entity_data.pop(file_key) # 파일 데이터 추출 및 제거
96
- if files_data:
97
- from .files import FileHandler
98
- file_infos[file_key] = await FileHandler.save_files(
99
- files=files_data,
107
+ # 파일 처리 및 저장
108
+ file_infos = {}
109
+ if separated_files:
110
+ from .files import FileHandler
111
+ for field_name, files in separated_files.items():
112
+ saved_files = await FileHandler.save_files(
113
+ files=files,
100
114
  storage_dir=storage_dir,
101
115
  entity_name=self.model.__tablename__,
102
116
  entity_ulid=result.ulid,
103
117
  db_session=self.db_session
104
118
  )
119
+ file_infos[field_name] = saved_files
120
+
121
+ # extra_data 업데이트
122
+ if not hasattr(result, 'extra_data'):
123
+ result.extra_data = {}
124
+ if not result.extra_data:
125
+ result.extra_data = {}
126
+
127
+ result.extra_data[field_name] = [
128
+ {
129
+ 'original_name': f['original_name'],
130
+ 'storage_path': f['storage_path'],
131
+ 'mime_type': f['mime_type'],
132
+ 'size': f['size'],
133
+ 'checksum': f['checksum']
134
+ } for f in saved_files
135
+ ]
136
+
137
+ # extra_data 업데이트된 엔티티 저장
138
+ await self.db_session.flush()
105
139
 
106
140
  # 결과 반환
107
141
  if response_model:
@@ -126,7 +160,7 @@ class BaseService(Generic[ModelType]):
126
160
  raise CustomException(
127
161
  ErrorCode.INTERNAL_ERROR,
128
162
  detail=str(e),
129
- source_function=f"{self.__class__.__name__}.create",
163
+ source_function=f"base_service.{self.__class__.__name__}.create",
130
164
  original_error=e
131
165
  )
132
166
 
@@ -153,7 +187,7 @@ class BaseService(Generic[ModelType]):
153
187
  raise CustomException(
154
188
  ErrorCode.INVALID_INPUT,
155
189
  detail="Either 'ulid' or 'conditions' must be provided.",
156
- source_function="database.update_entity"
190
+ source_function=f"base_service.{self.__class__.__name__}.update.ulid_or_conditions"
157
191
  )
158
192
 
159
193
  # ulid로 조건 생성
@@ -162,7 +196,7 @@ class BaseService(Generic[ModelType]):
162
196
  raise CustomException(
163
197
  ErrorCode.VALIDATION_ERROR,
164
198
  detail=ulid,
165
- source_function=f"{self.__class__.__name__}.update"
199
+ source_function=f"base_service.{self.__class__.__name__}.update.ulid_or_conditions"
166
200
  )
167
201
 
168
202
  conditions = {"ulid": ulid}
@@ -183,7 +217,7 @@ class BaseService(Generic[ModelType]):
183
217
  raise CustomException(
184
218
  ErrorCode.INTERNAL_ERROR,
185
219
  detail=str(e),
186
- source_function=f"{self.__class__.__name__}.update",
220
+ source_function=f"base_service.{self.__class__.__name__}.update",
187
221
  original_error=e
188
222
  )
189
223
 
@@ -201,14 +235,14 @@ class BaseService(Generic[ModelType]):
201
235
  raise CustomException(
202
236
  ErrorCode.VALIDATION_ERROR,
203
237
  detail=ulid,
204
- source_function=f"{self.__class__.__name__}.delete"
238
+ source_function=f"base_service.{self.__class__.__name__}.delete.ulid_validation"
205
239
  )
206
240
 
207
241
  if not ulid and not conditions:
208
242
  raise CustomException(
209
243
  ErrorCode.INVALID_INPUT,
210
244
  detail="Either 'ulid' or 'conditions' must be provided.",
211
- source_function="database.update_entity"
245
+ source_function=f"base_service.{self.__class__.__name__}.delete.ulid_or_conditions"
212
246
  )
213
247
 
214
248
  # ulid로 조건 생성
@@ -226,7 +260,7 @@ class BaseService(Generic[ModelType]):
226
260
  raise CustomException(
227
261
  ErrorCode.INTERNAL_ERROR,
228
262
  detail=str(e),
229
- source_function=f"{self.__class__.__name__}.delete",
263
+ source_function=f"base_service.{self.__class__.__name__}.delete",
230
264
  original_error=e
231
265
  )
232
266
 
@@ -282,7 +316,7 @@ class BaseService(Generic[ModelType]):
282
316
  except Exception as e:
283
317
  raise CustomException(
284
318
  ErrorCode.INTERNAL_ERROR,
285
- source_function=f"{self.__class__.__name__}.list",
319
+ source_function=f"base_service.{self.__class__.__name__}.list",
286
320
  original_error=e
287
321
  )
288
322
 
@@ -304,7 +338,7 @@ class BaseService(Generic[ModelType]):
304
338
  raise CustomException(
305
339
  ErrorCode.INVALID_INPUT,
306
340
  detail="Either 'ulid' or 'conditions' must be provided.",
307
- source_function="database.update_entity"
341
+ source_function=f"base_service.{self.__class__.__name__}.get.ulid_or_conditions"
308
342
  )
309
343
 
310
344
  # ulid로 조건 생성
@@ -313,7 +347,7 @@ class BaseService(Generic[ModelType]):
313
347
  raise CustomException(
314
348
  ErrorCode.VALIDATION_ERROR,
315
349
  detail=ulid,
316
- source_function=f"{self.__class__.__name__}.update"
350
+ source_function=f"base_service.{self.__class__.__name__}.get.ulid_validation"
317
351
  )
318
352
 
319
353
  conditions = {"ulid": ulid}
@@ -331,7 +365,7 @@ class BaseService(Generic[ModelType]):
331
365
  raise CustomException(
332
366
  ErrorCode.INTERNAL_ERROR,
333
367
  detail=str(e),
334
- source_function=f"{self.__class__.__name__}.get",
368
+ source_function=f"base_service.{self.__class__.__name__}.get",
335
369
  original_error=e
336
370
  )
337
371
 
aiteamutils/database.py CHANGED
@@ -341,7 +341,7 @@ async def update_entity(
341
341
  raise CustomException(
342
342
  ErrorCode.NOT_FOUND,
343
343
  detail=f"{model.__name__}|{conditions}.",
344
- source_function="database.update_entity"
344
+ source_function="database.update_entity.not_entity"
345
345
  )
346
346
 
347
347
  # 기존 데이터를 딕셔너리로 변환
@@ -394,7 +394,7 @@ async def delete_entity(
394
394
  raise CustomException(
395
395
  ErrorCode.NOT_FOUND,
396
396
  detail=f"{model.__name__}|{conditions}.",
397
- source_function="database.delete_entity"
397
+ source_function="database.delete_entity.not_entity"
398
398
  )
399
399
 
400
400
  entity.is_deleted = True
aiteamutils/validators.py CHANGED
@@ -1,12 +1,12 @@
1
1
  """유효성 검사 관련 유틸리티 함수들을 모아둔 모듈입니다."""
2
2
 
3
- from typing import Type, Dict, Any, Callable, TypeVar, Optional, List, Union
3
+ from typing import Type, Dict, Any, Callable, TypeVar, Optional, List, Union, Annotated
4
4
  from functools import wraps
5
5
  from sqlalchemy import Table
6
6
  from sqlalchemy.ext.asyncio import AsyncSession
7
7
  from fastapi import Request
8
8
  from inspect import signature
9
- from pydantic import BaseModel, field_validator
9
+ from pydantic import BaseModel, field_validator, Field
10
10
  from datetime import datetime, date
11
11
  import re
12
12
 
@@ -234,64 +234,3 @@ class Validator:
234
234
  source_function="Validator.validate_datetime",
235
235
  original_error=str(e)
236
236
  )
237
-
238
- def _create_validator(field_names: tuple[str, ...],
239
- validate_func: Callable,
240
- field_type: Type,
241
- error_code: ErrorCode,
242
- source_prefix: str):
243
- """공통 validator 생성 함수
244
- Args:
245
- field_names: 검증할 필드명들
246
- validate_func: 실제 검증을 수행할 함수
247
- field_type: 필드 타입 (date 또는 datetime)
248
- error_code: 에러 발생시 사용할 에러 코드
249
- source_prefix: 에러 발생시 사용할 source_function 접두사
250
- """
251
- def decorator(cls):
252
- for field_name in field_names:
253
- field = cls.model_fields.get(field_name)
254
- if field:
255
- field.annotation = Optional[field_type]
256
- field.default = None
257
-
258
- @field_validator(field_name, mode='before')
259
- @classmethod
260
- def validate(cls, value: Any, info: Any) -> Any:
261
- # 빈 값 처리를 가장 먼저, 더 엄격하게
262
- if not value or str(value).strip() == "":
263
- return None
264
- try:
265
- return validate_func(value, field_name)
266
- except CustomException as e:
267
- raise e
268
- except Exception as e:
269
- raise CustomException(
270
- error_code=error_code,
271
- detail=f"{field_name}|{value}",
272
- source_function=f"{source_prefix}.{field_name}",
273
- original_error=str(e)
274
- )
275
- setattr(cls, f'validate_{field_name}', validate)
276
- return cls
277
- return decorator
278
-
279
- def date_validator(*field_names: str):
280
- """날짜 필드 유효성 검사 데코레이터"""
281
- return _create_validator(
282
- field_names=field_names,
283
- validate_func=Validator.validate_date,
284
- field_type=date,
285
- error_code=ErrorCode.VALIDATION_ERROR,
286
- source_prefix="date_validator"
287
- )
288
-
289
- def datetime_validator(*field_names: str):
290
- """날짜+시간 필드 유효성 검사 데코레이터"""
291
- return _create_validator(
292
- field_names=field_names,
293
- validate_func=Validator.validate_datetime,
294
- field_type=datetime,
295
- error_code=ErrorCode.VALIDATION_ERROR,
296
- source_prefix="datetime_validator"
297
- )
aiteamutils/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  """버전 정보"""
2
- __version__ = "0.2.136"
2
+ __version__ = "0.2.138"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.136
3
+ Version: 0.2.138
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=kRBpRjark0M8ZwFfmKiMFol6CbIILN3WE4f6_P6iIq0,1089
2
2
  aiteamutils/base_model.py,sha256=0rs4cjnF2ea3Q2vBTj6F64BGk7ZglJsChsS7ne_R_tg,4056
3
- aiteamutils/base_repository.py,sha256=vzBw3g3jCJetTDblZvZenEGXk89Qu_65_02C7QTcf8Q,4615
4
- aiteamutils/base_service.py,sha256=jWN0QTIIe5SkNitA_EP2GF0t1tyrCm1VftQtR3hJMT8,12507
3
+ aiteamutils/base_repository.py,sha256=Oy2zE1i5qx60Xf1tnsaKLyFWapiPqt5JH8NejwNrPWg,4647
4
+ aiteamutils/base_service.py,sha256=QSUzdIcV88EdmjEF7vMyrN5CjKhS6HTbsoXSp8P9Gag,14432
5
5
  aiteamutils/cache.py,sha256=07xBGlgAwOTAdY5mnMOQJ5EBxVwe8glVD7DkGEkxCtw,1373
6
6
  aiteamutils/config.py,sha256=YdalpJb70-txhGJAS4aaKglEZAFVWgfzw5BXSWpkUz4,3232
7
- aiteamutils/database.py,sha256=_aiS_akyJHHFSWZCmPcO82_O32xdUnXlNrPW5KhzxaA,20396
7
+ aiteamutils/database.py,sha256=msvBKtxWeQVOo0v2Q9i2azuTNtnUItuNNar52gdRZTo,20418
8
8
  aiteamutils/enums.py,sha256=7WLqlcJqQWtETAga2WAxNp3dJTQIAd2TW-4WzkoHHa8,2498
9
9
  aiteamutils/exceptions.py,sha256=pgf3ersezObyl17wAO3I2fb8m9t2OzWDX1mSjwAWm2Y,16035
10
10
  aiteamutils/files.py,sha256=tdvivl3XLNv7Al7H1gGFczmrHM8XlQpiZsEc2xQ_UTU,8829
11
11
  aiteamutils/security.py,sha256=McUl3t5Z5SyUDVUHymHdDkYyF4YSeg4g9fFMML4W6Kw,11630
12
- aiteamutils/validators.py,sha256=msOrha36xWsapm4VAh63YmFq1GVyC9tzZcjXYFCEZ_g,11949
13
- aiteamutils/version.py,sha256=GEzMhIGk9CTUp2zEaaKPPRMurlXlp9MeMYAAzWYudjo,43
14
- aiteamutils-0.2.136.dist-info/METADATA,sha256=qXiXhvObJ6S8Xlm7NrCwLUMQn6cTLZXnJa-IEDgZ7PY,1719
15
- aiteamutils-0.2.136.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- aiteamutils-0.2.136.dist-info/RECORD,,
12
+ aiteamutils/validators.py,sha256=_WHN6jqJQzKM5uPTg-Da8U2qqevS84XeKMkCCF4C_lY,9591
13
+ aiteamutils/version.py,sha256=xZySMcOyLEq-gP55_1pxieeIS9BbYr1Tg7GT4OJHMjI,43
14
+ aiteamutils-0.2.138.dist-info/METADATA,sha256=Hc55YOzeewQdyvH7AGj17BxER6ZiOIptMcPCY87XyXE,1719
15
+ aiteamutils-0.2.138.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ aiteamutils-0.2.138.dist-info/RECORD,,