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.
@@ -39,181 +39,186 @@ 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()]}")
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 file_fields.items():
96
- logger.info(f"[파일 처리] 필드: {field_name}, 파일 수: {len(files)}")
97
- logger.info(f"[파일 상세] 필드: {field_name}, 파일 내용: {files}")
98
- separated_files[field_name] = files
99
- del extra_data[field_name]
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
- 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
- }
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
- 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
- }
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
- logger.info("[기존 파일 DB 레코드 삭제 완료]")
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 isinstance(entity_result, type('TempEntity', (), {})):
150
- logger.info("[임시 엔티티 감지] 파일 저장 건너뛰기")
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
- 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
-
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
- 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"])
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
- e.error_code,
201
- detail=e.detail,
202
- source_function=f"{self.__class__.__name__}._process_files",
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"[예외 발생] {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"])
217
+ logger.error(f"[파일 삭제 중 에러 발생] {str(e)}")
213
218
  raise CustomException(
214
219
  ErrorCode.FILE_SYSTEM_ERROR,
215
- detail=f"File processing error: {str(e)}",
216
- source_function=f"{self.__class__.__name__}._process_files",
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, _ = await self._process_files(
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
- 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"
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, _ = await self._process_files(
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
- _, file_infos = await self._process_files(
381
- entity_data=entity_data,
382
- entity_result=result,
383
- storage_dir=storage_dir,
384
- operation="update"
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
- for key, value in file_infos.items():
391
- processed_result[key] = value
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
- _, _ = await self._process_files(
444
- entity_data={},
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.157"
2
+ __version__ = "0.2.158"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.157
3
+ Version: 0.2.158
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=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=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=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,,