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 +10 -0
- aiteamutils/base_service.py +9 -17
- aiteamutils/files.py +35 -5
- aiteamutils/version.py +1 -1
- {aiteamutils-0.2.159.dist-info → aiteamutils-0.2.161.dist-info}/METADATA +1 -1
- {aiteamutils-0.2.159.dist-info → aiteamutils-0.2.161.dist-info}/RECORD +7 -7
- {aiteamutils-0.2.159.dist-info → aiteamutils-0.2.161.dist-info}/WHEEL +0 -0
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)"
|
aiteamutils/base_service.py
CHANGED
@@ -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":
|
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,
|
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, :
|
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.
|
2
|
+
__version__ = "0.2.161"
|
@@ -1,17 +1,17 @@
|
|
1
1
|
aiteamutils/__init__.py,sha256=kRBpRjark0M8ZwFfmKiMFol6CbIILN3WE4f6_P6iIq0,1089
|
2
|
-
aiteamutils/base_model.py,sha256=
|
2
|
+
aiteamutils/base_model.py,sha256=DFjDdNJ8dMFcWmeZwO1K69zPZE7tSu30SQ8si_1jhWk,4502
|
3
3
|
aiteamutils/base_repository.py,sha256=Oy2zE1i5qx60Xf1tnsaKLyFWapiPqt5JH8NejwNrPWg,4647
|
4
|
-
aiteamutils/base_service.py,sha256=
|
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=
|
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=
|
15
|
-
aiteamutils-0.2.
|
16
|
-
aiteamutils-0.2.
|
17
|
-
aiteamutils-0.2.
|
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,,
|
File without changes
|