aiteamutils 0.2.157__py3-none-any.whl → 0.2.159__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.
@@ -39,181 +39,191 @@ class BaseService(Generic[ModelType]):
39
39
  #######################
40
40
  # 입력 및 수정, 삭제 #
41
41
  #######################
42
- async def _process_files(
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(f"[파일 처리 시작] operation: {operation}, storage_dir: {storage_dir}")
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
- if operation != "delete" and 'extra_data' in entity_data_copy and isinstance(entity_data_copy['extra_data'], dict):
70
- logger.info(f"[extra_data 확인] {entity_data_copy['extra_data']}")
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()]}")
61
+ fields_to_remove = []
85
62
 
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
- )
63
+ # 파일 필드 체크 및 분리 (딕셔너리 수정 없이)
64
+ for field_name, files in extra_data.items():
65
+ logger.info(f"[필드 검사] 필드명: {field_name}, 타입: {type(files)}")
66
+ if files and isinstance(files, (list, tuple)):
67
+ # 파일 객체를 포함하는 튜플/리스트인 경우만 처리
68
+ if any(isinstance(item, (tuple, list)) and len(item) == 2 and hasattr(item[0], 'read') for item in files):
69
+ logger.info(f"[파일 필드 발견] {field_name}")
70
+ separated_files[field_name] = files
71
+ fields_to_remove.append(field_name)
93
72
 
94
- # 파일 필드 분리 제거
95
- for field_name, files in file_fields.items():
96
- logger.info(f"[파일 처리] 필드: {field_name}, 파일 수: {len(files)}")
97
- logger.info(f"[파일 상세] 필드: {field_name}, 파일 내용: {files}")
98
- separated_files[field_name] = files
73
+ # 파일 필드 제거 (별도 단계로 분리)
74
+ for field_name in fields_to_remove:
99
75
  del extra_data[field_name]
100
76
 
101
77
  entity_data_copy['extra_data'] = extra_data
102
- logger.info(f"[파일 분리 후 extra_data] {entity_data_copy['extra_data']}")
103
-
104
- # 기존 파일 삭제 (update 또는 delete 작업 시)
105
- if operation in ["update", "delete"]:
106
- logger.info("[기존 파일 삭제 시작]")
107
- from .files import FileHandler
108
- # files 테이블에서 기존 파일 정보 조회
109
- existing_files = await self.db_session.execute(
110
- text("""
111
- SELECT storage_path
112
- FROM files
113
- WHERE entity_name = :entity_name
114
- AND entity_ulid = :entity_ulid
115
- """),
116
- {
117
- "entity_name": self.model.__tablename__,
118
- "entity_ulid": entity_result.ulid
119
- }
78
+
79
+ logger.info("[파일 데이터 분리 완료]")
80
+ return entity_data_copy, separated_files
81
+
82
+ except Exception as e:
83
+ logger.error(f"[파일 분리 중 에러 발생] {str(e)}")
84
+ raise CustomException(
85
+ ErrorCode.INTERNAL_ERROR,
86
+ detail=str(e),
87
+ source_function=f"{self.__class__.__name__}._separate_file_data",
88
+ original_error=e
89
+ )
90
+
91
+ async def _save_files(
92
+ self,
93
+ separated_files: Dict[str, Any],
94
+ entity_result: Any,
95
+ storage_dir: str
96
+ ) -> Dict[str, Any]:
97
+ """분리된 파일 데이터를 저장하는 함수
98
+
99
+ Args:
100
+ separated_files (Dict[str, Any]): 분리된 파일 데이터
101
+ entity_result (Any): 생성/수정된 엔티티
102
+ storage_dir (str): 저장 디렉토리
103
+
104
+ Returns:
105
+ Dict[str, Any]: 저장된 파일 정보
106
+ """
107
+ try:
108
+ if not storage_dir:
109
+ raise CustomException(
110
+ ErrorCode.INVALID_INPUT,
111
+ detail="storage_dir is required for file upload",
112
+ source_function=f"{self.__class__.__name__}._save_files"
120
113
  )
121
- existing_files = existing_files.fetchall()
122
- logger.info(f"[기존 파일 조회 결과] {existing_files}")
123
-
124
- # 기존 파일 삭제
125
- for file_info in existing_files:
126
- logger.info(f"[파일 삭제] {file_info[0]}")
127
- await FileHandler.delete_files(file_info[0])
128
-
129
- # files 테이블에서 레코드 삭제
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
- """),
136
- {
137
- "entity_name": self.model.__tablename__,
138
- "entity_ulid": entity_result.ulid
139
- }
114
+
115
+ logger.info("[파일 저장 시작]")
116
+ file_infos = {}
117
+ from .files import FileHandler
118
+
119
+ for field_name, files in separated_files.items():
120
+ logger.info(f"[필드 처리] {field_name}, 파일 수: {len(files)}")
121
+ saved_files = await FileHandler.save_files(
122
+ files=files,
123
+ storage_dir=storage_dir,
124
+ entity_name=self.model.__tablename__,
125
+ entity_ulid=entity_result.ulid,
126
+ db_session=self.db_session,
127
+ column_name=field_name
140
128
  )
141
- logger.info("[기존 파일 DB 레코드 삭제 완료]")
142
-
143
- # 새 파일 저장 (create 또는 update 작업 시)
144
- if operation != "delete" and separated_files:
145
- logger.info("[새 파일 저장 시작]")
146
- from .files import FileHandler
129
+ file_infos[field_name] = saved_files
147
130
 
148
- # 임시 엔티티인 경우 파일 저장 건너뛰기
149
- if isinstance(entity_result, type('TempEntity', (), {})):
150
- logger.info("[임시 엔티티 감지] 파일 저장 건너뛰기")
151
- return entity_data_copy, {}
131
+ # extra_data 업데이트
132
+ if not hasattr(entity_result, 'extra_data') or not entity_result.extra_data:
133
+ entity_result.extra_data = {}
152
134
 
153
- for field_name, files in separated_files.items():
154
- logger.info(f"[필드 처리] {field_name}, 파일 수: {len(files)}")
155
- saved_files = await FileHandler.save_files(
156
- files=files,
157
- storage_dir=storage_dir,
158
- entity_name=self.model.__tablename__,
159
- entity_ulid=entity_result.ulid,
160
- db_session=self.db_session,
161
- column_name=field_name
162
- )
163
- file_infos[field_name] = saved_files
164
- logger.info(f"[파일 저장 완료] {field_name}: {len(saved_files)}개")
165
-
166
- # extra_data 업데이트 - 파일 정보 캐싱
167
- if not hasattr(entity_result, 'extra_data'):
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
-
135
+ entity_result.extra_data[field_name] = [
136
+ {
137
+ 'original_name': f['original_name'],
138
+ 'storage_path': f['storage_path'],
139
+ 'mime_type': f['mime_type'],
140
+ 'size': f['size'],
141
+ 'checksum': f['checksum'],
142
+ 'column_name': f['column_name']
143
+ } for f in saved_files
144
+ ]
145
+
146
+ await self.db_session.flush()
147
+ logger.info("[파일 저장 완료]")
148
+ return file_infos
149
+
191
150
  except CustomException as e:
192
- logger.error(f"[CustomException 발생] {e.error_code}: {e.detail}")
193
- if file_infos:
194
- from .files import FileHandler
195
- for file_list in file_infos.values():
196
- for file_info in file_list:
197
- logger.info(f"[에러 복구] 파일 삭제: {file_info['storage_path']}")
198
- await FileHandler.delete_files(file_info["storage_path"])
151
+ logger.error(f"[파일 저장 중 CustomException 발생] {e.error_code}: {e.detail}")
152
+ await self._cleanup_saved_files(file_infos)
153
+ raise e
154
+ except Exception as e:
155
+ logger.error(f"[파일 저장 에러 발생] {str(e)}")
156
+ await self._cleanup_saved_files(file_infos)
199
157
  raise CustomException(
200
- e.error_code,
201
- detail=e.detail,
202
- source_function=f"{self.__class__.__name__}._process_files",
158
+ ErrorCode.FILE_SYSTEM_ERROR,
159
+ detail=str(e),
160
+ source_function=f"{self.__class__.__name__}._save_files",
203
161
  original_error=e
204
162
  )
163
+
164
+ async def _cleanup_saved_files(self, file_infos: Dict[str, Any]):
165
+ """저장된 파일들을 정리(삭제)하는 함수"""
166
+ if file_infos:
167
+ from .files import FileHandler
168
+ for file_list in file_infos.values():
169
+ for file_info in file_list:
170
+ logger.info(f"[에러 복구] 파일 삭제: {file_info['storage_path']}")
171
+ await FileHandler.delete_files(file_info["storage_path"])
172
+
173
+ async def _delete_files(
174
+ self,
175
+ entity_result: Any
176
+ ) -> None:
177
+ """엔티티와 관련된 파일들을 삭제하는 함수
178
+
179
+ Args:
180
+ entity_result (Any): 삭제할 파일이 있는 엔티티
181
+ """
182
+ try:
183
+ logger.info("[파일 삭제 시작]")
184
+ from .files import FileHandler
185
+
186
+ # files 테이블에서 기존 파일 정보 조회
187
+ existing_files = await self.db_session.execute(
188
+ text("""
189
+ SELECT storage_path
190
+ FROM files
191
+ WHERE entity_name = :entity_name
192
+ AND entity_ulid = :entity_ulid
193
+ """),
194
+ {
195
+ "entity_name": self.model.__tablename__,
196
+ "entity_ulid": entity_result.ulid
197
+ }
198
+ )
199
+ existing_files = existing_files.fetchall()
200
+ logger.info(f"[기존 파일 조회 결과] {existing_files}")
201
+
202
+ # 기존 파일 삭제
203
+ for file_info in existing_files:
204
+ logger.info(f"[파일 삭제] {file_info[0]}")
205
+ await FileHandler.delete_files(file_info[0])
206
+
207
+ # files 테이블에서 레코드 삭제
208
+ await self.db_session.execute(
209
+ text("""
210
+ DELETE FROM files
211
+ WHERE entity_name = :entity_name
212
+ AND entity_ulid = :entity_ulid
213
+ """),
214
+ {
215
+ "entity_name": self.model.__tablename__,
216
+ "entity_ulid": entity_result.ulid
217
+ }
218
+ )
219
+ logger.info("[파일 DB 레코드 삭제 완료]")
220
+
205
221
  except Exception as e:
206
- logger.error(f"[예외 발생] {str(e)}")
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"])
222
+ logger.error(f"[파일 삭제 중 에러 발생] {str(e)}")
213
223
  raise CustomException(
214
224
  ErrorCode.FILE_SYSTEM_ERROR,
215
- detail=f"File processing error: {str(e)}",
216
- source_function=f"{self.__class__.__name__}._process_files",
225
+ detail=str(e),
226
+ source_function=f"{self.__class__.__name__}._delete_files",
217
227
  original_error=e
218
228
  )
219
229
 
@@ -254,16 +264,8 @@ class BaseService(Generic[ModelType]):
254
264
  if fk_check:
255
265
  await validate_unique_fields(self.db_session, fk_check, find_value=False)
256
266
 
257
- # 파일 데이터 분리를 위한 임시 엔티티 생성
258
- temp_entity = type('TempEntity', (), {'ulid': str(ULID())})()
259
-
260
267
  # 파일 데이터 분리
261
- entity_data_copy, _ = await self._process_files(
262
- entity_data=entity_data,
263
- entity_result=temp_entity,
264
- storage_dir=storage_dir,
265
- operation="create"
266
- )
268
+ entity_data_copy, separated_files = await self._separate_file_data(entity_data)
267
269
 
268
270
  # 엔티티 생성 (파일 데이터가 제거된 상태)
269
271
  result = await self.repository.create(
@@ -271,20 +273,19 @@ class BaseService(Generic[ModelType]):
271
273
  exclude_entities=exclude_entities
272
274
  )
273
275
 
274
- # 실제 파일 저장 및 엔티티 업데이트
275
- if storage_dir:
276
- _, file_infos = await self._process_files(
277
- entity_data=entity_data, # 원본 데이터 사용
278
- entity_result=result, # 실제 엔티티 사용
279
- storage_dir=storage_dir,
280
- operation="create"
276
+ # 파일 저장
277
+ file_infos = {}
278
+ if storage_dir and separated_files:
279
+ file_infos = await self._save_files(
280
+ separated_files=separated_files,
281
+ entity_result=result,
282
+ storage_dir=storage_dir
281
283
  )
282
284
 
283
285
  # 결과 반환
284
286
  if response_model:
285
287
  processed_result = process_response(result, response_model)
286
- # 파일 정보 추가
287
- if storage_dir and 'file_infos' in locals():
288
+ if file_infos:
288
289
  for key, value in file_infos.items():
289
290
  processed_result[key] = value
290
291
  return processed_result
@@ -362,12 +363,7 @@ class BaseService(Generic[ModelType]):
362
363
  )
363
364
 
364
365
  # 파일 데이터 분리
365
- entity_data_copy, _ = await self._process_files(
366
- entity_data=entity_data,
367
- entity_result=existing_entity,
368
- storage_dir=storage_dir,
369
- operation="update"
370
- )
366
+ entity_data_copy, separated_files = await self._separate_file_data(entity_data)
371
367
 
372
368
  # 엔티티 수정 (파일 데이터가 제거된 상태)
373
369
  result = await self.repository.update(
@@ -376,19 +372,21 @@ class BaseService(Generic[ModelType]):
376
372
  exclude_entities=exclude_entities
377
373
  )
378
374
 
379
- # 실제 파일 처리
380
- _, file_infos = await self._process_files(
381
- entity_data=entity_data,
382
- entity_result=result,
383
- storage_dir=storage_dir,
384
- operation="update"
385
- )
375
+ # 기존 파일 삭제 후 새 파일 저장
376
+ file_infos = {}
377
+ if storage_dir and separated_files:
378
+ await self._delete_files(result)
379
+ file_infos = await self._save_files(
380
+ separated_files=separated_files,
381
+ entity_result=result,
382
+ storage_dir=storage_dir
383
+ )
386
384
 
387
385
  if response_model:
388
386
  processed_result = process_response(result, response_model)
389
- # 파일 정보 추가
390
- for key, value in file_infos.items():
391
- processed_result[key] = value
387
+ if file_infos:
388
+ for key, value in file_infos.items():
389
+ processed_result[key] = value
392
390
  return processed_result
393
391
  else:
394
392
  return result
@@ -439,13 +437,9 @@ class BaseService(Generic[ModelType]):
439
437
  if not entity:
440
438
  return False
441
439
 
442
- # 파일 처리 (삭제)
443
- _, _ = await self._process_files(
444
- entity_data={},
445
- entity_result=entity,
446
- storage_dir=storage_dir,
447
- operation="delete"
448
- )
440
+ # 파일 삭제
441
+ if storage_dir:
442
+ await self._delete_files(entity)
449
443
 
450
444
  return await self.repository.delete(
451
445
  conditions=conditions
aiteamutils/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  """버전 정보"""
2
- __version__ = "0.2.157"
2
+ __version__ = "0.2.159"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.157
3
+ Version: 0.2.159
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,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=uoeeYUr-TAm2CaGUvjO74U2_nAAQdC6ZTSv8hiVsrcg,23866
4
+ aiteamutils/base_service.py,sha256=uYEym6Cp2prK_ZbzAtrQaYLZRqn-AolTwoso_ASEqCk,22043
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=zzhDPxUCDTkincSRtbo_y2fTIxFwm8eppgXa_Unb-L0,43
15
- aiteamutils-0.2.157.dist-info/METADATA,sha256=g7aZN3zbtNOAnu07UJSQWePUB-o10qRpBuiJmXj_6FQ,1743
16
- aiteamutils-0.2.157.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- aiteamutils-0.2.157.dist-info/RECORD,,
14
+ aiteamutils/version.py,sha256=xr1O2ZYSzlPjLr1SLG4IBRNdFzNlMV4GLn1gHFE6Kd8,43
15
+ aiteamutils-0.2.159.dist-info/METADATA,sha256=gmkHCBPBGe1kRWjRwOBNlhg8lywLG2N0EG4IXLNP-D0,1743
16
+ aiteamutils-0.2.159.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ aiteamutils-0.2.159.dist-info/RECORD,,