synapse-sdk 2025.9.5__py3-none-any.whl → 2025.10.6__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 (78) hide show
  1. synapse_sdk/clients/base.py +129 -9
  2. synapse_sdk/devtools/docs/docs/api/clients/base.md +230 -8
  3. synapse_sdk/devtools/docs/docs/api/plugins/models.md +58 -3
  4. synapse_sdk/devtools/docs/docs/plugins/categories/neural-net-plugins/train-action-overview.md +663 -0
  5. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/pre-annotation-plugin-overview.md +198 -0
  6. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-action-development.md +1645 -0
  7. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-overview.md +717 -0
  8. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-template-development.md +1380 -0
  9. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-action.md +934 -0
  10. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-overview.md +585 -0
  11. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-template.md +715 -0
  12. synapse_sdk/devtools/docs/docs/plugins/export-plugins.md +39 -0
  13. synapse_sdk/devtools/docs/docs/plugins/plugins.md +12 -5
  14. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/base.md +230 -8
  15. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/plugins/models.md +114 -0
  16. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/neural-net-plugins/train-action-overview.md +621 -0
  17. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/pre-annotation-plugin-overview.md +198 -0
  18. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-action-development.md +1645 -0
  19. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-overview.md +717 -0
  20. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-template-development.md +1380 -0
  21. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-action.md +934 -0
  22. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-overview.md +585 -0
  23. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-template.md +715 -0
  24. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/export-plugins.md +39 -0
  25. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current.json +16 -4
  26. synapse_sdk/devtools/docs/sidebars.ts +45 -1
  27. synapse_sdk/plugins/README.md +487 -80
  28. synapse_sdk/plugins/categories/base.py +1 -0
  29. synapse_sdk/plugins/categories/export/actions/export/action.py +8 -3
  30. synapse_sdk/plugins/categories/export/actions/export/utils.py +108 -8
  31. synapse_sdk/plugins/categories/export/templates/config.yaml +18 -0
  32. synapse_sdk/plugins/categories/export/templates/plugin/export.py +97 -0
  33. synapse_sdk/plugins/categories/neural_net/actions/train.py +592 -22
  34. synapse_sdk/plugins/categories/neural_net/actions/tune.py +150 -3
  35. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
  36. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
  37. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/action.py +10 -0
  38. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
  39. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +145 -0
  40. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
  41. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
  42. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
  43. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +97 -0
  44. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +250 -0
  45. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
  46. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
  47. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +284 -0
  48. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
  49. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
  50. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +87 -0
  51. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +127 -0
  52. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
  53. synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +2 -1
  54. synapse_sdk/plugins/categories/upload/actions/upload/action.py +8 -1
  55. synapse_sdk/plugins/categories/upload/actions/upload/context.py +0 -1
  56. synapse_sdk/plugins/categories/upload/actions/upload/models.py +134 -94
  57. synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +2 -2
  58. synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +6 -2
  59. synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +24 -9
  60. synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +130 -18
  61. synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +147 -37
  62. synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +10 -5
  63. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +31 -6
  64. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +65 -37
  65. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +17 -2
  66. synapse_sdk/plugins/categories/upload/templates/README.md +394 -0
  67. synapse_sdk/plugins/models.py +62 -0
  68. synapse_sdk/utils/file/download.py +261 -0
  69. {synapse_sdk-2025.9.5.dist-info → synapse_sdk-2025.10.6.dist-info}/METADATA +15 -2
  70. {synapse_sdk-2025.9.5.dist-info → synapse_sdk-2025.10.6.dist-info}/RECORD +74 -43
  71. synapse_sdk/devtools/docs/docs/plugins/developing-upload-template.md +0 -1463
  72. synapse_sdk/devtools/docs/docs/plugins/upload-plugins.md +0 -1964
  73. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/developing-upload-template.md +0 -1463
  74. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/upload-plugins.md +0 -2077
  75. {synapse_sdk-2025.9.5.dist-info → synapse_sdk-2025.10.6.dist-info}/WHEEL +0 -0
  76. {synapse_sdk-2025.9.5.dist-info → synapse_sdk-2025.10.6.dist-info}/entry_points.txt +0 -0
  77. {synapse_sdk-2025.9.5.dist-info → synapse_sdk-2025.10.6.dist-info}/licenses/LICENSE +0 -0
  78. {synapse_sdk-2025.9.5.dist-info → synapse_sdk-2025.10.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,715 @@
1
+ ---
2
+ id: upload-plugin-template
3
+ title: Upload Plugin Template Development
4
+ sidebar_position: 3
5
+ ---
6
+
7
+ # Upload Plugin Template Development with BaseUploader
8
+
9
+ This guide is for plugin developers who want to create custom upload plugins using the BaseUploader template. The BaseUploader provides a workflow-based foundation for file processing and organization within upload plugins.
10
+
11
+ ## Overview
12
+
13
+ The BaseUploader template (`synapse_sdk.plugins.categories.upload.templates.plugin`) provides a structured approach to building upload plugins. It handles the common upload workflow while allowing customization through method overrides.
14
+
15
+ ### BaseUploader Workflow
16
+
17
+ The BaseUploader implements a 6-step workflow pipeline:
18
+
19
+ ```
20
+ 1. setup_directories() # Create custom directory structures
21
+ 2. organize_files() # Organize and structure files
22
+ 3. before_process() # Pre-processing hooks
23
+ 4. process_files() # Main processing logic (REQUIRED)
24
+ 5. after_process() # Post-processing hooks
25
+ 6. validate_files() # Final validation
26
+ ```
27
+
28
+ ## Getting Started
29
+
30
+ ### Template Structure
31
+
32
+ When you create an upload plugin, you get this structure:
33
+
34
+ ```
35
+ synapse-{plugin-code}-plugin/
36
+ ├── config.yaml # Plugin metadata and configuration
37
+ ├── plugin/ # Source code directory
38
+ │ ├── __init__.py
39
+ │ └── upload.py # Main upload implementation with BaseUploader
40
+ ├── requirements.txt # Python dependencies
41
+ ├── pyproject.toml # Package configuration
42
+ └── README.md # Plugin documentation
43
+ ```
44
+
45
+ ### Basic Plugin Implementation
46
+
47
+ ```python
48
+ # plugin/__init__.py
49
+ from pathlib import Path
50
+ from typing import Any, Dict, List
51
+
52
+ class BaseUploader:
53
+ """Base class with common upload functionality."""
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
+ # Core workflow methods available for override
64
+ def setup_directories(self) -> None:
65
+ """Setup custom directories - override as needed."""
66
+ pass
67
+
68
+ def organize_files(self, files: List) -> List:
69
+ """Organize files - override for custom logic."""
70
+ return files
71
+
72
+ def before_process(self, organized_files: List) -> List:
73
+ """Pre-process hook - override as needed."""
74
+ return organized_files
75
+
76
+ def process_files(self, organized_files: List) -> List:
77
+ """Main processing - MUST be overridden."""
78
+ return organized_files
79
+
80
+ def after_process(self, processed_files: List) -> List:
81
+ """Post-process hook - override as needed."""
82
+ return processed_files
83
+
84
+ def validate_files(self, files: List) -> List:
85
+ """Validation - override for custom validation."""
86
+ return self._filter_valid_files(files)
87
+
88
+ def handle_upload_files(self) -> List:
89
+ """Main entry point - executes the workflow."""
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
+ """Custom upload plugin implementation."""
104
+
105
+ def process_files(self, organized_files: List) -> List:
106
+ """Required: Implement your file processing logic."""
107
+ # Your custom processing logic here
108
+ return organized_files
109
+ ```
110
+
111
+ ## Core Methods Reference
112
+
113
+ ### Required Method
114
+
115
+ #### `process_files(organized_files: List) -> List`
116
+
117
+ **Purpose**: Main processing method that must be implemented by all plugins.
118
+
119
+ **When to use**: Always - this is where your plugin's core logic goes.
120
+
121
+ **Example**:
122
+
123
+ ```python
124
+ def process_files(self, organized_files: List) -> List:
125
+ """Convert TIFF images to JPEG format."""
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
+ # Convert TIFF to JPEG
135
+ jpeg_path = self.convert_tiff_to_jpeg(file_path)
136
+ converted_files[spec_name] = jpeg_path
137
+ self.run.log_message(f"Converted {file_path} to {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
+ ### Optional Hook Methods
148
+
149
+ #### `setup_directories() -> None`
150
+
151
+ **Purpose**: Create custom directory structures before processing begins.
152
+
153
+ **When to use**: When your plugin needs specific directories for processing, temporary files, or output.
154
+
155
+ **Example**:
156
+
157
+ ```python
158
+ def setup_directories(self):
159
+ """Create processing directories."""
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("Created processing directories")
164
+ ```
165
+
166
+ #### `organize_files(files: List) -> List`
167
+
168
+ **Purpose**: Reorganize and structure files before main processing.
169
+
170
+ **When to use**: When you need to group files differently, filter by criteria, or restructure the data.
171
+
172
+ **Example**:
173
+
174
+ ```python
175
+ def organize_files(self, files: List) -> List:
176
+ """Group files by size for optimized processing."""
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
+ # Process large files first
188
+ return large_files + small_files
189
+ ```
190
+
191
+ #### `before_process(organized_files: List) -> List`
192
+
193
+ **Purpose**: Pre-processing hook for setup tasks before main processing.
194
+
195
+ **When to use**: For validation, preparation, or initialization tasks.
196
+
197
+ **Example**:
198
+
199
+ ```python
200
+ def before_process(self, organized_files: List) -> List:
201
+ """Validate and prepare files for processing."""
202
+ self.run.log_message(f"Starting processing of {len(organized_files)} file groups")
203
+
204
+ # Check available disk space
205
+ if not self.check_disk_space(organized_files):
206
+ raise Exception("Insufficient disk space for processing")
207
+
208
+ return organized_files
209
+ ```
210
+
211
+ #### `after_process(processed_files: List) -> List`
212
+
213
+ **Purpose**: Post-processing hook for cleanup and finalization.
214
+
215
+ **When to use**: For cleanup, final transformations, or resource deallocation.
216
+
217
+ **Example**:
218
+
219
+ ```python
220
+ def after_process(self, processed_files: List) -> List:
221
+ """Clean up temporary files and generate summary."""
222
+ # Remove temporary files
223
+ temp_dir = self.path / 'temp'
224
+ if temp_dir.exists():
225
+ shutil.rmtree(temp_dir)
226
+
227
+ # Generate processing summary
228
+ summary = {
229
+ 'total_processed': len(processed_files),
230
+ 'processing_time': time.time() - self.start_time
231
+ }
232
+
233
+ self.run.log_message(f"Processing complete: {summary}")
234
+ return processed_files
235
+ ```
236
+
237
+ #### `validate_files(files: List) -> List`
238
+
239
+ **Purpose**: Custom validation logic beyond type checking.
240
+
241
+ **When to use**: When you need additional validation rules beyond built-in file type validation.
242
+
243
+ **Example**:
244
+
245
+ ```python
246
+ def validate_files(self, files: List) -> List:
247
+ """Custom validation with size and format checks."""
248
+ # First apply built-in validation
249
+ validated_files = super().validate_files(files)
250
+
251
+ # Then apply custom validation
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 failed validation: {file_group}")
258
+
259
+ return final_files
260
+ ```
261
+
262
+ #### `filter_files(organized_file: Dict[str, Any]) -> bool`
263
+
264
+ **Purpose**: Filter individual files based on custom criteria.
265
+
266
+ **When to use**: When you need to exclude specific files from processing.
267
+
268
+ **Example**:
269
+
270
+ ```python
271
+ def filter_files(self, organized_file: Dict[str, Any]) -> bool:
272
+ """Filter out small files."""
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: # Skip files smaller than 1KB
277
+ self.run.log_message(f"Skipping small file group: {total_size} bytes")
278
+ return False
279
+
280
+ return True
281
+ ```
282
+
283
+ ## File Type Validation System
284
+
285
+ The BaseUploader includes a built-in validation system that you can customize:
286
+
287
+ ### Default File Extensions
288
+
289
+ ```python
290
+ def get_file_extensions_config(self) -> Dict[str, List[str]]:
291
+ """Override to customize allowed file extensions."""
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
+ ### Custom Extension Configuration
303
+
304
+ ```python
305
+ class CustomUploader(BaseUploader):
306
+ def get_file_extensions_config(self) -> Dict[str, List[str]]:
307
+ """Add support for additional formats."""
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
+ ### Conversion Warnings
318
+
319
+ ```python
320
+ def get_conversion_warnings_config(self) -> Dict[str, str]:
321
+ """Override to customize conversion warnings."""
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
+ ## Real-World Examples
333
+
334
+ ### Example 1: Image Processing Plugin
335
+
336
+ ```python
337
+ from pathlib import Path
338
+ from typing import List
339
+ from plugin import BaseUploader
340
+
341
+ class ImageProcessingUploader(BaseUploader):
342
+ """Converts TIFF images to JPEG and generates thumbnails."""
343
+
344
+ def setup_directories(self):
345
+ """Create directories for processed images."""
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
+ """Convert images and generate thumbnails."""
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
+ # Convert to JPEG
360
+ jpeg_path = self.convert_to_jpeg(file_path)
361
+ converted_files[spec_name] = jpeg_path
362
+
363
+ # Generate thumbnail
364
+ thumbnail_path = self.generate_thumbnail(jpeg_path)
365
+ converted_files[f"{spec_name}_thumbnail"] = thumbnail_path
366
+
367
+ self.run.log_message(f"Processed {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
+ """Convert TIFF to JPEG using PIL."""
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
+ """Generate thumbnail."""
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
+ ### Example 2: Data Validation Plugin
403
+
404
+ ```python
405
+ class DataValidationUploader(BaseUploader):
406
+ """Validates data files and generates quality reports."""
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
+ # Initialize from 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
+ """Initialize validation engine."""
418
+ self.validation_results = []
419
+ self.run.log_message(f"Starting validation of {len(organized_files)} file groups")
420
+ return organized_files
421
+
422
+ def process_files(self, organized_files: List) -> List:
423
+ """Validate files and generate quality reports."""
424
+ processed_files = []
425
+
426
+ for file_group in organized_files:
427
+ validation_result = self.validate_file_group(file_group)
428
+
429
+ # Add validation metadata
430
+ file_group['validation'] = validation_result
431
+ file_group['quality_score'] = validation_result['score']
432
+
433
+ # Include file group based on validation results
434
+ if self.should_include_file_group(validation_result):
435
+ processed_files.append(file_group)
436
+ self.run.log_message(f"File group passed: score {validation_result['score']}")
437
+ else:
438
+ self.run.log_message(f"File group failed: {validation_result['errors']}")
439
+
440
+ return processed_files
441
+
442
+ def validate_file_group(self, file_group: Dict) -> Dict:
443
+ """Comprehensive validation of file group."""
444
+ files_dict = file_group.get('files', {})
445
+ errors = []
446
+ score = 100
447
+
448
+ for spec_name, file_path in files_dict.items():
449
+ # File existence
450
+ if not file_path.exists():
451
+ errors.append(f"File not found: {file_path}")
452
+ score -= 50
453
+ continue
454
+
455
+ # File size validation
456
+ file_size = file_path.stat().st_size
457
+ if file_size == 0:
458
+ errors.append(f"Empty file: {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
+ """Determine if file group should be included."""
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
+ ### Example 3: Batch Processing Plugin
479
+
480
+ ```python
481
+ class BatchProcessingUploader(BaseUploader):
482
+ """Processes files in configurable batches."""
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
+ """Organize files into processing batches."""
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
+ # Add remaining files
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"Organized into {len(batches)} batches")
517
+ return batches
518
+
519
+ def process_files(self, organized_files: List) -> List:
520
+ """Process files in batches."""
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
+ """Process batches sequentially."""
532
+ all_files = []
533
+
534
+ for i, batch in enumerate(batches, 1):
535
+ self.run.log_message(f"Processing batch {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
+ """Process batches in parallel using 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['batch_id']} complete")
559
+ except Exception as e:
560
+ self.run.log_message(f"Batch {batch['batch_id']} failed: {e}")
561
+
562
+ return all_files
563
+
564
+ def process_single_batch(self, batch: Dict) -> List:
565
+ """Process a single batch of files."""
566
+ batch_files = batch['files']
567
+ processed_files = []
568
+
569
+ for file_group in batch_files:
570
+ # Add batch metadata
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
+ ## Best Practices
579
+
580
+ ### 1. Code Organization
581
+
582
+ - Keep `process_files()` focused on core logic
583
+ - Use hook methods for setup, cleanup, and validation
584
+ - Separate concerns using helper methods
585
+
586
+ ### 2. Error Handling
587
+
588
+ - Implement comprehensive error handling
589
+ - Log errors with context information
590
+ - Fail gracefully when possible
591
+
592
+ ### 3. Performance
593
+
594
+ - Profile your processing logic
595
+ - Use appropriate data structures
596
+ - Consider memory usage for large files
597
+ - Implement async processing for I/O-heavy operations
598
+
599
+ ### 4. Testing
600
+
601
+ - Write unit tests for all methods
602
+ - Include integration tests with real files
603
+ - Test error conditions and edge cases
604
+
605
+ ### 5. Logging
606
+
607
+ - Log important operations and milestones
608
+ - Include timing information
609
+ - Use structured logging for analysis
610
+
611
+ ### 6. Configuration
612
+
613
+ - Use `extra_params` for plugin configuration
614
+ - Provide sensible defaults
615
+ - Validate configuration parameters
616
+
617
+ ## Integration with Upload Action
618
+
619
+ Your BaseUploader plugin integrates with the upload action workflow:
620
+
621
+ 1. **File Discovery**: Upload action discovers and organizes files
622
+ 2. **Plugin Invocation**: Your `handle_upload_files()` is called with organized files
623
+ 3. **Workflow Execution**: BaseUploader runs its 6-step workflow
624
+ 4. **Return Results**: Processed files are returned to upload action
625
+ 5. **Upload & Data Unit Creation**: Upload action completes the upload
626
+
627
+ ### Data Flow
628
+
629
+ ```
630
+ Upload Action (OrganizeFilesStep)
631
+ ↓ organized_files
632
+ BaseUploader.handle_upload_files()
633
+ ↓ setup_directories()
634
+ ↓ organize_files()
635
+ ↓ before_process()
636
+ ↓ process_files() ← Your custom logic
637
+ ↓ after_process()
638
+ ↓ validate_files()
639
+ ↓ processed_files
640
+ Upload Action (UploadFilesStep, GenerateDataUnitsStep)
641
+ ```
642
+
643
+ ## Configuration
644
+
645
+ ### Plugin Configuration (config.yaml)
646
+
647
+ ```yaml
648
+ code: "my-upload-plugin"
649
+ name: "My Upload Plugin"
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
+ ### Dependencies (requirements.txt)
662
+
663
+ ```txt
664
+ synapse-sdk>=1.0.0
665
+ pillow>=10.0.0 # For image processing
666
+ pandas>=2.0.0 # For data processing
667
+ ```
668
+
669
+ ## Testing Your Plugin
670
+
671
+ ### Unit Testing
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
+ """Test file processing."""
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
+ ### Integration Testing
700
+
701
+ ```bash
702
+ # Test with sample data
703
+ synapse plugin run upload '{
704
+ "name": "Test Upload",
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
+ ## See Also
713
+
714
+ - [Upload Plugin Overview](./upload-plugin-overview.md) - User guide and configuration reference
715
+ - [Upload Action Development](./upload-plugin-action.md) - SDK developer guide for action architecture and internals