synapse-sdk 2025.9.1__py3-none-any.whl → 2025.9.4__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.
Potentially problematic release.
This version of synapse-sdk might be problematic. Click here for more details.
- synapse_sdk/devtools/docs/docs/api/clients/annotation-mixin.md +378 -0
- synapse_sdk/devtools/docs/docs/api/clients/backend.md +368 -1
- synapse_sdk/devtools/docs/docs/api/clients/core-mixin.md +477 -0
- synapse_sdk/devtools/docs/docs/api/clients/data-collection-mixin.md +422 -0
- synapse_sdk/devtools/docs/docs/api/clients/hitl-mixin.md +554 -0
- synapse_sdk/devtools/docs/docs/api/clients/index.md +391 -0
- synapse_sdk/devtools/docs/docs/api/clients/integration-mixin.md +571 -0
- synapse_sdk/devtools/docs/docs/api/clients/ml-mixin.md +578 -0
- synapse_sdk/devtools/docs/docs/plugins/developing-upload-template.md +1463 -0
- synapse_sdk/devtools/docs/docs/plugins/export-plugins.md +161 -34
- synapse_sdk/devtools/docs/docs/plugins/upload-plugins.md +1497 -213
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/annotation-mixin.md +289 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/backend.md +378 -11
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/core-mixin.md +417 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/data-collection-mixin.md +356 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/hitl-mixin.md +192 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/index.md +391 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/integration-mixin.md +479 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/ml-mixin.md +284 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/developing-upload-template.md +1463 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/export-plugins.md +161 -34
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/upload-plugins.md +1752 -572
- synapse_sdk/devtools/docs/sidebars.ts +7 -0
- synapse_sdk/plugins/README.md +1 -2
- synapse_sdk/plugins/categories/base.py +7 -0
- synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
- synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
- synapse_sdk/plugins/categories/export/actions/export/action.py +160 -0
- synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
- synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
- synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
- synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
- synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
- synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +1 -1
- synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +1 -2
- synapse_sdk/plugins/categories/upload/actions/upload/action.py +154 -531
- synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
- synapse_sdk/plugins/categories/upload/actions/upload/factory.py +143 -0
- synapse_sdk/plugins/categories/upload/actions/upload/models.py +66 -29
- synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +182 -0
- synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +106 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +62 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +80 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +66 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +101 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +89 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +96 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +61 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +86 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +34 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +233 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +253 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/async_upload.py +109 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +43 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +45 -0
- synapse_sdk/plugins/categories/upload/actions/upload/utils.py +194 -83
- synapse_sdk/plugins/categories/upload/templates/config.yaml +4 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +269 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +71 -27
- synapse_sdk/plugins/models.py +7 -0
- synapse_sdk/shared/__init__.py +21 -0
- {synapse_sdk-2025.9.1.dist-info → synapse_sdk-2025.9.4.dist-info}/METADATA +2 -1
- {synapse_sdk-2025.9.1.dist-info → synapse_sdk-2025.9.4.dist-info}/RECORD +79 -28
- synapse_sdk/plugins/categories/export/actions/export.py +0 -385
- synapse_sdk/plugins/categories/export/enums.py +0 -7
- {synapse_sdk-2025.9.1.dist-info → synapse_sdk-2025.9.4.dist-info}/WHEEL +0 -0
- {synapse_sdk-2025.9.1.dist-info → synapse_sdk-2025.9.4.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-2025.9.1.dist-info → synapse_sdk-2025.9.4.dist-info}/licenses/LICENSE +0 -0
- {synapse_sdk-2025.9.1.dist-info → synapse_sdk-2025.9.4.dist-info}/top_level.txt +0 -0
synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/core-mixin.md
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: core-mixin
|
|
3
|
+
title: CoreClientMixin
|
|
4
|
+
sidebar_position: 12
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# CoreClientMixin
|
|
8
|
+
|
|
9
|
+
Synapse 백엔드를 위한 핵심 파일 업로드 및 기본 작업을 제공합니다.
|
|
10
|
+
|
|
11
|
+
## 개요
|
|
12
|
+
|
|
13
|
+
`CoreClientMixin`은 핵심 시스템 작업, 특히 대용량 파일을 위한 청크 업로드를 포함한 파일 업로드 기능을 처리합니다. 이 믹스인은 `BackendClient`에 자동으로 포함되며 다른 믹스인에서 사용하는 필수 기능을 제공합니다.
|
|
14
|
+
|
|
15
|
+
## 파일 업로드 작업
|
|
16
|
+
|
|
17
|
+
### `create_chunked_upload(file_path)`
|
|
18
|
+
|
|
19
|
+
최적의 성능과 안정성을 위해 청크 업로드를 사용하여 대용량 파일을 업로드합니다.
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
# 대용량 파일 업로드
|
|
25
|
+
file_path = Path('/path/to/large_dataset.zip')
|
|
26
|
+
result = client.create_chunked_upload(file_path)
|
|
27
|
+
print(f"업로드 완료: {result}")
|
|
28
|
+
print(f"파일 ID: {result['id']}")
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**매개변수:**
|
|
32
|
+
|
|
33
|
+
- `file_path` (str | Path): 업로드할 파일의 경로
|
|
34
|
+
|
|
35
|
+
**반환값:**
|
|
36
|
+
|
|
37
|
+
- `dict`: 파일 ID와 메타데이터가 포함된 업로드 결과
|
|
38
|
+
|
|
39
|
+
**기능:**
|
|
40
|
+
|
|
41
|
+
- **50MB 청크**: 성능을 위한 최적 청크 크기 사용
|
|
42
|
+
- **MD5 무결성**: 자동 체크섬 검증
|
|
43
|
+
- **재개 기능**: 중단된 업로드 재개 가능
|
|
44
|
+
- **진행률 추적**: 업로드 진행률 모니터링 지원
|
|
45
|
+
- **오류 복구**: 실패한 청크에 대한 자동 재시도
|
|
46
|
+
|
|
47
|
+
### 업로드 프로세스 세부사항
|
|
48
|
+
|
|
49
|
+
청크 업로드 프로세스는 다음과 같이 작동합니다:
|
|
50
|
+
|
|
51
|
+
1. **파일 분석**: 파일 크기 및 MD5 해시 계산
|
|
52
|
+
2. **청크 생성**: 파일을 50MB 청크로 분할
|
|
53
|
+
3. **순차 업로드**: 청크를 하나씩 업로드
|
|
54
|
+
4. **무결성 검사**: MD5로 각 청크 검증
|
|
55
|
+
5. **조립**: 서버에서 청크를 최종 파일로 조립
|
|
56
|
+
6. **검증**: 완성된 파일의 최종 무결성 검사
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import hashlib
|
|
60
|
+
import os
|
|
61
|
+
from pathlib import Path
|
|
62
|
+
|
|
63
|
+
def upload_with_progress(file_path):
|
|
64
|
+
"""자세한 진행률 추적과 함께 파일 업로드."""
|
|
65
|
+
|
|
66
|
+
file_path = Path(file_path)
|
|
67
|
+
|
|
68
|
+
# 파일 정보 가져오기
|
|
69
|
+
file_size = os.path.getsize(file_path)
|
|
70
|
+
print(f"업로드 중인 파일: {file_path.name}")
|
|
71
|
+
print(f"파일 크기: {file_size / (1024*1024):.2f} MB")
|
|
72
|
+
|
|
73
|
+
# MD5 계산 (클라이언트에서 자동으로 수행됨)
|
|
74
|
+
hash_md5 = hashlib.md5()
|
|
75
|
+
with open(file_path, "rb") as f:
|
|
76
|
+
for chunk in iter(lambda: f.read(4096), b""):
|
|
77
|
+
hash_md5.update(chunk)
|
|
78
|
+
|
|
79
|
+
print(f"MD5 체크섬: {hash_md5.hexdigest()}")
|
|
80
|
+
|
|
81
|
+
# 청크 업로드로 업로드
|
|
82
|
+
try:
|
|
83
|
+
result = client.create_chunked_upload(file_path)
|
|
84
|
+
print("업로드 성공!")
|
|
85
|
+
return result
|
|
86
|
+
except Exception as e:
|
|
87
|
+
print(f"업로드 실패: {e}")
|
|
88
|
+
raise
|
|
89
|
+
|
|
90
|
+
# 사용법
|
|
91
|
+
upload_result = upload_with_progress('/path/to/large_file.zip')
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 고급 업로드 시나리오
|
|
95
|
+
|
|
96
|
+
### 일괄 파일 업로드
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
def batch_chunked_upload(file_paths, max_concurrent=3):
|
|
100
|
+
"""동시성 제어와 함께 여러 대용량 파일 업로드."""
|
|
101
|
+
import concurrent.futures
|
|
102
|
+
import threading
|
|
103
|
+
|
|
104
|
+
upload_results = []
|
|
105
|
+
failed_uploads = []
|
|
106
|
+
|
|
107
|
+
def upload_single_file(file_path):
|
|
108
|
+
try:
|
|
109
|
+
print(f"업로드 시작: {file_path}")
|
|
110
|
+
result = client.create_chunked_upload(file_path)
|
|
111
|
+
print(f"업로드 완료: {file_path}")
|
|
112
|
+
return {'file_path': file_path, 'result': result, 'status': 'success'}
|
|
113
|
+
except Exception as e:
|
|
114
|
+
print(f"업로드 실패: {file_path} - {e}")
|
|
115
|
+
return {'file_path': file_path, 'error': str(e), 'status': 'failed'}
|
|
116
|
+
|
|
117
|
+
# 동시 업로드를 위해 ThreadPoolExecutor 사용
|
|
118
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_concurrent) as executor:
|
|
119
|
+
future_to_file = {
|
|
120
|
+
executor.submit(upload_single_file, file_path): file_path
|
|
121
|
+
for file_path in file_paths
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for future in concurrent.futures.as_completed(future_to_file):
|
|
125
|
+
result = future.result()
|
|
126
|
+
|
|
127
|
+
if result['status'] == 'success':
|
|
128
|
+
upload_results.append(result)
|
|
129
|
+
else:
|
|
130
|
+
failed_uploads.append(result)
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
'successful': upload_results,
|
|
134
|
+
'failed': failed_uploads,
|
|
135
|
+
'total': len(file_paths)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# 여러 파일 업로드
|
|
139
|
+
file_list = [
|
|
140
|
+
Path('/data/file1.zip'),
|
|
141
|
+
Path('/data/file2.zip'),
|
|
142
|
+
Path('/data/file3.zip')
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
batch_results = batch_chunked_upload(file_list, max_concurrent=2)
|
|
146
|
+
print(f"성공한 업로드: {len(batch_results['successful'])}")
|
|
147
|
+
print(f"실패한 업로드: {len(batch_results['failed'])}")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 재시도 로직이 있는 업로드
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
import time
|
|
154
|
+
from synapse_sdk.clients.exceptions import ClientError
|
|
155
|
+
|
|
156
|
+
def robust_chunked_upload(file_path, max_retries=3, retry_delay=5):
|
|
157
|
+
"""향상된 안정성을 위한 재시도 로직이 있는 업로드."""
|
|
158
|
+
|
|
159
|
+
for attempt in range(max_retries):
|
|
160
|
+
try:
|
|
161
|
+
result = client.create_chunked_upload(file_path)
|
|
162
|
+
print(f"{attempt + 1}번째 시도에서 업로드 성공")
|
|
163
|
+
return result
|
|
164
|
+
|
|
165
|
+
except ClientError as e:
|
|
166
|
+
if e.status_code == 413: # 파일이 너무 큼
|
|
167
|
+
print(f"파일 {file_path}이 업로드하기에 너무 큽니다")
|
|
168
|
+
raise
|
|
169
|
+
elif e.status_code == 507: # 저장 공간 부족
|
|
170
|
+
print("서버 저장 공간이 가득 참")
|
|
171
|
+
raise
|
|
172
|
+
elif e.status_code >= 500: # 서버 오류
|
|
173
|
+
if attempt < max_retries - 1:
|
|
174
|
+
print(f"{attempt + 1}번째 시도에서 서버 오류, {retry_delay}초 후 재시도...")
|
|
175
|
+
time.sleep(retry_delay)
|
|
176
|
+
retry_delay *= 2 # 지수 백오프
|
|
177
|
+
else:
|
|
178
|
+
print(f"{max_retries}번의 시도 후 업로드 실패")
|
|
179
|
+
raise
|
|
180
|
+
else:
|
|
181
|
+
print(f"오류로 업로드 실패: {e}")
|
|
182
|
+
raise
|
|
183
|
+
|
|
184
|
+
except OSError as e:
|
|
185
|
+
print(f"파일 시스템 오류: {e}")
|
|
186
|
+
raise
|
|
187
|
+
|
|
188
|
+
except Exception as e:
|
|
189
|
+
if attempt < max_retries - 1:
|
|
190
|
+
print(f"{attempt + 1}번째 시도에서 예상치 못한 오류: {e}")
|
|
191
|
+
time.sleep(retry_delay)
|
|
192
|
+
else:
|
|
193
|
+
print(f"{max_retries}번의 시도 후 오류로 업로드 실패: {e}")
|
|
194
|
+
raise
|
|
195
|
+
|
|
196
|
+
# 안정적인 업로드 사용
|
|
197
|
+
try:
|
|
198
|
+
result = robust_chunked_upload('/path/to/file.zip')
|
|
199
|
+
print(f"파일이 성공적으로 업로드됨: {result['id']}")
|
|
200
|
+
except Exception as e:
|
|
201
|
+
print(f"최종 업로드 실패: {e}")
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### 업로드 진행률 모니터링
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
import os
|
|
208
|
+
from tqdm import tqdm
|
|
209
|
+
|
|
210
|
+
class ProgressTracker:
|
|
211
|
+
"""파일 업로드를 위한 간단한 진행률 추적기."""
|
|
212
|
+
|
|
213
|
+
def __init__(self, total_size):
|
|
214
|
+
self.total_size = total_size
|
|
215
|
+
self.uploaded_size = 0
|
|
216
|
+
self.progress_bar = tqdm(total=total_size, unit='B', unit_scale=True, desc="업로드 중")
|
|
217
|
+
|
|
218
|
+
def update(self, chunk_size):
|
|
219
|
+
self.uploaded_size += chunk_size
|
|
220
|
+
self.progress_bar.update(chunk_size)
|
|
221
|
+
|
|
222
|
+
if self.uploaded_size >= self.total_size:
|
|
223
|
+
self.progress_bar.close()
|
|
224
|
+
|
|
225
|
+
def get_progress_percentage(self):
|
|
226
|
+
return (self.uploaded_size / self.total_size) * 100 if self.total_size > 0 else 0
|
|
227
|
+
|
|
228
|
+
def upload_with_progress_bar(file_path):
|
|
229
|
+
"""시각적 진행률 바와 함께 파일 업로드."""
|
|
230
|
+
|
|
231
|
+
file_path = Path(file_path)
|
|
232
|
+
file_size = os.path.getsize(file_path)
|
|
233
|
+
|
|
234
|
+
# 진행률 추적기 생성
|
|
235
|
+
tracker = ProgressTracker(file_size)
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
# 참고: 실제 청크 업로드는 청크 수준 진행률을 노출하지 않음
|
|
239
|
+
# 이것은 진행률을 추적할 수 있는 방법에 대한 개념적 예제
|
|
240
|
+
print(f"{file_path.name} 업로드 시작 ({file_size / (1024*1024):.2f} MB)")
|
|
241
|
+
|
|
242
|
+
result = client.create_chunked_upload(file_path)
|
|
243
|
+
|
|
244
|
+
# 진행률 완료 시뮬레이션
|
|
245
|
+
tracker.update(file_size)
|
|
246
|
+
print(f"업로드 완료: {result['id']}")
|
|
247
|
+
|
|
248
|
+
return result
|
|
249
|
+
|
|
250
|
+
except Exception as e:
|
|
251
|
+
tracker.progress_bar.close()
|
|
252
|
+
print(f"업로드 실패: {e}")
|
|
253
|
+
raise
|
|
254
|
+
|
|
255
|
+
# 사용법
|
|
256
|
+
upload_result = upload_with_progress_bar('/path/to/large_file.zip')
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## 파일 검증
|
|
260
|
+
|
|
261
|
+
### 업로드 전 검증
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
def validate_file_for_upload(file_path, max_size_gb=10):
|
|
265
|
+
"""업로드를 시도하기 전에 파일을 검증."""
|
|
266
|
+
|
|
267
|
+
file_path = Path(file_path)
|
|
268
|
+
|
|
269
|
+
# 파일 존재 확인
|
|
270
|
+
if not file_path.exists():
|
|
271
|
+
raise FileNotFoundError(f"파일을 찾을 수 없음: {file_path}")
|
|
272
|
+
|
|
273
|
+
# 파일인지 확인 (디렉토리가 아닌)
|
|
274
|
+
if not file_path.is_file():
|
|
275
|
+
raise ValueError(f"경로가 파일이 아님: {file_path}")
|
|
276
|
+
|
|
277
|
+
# 파일 크기 확인
|
|
278
|
+
file_size = os.path.getsize(file_path)
|
|
279
|
+
max_size_bytes = max_size_gb * 1024 * 1024 * 1024
|
|
280
|
+
|
|
281
|
+
if file_size > max_size_bytes:
|
|
282
|
+
raise ValueError(f"파일이 너무 큼: {file_size / (1024**3):.2f} GB (최대: {max_size_gb} GB)")
|
|
283
|
+
|
|
284
|
+
# 파일 권한 확인
|
|
285
|
+
if not os.access(file_path, os.R_OK):
|
|
286
|
+
raise PermissionError(f"파일을 읽을 수 없음: {file_path}")
|
|
287
|
+
|
|
288
|
+
# 기본 파일 무결성 검사
|
|
289
|
+
try:
|
|
290
|
+
with open(file_path, 'rb') as f:
|
|
291
|
+
f.read(1024) # 첫 1KB 읽기 시도
|
|
292
|
+
except Exception as e:
|
|
293
|
+
raise ValueError(f"파일이 손상된 것으로 보임: {e}")
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
'valid': True,
|
|
297
|
+
'file_size': file_size,
|
|
298
|
+
'file_path': str(file_path)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
def safe_chunked_upload(file_path):
|
|
302
|
+
"""사전 검증과 함께 업로드."""
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
# 먼저 파일 검증
|
|
306
|
+
validation = validate_file_for_upload(file_path)
|
|
307
|
+
print(f"파일 검증 통과: {validation['file_size'] / (1024*1024):.2f} MB")
|
|
308
|
+
|
|
309
|
+
# 업로드 진행
|
|
310
|
+
result = client.create_chunked_upload(file_path)
|
|
311
|
+
print(f"업로드 성공: {result['id']}")
|
|
312
|
+
|
|
313
|
+
return result
|
|
314
|
+
|
|
315
|
+
except (FileNotFoundError, ValueError, PermissionError) as e:
|
|
316
|
+
print(f"검증 실패: {e}")
|
|
317
|
+
return None
|
|
318
|
+
except Exception as e:
|
|
319
|
+
print(f"업로드 실패: {e}")
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
# 사용법
|
|
323
|
+
upload_result = safe_chunked_upload('/path/to/file.zip')
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## 성능 최적화
|
|
327
|
+
|
|
328
|
+
### 최적화된 업로드 전략
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
def optimized_upload_strategy(file_path):
|
|
332
|
+
"""파일 특성에 따른 최적 업로드 전략 선택."""
|
|
333
|
+
|
|
334
|
+
file_path = Path(file_path)
|
|
335
|
+
file_size = os.path.getsize(file_path)
|
|
336
|
+
|
|
337
|
+
# 임계값 (바이트 단위)
|
|
338
|
+
SMALL_FILE_THRESHOLD = 10 * 1024 * 1024 # 10MB
|
|
339
|
+
LARGE_FILE_THRESHOLD = 100 * 1024 * 1024 # 100MB
|
|
340
|
+
|
|
341
|
+
if file_size < SMALL_FILE_THRESHOLD:
|
|
342
|
+
print(f"작은 파일 ({file_size / (1024*1024):.2f} MB) - 일반 업로드 사용")
|
|
343
|
+
# 작은 파일의 경우 다른 업로드 방법을 사용할 수 있음
|
|
344
|
+
# CoreClientMixin은 청크 업로드만 제공하므로 이것은 개념적임
|
|
345
|
+
return client.create_chunked_upload(file_path)
|
|
346
|
+
|
|
347
|
+
elif file_size < LARGE_FILE_THRESHOLD:
|
|
348
|
+
print(f"중간 파일 ({file_size / (1024*1024):.2f} MB) - 청크 업로드 사용")
|
|
349
|
+
return client.create_chunked_upload(file_path)
|
|
350
|
+
|
|
351
|
+
else:
|
|
352
|
+
print(f"대용량 파일 ({file_size / (1024*1024):.2f} MB) - 최적화된 청크 업로드 사용")
|
|
353
|
+
# 매우 큰 파일의 경우 추가 최적화를 원할 수 있음
|
|
354
|
+
return robust_chunked_upload(file_path, max_retries=5)
|
|
355
|
+
|
|
356
|
+
# 사용법
|
|
357
|
+
result = optimized_upload_strategy('/path/to/any_size_file.zip')
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## 오류 처리
|
|
361
|
+
|
|
362
|
+
```python
|
|
363
|
+
from synapse_sdk.clients.exceptions import ClientError
|
|
364
|
+
|
|
365
|
+
def handle_upload_errors():
|
|
366
|
+
"""업로드를 위한 포괄적인 오류 처리."""
|
|
367
|
+
|
|
368
|
+
try:
|
|
369
|
+
result = client.create_chunked_upload('/path/to/file.zip')
|
|
370
|
+
return result
|
|
371
|
+
|
|
372
|
+
except FileNotFoundError:
|
|
373
|
+
print("오류: 파일을 찾을 수 없음")
|
|
374
|
+
return None
|
|
375
|
+
|
|
376
|
+
except PermissionError:
|
|
377
|
+
print("오류: 권한이 거부됨 - 파일 권한을 확인하세요")
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
except ClientError as e:
|
|
381
|
+
if e.status_code == 413:
|
|
382
|
+
print("오류: 업로드하기에 파일이 너무 큼")
|
|
383
|
+
elif e.status_code == 507:
|
|
384
|
+
print("오류: 서버 저장 공간이 가득 �참")
|
|
385
|
+
elif e.status_code == 429:
|
|
386
|
+
print("오류: 요청 제한 - 너무 많은 요청")
|
|
387
|
+
elif e.status_code >= 500:
|
|
388
|
+
print(f"오류: 서버 오류 ({e.status_code})")
|
|
389
|
+
else:
|
|
390
|
+
print(f"오류: 클라이언트 오류 ({e.status_code}): {e}")
|
|
391
|
+
return None
|
|
392
|
+
|
|
393
|
+
except OSError as e:
|
|
394
|
+
print(f"오류: 운영 체제 오류: {e}")
|
|
395
|
+
return None
|
|
396
|
+
|
|
397
|
+
except MemoryError:
|
|
398
|
+
print("오류: 업로드를 위한 메모리 부족")
|
|
399
|
+
return None
|
|
400
|
+
|
|
401
|
+
except Exception as e:
|
|
402
|
+
print(f"오류: 예상치 못한 오류: {e}")
|
|
403
|
+
return None
|
|
404
|
+
|
|
405
|
+
# 오류 처리 사용
|
|
406
|
+
upload_result = handle_upload_errors()
|
|
407
|
+
if upload_result:
|
|
408
|
+
print(f"업로드 성공: {upload_result['id']}")
|
|
409
|
+
else:
|
|
410
|
+
print("업로드 실패")
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## 참고
|
|
414
|
+
|
|
415
|
+
- [BackendClient](./backend.md) - 메인 백엔드 클라이언트
|
|
416
|
+
- [DataCollectionClientMixin](./data-collection-mixin.md) - 청크 업로드를 사용하는 데이터 수집 작업
|
|
417
|
+
- [MLClientMixin](./ml-mixin.md) - 청크 업로드를 사용하는 ML 모델 업로드
|