aiteamutils 0.2.156__py3-none-any.whl → 0.2.158__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_service.py
CHANGED
@@ -39,175 +39,186 @@ class BaseService(Generic[ModelType]):
|
|
39
39
|
#######################
|
40
40
|
# 입력 및 수정, 삭제 #
|
41
41
|
#######################
|
42
|
-
async def
|
42
|
+
async def _separate_file_data(
|
43
43
|
self,
|
44
|
-
entity_data: Dict[str, Any]
|
45
|
-
entity_result: Any,
|
46
|
-
storage_dir: str,
|
47
|
-
operation: Literal["create", "update", "delete"] = "create"
|
44
|
+
entity_data: Dict[str, Any]
|
48
45
|
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
49
|
-
"""파일
|
46
|
+
"""파일 데이터를 엔티티 데이터에서 분리하는 함수
|
50
47
|
|
51
48
|
Args:
|
52
49
|
entity_data (Dict[str, Any]): 엔티티 데이터
|
53
|
-
entity_result (Any): 생성/수정된 엔티티 결과
|
54
|
-
storage_dir (str): 저장 디렉토리 경로
|
55
|
-
operation (str): 수행할 작업 유형 ("create", "update", "delete")
|
56
50
|
|
57
51
|
Returns:
|
58
|
-
Tuple[Dict[str, Any], Dict[str, Any]]: (
|
52
|
+
Tuple[Dict[str, Any], Dict[str, Any]]: (파일 제외된 엔티티 데이터, 분리된 파일 데이터)
|
59
53
|
"""
|
60
54
|
try:
|
61
|
-
logger.info(
|
62
|
-
logger.info(f"[엔티티 데이터] {entity_data}")
|
63
|
-
|
55
|
+
logger.info("[파일 데이터 분리 시작]")
|
64
56
|
entity_data_copy = entity_data.copy()
|
65
|
-
file_infos = {}
|
66
|
-
|
67
|
-
# 파일 데이터 분리
|
68
57
|
separated_files = {}
|
69
|
-
|
70
|
-
|
58
|
+
|
59
|
+
if 'extra_data' in entity_data_copy and isinstance(entity_data_copy['extra_data'], dict):
|
71
60
|
extra_data = entity_data_copy['extra_data'].copy()
|
72
|
-
# 파일 필드 체크 로직 수정
|
73
|
-
file_fields = {}
|
74
|
-
for k, v in extra_data.items():
|
75
|
-
logger.info(f"[필드 검사] 필드명: {k}, 타입: {type(v)}, 값: {v}")
|
76
|
-
if v and isinstance(v, (list, tuple)):
|
77
|
-
logger.info(f"[리스트/튜플 검사] 필드명: {k}, 첫 번째 항목 타입: {type(v[0]) if v else 'None'}")
|
78
|
-
# 파일 객체를 포함하는 튜플/리스트인 경우만 처리
|
79
|
-
if any(isinstance(item, (tuple, list)) and len(item) == 2 and hasattr(item[0], 'read') for item in v):
|
80
|
-
logger.info(f"[파일 객체 포함 필드 발견] {k}")
|
81
|
-
file_fields[k] = v
|
82
|
-
|
83
|
-
logger.info(f"[파일 필드 확인] {file_fields}")
|
84
|
-
logger.info(f"[파일 필드 타입] {[(k, type(v), type(v[0]) if isinstance(v, list) and v else None) for k, v in file_fields.items()]}")
|
85
|
-
|
86
|
-
if file_fields and not storage_dir:
|
87
|
-
logger.error("[에러] storage_dir이 필요하지만 제공되지 않음")
|
88
|
-
raise CustomException(
|
89
|
-
ErrorCode.INVALID_INPUT,
|
90
|
-
detail="storage_dir is required for file upload",
|
91
|
-
source_function=f"{self.__class__.__name__}._process_files"
|
92
|
-
)
|
93
61
|
|
94
|
-
# 파일 필드
|
95
|
-
for field_name, files in
|
96
|
-
logger.info(f"[
|
97
|
-
|
98
|
-
|
99
|
-
|
62
|
+
# 파일 필드 체크
|
63
|
+
for field_name, files in extra_data.items():
|
64
|
+
logger.info(f"[필드 검사] 필드명: {field_name}, 타입: {type(files)}")
|
65
|
+
if files and isinstance(files, (list, tuple)):
|
66
|
+
# 파일 객체를 포함하는 튜플/리스트인 경우만 처리
|
67
|
+
if any(isinstance(item, (tuple, list)) and len(item) == 2 and hasattr(item[0], 'read') for item in files):
|
68
|
+
logger.info(f"[파일 필드 발견] {field_name}")
|
69
|
+
separated_files[field_name] = files
|
70
|
+
del extra_data[field_name]
|
100
71
|
|
101
72
|
entity_data_copy['extra_data'] = extra_data
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
73
|
+
|
74
|
+
logger.info("[파일 데이터 분리 완료]")
|
75
|
+
return entity_data_copy, separated_files
|
76
|
+
|
77
|
+
except Exception as e:
|
78
|
+
logger.error(f"[파일 분리 중 에러 발생] {str(e)}")
|
79
|
+
raise CustomException(
|
80
|
+
ErrorCode.INTERNAL_ERROR,
|
81
|
+
detail=str(e),
|
82
|
+
source_function=f"{self.__class__.__name__}._separate_file_data",
|
83
|
+
original_error=e
|
84
|
+
)
|
85
|
+
|
86
|
+
async def _save_files(
|
87
|
+
self,
|
88
|
+
separated_files: Dict[str, Any],
|
89
|
+
entity_result: Any,
|
90
|
+
storage_dir: str
|
91
|
+
) -> Dict[str, Any]:
|
92
|
+
"""분리된 파일 데이터를 저장하는 함수
|
93
|
+
|
94
|
+
Args:
|
95
|
+
separated_files (Dict[str, Any]): 분리된 파일 데이터
|
96
|
+
entity_result (Any): 생성/수정된 엔티티
|
97
|
+
storage_dir (str): 저장 디렉토리
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
Dict[str, Any]: 저장된 파일 정보
|
101
|
+
"""
|
102
|
+
try:
|
103
|
+
if not storage_dir:
|
104
|
+
raise CustomException(
|
105
|
+
ErrorCode.INVALID_INPUT,
|
106
|
+
detail="storage_dir is required for file upload",
|
107
|
+
source_function=f"{self.__class__.__name__}._save_files"
|
120
108
|
)
|
121
|
-
|
122
|
-
|
109
|
+
|
110
|
+
logger.info("[파일 저장 시작]")
|
111
|
+
file_infos = {}
|
112
|
+
from .files import FileHandler
|
113
|
+
|
114
|
+
for field_name, files in separated_files.items():
|
115
|
+
logger.info(f"[필드 처리] {field_name}, 파일 수: {len(files)}")
|
116
|
+
saved_files = await FileHandler.save_files(
|
117
|
+
files=files,
|
118
|
+
storage_dir=storage_dir,
|
119
|
+
entity_name=self.model.__tablename__,
|
120
|
+
entity_ulid=entity_result.ulid,
|
121
|
+
db_session=self.db_session,
|
122
|
+
column_name=field_name
|
123
|
+
)
|
124
|
+
file_infos[field_name] = saved_files
|
123
125
|
|
124
|
-
#
|
125
|
-
|
126
|
-
|
127
|
-
await FileHandler.delete_files(file_info[0])
|
126
|
+
# extra_data 업데이트
|
127
|
+
if not hasattr(entity_result, 'extra_data') or not entity_result.extra_data:
|
128
|
+
entity_result.extra_data = {}
|
128
129
|
|
129
|
-
|
130
|
-
await self.db_session.execute(
|
131
|
-
text("""
|
132
|
-
DELETE FROM files
|
133
|
-
WHERE entity_name = :entity_name
|
134
|
-
AND entity_ulid = :entity_ulid
|
135
|
-
"""),
|
130
|
+
entity_result.extra_data[field_name] = [
|
136
131
|
{
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
files=files,
|
151
|
-
storage_dir=storage_dir,
|
152
|
-
entity_name=self.model.__tablename__,
|
153
|
-
entity_ulid=entity_result.ulid,
|
154
|
-
db_session=self.db_session,
|
155
|
-
column_name=field_name
|
156
|
-
)
|
157
|
-
file_infos[field_name] = saved_files
|
158
|
-
logger.info(f"[파일 저장 완료] {field_name}: {len(saved_files)}개")
|
159
|
-
|
160
|
-
# extra_data 업데이트 - 파일 정보 캐싱
|
161
|
-
if not hasattr(entity_result, 'extra_data'):
|
162
|
-
entity_result.extra_data = {}
|
163
|
-
if not entity_result.extra_data:
|
164
|
-
entity_result.extra_data = {}
|
165
|
-
|
166
|
-
entity_result.extra_data[field_name] = [
|
167
|
-
{
|
168
|
-
'original_name': f['original_name'],
|
169
|
-
'storage_path': f['storage_path'],
|
170
|
-
'mime_type': f['mime_type'],
|
171
|
-
'size': f['size'],
|
172
|
-
'checksum': f['checksum'],
|
173
|
-
'column_name': f['column_name']
|
174
|
-
} for f in saved_files
|
175
|
-
]
|
176
|
-
logger.info(f"[extra_data 업데이트 완료] {field_name}")
|
177
|
-
|
178
|
-
# extra_data 업데이트된 엔티티 저장
|
179
|
-
await self.db_session.flush()
|
180
|
-
logger.info("[DB flush 완료]")
|
181
|
-
|
182
|
-
logger.info("[파일 처리 완료]")
|
183
|
-
return entity_data_copy, file_infos
|
184
|
-
|
132
|
+
'original_name': f['original_name'],
|
133
|
+
'storage_path': f['storage_path'],
|
134
|
+
'mime_type': f['mime_type'],
|
135
|
+
'size': f['size'],
|
136
|
+
'checksum': f['checksum'],
|
137
|
+
'column_name': f['column_name']
|
138
|
+
} for f in saved_files
|
139
|
+
]
|
140
|
+
|
141
|
+
await self.db_session.flush()
|
142
|
+
logger.info("[파일 저장 완료]")
|
143
|
+
return file_infos
|
144
|
+
|
185
145
|
except CustomException as e:
|
186
|
-
logger.error(f"[CustomException 발생] {e.error_code}: {e.detail}")
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
await FileHandler.delete_files(file_info["storage_path"])
|
146
|
+
logger.error(f"[파일 저장 중 CustomException 발생] {e.error_code}: {e.detail}")
|
147
|
+
await self._cleanup_saved_files(file_infos)
|
148
|
+
raise e
|
149
|
+
except Exception as e:
|
150
|
+
logger.error(f"[파일 저장 중 에러 발생] {str(e)}")
|
151
|
+
await self._cleanup_saved_files(file_infos)
|
193
152
|
raise CustomException(
|
194
|
-
|
195
|
-
detail=e
|
196
|
-
source_function=f"{self.__class__.__name__}.
|
153
|
+
ErrorCode.FILE_SYSTEM_ERROR,
|
154
|
+
detail=str(e),
|
155
|
+
source_function=f"{self.__class__.__name__}._save_files",
|
197
156
|
original_error=e
|
198
157
|
)
|
158
|
+
|
159
|
+
async def _cleanup_saved_files(self, file_infos: Dict[str, Any]):
|
160
|
+
"""저장된 파일들을 정리(삭제)하는 함수"""
|
161
|
+
if file_infos:
|
162
|
+
from .files import FileHandler
|
163
|
+
for file_list in file_infos.values():
|
164
|
+
for file_info in file_list:
|
165
|
+
logger.info(f"[에러 복구] 파일 삭제: {file_info['storage_path']}")
|
166
|
+
await FileHandler.delete_files(file_info["storage_path"])
|
167
|
+
|
168
|
+
async def _delete_files(
|
169
|
+
self,
|
170
|
+
entity_result: Any
|
171
|
+
) -> None:
|
172
|
+
"""엔티티와 관련된 파일들을 삭제하는 함수
|
173
|
+
|
174
|
+
Args:
|
175
|
+
entity_result (Any): 삭제할 파일이 있는 엔티티
|
176
|
+
"""
|
177
|
+
try:
|
178
|
+
logger.info("[파일 삭제 시작]")
|
179
|
+
from .files import FileHandler
|
180
|
+
|
181
|
+
# files 테이블에서 기존 파일 정보 조회
|
182
|
+
existing_files = await self.db_session.execute(
|
183
|
+
text("""
|
184
|
+
SELECT storage_path
|
185
|
+
FROM files
|
186
|
+
WHERE entity_name = :entity_name
|
187
|
+
AND entity_ulid = :entity_ulid
|
188
|
+
"""),
|
189
|
+
{
|
190
|
+
"entity_name": self.model.__tablename__,
|
191
|
+
"entity_ulid": entity_result.ulid
|
192
|
+
}
|
193
|
+
)
|
194
|
+
existing_files = existing_files.fetchall()
|
195
|
+
logger.info(f"[기존 파일 조회 결과] {existing_files}")
|
196
|
+
|
197
|
+
# 기존 파일 삭제
|
198
|
+
for file_info in existing_files:
|
199
|
+
logger.info(f"[파일 삭제] {file_info[0]}")
|
200
|
+
await FileHandler.delete_files(file_info[0])
|
201
|
+
|
202
|
+
# files 테이블에서 레코드 삭제
|
203
|
+
await self.db_session.execute(
|
204
|
+
text("""
|
205
|
+
DELETE FROM files
|
206
|
+
WHERE entity_name = :entity_name
|
207
|
+
AND entity_ulid = :entity_ulid
|
208
|
+
"""),
|
209
|
+
{
|
210
|
+
"entity_name": self.model.__tablename__,
|
211
|
+
"entity_ulid": entity_result.ulid
|
212
|
+
}
|
213
|
+
)
|
214
|
+
logger.info("[파일 DB 레코드 삭제 완료]")
|
215
|
+
|
199
216
|
except Exception as e:
|
200
|
-
logger.error(f"[
|
201
|
-
if file_infos:
|
202
|
-
from .files import FileHandler
|
203
|
-
for file_list in file_infos.values():
|
204
|
-
for file_info in file_list:
|
205
|
-
logger.info(f"[에러 복구] 파일 삭제: {file_info['storage_path']}")
|
206
|
-
await FileHandler.delete_files(file_info["storage_path"])
|
217
|
+
logger.error(f"[파일 삭제 중 에러 발생] {str(e)}")
|
207
218
|
raise CustomException(
|
208
219
|
ErrorCode.FILE_SYSTEM_ERROR,
|
209
|
-
detail=
|
210
|
-
source_function=f"{self.__class__.__name__}.
|
220
|
+
detail=str(e),
|
221
|
+
source_function=f"{self.__class__.__name__}._delete_files",
|
211
222
|
original_error=e
|
212
223
|
)
|
213
224
|
|
@@ -248,26 +259,28 @@ class BaseService(Generic[ModelType]):
|
|
248
259
|
if fk_check:
|
249
260
|
await validate_unique_fields(self.db_session, fk_check, find_value=False)
|
250
261
|
|
251
|
-
#
|
262
|
+
# 파일 데이터 분리
|
263
|
+
entity_data_copy, separated_files = await self._separate_file_data(entity_data)
|
264
|
+
|
265
|
+
# 엔티티 생성 (파일 데이터가 제거된 상태)
|
252
266
|
result = await self.repository.create(
|
253
|
-
entity_data=
|
267
|
+
entity_data=entity_data_copy,
|
254
268
|
exclude_entities=exclude_entities
|
255
269
|
)
|
256
270
|
|
257
|
-
# 파일
|
258
|
-
|
259
|
-
|
260
|
-
|
271
|
+
# 파일 저장
|
272
|
+
file_infos = {}
|
273
|
+
if storage_dir and separated_files:
|
274
|
+
file_infos = await self._save_files(
|
275
|
+
separated_files=separated_files,
|
261
276
|
entity_result=result,
|
262
|
-
storage_dir=storage_dir
|
263
|
-
operation="create"
|
277
|
+
storage_dir=storage_dir
|
264
278
|
)
|
265
279
|
|
266
280
|
# 결과 반환
|
267
281
|
if response_model:
|
268
282
|
processed_result = process_response(result, response_model)
|
269
|
-
|
270
|
-
if storage_dir and 'file_infos' in locals():
|
283
|
+
if file_infos:
|
271
284
|
for key, value in file_infos.items():
|
272
285
|
processed_result[key] = value
|
273
286
|
return processed_result
|
@@ -345,12 +358,7 @@ class BaseService(Generic[ModelType]):
|
|
345
358
|
)
|
346
359
|
|
347
360
|
# 파일 데이터 분리
|
348
|
-
entity_data_copy,
|
349
|
-
entity_data=entity_data,
|
350
|
-
entity_result=existing_entity,
|
351
|
-
storage_dir=storage_dir,
|
352
|
-
operation="update"
|
353
|
-
)
|
361
|
+
entity_data_copy, separated_files = await self._separate_file_data(entity_data)
|
354
362
|
|
355
363
|
# 엔티티 수정 (파일 데이터가 제거된 상태)
|
356
364
|
result = await self.repository.update(
|
@@ -359,19 +367,21 @@ class BaseService(Generic[ModelType]):
|
|
359
367
|
exclude_entities=exclude_entities
|
360
368
|
)
|
361
369
|
|
362
|
-
#
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
370
|
+
# 기존 파일 삭제 후 새 파일 저장
|
371
|
+
file_infos = {}
|
372
|
+
if storage_dir and separated_files:
|
373
|
+
await self._delete_files(result)
|
374
|
+
file_infos = await self._save_files(
|
375
|
+
separated_files=separated_files,
|
376
|
+
entity_result=result,
|
377
|
+
storage_dir=storage_dir
|
378
|
+
)
|
369
379
|
|
370
380
|
if response_model:
|
371
381
|
processed_result = process_response(result, response_model)
|
372
|
-
|
373
|
-
|
374
|
-
|
382
|
+
if file_infos:
|
383
|
+
for key, value in file_infos.items():
|
384
|
+
processed_result[key] = value
|
375
385
|
return processed_result
|
376
386
|
else:
|
377
387
|
return result
|
@@ -422,13 +432,9 @@ class BaseService(Generic[ModelType]):
|
|
422
432
|
if not entity:
|
423
433
|
return False
|
424
434
|
|
425
|
-
# 파일
|
426
|
-
|
427
|
-
|
428
|
-
entity_result=entity,
|
429
|
-
storage_dir=storage_dir,
|
430
|
-
operation="delete"
|
431
|
-
)
|
435
|
+
# 파일 삭제
|
436
|
+
if storage_dir:
|
437
|
+
await self._delete_files(entity)
|
432
438
|
|
433
439
|
return await self.repository.delete(
|
434
440
|
conditions=conditions
|
aiteamutils/version.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
"""버전 정보"""
|
2
|
-
__version__ = "0.2.
|
2
|
+
__version__ = "0.2.158"
|
@@ -1,7 +1,7 @@
|
|
1
1
|
aiteamutils/__init__.py,sha256=kRBpRjark0M8ZwFfmKiMFol6CbIILN3WE4f6_P6iIq0,1089
|
2
2
|
aiteamutils/base_model.py,sha256=yBZqzTDF9PA4wCAvmYfG12FdVwLtxOEUCcA3z2i6fXU,4176
|
3
3
|
aiteamutils/base_repository.py,sha256=Oy2zE1i5qx60Xf1tnsaKLyFWapiPqt5JH8NejwNrPWg,4647
|
4
|
-
aiteamutils/base_service.py,sha256=
|
4
|
+
aiteamutils/base_service.py,sha256=Te4pM8MaIQBPKVj2hXp-qGIg33LhZFGAuyTEa6cOedQ,21775
|
5
5
|
aiteamutils/cache.py,sha256=07xBGlgAwOTAdY5mnMOQJ5EBxVwe8glVD7DkGEkxCtw,1373
|
6
6
|
aiteamutils/config.py,sha256=YdalpJb70-txhGJAS4aaKglEZAFVWgfzw5BXSWpkUz4,3232
|
7
7
|
aiteamutils/database.py,sha256=msvBKtxWeQVOo0v2Q9i2azuTNtnUItuNNar52gdRZTo,20418
|
@@ -11,7 +11,7 @@ aiteamutils/files.py,sha256=Qq2w3VAEOzvsDirYtxRTN48LnIzf4TPUH2LftmyYtQk,12831
|
|
11
11
|
aiteamutils/models.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
|
12
12
|
aiteamutils/security.py,sha256=McUl3t5Z5SyUDVUHymHdDkYyF4YSeg4g9fFMML4W6Kw,11630
|
13
13
|
aiteamutils/validators.py,sha256=_WHN6jqJQzKM5uPTg-Da8U2qqevS84XeKMkCCF4C_lY,9591
|
14
|
-
aiteamutils/version.py,sha256=
|
15
|
-
aiteamutils-0.2.
|
16
|
-
aiteamutils-0.2.
|
17
|
-
aiteamutils-0.2.
|
14
|
+
aiteamutils/version.py,sha256=v8SkzOaeH4MzML2BpBBvIp2vQgQYhhiXyS2BKDpsXUY,43
|
15
|
+
aiteamutils-0.2.158.dist-info/METADATA,sha256=tgeuWv9wOx5PnBCXNTwEXAlR5TFWcHXzKyJV5cYmD4U,1743
|
16
|
+
aiteamutils-0.2.158.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
17
|
+
aiteamutils-0.2.158.dist-info/RECORD,,
|
File without changes
|