aiteamutils 0.2.143__py3-none-any.whl → 0.2.145__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.
@@ -6,6 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
6
6
  from datetime import datetime
7
7
  from ulid import ULID
8
8
  from sqlalchemy import text
9
+ import logging
9
10
 
10
11
  #패키지 라이브러리
11
12
  from .exceptions import ErrorCode, CustomException
@@ -17,6 +18,8 @@ from .database import (
17
18
  from .security import hash_password, verify_jwt_token, verify_role_permission
18
19
  ModelType = TypeVar("ModelType", bound=DeclarativeBase)
19
20
 
21
+ logger = logging.getLogger(__name__)
22
+
20
23
  class BaseService(Generic[ModelType]):
21
24
  ##################
22
25
  # 초기화 영역 #
@@ -55,16 +58,22 @@ class BaseService(Generic[ModelType]):
55
58
  Tuple[Dict[str, Any], Dict[str, Any]]: (처리된 엔티티 데이터, 파일 정보)
56
59
  """
57
60
  try:
61
+ logger.info(f"[파일 처리 시작] operation: {operation}, storage_dir: {storage_dir}")
62
+ logger.info(f"[엔티티 데이터] {entity_data}")
63
+
58
64
  entity_data_copy = entity_data.copy()
59
65
  file_infos = {}
60
66
 
61
67
  # 파일 데이터 분리
62
68
  separated_files = {}
63
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']}")
64
71
  extra_data = entity_data_copy['extra_data'].copy()
65
72
  file_fields = {k: v for k, v in extra_data.items() if k.endswith('_files')}
73
+ logger.info(f"[파일 필드 확인] {file_fields}")
66
74
 
67
75
  if file_fields and not storage_dir:
76
+ logger.error("[에러] storage_dir이 필요하지만 제공되지 않음")
68
77
  raise CustomException(
69
78
  ErrorCode.INVALID_INPUT,
70
79
  detail="storage_dir is required for file upload",
@@ -74,6 +83,7 @@ class BaseService(Generic[ModelType]):
74
83
  # 파일 필드 분리 및 제거
75
84
  for field_name, files in file_fields.items():
76
85
  if files:
86
+ logger.info(f"[파일 처리] 필드: {field_name}, 파일 수: {len(files)}")
77
87
  separated_files[field_name] = files
78
88
  del extra_data[field_name]
79
89
 
@@ -81,6 +91,7 @@ class BaseService(Generic[ModelType]):
81
91
 
82
92
  # 기존 파일 삭제 (update 또는 delete 작업 시)
83
93
  if operation in ["update", "delete"]:
94
+ logger.info("[기존 파일 삭제 시작]")
84
95
  from .files import FileHandler
85
96
  # files 테이블에서 기존 파일 정보 조회
86
97
  existing_files = await self.db_session.execute(
@@ -96,9 +107,11 @@ class BaseService(Generic[ModelType]):
96
107
  }
97
108
  )
98
109
  existing_files = existing_files.fetchall()
110
+ logger.info(f"[기존 파일 조회 결과] {existing_files}")
99
111
 
100
112
  # 기존 파일 삭제
101
113
  for file_info in existing_files:
114
+ logger.info(f"[파일 삭제] {file_info[0]}")
102
115
  await FileHandler.delete_files(file_info[0])
103
116
 
104
117
  # files 테이블에서 레코드 삭제
@@ -113,11 +126,14 @@ class BaseService(Generic[ModelType]):
113
126
  "entity_ulid": entity_result.ulid
114
127
  }
115
128
  )
129
+ logger.info("[기존 파일 DB 레코드 삭제 완료]")
116
130
 
117
131
  # 새 파일 저장 (create 또는 update 작업 시)
118
132
  if operation != "delete" and separated_files:
133
+ logger.info("[새 파일 저장 시작]")
119
134
  from .files import FileHandler
120
135
  for field_name, files in separated_files.items():
136
+ logger.info(f"[필드 처리] {field_name}, 파일 수: {len(files)}")
121
137
  saved_files = await FileHandler.save_files(
122
138
  files=files,
123
139
  storage_dir=storage_dir,
@@ -127,6 +143,7 @@ class BaseService(Generic[ModelType]):
127
143
  column_name=field_name.replace('_files', '')
128
144
  )
129
145
  file_infos[field_name] = saved_files
146
+ logger.info(f"[파일 저장 완료] {field_name}: {len(saved_files)}개")
130
147
 
131
148
  # extra_data 업데이트 - 파일 정보 캐싱
132
149
  if not hasattr(entity_result, 'extra_data'):
@@ -144,17 +161,22 @@ class BaseService(Generic[ModelType]):
144
161
  'column_name': f['column_name']
145
162
  } for f in saved_files
146
163
  ]
164
+ logger.info(f"[extra_data 업데이트 완료] {field_name}")
147
165
 
148
166
  # extra_data 업데이트된 엔티티 저장
149
167
  await self.db_session.flush()
168
+ logger.info("[DB flush 완료]")
150
169
 
170
+ logger.info("[파일 처리 완료]")
151
171
  return entity_data_copy, file_infos
152
172
 
153
173
  except CustomException as e:
174
+ logger.error(f"[CustomException 발생] {e.error_code}: {e.detail}")
154
175
  if file_infos:
155
176
  from .files import FileHandler
156
177
  for file_list in file_infos.values():
157
178
  for file_info in file_list:
179
+ logger.info(f"[에러 복구] 파일 삭제: {file_info['storage_path']}")
158
180
  await FileHandler.delete_files(file_info["storage_path"])
159
181
  raise CustomException(
160
182
  e.error_code,
@@ -163,14 +185,16 @@ class BaseService(Generic[ModelType]):
163
185
  original_error=e
164
186
  )
165
187
  except Exception as e:
188
+ logger.error(f"[예외 발생] {str(e)}")
166
189
  if file_infos:
167
190
  from .files import FileHandler
168
191
  for file_list in file_infos.values():
169
192
  for file_info in file_list:
193
+ logger.info(f"[에러 복구] 파일 삭제: {file_info['storage_path']}")
170
194
  await FileHandler.delete_files(file_info["storage_path"])
171
195
  raise CustomException(
172
196
  ErrorCode.FILE_SYSTEM_ERROR,
173
- detail=str(e),
197
+ detail=f"File processing error: {str(e)}",
174
198
  source_function=f"{self.__class__.__name__}._process_files",
175
199
  original_error=e
176
200
  )
@@ -248,14 +272,27 @@ class BaseService(Generic[ModelType]):
248
272
  return result
249
273
 
250
274
  except CustomException as e:
251
- raise e
252
- except Exception as e:
253
275
  raise CustomException(
254
- ErrorCode.INTERNAL_ERROR,
255
- detail=str(e),
256
- source_function=f"base_service.{self.__class__.__name__}.create.exception",
276
+ e.error_code,
277
+ detail=f"{e.detail}|{str(e)}",
278
+ source_function=f"base_service.{self.__class__.__name__}.create",
257
279
  original_error=e
258
280
  )
281
+ except Exception as e:
282
+ if isinstance(e, str) and "FILE_SYSTEM_ERROR" in str(e):
283
+ raise CustomException(
284
+ ErrorCode.FILE_SYSTEM_ERROR,
285
+ detail=f"{str(e)}",
286
+ source_function=f"base_service.{self.__class__.__name__}.create",
287
+ original_error=e
288
+ )
289
+ else:
290
+ raise CustomException(
291
+ ErrorCode.INTERNAL_ERROR,
292
+ detail=f"{str(e)}",
293
+ source_function=f"base_service.{self.__class__.__name__}.create",
294
+ original_error=e
295
+ )
259
296
 
260
297
  async def update(
261
298
  self,
aiteamutils/files.py CHANGED
@@ -8,22 +8,38 @@ from mimetypes import guess_type
8
8
  from sqlalchemy import text
9
9
  from sqlalchemy.ext.asyncio import AsyncSession
10
10
  from ulid import ULID
11
+ import logging
11
12
 
12
13
  from .exceptions import ErrorCode, CustomException
13
14
 
15
+ logger = logging.getLogger(__name__)
16
+
14
17
  class FileHandler:
15
18
  """파일 처리를 위한 핵심 기능 제공 클래스"""
16
19
 
17
20
  @staticmethod
18
21
  def _create_directory(directory: str) -> None:
19
- """디렉토리가 없는 경우 생성
20
-
21
- Args:
22
- directory (str): 생성할 디렉토리 경로
23
- """
22
+ """디렉토리가 없는 경우 생성"""
24
23
  try:
24
+ logger.info(f"[디렉토리 생성 시도] {directory}")
25
+ if os.path.exists(directory):
26
+ logger.info(f"[디렉토리 이미 존재] {directory}")
27
+ return
28
+
25
29
  os.makedirs(directory, exist_ok=True)
30
+ logger.info(f"[디렉토리 생성 완료] {directory}")
31
+
32
+ # 권한 확인
33
+ if not os.access(directory, os.W_OK):
34
+ logger.error(f"[권한 에러] 디렉토리에 쓰기 권한 없음: {directory}")
35
+ raise CustomException(
36
+ ErrorCode.FILE_SYSTEM_ERROR,
37
+ detail=f"{directory}|No write permission",
38
+ source_function="FileHandler._create_directory"
39
+ )
40
+
26
41
  except Exception as e:
42
+ logger.error(f"[디렉토리 생성 실패] {directory}: {str(e)}")
27
43
  raise CustomException(
28
44
  ErrorCode.FILE_SYSTEM_ERROR,
29
45
  detail=f"{directory}|{str(e)}",
@@ -80,19 +96,10 @@ class FileHandler:
80
96
  entity_name: str,
81
97
  entity_ulid: str
82
98
  ) -> Dict[str, Any]:
83
- """파일을 저장하고 메타데이터 반환 (내부 사용)
84
-
85
- Args:
86
- file (BinaryIO): 저장할 파일 객체
87
- original_name (str): 원본 파일명
88
- storage_dir (str): 저장 디렉토리 경로
89
- entity_name (str): 엔티티 이름
90
- entity_ulid (str): 엔티티 ULID
91
-
92
- Returns:
93
- Dict[str, Any]: 저장된 파일의 메타데이터
94
- """
99
+ """파일을 저장하고 메타데이터 반환"""
95
100
  try:
101
+ logger.info(f"[파일 저장 시작] 원본 파일명: {original_name}, 저장 경로: {storage_dir}")
102
+
96
103
  # 저장 디렉토리 생성
97
104
  FileHandler._create_directory(storage_dir)
98
105
 
@@ -100,23 +107,30 @@ class FileHandler:
100
107
  file_ext = os.path.splitext(original_name)[1]
101
108
  storage_filename = f"{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}_{entity_ulid}{file_ext}"
102
109
  storage_path = os.path.join(storage_dir, storage_filename)
110
+ logger.info(f"[저장 파일명] {storage_filename}")
103
111
 
104
112
  # 파일 저장
105
113
  current_position = file.tell()
106
114
  file.seek(0)
107
115
 
108
- async with aiofiles.open(storage_path, 'wb') as f:
109
- while chunk := file.read(8192):
110
- await f.write(chunk)
116
+ try:
117
+ async with aiofiles.open(storage_path, 'wb') as f:
118
+ while chunk := file.read(8192):
119
+ await f.write(chunk)
120
+ logger.info(f"[파일 쓰기 완료] {storage_path}")
121
+ except Exception as e:
122
+ logger.error(f"[파일 쓰기 실패] {storage_path}: {str(e)}")
123
+ raise
111
124
 
112
125
  # 체크섬 계산
113
126
  file.seek(0)
114
127
  checksum = await FileHandler._calculate_checksum(file)
128
+ logger.info(f"[체크섬 계산 완료] {checksum}")
115
129
 
116
130
  # 파일 포인터 복구
117
131
  file.seek(current_position)
118
132
 
119
- return {
133
+ file_info = {
120
134
  "original_name": original_name,
121
135
  "storage_path": storage_path,
122
136
  "mime_type": FileHandler._get_mime_type(original_name),
@@ -125,9 +139,14 @@ class FileHandler:
125
139
  "entity_name": entity_name,
126
140
  "entity_ulid": entity_ulid
127
141
  }
142
+ logger.info(f"[파일 정보] {file_info}")
143
+ return file_info
144
+
128
145
  except CustomException as e:
146
+ logger.error(f"[CustomException 발생] {e.error_code}: {e.detail}")
129
147
  raise e
130
148
  except Exception as e:
149
+ logger.error(f"[파일 저장 실패] {str(e)}")
131
150
  raise CustomException(
132
151
  ErrorCode.FILE_SYSTEM_ERROR,
133
152
  detail=f"{storage_path}|{str(e)}",
@@ -144,24 +163,16 @@ class FileHandler:
144
163
  db_session: AsyncSession,
145
164
  column_name: str = None
146
165
  ) -> List[Dict[str, Any]]:
147
- """파일(들)을 저장하고 메타데이터 반환
166
+ """파일(들)을 저장하고 메타데이터 반환"""
167
+ logger.info(f"[다중 파일 저장 시작] storage_dir: {storage_dir}, entity_name: {entity_name}")
148
168
 
149
- Args:
150
- files: 단일 파일 튜플 (file, original_name) 또는 파일 튜플 리스트
151
- storage_dir (str): 저장 디렉토리 경로
152
- entity_name (str): 엔티티 이름
153
- entity_ulid (str): 엔티티 ULID
154
- db_session (AsyncSession): DB 세션
155
- column_name (str): 파일이 속한 컬럼 이름
156
-
157
- Returns:
158
- List[Dict[str, Any]]: 저장된 파일들의 메타데이터 리스트
159
- """
160
169
  file_infos = []
161
170
  # 단일 파일인 경우 리스트로 변환
162
171
  files_list = [files] if isinstance(files, tuple) else files
172
+ logger.info(f"[처리할 파일 수] {len(files_list)}")
163
173
 
164
174
  for file, original_name in files_list:
175
+ logger.info(f"[개별 파일 처리] {original_name}")
165
176
  # 파일 저장 및 메타데이터 생성
166
177
  file_info = await FileHandler._save_file(
167
178
  file=file,
@@ -171,32 +182,39 @@ class FileHandler:
171
182
  entity_ulid=entity_ulid
172
183
  )
173
184
 
174
- # DB에 파일 정보 저장 (트랜잭션은 BaseService에서 관리)
175
- result = await db_session.execute(
176
- text("""
177
- INSERT INTO files (
178
- entity_name, entity_ulid, original_name, storage_path,
179
- mime_type, size, checksum, column_name
180
- ) VALUES (
181
- :entity_name, :entity_ulid, :original_name, :storage_path,
182
- :mime_type, :size, :checksum, :column_name
183
- ) RETURNING *
184
- """),
185
- {
186
- "entity_name": entity_name,
187
- "entity_ulid": entity_ulid,
188
- "original_name": file_info["original_name"],
189
- "storage_path": file_info["storage_path"],
190
- "mime_type": file_info["mime_type"],
191
- "size": file_info["size"],
192
- "checksum": file_info["checksum"],
193
- "column_name": column_name
194
- }
195
- )
196
-
197
- file_info = dict(result.fetchone())
198
- file_infos.append(file_info)
185
+ # DB에 파일 정보 저장
186
+ try:
187
+ logger.info(f"[DB 저장 시작] {original_name}")
188
+ result = await db_session.execute(
189
+ text("""
190
+ INSERT INTO files (
191
+ entity_name, entity_ulid, original_name, storage_path,
192
+ mime_type, size, checksum, column_name
193
+ ) VALUES (
194
+ :entity_name, :entity_ulid, :original_name, :storage_path,
195
+ :mime_type, :size, :checksum, :column_name
196
+ ) RETURNING *
197
+ """),
198
+ {
199
+ "entity_name": entity_name,
200
+ "entity_ulid": entity_ulid,
201
+ "original_name": file_info["original_name"],
202
+ "storage_path": file_info["storage_path"],
203
+ "mime_type": file_info["mime_type"],
204
+ "size": file_info["size"],
205
+ "checksum": file_info["checksum"],
206
+ "column_name": column_name
207
+ }
208
+ )
209
+
210
+ file_info = dict(result.fetchone())
211
+ file_infos.append(file_info)
212
+ logger.info(f"[DB 저장 완료] {original_name}")
213
+ except Exception as e:
214
+ logger.error(f"[DB 저장 실패] {original_name}: {str(e)}")
215
+ raise
199
216
 
217
+ logger.info(f"[다중 파일 저장 완료] 성공: {len(file_infos)}개")
200
218
  return file_infos
201
219
 
202
220
  @staticmethod
aiteamutils/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  """버전 정보"""
2
- __version__ = "0.2.143"
2
+ __version__ = "0.2.145"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiteamutils
3
- Version: 0.2.143
3
+ Version: 0.2.145
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,16 +1,16 @@
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=3woNhOtjPc0QNwTcyAH1h6psqDY25sV_4wD-tr3jHk4,20101
4
+ aiteamutils/base_service.py,sha256=dqs0b5xyCdBibgbgHKG9LCyeyQW1MNwiwuOrmSluJHI,22349
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=1XuLAvsQbnURHSZ2IyZIXrZH6OWgnC5Tl1vcTDyi1GY,9017
10
+ aiteamutils/files.py,sha256=hBCnXCa8wrBK05aSZAF08tcH_eiYmVQqUaSQqCgib7o,10311
11
11
  aiteamutils/security.py,sha256=McUl3t5Z5SyUDVUHymHdDkYyF4YSeg4g9fFMML4W6Kw,11630
12
12
  aiteamutils/validators.py,sha256=_WHN6jqJQzKM5uPTg-Da8U2qqevS84XeKMkCCF4C_lY,9591
13
- aiteamutils/version.py,sha256=9G4P9qltI2Vtwf4PoIM_54O-NL1h1zyzt3BwyK86o58,43
14
- aiteamutils-0.2.143.dist-info/METADATA,sha256=d9ElwAvXGoaW14-54fZWBRW9rSx8sEzdHCa7j12nO1g,1743
15
- aiteamutils-0.2.143.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- aiteamutils-0.2.143.dist-info/RECORD,,
13
+ aiteamutils/version.py,sha256=qTfcWqtaTYCddRCJFGc8bvpJSvvkq6vXGLan238hPT4,43
14
+ aiteamutils-0.2.145.dist-info/METADATA,sha256=SSsB1r0zBl1--1_PNz-nvFAlMP1VFduSYZIwaSrKxZc,1743
15
+ aiteamutils-0.2.145.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ aiteamutils-0.2.145.dist-info/RECORD,,