aiteamutils 0.2.157__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,181 +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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
"""),
|
136
|
-
{
|
137
|
-
"entity_name": self.model.__tablename__,
|
138
|
-
"entity_ulid": entity_result.ulid
|
139
|
-
}
|
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
|
140
123
|
)
|
141
|
-
|
142
|
-
|
143
|
-
# 새 파일 저장 (create 또는 update 작업 시)
|
144
|
-
if operation != "delete" and separated_files:
|
145
|
-
logger.info("[새 파일 저장 시작]")
|
146
|
-
from .files import FileHandler
|
124
|
+
file_infos[field_name] = saved_files
|
147
125
|
|
148
|
-
#
|
149
|
-
if
|
150
|
-
|
151
|
-
return entity_data_copy, {}
|
126
|
+
# extra_data 업데이트
|
127
|
+
if not hasattr(entity_result, 'extra_data') or not entity_result.extra_data:
|
128
|
+
entity_result.extra_data = {}
|
152
129
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
entity_result.extra_data = {}
|
169
|
-
if not entity_result.extra_data:
|
170
|
-
entity_result.extra_data = {}
|
171
|
-
|
172
|
-
entity_result.extra_data[field_name] = [
|
173
|
-
{
|
174
|
-
'original_name': f['original_name'],
|
175
|
-
'storage_path': f['storage_path'],
|
176
|
-
'mime_type': f['mime_type'],
|
177
|
-
'size': f['size'],
|
178
|
-
'checksum': f['checksum'],
|
179
|
-
'column_name': f['column_name']
|
180
|
-
} for f in saved_files
|
181
|
-
]
|
182
|
-
logger.info(f"[extra_data 업데이트 완료] {field_name}")
|
183
|
-
|
184
|
-
# extra_data 업데이트된 엔티티 저장
|
185
|
-
await self.db_session.flush()
|
186
|
-
logger.info("[DB flush 완료]")
|
187
|
-
|
188
|
-
logger.info("[파일 처리 완료]")
|
189
|
-
return entity_data_copy, file_infos
|
190
|
-
|
130
|
+
entity_result.extra_data[field_name] = [
|
131
|
+
{
|
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
|
+
|
191
145
|
except CustomException as e:
|
192
|
-
logger.error(f"[CustomException 발생] {e.error_code}: {e.detail}")
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
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)
|
199
152
|
raise CustomException(
|
200
|
-
|
201
|
-
detail=e
|
202
|
-
source_function=f"{self.__class__.__name__}.
|
153
|
+
ErrorCode.FILE_SYSTEM_ERROR,
|
154
|
+
detail=str(e),
|
155
|
+
source_function=f"{self.__class__.__name__}._save_files",
|
203
156
|
original_error=e
|
204
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
|
+
|
205
216
|
except Exception as e:
|
206
|
-
logger.error(f"[
|
207
|
-
if file_infos:
|
208
|
-
from .files import FileHandler
|
209
|
-
for file_list in file_infos.values():
|
210
|
-
for file_info in file_list:
|
211
|
-
logger.info(f"[에러 복구] 파일 삭제: {file_info['storage_path']}")
|
212
|
-
await FileHandler.delete_files(file_info["storage_path"])
|
217
|
+
logger.error(f"[파일 삭제 중 에러 발생] {str(e)}")
|
213
218
|
raise CustomException(
|
214
219
|
ErrorCode.FILE_SYSTEM_ERROR,
|
215
|
-
detail=
|
216
|
-
source_function=f"{self.__class__.__name__}.
|
220
|
+
detail=str(e),
|
221
|
+
source_function=f"{self.__class__.__name__}._delete_files",
|
217
222
|
original_error=e
|
218
223
|
)
|
219
224
|
|
@@ -254,16 +259,8 @@ class BaseService(Generic[ModelType]):
|
|
254
259
|
if fk_check:
|
255
260
|
await validate_unique_fields(self.db_session, fk_check, find_value=False)
|
256
261
|
|
257
|
-
# 파일 데이터 분리를 위한 임시 엔티티 생성
|
258
|
-
temp_entity = type('TempEntity', (), {'ulid': str(ULID())})()
|
259
|
-
|
260
262
|
# 파일 데이터 분리
|
261
|
-
entity_data_copy,
|
262
|
-
entity_data=entity_data,
|
263
|
-
entity_result=temp_entity,
|
264
|
-
storage_dir=storage_dir,
|
265
|
-
operation="create"
|
266
|
-
)
|
263
|
+
entity_data_copy, separated_files = await self._separate_file_data(entity_data)
|
267
264
|
|
268
265
|
# 엔티티 생성 (파일 데이터가 제거된 상태)
|
269
266
|
result = await self.repository.create(
|
@@ -271,20 +268,19 @@ class BaseService(Generic[ModelType]):
|
|
271
268
|
exclude_entities=exclude_entities
|
272
269
|
)
|
273
270
|
|
274
|
-
#
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
271
|
+
# 파일 저장
|
272
|
+
file_infos = {}
|
273
|
+
if storage_dir and separated_files:
|
274
|
+
file_infos = await self._save_files(
|
275
|
+
separated_files=separated_files,
|
276
|
+
entity_result=result,
|
277
|
+
storage_dir=storage_dir
|
281
278
|
)
|
282
279
|
|
283
280
|
# 결과 반환
|
284
281
|
if response_model:
|
285
282
|
processed_result = process_response(result, response_model)
|
286
|
-
|
287
|
-
if storage_dir and 'file_infos' in locals():
|
283
|
+
if file_infos:
|
288
284
|
for key, value in file_infos.items():
|
289
285
|
processed_result[key] = value
|
290
286
|
return processed_result
|
@@ -362,12 +358,7 @@ class BaseService(Generic[ModelType]):
|
|
362
358
|
)
|
363
359
|
|
364
360
|
# 파일 데이터 분리
|
365
|
-
entity_data_copy,
|
366
|
-
entity_data=entity_data,
|
367
|
-
entity_result=existing_entity,
|
368
|
-
storage_dir=storage_dir,
|
369
|
-
operation="update"
|
370
|
-
)
|
361
|
+
entity_data_copy, separated_files = await self._separate_file_data(entity_data)
|
371
362
|
|
372
363
|
# 엔티티 수정 (파일 데이터가 제거된 상태)
|
373
364
|
result = await self.repository.update(
|
@@ -376,19 +367,21 @@ class BaseService(Generic[ModelType]):
|
|
376
367
|
exclude_entities=exclude_entities
|
377
368
|
)
|
378
369
|
|
379
|
-
#
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
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
|
+
)
|
386
379
|
|
387
380
|
if response_model:
|
388
381
|
processed_result = process_response(result, response_model)
|
389
|
-
|
390
|
-
|
391
|
-
|
382
|
+
if file_infos:
|
383
|
+
for key, value in file_infos.items():
|
384
|
+
processed_result[key] = value
|
392
385
|
return processed_result
|
393
386
|
else:
|
394
387
|
return result
|
@@ -439,13 +432,9 @@ class BaseService(Generic[ModelType]):
|
|
439
432
|
if not entity:
|
440
433
|
return False
|
441
434
|
|
442
|
-
# 파일
|
443
|
-
|
444
|
-
|
445
|
-
entity_result=entity,
|
446
|
-
storage_dir=storage_dir,
|
447
|
-
operation="delete"
|
448
|
-
)
|
435
|
+
# 파일 삭제
|
436
|
+
if storage_dir:
|
437
|
+
await self._delete_files(entity)
|
449
438
|
|
450
439
|
return await self.repository.delete(
|
451
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
|