aiteamutils 0.2.140__py3-none-any.whl → 0.2.141__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
@@ -1,10 +1,11 @@
|
|
1
1
|
#기본 라이브러리
|
2
2
|
from fastapi import Request
|
3
|
-
from typing import TypeVar, Generic, Type, Dict, Any, Union, List, Optional, Literal
|
3
|
+
from typing import TypeVar, Generic, Type, Dict, Any, Union, List, Optional, Literal, Tuple
|
4
4
|
from sqlalchemy.orm import DeclarativeBase
|
5
5
|
from sqlalchemy.ext.asyncio import AsyncSession
|
6
6
|
from datetime import datetime
|
7
7
|
from ulid import ULID
|
8
|
+
from sqlalchemy import text
|
8
9
|
|
9
10
|
#패키지 라이브러리
|
10
11
|
from .exceptions import ErrorCode, CustomException
|
@@ -35,42 +36,31 @@ class BaseService(Generic[ModelType]):
|
|
35
36
|
#######################
|
36
37
|
# 입력 및 수정, 삭제 #
|
37
38
|
#######################
|
38
|
-
async def
|
39
|
+
async def _process_files(
|
39
40
|
self,
|
40
|
-
request: Request,
|
41
41
|
entity_data: Dict[str, Any],
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
role_permission: str | None = None,
|
48
|
-
token_settings: Dict[str, Any] | None = None,
|
49
|
-
storage_dir: str | None = None
|
50
|
-
) -> ModelType:
|
51
|
-
|
52
|
-
if role_permission:
|
53
|
-
permission_result = await verify_role_permission(
|
54
|
-
request=request,
|
55
|
-
role_permission=role_permission,
|
56
|
-
token_settings=token_settings,
|
57
|
-
org_ulid_position=org_ulid_position
|
58
|
-
)
|
59
|
-
|
60
|
-
if not permission_result:
|
61
|
-
raise CustomException(
|
62
|
-
ErrorCode.FORBIDDEN,
|
63
|
-
detail=f"{role_permission}",
|
64
|
-
source_function=f"base_service.{self.__class__.__name__}.create.permission_result"
|
65
|
-
)
|
42
|
+
entity_result: Any,
|
43
|
+
storage_dir: str,
|
44
|
+
operation: Literal["create", "update", "delete"] = "create"
|
45
|
+
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
46
|
+
"""파일 처리를 위한 내부 메서드
|
66
47
|
|
48
|
+
Args:
|
49
|
+
entity_data (Dict[str, Any]): 엔티티 데이터
|
50
|
+
entity_result (Any): 생성/수정된 엔티티 결과
|
51
|
+
storage_dir (str): 저장 디렉토리 경로
|
52
|
+
operation (str): 수행할 작업 유형 ("create", "update", "delete")
|
53
|
+
|
54
|
+
Returns:
|
55
|
+
Tuple[Dict[str, Any], Dict[str, Any]]: (처리된 엔티티 데이터, 파일 정보)
|
56
|
+
"""
|
67
57
|
try:
|
68
|
-
# 파일 데이터 분리
|
69
58
|
entity_data_copy = entity_data.copy()
|
70
|
-
|
59
|
+
file_infos = {}
|
71
60
|
|
72
|
-
#
|
73
|
-
|
61
|
+
# 파일 데이터 분리
|
62
|
+
separated_files = {}
|
63
|
+
if operation != "delete" and 'extra_data' in entity_data_copy and isinstance(entity_data_copy['extra_data'], dict):
|
74
64
|
extra_data = entity_data_copy['extra_data'].copy()
|
75
65
|
file_fields = {k: v for k, v in extra_data.items() if k.endswith('_files')}
|
76
66
|
|
@@ -78,18 +68,144 @@ class BaseService(Generic[ModelType]):
|
|
78
68
|
raise CustomException(
|
79
69
|
ErrorCode.INVALID_INPUT,
|
80
70
|
detail="storage_dir is required for file upload",
|
81
|
-
source_function=f"
|
71
|
+
source_function=f"{self.__class__.__name__}._process_files"
|
82
72
|
)
|
83
73
|
|
84
74
|
# 파일 필드 분리 및 제거
|
85
75
|
for field_name, files in file_fields.items():
|
86
76
|
if files:
|
87
77
|
separated_files[field_name] = files
|
88
|
-
# extra_data에서 파일 필드 제거
|
89
78
|
del extra_data[field_name]
|
90
79
|
|
91
80
|
entity_data_copy['extra_data'] = extra_data
|
92
81
|
|
82
|
+
# 기존 파일 삭제 (update 또는 delete 작업 시)
|
83
|
+
if operation in ["update", "delete"]:
|
84
|
+
from .files import FileHandler
|
85
|
+
# files 테이블에서 기존 파일 정보 조회
|
86
|
+
existing_files = await self.db_session.execute(
|
87
|
+
text("""
|
88
|
+
SELECT storage_path
|
89
|
+
FROM files
|
90
|
+
WHERE entity_name = :entity_name
|
91
|
+
AND entity_ulid = :entity_ulid
|
92
|
+
"""),
|
93
|
+
{
|
94
|
+
"entity_name": self.model.__tablename__,
|
95
|
+
"entity_ulid": entity_result.ulid
|
96
|
+
}
|
97
|
+
)
|
98
|
+
existing_files = existing_files.fetchall()
|
99
|
+
|
100
|
+
# 기존 파일 삭제
|
101
|
+
for file_info in existing_files:
|
102
|
+
await FileHandler.delete_files(file_info[0])
|
103
|
+
|
104
|
+
# files 테이블에서 레코드 삭제
|
105
|
+
await self.db_session.execute(
|
106
|
+
text("""
|
107
|
+
DELETE FROM files
|
108
|
+
WHERE entity_name = :entity_name
|
109
|
+
AND entity_ulid = :entity_ulid
|
110
|
+
"""),
|
111
|
+
{
|
112
|
+
"entity_name": self.model.__tablename__,
|
113
|
+
"entity_ulid": entity_result.ulid
|
114
|
+
}
|
115
|
+
)
|
116
|
+
|
117
|
+
# 새 파일 저장 (create 또는 update 작업 시)
|
118
|
+
if operation != "delete" and separated_files:
|
119
|
+
from .files import FileHandler
|
120
|
+
for field_name, files in separated_files.items():
|
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.replace('_files', '')
|
128
|
+
)
|
129
|
+
file_infos[field_name] = saved_files
|
130
|
+
|
131
|
+
# extra_data 업데이트 - 파일 정보 캐싱
|
132
|
+
if not hasattr(entity_result, 'extra_data'):
|
133
|
+
entity_result.extra_data = {}
|
134
|
+
if not entity_result.extra_data:
|
135
|
+
entity_result.extra_data = {}
|
136
|
+
|
137
|
+
entity_result.extra_data[field_name] = [
|
138
|
+
{
|
139
|
+
'original_name': f['original_name'],
|
140
|
+
'storage_path': f['storage_path'],
|
141
|
+
'mime_type': f['mime_type'],
|
142
|
+
'size': f['size'],
|
143
|
+
'checksum': f['checksum'],
|
144
|
+
'column_name': f['column_name']
|
145
|
+
} for f in saved_files
|
146
|
+
]
|
147
|
+
|
148
|
+
# extra_data 업데이트된 엔티티 저장
|
149
|
+
await self.db_session.flush()
|
150
|
+
|
151
|
+
return entity_data_copy, file_infos
|
152
|
+
|
153
|
+
except CustomException as e:
|
154
|
+
# 파일 저장 실패 시 저장된 파일들 삭제
|
155
|
+
if file_infos:
|
156
|
+
from .files import FileHandler
|
157
|
+
for file_list in file_infos.values():
|
158
|
+
for file_info in file_list:
|
159
|
+
await FileHandler.delete_files(file_info["storage_path"])
|
160
|
+
raise CustomException(
|
161
|
+
e.error_code,
|
162
|
+
detail=e.detail,
|
163
|
+
source_function=f"{self.__class__.__name__}._process_files",
|
164
|
+
original_error=e
|
165
|
+
)
|
166
|
+
except Exception as e:
|
167
|
+
# 파일 저장 실패 시 저장된 파일들 삭제
|
168
|
+
if file_infos:
|
169
|
+
from .files import FileHandler
|
170
|
+
for file_list in file_infos.values():
|
171
|
+
for file_info in file_list:
|
172
|
+
await FileHandler.delete_files(file_info["storage_path"])
|
173
|
+
raise CustomException(
|
174
|
+
ErrorCode.FILE_SYSTEM_ERROR,
|
175
|
+
detail=str(e),
|
176
|
+
source_function=f"{self.__class__.__name__}._process_files",
|
177
|
+
original_error=e
|
178
|
+
)
|
179
|
+
|
180
|
+
async def create(
|
181
|
+
self,
|
182
|
+
request: Request,
|
183
|
+
entity_data: Dict[str, Any],
|
184
|
+
response_model: Any = None,
|
185
|
+
exclude_entities: List[str] | None = None,
|
186
|
+
unique_check: List[Dict[str, Any]] | None = None,
|
187
|
+
fk_check: List[Dict[str, Any]] | None = None,
|
188
|
+
org_ulid_position: str = "organization_ulid",
|
189
|
+
role_permission: str | None = None,
|
190
|
+
token_settings: Dict[str, Any] | None = None,
|
191
|
+
storage_dir: str | None = None
|
192
|
+
) -> ModelType:
|
193
|
+
try:
|
194
|
+
if role_permission:
|
195
|
+
permission_result = await verify_role_permission(
|
196
|
+
request=request,
|
197
|
+
role_permission=role_permission,
|
198
|
+
token_settings=token_settings,
|
199
|
+
org_ulid_position=org_ulid_position
|
200
|
+
)
|
201
|
+
|
202
|
+
if not permission_result:
|
203
|
+
raise CustomException(
|
204
|
+
ErrorCode.FORBIDDEN,
|
205
|
+
detail=f"{role_permission}",
|
206
|
+
source_function=f"base_service.{self.__class__.__name__}.create.permission_result"
|
207
|
+
)
|
208
|
+
|
93
209
|
async with self.db_session.begin():
|
94
210
|
# 고유 검사 수행
|
95
211
|
if unique_check:
|
@@ -98,69 +214,30 @@ class BaseService(Generic[ModelType]):
|
|
98
214
|
if fk_check:
|
99
215
|
await validate_unique_fields(self.db_session, fk_check, find_value=False)
|
100
216
|
|
101
|
-
# 엔티티 생성
|
217
|
+
# 파일 데이터 분리를 위한 임시 엔티티 생성
|
218
|
+
temp_entity = type('TempEntity', (), {'ulid': str(ULID())})()
|
219
|
+
|
220
|
+
# 파일 데이터 분리
|
221
|
+
entity_data_copy, _ = await self._process_files(
|
222
|
+
entity_data=entity_data,
|
223
|
+
entity_result=temp_entity,
|
224
|
+
storage_dir=storage_dir,
|
225
|
+
operation="create"
|
226
|
+
)
|
227
|
+
|
228
|
+
# 엔티티 생성 (파일 데이터가 제거된 상태)
|
102
229
|
result = await self.repository.create(
|
103
230
|
entity_data=entity_data_copy,
|
104
231
|
exclude_entities=exclude_entities
|
105
232
|
)
|
106
233
|
|
107
|
-
# 파일 처리
|
108
|
-
file_infos =
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
files=files,
|
115
|
-
storage_dir=storage_dir,
|
116
|
-
entity_name=self.model.__tablename__,
|
117
|
-
entity_ulid=result.ulid,
|
118
|
-
db_session=self.db_session,
|
119
|
-
column_name=field_name.replace('_files', '') # _files 접미사 제거
|
120
|
-
)
|
121
|
-
file_infos[field_name] = saved_files
|
122
|
-
|
123
|
-
# extra_data 업데이트 - 파일 정보 캐싱
|
124
|
-
if not hasattr(result, 'extra_data'):
|
125
|
-
result.extra_data = {}
|
126
|
-
if not result.extra_data:
|
127
|
-
result.extra_data = {}
|
128
|
-
|
129
|
-
result.extra_data[field_name] = [
|
130
|
-
{
|
131
|
-
'original_name': f['original_name'],
|
132
|
-
'storage_path': f['storage_path'],
|
133
|
-
'mime_type': f['mime_type'],
|
134
|
-
'size': f['size'],
|
135
|
-
'checksum': f['checksum'],
|
136
|
-
'column_name': f['column_name']
|
137
|
-
} for f in saved_files
|
138
|
-
]
|
139
|
-
|
140
|
-
# extra_data 업데이트된 엔티티 저장
|
141
|
-
await self.db_session.flush()
|
142
|
-
except CustomException as e:
|
143
|
-
# 파일 저장 실패 시 저장된 파일들 삭제
|
144
|
-
for file_list in file_infos.values():
|
145
|
-
for file_info in file_list:
|
146
|
-
await FileHandler.delete_files(file_info["storage_path"])
|
147
|
-
raise CustomException(
|
148
|
-
e.error_code,
|
149
|
-
detail=e.detail,
|
150
|
-
source_function=f"{self.__class__.__name__}.create.file_processing",
|
151
|
-
original_error=e
|
152
|
-
)
|
153
|
-
except Exception as e:
|
154
|
-
# 파일 저장 실패 시 저장된 파일들 삭제
|
155
|
-
for file_list in file_infos.values():
|
156
|
-
for file_info in file_list:
|
157
|
-
await FileHandler.delete_files(file_info["storage_path"])
|
158
|
-
raise CustomException(
|
159
|
-
ErrorCode.FILE_SYSTEM_ERROR,
|
160
|
-
detail=str(e),
|
161
|
-
source_function=f"{self.__class__.__name__}.create.file_processing",
|
162
|
-
original_error=e
|
163
|
-
)
|
234
|
+
# 실제 파일 처리
|
235
|
+
_, file_infos = await self._process_files(
|
236
|
+
entity_data=entity_data,
|
237
|
+
entity_result=result,
|
238
|
+
storage_dir=storage_dir,
|
239
|
+
operation="create"
|
240
|
+
)
|
164
241
|
|
165
242
|
# 결과 반환
|
166
243
|
if response_model:
|
@@ -173,22 +250,15 @@ class BaseService(Generic[ModelType]):
|
|
173
250
|
return result
|
174
251
|
|
175
252
|
except CustomException as e:
|
176
|
-
# 파일 저장 실패 시 저장된 파일들 삭제
|
177
|
-
if 'file_infos' in locals():
|
178
|
-
for file_list in file_infos.values():
|
179
|
-
for file_info in file_list:
|
180
|
-
from .files import FileHandler
|
181
|
-
await FileHandler.delete_files(file_info["storage_path"])
|
182
253
|
raise e
|
183
254
|
except Exception as e:
|
184
|
-
# 다른 예외 처리
|
185
255
|
raise CustomException(
|
186
256
|
ErrorCode.INTERNAL_ERROR,
|
187
257
|
detail=str(e),
|
188
258
|
source_function=f"base_service.{self.__class__.__name__}.create",
|
189
259
|
original_error=e
|
190
260
|
)
|
191
|
-
|
261
|
+
|
192
262
|
async def update(
|
193
263
|
self,
|
194
264
|
request: Request,
|
@@ -200,7 +270,8 @@ class BaseService(Generic[ModelType]):
|
|
200
270
|
response_model: Any = None,
|
201
271
|
org_ulid_position: str = "organization_ulid",
|
202
272
|
role_permission: str = "update",
|
203
|
-
token_settings: Dict[str, Any] | None = None
|
273
|
+
token_settings: Dict[str, Any] | None = None,
|
274
|
+
storage_dir: str | None = None
|
204
275
|
) -> ModelType:
|
205
276
|
try:
|
206
277
|
async with self.db_session.begin():
|
@@ -226,16 +297,47 @@ class BaseService(Generic[ModelType]):
|
|
226
297
|
|
227
298
|
conditions = {"ulid": ulid}
|
228
299
|
|
229
|
-
|
300
|
+
# 기존 엔티티 조회
|
301
|
+
existing_entity = await self.repository.get(conditions=conditions)
|
302
|
+
if not existing_entity:
|
303
|
+
raise CustomException(
|
304
|
+
ErrorCode.NOT_FOUND,
|
305
|
+
detail=str(ulid or conditions),
|
306
|
+
source_function=f"base_service.{self.__class__.__name__}.update.get_entity"
|
307
|
+
)
|
308
|
+
|
309
|
+
# 파일 데이터 분리
|
310
|
+
entity_data_copy, _ = await self._process_files(
|
230
311
|
entity_data=entity_data,
|
312
|
+
entity_result=existing_entity,
|
313
|
+
storage_dir=storage_dir,
|
314
|
+
operation="update"
|
315
|
+
)
|
316
|
+
|
317
|
+
# 엔티티 수정 (파일 데이터가 제거된 상태)
|
318
|
+
result = await self.repository.update(
|
319
|
+
entity_data=entity_data_copy,
|
231
320
|
conditions=conditions,
|
232
321
|
exclude_entities=exclude_entities
|
233
322
|
)
|
234
323
|
|
324
|
+
# 실제 파일 처리
|
325
|
+
_, file_infos = await self._process_files(
|
326
|
+
entity_data=entity_data,
|
327
|
+
entity_result=result,
|
328
|
+
storage_dir=storage_dir,
|
329
|
+
operation="update"
|
330
|
+
)
|
331
|
+
|
235
332
|
if response_model:
|
236
|
-
|
333
|
+
processed_result = process_response(result, response_model)
|
334
|
+
# 파일 정보 추가
|
335
|
+
for key, value in file_infos.items():
|
336
|
+
processed_result[key] = value
|
337
|
+
return processed_result
|
237
338
|
else:
|
238
339
|
return result
|
340
|
+
|
239
341
|
except CustomException as e:
|
240
342
|
raise e
|
241
343
|
except Exception as e:
|
@@ -253,7 +355,8 @@ class BaseService(Generic[ModelType]):
|
|
253
355
|
conditions: Dict[str, Any] | None = None,
|
254
356
|
org_ulid_position: str = "organization_ulid",
|
255
357
|
role_permission: str = "delete",
|
256
|
-
token_settings: Dict[str, Any] | None = None
|
358
|
+
token_settings: Dict[str, Any] | None = None,
|
359
|
+
storage_dir: str | None = None
|
257
360
|
) -> bool:
|
258
361
|
try:
|
259
362
|
if not ULID.from_str(ulid):
|
@@ -276,6 +379,19 @@ class BaseService(Generic[ModelType]):
|
|
276
379
|
|
277
380
|
conditions["is_deleted"] = False
|
278
381
|
|
382
|
+
# 엔티티 조회 (파일 삭제를 위해)
|
383
|
+
entity = await self.repository.get(conditions=conditions)
|
384
|
+
if not entity:
|
385
|
+
return False
|
386
|
+
|
387
|
+
# 파일 처리 (삭제)
|
388
|
+
_, _ = await self._process_files(
|
389
|
+
entity_data={},
|
390
|
+
entity_result=entity,
|
391
|
+
storage_dir=storage_dir,
|
392
|
+
operation="delete"
|
393
|
+
)
|
394
|
+
|
279
395
|
return await self.repository.delete(
|
280
396
|
conditions=conditions
|
281
397
|
)
|
aiteamutils/version.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
"""버전 정보"""
|
2
|
-
__version__ = "0.2.
|
2
|
+
__version__ = "0.2.141"
|
@@ -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=3M2Mnt5Woz9upNethadxrRTd8kpmxgE_uPIgYYjqTWA,20223
|
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
|
@@ -10,7 +10,7 @@ aiteamutils/exceptions.py,sha256=pgf3ersezObyl17wAO3I2fb8m9t2OzWDX1mSjwAWm2Y,160
|
|
10
10
|
aiteamutils/files.py,sha256=Dfi1rYMBZwV-3GMqZ77_I4BNMpnyL-OBaj9WwZVhCbs,8999
|
11
11
|
aiteamutils/security.py,sha256=McUl3t5Z5SyUDVUHymHdDkYyF4YSeg4g9fFMML4W6Kw,11630
|
12
12
|
aiteamutils/validators.py,sha256=_WHN6jqJQzKM5uPTg-Da8U2qqevS84XeKMkCCF4C_lY,9591
|
13
|
-
aiteamutils/version.py,sha256=
|
14
|
-
aiteamutils-0.2.
|
15
|
-
aiteamutils-0.2.
|
16
|
-
aiteamutils-0.2.
|
13
|
+
aiteamutils/version.py,sha256=0QCUDt0xcylZJRMZSBiqnFqBsyUsdARPHNxrKiKuxJs,43
|
14
|
+
aiteamutils-0.2.141.dist-info/METADATA,sha256=p5daz8rrb2jslha3DxpLf38auLU9H7tIv8va5uxj_Ak,1743
|
15
|
+
aiteamutils-0.2.141.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
16
|
+
aiteamutils-0.2.141.dist-info/RECORD,,
|
File without changes
|