aiteamutils 0.2.159__py3-none-any.whl → 0.2.161__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_model.py CHANGED
@@ -129,6 +129,16 @@ class BaseFileModel(BaseColumn):
129
129
  nullable=False,
130
130
  doc="MIME 타입"
131
131
  )
132
+ mime_type_main: Mapped[str] = mapped_column(
133
+ String,
134
+ nullable=False,
135
+ doc="MIME 타입 주 분류 (예: image, video, application 등)"
136
+ )
137
+ mime_type_sub: Mapped[str] = mapped_column(
138
+ String,
139
+ nullable=False,
140
+ doc="MIME 타입 부 분류 (예: jpeg, mp4, pdf 등)"
141
+ )
132
142
  size: Mapped[int] = mapped_column(
133
143
  nullable=False,
134
144
  doc="파일 크기(bytes)"
@@ -37,7 +37,7 @@ class BaseService(Generic[ModelType]):
37
37
  self.additional_models = additional_models or {},
38
38
 
39
39
  #######################
40
- # 입력 수정, 삭제 #
40
+ # 파일 관련 메서드 #
41
41
  #######################
42
42
  async def _separate_file_data(
43
43
  self,
@@ -52,7 +52,6 @@ class BaseService(Generic[ModelType]):
52
52
  Tuple[Dict[str, Any], Dict[str, Any]]: (파일 제외된 엔티티 데이터, 분리된 파일 데이터)
53
53
  """
54
54
  try:
55
- logger.info("[파일 데이터 분리 시작]")
56
55
  entity_data_copy = entity_data.copy()
57
56
  separated_files = {}
58
57
 
@@ -62,11 +61,9 @@ class BaseService(Generic[ModelType]):
62
61
 
63
62
  # 파일 필드 체크 및 분리 (딕셔너리 수정 없이)
64
63
  for field_name, files in extra_data.items():
65
- logger.info(f"[필드 검사] 필드명: {field_name}, 타입: {type(files)}")
66
64
  if files and isinstance(files, (list, tuple)):
67
65
  # 파일 객체를 포함하는 튜플/리스트인 경우만 처리
68
66
  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
67
  separated_files[field_name] = files
71
68
  fields_to_remove.append(field_name)
72
69
 
@@ -76,11 +73,9 @@ class BaseService(Generic[ModelType]):
76
73
 
77
74
  entity_data_copy['extra_data'] = extra_data
78
75
 
79
- logger.info("[파일 데이터 분리 완료]")
80
76
  return entity_data_copy, separated_files
81
77
 
82
78
  except Exception as e:
83
- logger.error(f"[파일 분리 중 에러 발생] {str(e)}")
84
79
  raise CustomException(
85
80
  ErrorCode.INTERNAL_ERROR,
86
81
  detail=str(e),
@@ -112,12 +107,10 @@ class BaseService(Generic[ModelType]):
112
107
  source_function=f"{self.__class__.__name__}._save_files"
113
108
  )
114
109
 
115
- logger.info("[파일 저장 시작]")
116
110
  file_infos = {}
117
111
  from .files import FileHandler
118
112
 
119
113
  for field_name, files in separated_files.items():
120
- logger.info(f"[필드 처리] {field_name}, 파일 수: {len(files)}")
121
114
  saved_files = await FileHandler.save_files(
122
115
  files=files,
123
116
  storage_dir=storage_dir,
@@ -137,6 +130,8 @@ class BaseService(Generic[ModelType]):
137
130
  'original_name': f['original_name'],
138
131
  'storage_path': f['storage_path'],
139
132
  'mime_type': f['mime_type'],
133
+ 'mime_type_main': f['mime_type_main'],
134
+ 'mime_type_sub': f['mime_type_sub'],
140
135
  'size': f['size'],
141
136
  'checksum': f['checksum'],
142
137
  'column_name': f['column_name']
@@ -144,15 +139,12 @@ class BaseService(Generic[ModelType]):
144
139
  ]
145
140
 
146
141
  await self.db_session.flush()
147
- logger.info("[파일 저장 완료]")
148
142
  return file_infos
149
143
 
150
144
  except CustomException as e:
151
- logger.error(f"[파일 저장 중 CustomException 발생] {e.error_code}: {e.detail}")
152
145
  await self._cleanup_saved_files(file_infos)
153
146
  raise e
154
147
  except Exception as e:
155
- logger.error(f"[파일 저장 중 에러 발생] {str(e)}")
156
148
  await self._cleanup_saved_files(file_infos)
157
149
  raise CustomException(
158
150
  ErrorCode.FILE_SYSTEM_ERROR,
@@ -167,7 +159,6 @@ class BaseService(Generic[ModelType]):
167
159
  from .files import FileHandler
168
160
  for file_list in file_infos.values():
169
161
  for file_info in file_list:
170
- logger.info(f"[에러 복구] 파일 삭제: {file_info['storage_path']}")
171
162
  await FileHandler.delete_files(file_info["storage_path"])
172
163
 
173
164
  async def _delete_files(
@@ -180,7 +171,6 @@ class BaseService(Generic[ModelType]):
180
171
  entity_result (Any): 삭제할 파일이 있는 엔티티
181
172
  """
182
173
  try:
183
- logger.info("[파일 삭제 시작]")
184
174
  from .files import FileHandler
185
175
 
186
176
  # files 테이블에서 기존 파일 정보 조회
@@ -197,11 +187,9 @@ class BaseService(Generic[ModelType]):
197
187
  }
198
188
  )
199
189
  existing_files = existing_files.fetchall()
200
- logger.info(f"[기존 파일 조회 결과] {existing_files}")
201
190
 
202
191
  # 기존 파일 삭제
203
192
  for file_info in existing_files:
204
- logger.info(f"[파일 삭제] {file_info[0]}")
205
193
  await FileHandler.delete_files(file_info[0])
206
194
 
207
195
  # files 테이블에서 레코드 삭제
@@ -216,10 +204,8 @@ class BaseService(Generic[ModelType]):
216
204
  "entity_ulid": entity_result.ulid
217
205
  }
218
206
  )
219
- logger.info("[파일 DB 레코드 삭제 완료]")
220
207
 
221
208
  except Exception as e:
222
- logger.error(f"[파일 삭제 중 에러 발생] {str(e)}")
223
209
  raise CustomException(
224
210
  ErrorCode.FILE_SYSTEM_ERROR,
225
211
  detail=str(e),
@@ -227,6 +213,9 @@ class BaseService(Generic[ModelType]):
227
213
  original_error=e
228
214
  )
229
215
 
216
+ #######################
217
+ # 입력 및 수정 메서드 #
218
+ #######################
230
219
  async def create(
231
220
  self,
232
221
  request: Request,
@@ -401,6 +390,9 @@ class BaseService(Generic[ModelType]):
401
390
  original_error=e
402
391
  )
403
392
 
393
+ #######################
394
+ # 삭제 메서드 #
395
+ #######################
404
396
  async def delete(
405
397
  self,
406
398
  request: Request,
aiteamutils/files.py CHANGED
@@ -48,6 +48,25 @@ class FileHandler:
48
48
  original_error=e
49
49
  )
50
50
 
51
+ @staticmethod
52
+ def _split_mime_type(mime_type: str) -> Tuple[str, str]:
53
+ """MIME 타입을 주 타입과 부 타입으로 분리
54
+
55
+ Args:
56
+ mime_type (str): MIME 타입 문자열 (예: "image/jpeg")
57
+
58
+ Returns:
59
+ Tuple[str, str]: (주 타입, 부 타입) 튜플
60
+ """
61
+ try:
62
+ if not mime_type or '/' not in mime_type:
63
+ return 'application', 'octet-stream'
64
+ main_type, sub_type = mime_type.split('/', 1)
65
+ return main_type.lower(), sub_type.lower()
66
+ except Exception as e:
67
+ logger.error(f"[MIME 타입 분리 실패] {mime_type}: {str(e)}")
68
+ return 'application', 'octet-stream'
69
+
51
70
  @staticmethod
52
71
  async def _calculate_checksum(file: BinaryIO) -> str:
53
72
  """파일의 SHA-256 체크섬 계산
@@ -131,10 +150,17 @@ class FileHandler:
131
150
  # 파일 포인터 복구
132
151
  file.seek(current_position)
133
152
 
153
+ # MIME 타입 처리
154
+ mime_type = FileHandler._get_mime_type(original_name)
155
+ mime_type_main, mime_type_sub = FileHandler._split_mime_type(mime_type)
156
+ logger.info(f"[MIME 타입 분리] {mime_type} -> {mime_type_main}/{mime_type_sub}")
157
+
134
158
  file_info = {
135
159
  "original_name": original_name,
136
160
  "storage_path": storage_path,
137
- "mime_type": FileHandler._get_mime_type(original_name),
161
+ "mime_type": mime_type,
162
+ "mime_type_main": mime_type_main,
163
+ "mime_type_sub": mime_type_sub,
138
164
  "size": os.path.getsize(storage_path),
139
165
  "checksum": checksum,
140
166
  "entity_name": entity_name,
@@ -202,12 +228,12 @@ class FileHandler:
202
228
  text("""
203
229
  INSERT INTO files (
204
230
  ulid, entity_name, entity_ulid, original_name, storage_path,
205
- mime_type, size, checksum, column_name, created_at, updated_at,
206
- is_deleted
231
+ mime_type, mime_type_main, mime_type_sub, size, checksum,
232
+ column_name, created_at, updated_at, is_deleted
207
233
  ) VALUES (
208
234
  :ulid, :entity_name, :entity_ulid, :original_name, :storage_path,
209
- :mime_type, :size, :checksum, :column_name, :created_at, :updated_at,
210
- :is_deleted
235
+ :mime_type, :mime_type_main, :mime_type_sub, :size, :checksum,
236
+ :column_name, :created_at, :updated_at, :is_deleted
211
237
  ) RETURNING *
212
238
  """),
213
239
  {
@@ -217,6 +243,8 @@ class FileHandler:
217
243
  "original_name": file_info["original_name"],
218
244
  "storage_path": file_info["storage_path"],
219
245
  "mime_type": file_info["mime_type"],
246
+ "mime_type_main": file_info["mime_type_main"],
247
+ "mime_type_sub": file_info["mime_type_sub"],
220
248
  "size": file_info["size"],
221
249
  "checksum": file_info["checksum"],
222
250
  "column_name": column_name,
@@ -234,6 +262,8 @@ class FileHandler:
234
262
  "original_name": row.original_name,
235
263
  "storage_path": row.storage_path,
236
264
  "mime_type": row.mime_type,
265
+ "mime_type_main": row.mime_type_main,
266
+ "mime_type_sub": row.mime_type_sub,
237
267
  "size": row.size,
238
268
  "checksum": row.checksum,
239
269
  "column_name": row.column_name,
aiteamutils/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  """버전 정보"""
2
- __version__ = "0.2.159"
2
+ __version__ = "0.2.161"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.159
3
+ Version: 0.2.161
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,17 +1,17 @@
1
1
  aiteamutils/__init__.py,sha256=kRBpRjark0M8ZwFfmKiMFol6CbIILN3WE4f6_P6iIq0,1089
2
- aiteamutils/base_model.py,sha256=yBZqzTDF9PA4wCAvmYfG12FdVwLtxOEUCcA3z2i6fXU,4176
2
+ aiteamutils/base_model.py,sha256=DFjDdNJ8dMFcWmeZwO1K69zPZE7tSu30SQ8si_1jhWk,4502
3
3
  aiteamutils/base_repository.py,sha256=Oy2zE1i5qx60Xf1tnsaKLyFWapiPqt5JH8NejwNrPWg,4647
4
- aiteamutils/base_service.py,sha256=uYEym6Cp2prK_ZbzAtrQaYLZRqn-AolTwoso_ASEqCk,22043
4
+ aiteamutils/base_service.py,sha256=JIeRtFn1Ll4Qcq3v88pgS9lmEQLQoVyyOKqYR8n02S4,21192
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
8
8
  aiteamutils/enums.py,sha256=7WLqlcJqQWtETAga2WAxNp3dJTQIAd2TW-4WzkoHHa8,2498
9
9
  aiteamutils/exceptions.py,sha256=pgf3ersezObyl17wAO3I2fb8m9t2OzWDX1mSjwAWm2Y,16035
10
- aiteamutils/files.py,sha256=Qq2w3VAEOzvsDirYtxRTN48LnIzf4TPUH2LftmyYtQk,12831
10
+ aiteamutils/files.py,sha256=fxnCu9rErd4vCovMi0jy4adLUiA7rx_q4RdOL4wSgsU,14258
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=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,,
14
+ aiteamutils/version.py,sha256=M1uxYIyHaq1jeL34T8UnMNaPGC2Tg5FBCVdajkMtNpE,43
15
+ aiteamutils-0.2.161.dist-info/METADATA,sha256=MEeOAEX3rJiiOvxkeP2-P4pm2sMMSoUTzZQBu0Wt-KU,1743
16
+ aiteamutils-0.2.161.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ aiteamutils-0.2.161.dist-info/RECORD,,