synapse-sdk 2025.10.1__py3-none-any.whl → 2025.10.3__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.

Files changed (44) hide show
  1. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-action.md +934 -0
  2. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-overview.md +560 -0
  3. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-template.md +715 -0
  4. synapse_sdk/devtools/docs/docs/plugins/plugins.md +12 -5
  5. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-action.md +934 -0
  6. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-overview.md +560 -0
  7. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-template.md +715 -0
  8. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current.json +16 -4
  9. synapse_sdk/devtools/docs/sidebars.ts +13 -1
  10. synapse_sdk/plugins/README.md +487 -80
  11. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
  12. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
  13. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/action.py +10 -0
  14. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
  15. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +145 -0
  16. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
  17. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
  18. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
  19. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +97 -0
  20. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +250 -0
  21. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
  22. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
  23. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +284 -0
  24. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
  25. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
  26. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +87 -0
  27. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +127 -0
  28. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
  29. synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +2 -1
  30. synapse_sdk/plugins/categories/upload/actions/upload/models.py +134 -94
  31. synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +2 -2
  32. synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +106 -14
  33. synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +113 -36
  34. synapse_sdk/plugins/categories/upload/templates/README.md +365 -0
  35. {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.3.dist-info}/METADATA +1 -1
  36. {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.3.dist-info}/RECORD +40 -20
  37. synapse_sdk/devtools/docs/docs/plugins/developing-upload-template.md +0 -1463
  38. synapse_sdk/devtools/docs/docs/plugins/upload-plugins.md +0 -1964
  39. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/developing-upload-template.md +0 -1463
  40. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/upload-plugins.md +0 -2077
  41. {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.3.dist-info}/WHEEL +0 -0
  42. {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.3.dist-info}/entry_points.txt +0 -0
  43. {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.3.dist-info}/licenses/LICENSE +0 -0
  44. {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,715 @@
1
+ ---
2
+ id: upload-plugin-template
3
+ title: 업로드 플러그인 템플릿 개발
4
+ sidebar_position: 3
5
+ ---
6
+
7
+ # BaseUploader를 사용한 업로드 플러그인 템플릿 개발
8
+
9
+ 이 가이드는 BaseUploader 템플릿을 사용하여 사용자 정의 업로드 플러그인을 만들려는 플러그인 개발자를 위한 것입니다. BaseUploader는 업로드 플러그인 내에서 파일 처리 및 구성을 위한 워크플로우 기반의 기초를 제공합니다.
10
+
11
+ ## 개요
12
+
13
+ BaseUploader 템플릿 (`synapse_sdk.plugins.categories.upload.templates.plugin`)은 업로드 플러그인 구축을 위한 구조화된 접근 방식을 제공합니다. 메서드 재정의를 통해 사용자 정의를 허용하면서 일반적인 업로드 워크플로우를 처리합니다.
14
+
15
+ ### BaseUploader 워크플로우
16
+
17
+ BaseUploader는 6단계 워크플로우 파이프라인을 구현합니다:
18
+
19
+ ```
20
+ 1. setup_directories() # 사용자 정의 디렉토리 구조 생성
21
+ 2. organize_files() # 파일 구성 및 구조화
22
+ 3. before_process() # 전처리 후크
23
+ 4. process_files() # 주요 처리 로직 (필수)
24
+ 5. after_process() # 후처리 후크
25
+ 6. validate_files() # 최종 검증
26
+ ```
27
+
28
+ ## 시작하기
29
+
30
+ ### 템플릿 구조
31
+
32
+ 업로드 플러그인을 생성하면 다음과 같은 구조를 갖게 됩니다:
33
+
34
+ ```
35
+ synapse-{plugin-code}-plugin/
36
+ ├── config.yaml # 플러그인 메타데이터 및 구성
37
+ ├── plugin/ # 소스 코드 디렉토리
38
+ │ ├── __init__.py
39
+ │ └── upload.py # BaseUploader를 사용한 주요 업로드 구현
40
+ ├── requirements.txt # 파이썬 의존성
41
+ ├── pyproject.toml # 패키지 구성
42
+ └── README.md # 플러그인 문서
43
+ ```
44
+
45
+ ### 기본 플러그인 구현
46
+
47
+ ```python
48
+ # plugin/__init__.py
49
+ from pathlib import Path
50
+ from typing import Any, Dict, List
51
+
52
+ class BaseUploader:
53
+ """일반적인 업로드 기능을 가진 기본 클래스."""
54
+
55
+ def __init__(self, run, path: Path, file_specification: List = None,
56
+ organized_files: List = None, extra_params: Dict = None):
57
+ self.run = run
58
+ self.path = path
59
+ self.file_specification = file_specification or []
60
+ self.organized_files = organized_files or []
61
+ self.extra_params = extra_params or {}
62
+
63
+ # 재정의 가능한 핵심 워크플로우 메서드
64
+ def setup_directories(self) -> None:
65
+ """사용자 정의 디렉토리 설정 - 필요에 따라 재정의."""
66
+ pass
67
+
68
+ def organize_files(self, files: List) -> List:
69
+ """파일 구성 - 사용자 정의 로직을 위해 재정의."""
70
+ return files
71
+
72
+ def before_process(self, organized_files: List) -> List:
73
+ """전처리 후크 - 필요에 따라 재정의."""
74
+ return organized_files
75
+
76
+ def process_files(self, organized_files: List) -> List:
77
+ """주요 처리 - 반드시 재정의해야 함."""
78
+ return organized_files
79
+
80
+ def after_process(self, processed_files: List) -> List:
81
+ """후처리 후크 - 필요에 따라 재정의."""
82
+ return processed_files
83
+
84
+ def validate_files(self, files: List) -> List:
85
+ """검증 - 사용자 정의 검증을 위해 재정의."""
86
+ return self._filter_valid_files(files)
87
+
88
+ def handle_upload_files(self) -> List:
89
+ """주요 진입점 - 워크플로우를 실행합니다."""
90
+ self.setup_directories()
91
+ current_files = self.organized_files
92
+ current_files = self.organize_files(current_files)
93
+ current_files = self.before_process(current_files)
94
+ current_files = self.process_files(current_files)
95
+ current_files = self.after_process(current_files)
96
+ current_files = self.validate_files(current_files)
97
+ return current_files
98
+
99
+ # plugin/upload.py
100
+ from . import BaseUploader
101
+
102
+ class Uploader(BaseUploader):
103
+ """사용자 정의 업로드 플러그인 구현."""
104
+
105
+ def process_files(self, organized_files: List) -> List:
106
+ """필수: 파일 처리 로직을 구현하십시오."""
107
+ # 여기에 사용자 정의 처리 로직
108
+ return organized_files
109
+ ```
110
+
111
+ ## 핵심 메서드 참조
112
+
113
+ ### 필수 메서드
114
+
115
+ #### `process_files(organized_files: List) -> List`
116
+
117
+ **목적**: 모든 플러그인에서 구현해야 하는 주요 처리 메서드.
118
+
119
+ **사용 시기**: 항상 - 플러그인의 핵심 로직이 여기에 들어갑니다.
120
+
121
+ **예시**:
122
+
123
+ ```python
124
+ def process_files(self, organized_files: List) -> List:
125
+ """TIFF 이미지를 JPEG 형식으로 변환합니다."""
126
+ processed_files = []
127
+
128
+ for file_group in organized_files:
129
+ files_dict = file_group.get('files', {})
130
+ converted_files = {}
131
+
132
+ for spec_name, file_path in files_dict.items():
133
+ if file_path.suffix.lower() in ['.tif', '.tiff']:
134
+ # TIFF를 JPEG로 변환
135
+ jpeg_path = self.convert_tiff_to_jpeg(file_path)
136
+ converted_files[spec_name] = jpeg_path
137
+ self.run.log_message(f"{file_path}를 {jpeg_path}로 변환했습니다.")
138
+ else:
139
+ converted_files[spec_name] = file_path
140
+
141
+ file_group['files'] = converted_files
142
+ processed_files.append(file_group)
143
+
144
+ return processed_files
145
+ ```
146
+
147
+ ### 선택적 후크 메서드
148
+
149
+ #### `setup_directories() -> None`
150
+
151
+ **목적**: 처리가 시작되기 전에 사용자 정의 디렉토리 구조를 생성합니다.
152
+
153
+ **사용 시기**: 플러그인이 처리, 임시 파일 또는 출력을 위해 특정 디렉토리가 필요할 때.
154
+
155
+ **예시**:
156
+
157
+ ```python
158
+ def setup_directories(self):
159
+ """처리 디렉토리를 생성합니다."""
160
+ (self.path / 'temp').mkdir(exist_ok=True)
161
+ (self.path / 'processed').mkdir(exist_ok=True)
162
+ (self.path / 'thumbnails').mkdir(exist_ok=True)
163
+ self.run.log_message("처리 디렉토리를 생성했습니다.")
164
+ ```
165
+
166
+ #### `organize_files(files: List) -> List`
167
+
168
+ **목적**: 주요 처리 전에 파일을 재구성하고 구조화합니다.
169
+
170
+ **사용 시기**: 파일을 다르게 그룹화하거나, 기준으로 필터링하거나, 데이터를 재구성해야 할 때.
171
+
172
+ **예시**:
173
+
174
+ ```python
175
+ def organize_files(self, files: List) -> List:
176
+ """최적화된 처리를 위해 크기별로 파일을 그룹화합니다."""
177
+ large_files = []
178
+ small_files = []
179
+
180
+ for file_group in files:
181
+ total_size = sum(f.stat().st_size for f in file_group.get('files', {}).values())
182
+ if total_size > 100 * 1024 * 1024: # 100MB
183
+ large_files.append(file_group)
184
+ else:
185
+ small_files.append(file_group)
186
+
187
+ # 큰 파일 먼저 처리
188
+ return large_files + small_files
189
+ ```
190
+
191
+ #### `before_process(organized_files: List) -> List`
192
+
193
+ **목적**: 주요 처리 전 설정 작업을 위한 전처리 후크.
194
+
195
+ **사용 시기**: 검증, 준비 또는 초기화 작업에 사용합니다.
196
+
197
+ **예시**:
198
+
199
+ ```python
200
+ def before_process(self, organized_files: List) -> List:
201
+ """처리를 위해 파일을 검증하고 준비합니다."""
202
+ self.run.log_message(f"{len(organized_files)}개 파일 그룹의 처리를 시작합니다.")
203
+
204
+ # 사용 가능한 디스크 공간 확인
205
+ if not self.check_disk_space(organized_files):
206
+ raise Exception("처리에 필요한 디스크 공간이 부족합니다.")
207
+
208
+ return organized_files
209
+ ```
210
+
211
+ #### `after_process(processed_files: List) -> List`
212
+
213
+ **목적**: 정리 및 최종화를 위한 후처리 후크.
214
+
215
+ **사용 시기**: 정리, 최종 변환 또는 리소스 할당 해제에 사용합니다.
216
+
217
+ **예시**:
218
+
219
+ ```python
220
+ def after_process(self, processed_files: List) -> List:
221
+ """임시 파일을 정리하고 요약을 생성합니다."""
222
+ # 임시 파일 제거
223
+ temp_dir = self.path / 'temp'
224
+ if temp_dir.exists():
225
+ shutil.rmtree(temp_dir)
226
+
227
+ # 처리 요약 생성
228
+ summary = {
229
+ 'total_processed': len(processed_files),
230
+ 'processing_time': time.time() - self.start_time
231
+ }
232
+
233
+ self.run.log_message(f"처리가 완료되었습니다: {summary}")
234
+ return processed_files
235
+ ```
236
+
237
+ #### `validate_files(files: List) -> List`
238
+
239
+ **목적**: 유형 검사를 넘어서는 사용자 정의 검증 로직.
240
+
241
+ **사용 시기**: 내장 파일 유형 검증 외에 추가적인 검증 규칙이 필요할 때.
242
+
243
+ **예시**:
244
+
245
+ ```python
246
+ def validate_files(self, files: List) -> List:
247
+ """크기 및 형식 검사를 포함한 사용자 정의 검증."""
248
+ # 먼저 내장 검증 적용
249
+ validated_files = super().validate_files(files)
250
+
251
+ # 그런 다음 사용자 정의 검증 적용
252
+ final_files = []
253
+ for file_group in validated_files:
254
+ if self.validate_file_group(file_group):
255
+ final_files.append(file_group)
256
+ else:
257
+ self.run.log_message(f"파일 그룹이 검증에 실패했습니다: {file_group}")
258
+
259
+ return final_files
260
+ ```
261
+
262
+ #### `filter_files(organized_file: Dict[str, Any]) -> bool`
263
+
264
+ **목적**: 사용자 정의 기준에 따라 개별 파일을 필터링합니다.
265
+
266
+ **사용 시기**: 처리에서 특정 파일을 제외해야 할 때.
267
+
268
+ **예시**:
269
+
270
+ ```python
271
+ def filter_files(self, organized_file: Dict[str, Any]) -> bool:
272
+ """작은 파일을 필터링합니다."""
273
+ files_dict = organized_file.get('files', {})
274
+ total_size = sum(f.stat().st_size for f in files_dict.values())
275
+
276
+ if total_size < 1024: # 1KB보다 작은 파일 건너뛰기
277
+ self.run.log_message(f"작은 파일 그룹 건너뛰기: {total_size} 바이트")
278
+ return False
279
+
280
+ return True
281
+ ```
282
+
283
+ ## 파일 유형 검증 시스템
284
+
285
+ BaseUploader에는 사용자 정의할 수 있는 내장 검증 시스템이 포함되어 있습니다:
286
+
287
+ ### 기본 파일 확장자
288
+
289
+ ```python
290
+ def get_file_extensions_config(self) -> Dict[str, List[str]]:
291
+ """허용되는 파일 확장자를 사용자 정의하려면 재정의하십시오."""
292
+ return {
293
+ 'pcd': ['.pcd'],
294
+ 'text': ['.txt', '.html'],
295
+ 'audio': ['.wav', '.mp3'],
296
+ 'data': ['.bin', '.json', '.fbx'],
297
+ 'image': ['.jpg', '.jpeg', '.png'],
298
+ 'video': ['.mp4'],
299
+ }
300
+ ```
301
+
302
+ ### 사용자 정의 확장자 구성
303
+
304
+ ```python
305
+ class CustomUploader(BaseUploader):
306
+ def get_file_extensions_config(self) -> Dict[str, List[str]]:
307
+ """추가 형식에 대한 지원을 추가합니다."""
308
+ config = super().get_file_extensions_config()
309
+ config.update({
310
+ 'cad': ['.dwg', '.dxf', '.step'],
311
+ 'archive': ['.zip', '.rar', '.7z'],
312
+ 'document': ['.pdf', '.docx', '.xlsx']
313
+ })
314
+ return config
315
+ ```
316
+
317
+ ### 변환 경고
318
+
319
+ ```python
320
+ def get_conversion_warnings_config(self) -> Dict[str, str]:
321
+ """변환 경고를 사용자 정의하려면 재정의하십시오."""
322
+ return {
323
+ '.tif': ' .jpg, .png',
324
+ '.tiff': ' .jpg, .png',
325
+ '.avi': ' .mp4',
326
+ '.mov': ' .mp4',
327
+ '.raw': ' .jpg, .png',
328
+ '.bmp': ' .jpg, .png',
329
+ }
330
+ ```
331
+
332
+ ## 실제 예제
333
+
334
+ ### 예제 1: 이미지 처리 플러그인
335
+
336
+ ```python
337
+ from pathlib import Path
338
+ from typing import List
339
+ from plugin import BaseUploader
340
+
341
+ class ImageProcessingUploader(BaseUploader):
342
+ """TIFF 이미지를 JPEG로 변환하고 썸네일을 생성합니다."""
343
+
344
+ def setup_directories(self):
345
+ """처리된 이미지를 위한 디렉토리를 생성합니다."""
346
+ (self.path / 'processed').mkdir(exist_ok=True)
347
+ (self.path / 'thumbnails').mkdir(exist_ok=True)
348
+
349
+ def process_files(self, organized_files: List) -> List:
350
+ """이미지를 변환하고 썸네일을 생성합니다."""
351
+ processed_files = []
352
+
353
+ for file_group in organized_files:
354
+ files_dict = file_group.get('files', {})
355
+ converted_files = {}
356
+
357
+ for spec_name, file_path in files_dict.items():
358
+ if file_path.suffix.lower() in ['.tif', '.tiff']:
359
+ # JPEG로 변환
360
+ jpeg_path = self.convert_to_jpeg(file_path)
361
+ converted_files[spec_name] = jpeg_path
362
+
363
+ # 썸네일 생성
364
+ thumbnail_path = self.generate_thumbnail(jpeg_path)
365
+ converted_files[f"{spec_name}_thumbnail"] = thumbnail_path
366
+
367
+ self.run.log_message(f"{file_path.name}을(를) 처리했습니다.")
368
+ else:
369
+ converted_files[spec_name] = file_path
370
+
371
+ file_group['files'] = converted_files
372
+ processed_files.append(file_group)
373
+
374
+ return processed_files
375
+
376
+ def convert_to_jpeg(self, tiff_path: Path) -> Path:
377
+ """PIL을 사용하여 TIFF를 JPEG로 변환합니다."""
378
+ from PIL import Image
379
+
380
+ output_path = self.path / 'processed' / f"{tiff_path.stem}.jpg"
381
+
382
+ with Image.open(tiff_path) as img:
383
+ if img.mode in ('RGBA', 'LA', 'P'):
384
+ img = img.convert('RGB')
385
+ img.save(output_path, 'JPEG', quality=95)
386
+
387
+ return output_path
388
+
389
+ def generate_thumbnail(self, image_path: Path) -> Path:
390
+ """썸네일을 생성합니다."""
391
+ from PIL import Image
392
+
393
+ thumbnail_path = self.path / 'thumbnails' / f"{image_path.stem}_thumb.jpg"
394
+
395
+ with Image.open(image_path) as img:
396
+ img.thumbnail((200, 200), Image.Resampling.LANCZOS)
397
+ img.save(thumbnail_path, 'JPEG', quality=85)
398
+
399
+ return thumbnail_path
400
+ ```
401
+
402
+ ### 예제 2: 데이터 검증 플러그인
403
+
404
+ ```python
405
+ class DataValidationUploader(BaseUploader):
406
+ """데이터 파일을 검증하고 품질 보고서를 생성합니다."""
407
+
408
+ def __init__(self, run, path, file_specification=None,
409
+ organized_files=None, extra_params=None):
410
+ super().__init__(run, path, file_specification, organized_files, extra_params)
411
+
412
+ # extra_params에서 초기화
413
+ self.validation_config = extra_params.get('validation_config', {})
414
+ self.strict_mode = extra_params.get('strict_validation', False)
415
+
416
+ def before_process(self, organized_files: List) -> List:
417
+ """검증 엔진을 초기화합니다."""
418
+ self.validation_results = []
419
+ self.run.log_message(f"{len(organized_files)}개 파일 그룹의 검증을 시작합니다.")
420
+ return organized_files
421
+
422
+ def process_files(self, organized_files: List) -> List:
423
+ """파일을 검증하고 품질 보고서를 생성합니다."""
424
+ processed_files = []
425
+
426
+ for file_group in organized_files:
427
+ validation_result = self.validate_file_group(file_group)
428
+
429
+ # 검증 메타데이터 추가
430
+ file_group['validation'] = validation_result
431
+ file_group['quality_score'] = validation_result['score']
432
+
433
+ # 검증 결과에 따라 파일 그룹 포함
434
+ if self.should_include_file_group(validation_result):
435
+ processed_files.append(file_group)
436
+ self.run.log_message(f"파일 그룹 통과: 점수 {validation_result['score']}")
437
+ else:
438
+ self.run.log_message(f"파일 그룹 실패: {validation_result['errors']}")
439
+
440
+ return processed_files
441
+
442
+ def validate_file_group(self, file_group: Dict) -> Dict:
443
+ """파일 그룹의 포괄적인 검증."""
444
+ files_dict = file_group.get('files', {})
445
+ errors = []
446
+ score = 100
447
+
448
+ for spec_name, file_path in files_dict.items():
449
+ # 파일 존재 여부
450
+ if not file_path.exists():
451
+ errors.append(f"파일을 찾을 수 없음: {file_path}")
452
+ score -= 50
453
+ continue
454
+
455
+ # 파일 크기 검증
456
+ file_size = file_path.stat().st_size
457
+ if file_size == 0:
458
+ errors.append(f"빈 파일: {file_path}")
459
+ score -= 40
460
+ elif file_size > 1024 * 1024 * 1024: # 1GB
461
+ score -= 10
462
+
463
+ return {
464
+ 'score': max(0, score),
465
+ 'errors': errors,
466
+ 'validated_at': datetime.now().isoformat()
467
+ }
468
+
469
+ def should_include_file_group(self, validation_result: Dict) -> bool:
470
+ """파일 그룹을 포함해야 하는지 결정합니다."""
471
+ if validation_result['errors'] and self.strict_mode:
472
+ return False
473
+
474
+ min_score = self.validation_config.get('min_score', 50)
475
+ return validation_result['score'] >= min_score
476
+ ```
477
+
478
+ ### 예제 3: 배치 처리 플러그인
479
+
480
+ ```python
481
+ class BatchProcessingUploader(BaseUploader):
482
+ """구성 가능한 배치로 파일을 처리합니다."""
483
+
484
+ def __init__(self, run, path, file_specification=None,
485
+ organized_files=None, extra_params=None):
486
+ super().__init__(run, path, file_specification, organized_files, extra_params)
487
+
488
+ self.batch_size = extra_params.get('batch_size', 10)
489
+ self.parallel_processing = extra_params.get('use_parallel', True)
490
+ self.max_workers = extra_params.get('max_workers', 4)
491
+
492
+ def organize_files(self, files: List) -> List:
493
+ """파일을 처리 배치로 구성합니다."""
494
+ batches = []
495
+ current_batch = []
496
+
497
+ for file_group in files:
498
+ current_batch.append(file_group)
499
+
500
+ if len(current_batch) >= self.batch_size:
501
+ batches.append({
502
+ 'batch_id': len(batches) + 1,
503
+ 'files': current_batch,
504
+ 'batch_size': len(current_batch)
505
+ })
506
+ current_batch = []
507
+
508
+ # 남은 파일 추가
509
+ if current_batch:
510
+ batches.append({
511
+ 'batch_id': len(batches) + 1,
512
+ 'files': current_batch,
513
+ 'batch_size': len(current_batch)
514
+ })
515
+
516
+ self.run.log_message(f"{len(batches)}개의 배치로 구성되었습니다.")
517
+ return batches
518
+
519
+ def process_files(self, organized_files: List) -> List:
520
+ """배치로 파일을 처리합니다."""
521
+ all_processed_files = []
522
+
523
+ if self.parallel_processing:
524
+ all_processed_files = self.process_batches_parallel(organized_files)
525
+ else:
526
+ all_processed_files = self.process_batches_sequential(organized_files)
527
+
528
+ return all_processed_files
529
+
530
+ def process_batches_sequential(self, batches: List) -> List:
531
+ """배치를 순차적으로 처리합니다."""
532
+ all_files = []
533
+
534
+ for i, batch in enumerate(batches, 1):
535
+ self.run.log_message(f"{i}/{len(batches)} 배치 처리 중")
536
+ processed_batch = self.process_single_batch(batch)
537
+ all_files.extend(processed_batch)
538
+
539
+ return all_files
540
+
541
+ def process_batches_parallel(self, batches: List) -> List:
542
+ """ThreadPoolExecutor를 사용하여 배치를 병렬로 처리합니다."""
543
+ from concurrent.futures import ThreadPoolExecutor, as_completed
544
+
545
+ all_files = []
546
+
547
+ with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
548
+ future_to_batch = {
549
+ executor.submit(self.process_single_batch, batch): batch
550
+ for batch in batches
551
+ }
552
+
553
+ for future in as_completed(future_to_batch):
554
+ batch = future_to_batch[future]
555
+ try:
556
+ processed_files = future.result()
557
+ all_files.extend(processed_files)
558
+ self.run.log_message(f"배치 {batch['batch_id']} 완료")
559
+ except Exception as e:
560
+ self.run.log_message(f"배치 {batch['batch_id']} 실패: {e}")
561
+
562
+ return all_files
563
+
564
+ def process_single_batch(self, batch: Dict) -> List:
565
+ """단일 파일 배치를 처리합니다."""
566
+ batch_files = batch['files']
567
+ processed_files = []
568
+
569
+ for file_group in batch_files:
570
+ # 배치 메타데이터 추가
571
+ file_group['batch_processed'] = True
572
+ file_group['batch_id'] = batch['batch_id']
573
+ processed_files.append(file_group)
574
+
575
+ return processed_files
576
+ ```
577
+
578
+ ## 모범 사례
579
+
580
+ ### 1. 코드 구성
581
+
582
+ - `process_files()`를 핵심 로직에 집중
583
+ - 설정, 정리 및 검증을 위해 후크 메서드 사용
584
+ - 헬퍼 메서드를 사용하여 관심사 분리
585
+
586
+ ### 2. 오류 처리
587
+
588
+ - 포괄적인 오류 처리 구현
589
+ - 컨텍스트 정보와 함께 오류 기록
590
+ - 가능하면 정상적으로 실패
591
+
592
+ ### 3. 성능
593
+
594
+ - 처리 로직 프로파일링
595
+ - 적절한 데이터 구조 사용
596
+ - 큰 파일에 대한 메모리 사용량 고려
597
+ - I/O 집약적인 작업에 대한 비동기 처리 구현
598
+
599
+ ### 4. 테스트
600
+
601
+ - 모든 메서드에 대한 단위 테스트 작성
602
+ - 실제 파일을 사용한 통합 테스트 포함
603
+ - 오류 조건 및 엣지 케이스 테스트
604
+
605
+ ### 5. 로깅
606
+
607
+ - 중요한 작업 및 마일스톤 기록
608
+ - 타이밍 정보 포함
609
+ - 분석을 위한 구조화된 로깅 사용
610
+
611
+ ### 6. 구성
612
+
613
+ - 플러그인 구성에 `extra_params` 사용
614
+ - 합리적인 기본값 제공
615
+ - 구성 매개변수 검증
616
+
617
+ ## 업로드 액션과의 통합
618
+
619
+ BaseUploader 플러그인은 업로드 액션 워크플로우와 통합됩니다:
620
+
621
+ 1. **파일 검색**: 업로드 액션이 파일을 검색하고 구성합니다.
622
+ 2. **플러그인 호출**: 구성된 파일과 함께 `handle_upload_files()`가 호출됩니다.
623
+ 3. **워크플로우 실행**: BaseUploader가 6단계 워크플로우를 실행합니다.
624
+ 4. **결과 반환**: 처리된 파일이 업로드 액션으로 반환됩니다.
625
+ 5. **업로드 및 데이터 단위 생성**: 업로드 액션이 업로드를 완료합니다.
626
+
627
+ ### 데이터 흐름
628
+
629
+ ```
630
+ 업로드 액션 (OrganizeFilesStep)
631
+ ↓ organized_files
632
+ BaseUploader.handle_upload_files()
633
+ ↓ setup_directories()
634
+ ↓ organize_files()
635
+ ↓ before_process()
636
+ ↓ process_files() ← 사용자 정의 로직
637
+ ↓ after_process()
638
+ ↓ validate_files()
639
+ ↓ processed_files
640
+ 업로드 액션 (UploadFilesStep, GenerateDataUnitsStep)
641
+ ```
642
+
643
+ ## 구성
644
+
645
+ ### 플러그인 구성 (config.yaml)
646
+
647
+ ```yaml
648
+ code: "my-upload-plugin"
649
+ name: "내 업로드 플러그인"
650
+ version: "1.0.0"
651
+ category: "upload"
652
+
653
+ package_manager: "pip"
654
+
655
+ actions:
656
+ upload:
657
+ entrypoint: "plugin.upload.Uploader"
658
+ method: "job"
659
+ ```
660
+
661
+ ### 의존성 (requirements.txt)
662
+
663
+ ```txt
664
+ synapse-sdk>=1.0.0
665
+ pillow>=10.0.0 # 이미지 처리용
666
+ pandas>=2.0.0 # 데이터 처리용
667
+ ```
668
+
669
+ ## 플러그인 테스트
670
+
671
+ ### 단위 테스트
672
+
673
+ ```python
674
+ import pytest
675
+ from unittest.mock import Mock
676
+ from pathlib import Path
677
+ from plugin.upload import Uploader
678
+
679
+ class TestUploader:
680
+
681
+ def setup_method(self):
682
+ self.mock_run = Mock()
683
+ self.test_path = Path('/tmp/test')
684
+ self.file_spec = [{'name': 'image_1', 'file_type': 'image'}]
685
+
686
+ def test_process_files(self):
687
+ """파일 처리 테스트."""
688
+ uploader = Uploader(
689
+ run=self.mock_run,
690
+ path=self.test_path,
691
+ file_specification=self.file_spec,
692
+ organized_files=[{'files': {}}]
693
+ )
694
+
695
+ result = uploader.process_files([{'files': {}}])
696
+ assert isinstance(result, list)
697
+ ```
698
+
699
+ ### 통합 테스트
700
+
701
+ ```bash
702
+ # 샘플 데이터로 테스트
703
+ synapse plugin run upload '{
704
+ "name": "테스트 업로드",
705
+ "use_single_path": true,
706
+ "path": "/test/data",
707
+ "storage": 1,
708
+ "data_collection": 5
709
+ }' --plugin my-upload-plugin --debug
710
+ ```
711
+
712
+ ## 참조
713
+
714
+ - [업로드 플러그인 개요](./upload-plugin-overview.md) - 사용자 가이드 및 구성 참조
715
+ - [업로드 액션 개발](./upload-plugin-action.md) - 액션 아키텍처 및 내부에 대한 SDK 개발자 가이드