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,934 @@
1
+ ---
2
+ id: upload-plugin-action
3
+ title: Upload Action Development
4
+ sidebar_position: 2
5
+ ---
6
+
7
+ # Upload Action Development
8
+
9
+ This guide is for SDK developers and contributors who want to understand, extend, or customize the upload action architecture.
10
+
11
+ ## Architecture Overview
12
+
13
+ The upload system uses a modern, extensible architecture built on proven design patterns. The refactored implementation transforms the previous monolithic approach into a modular, strategy-based system with clear separation of concerns.
14
+
15
+ ### Design Patterns
16
+
17
+ The architecture leverages several key design patterns:
18
+
19
+ - **Strategy Pattern**: Pluggable behaviors for validation, file discovery, metadata processing, upload operations, and data unit creation
20
+ - **Facade Pattern**: UploadOrchestrator provides a simplified interface to coordinate complex workflows
21
+ - **Factory Pattern**: StrategyFactory creates appropriate strategy implementations based on runtime parameters
22
+ - **Context Pattern**: UploadContext maintains shared state and communication between workflow components
23
+
24
+ ### Component Architecture
25
+
26
+ ```mermaid
27
+ classDiagram
28
+ %% Light/Dark mode compatible colors
29
+ classDef coreClass fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000000
30
+ classDef strategyClass fill:#e8f5e8,stroke:#388e3c,stroke-width:2px,color:#000000
31
+ classDef stepClass fill:#fff9c4,stroke:#f57c00,stroke-width:2px,color:#000000
32
+ classDef contextClass fill:#ffebee,stroke:#d32f2f,stroke-width:2px,color:#000000
33
+
34
+ class UploadAction {
35
+ +name: str = "upload"
36
+ +category: PluginCategory.UPLOAD
37
+ +method: RunMethod.JOB
38
+ +run_class: UploadRun
39
+ +params_model: UploadParams
40
+ +progress_categories: dict
41
+ +metrics_categories: dict
42
+ +strategy_factory: StrategyFactory
43
+ +step_registry: StepRegistry
44
+
45
+ +start() dict
46
+ +get_workflow_summary() dict
47
+ +_configure_workflow() None
48
+ +_configure_strategies() dict
49
+ }
50
+
51
+ class UploadOrchestrator {
52
+ +context: UploadContext
53
+ +step_registry: StepRegistry
54
+ +strategies: dict
55
+ +executed_steps: list
56
+ +current_step_index: int
57
+ +rollback_executed: bool
58
+
59
+ +execute() dict
60
+ +get_workflow_summary() dict
61
+ +get_executed_steps() list
62
+ +is_rollback_executed() bool
63
+ +_execute_step(step) StepResult
64
+ +_handle_step_failure(step, error) None
65
+ +_rollback_executed_steps() None
66
+ }
67
+
68
+ class UploadContext {
69
+ +params: dict
70
+ +run: UploadRun
71
+ +client: Any
72
+ +storage: Any
73
+ +pathlib_cwd: Path
74
+ +metadata: dict
75
+ +file_specifications: dict
76
+ +organized_files: list
77
+ +uploaded_files: list
78
+ +data_units: list
79
+ +metrics: dict
80
+ +errors: list
81
+ +strategies: dict
82
+ +rollback_data: dict
83
+
84
+ +update(result: StepResult) None
85
+ +get_result() dict
86
+ +has_errors() bool
87
+ +update_metrics(category, metrics) None
88
+ }
89
+
90
+ class StepRegistry {
91
+ +_steps: list
92
+ +register(step: BaseStep) None
93
+ +get_steps() list
94
+ +get_total_progress_weight() float
95
+ +clear() None
96
+ }
97
+
98
+ class StrategyFactory {
99
+ +create_validation_strategy(params, context) BaseValidationStrategy
100
+ +create_file_discovery_strategy(params, context) BaseFileDiscoveryStrategy
101
+ +create_metadata_strategy(params, context) BaseMetadataStrategy
102
+ +create_upload_strategy(params, context) BaseUploadStrategy
103
+ +create_data_unit_strategy(params, context) BaseDataUnitStrategy
104
+ +get_available_strategies() dict
105
+ }
106
+
107
+ class BaseStep {
108
+ <<abstract>>
109
+ +name: str
110
+ +progress_weight: float
111
+ +execute(context: UploadContext) StepResult
112
+ +can_skip(context: UploadContext) bool
113
+ +rollback(context: UploadContext) None
114
+ +create_success_result(data) StepResult
115
+ +create_error_result(error) StepResult
116
+ +create_skip_result() StepResult
117
+ }
118
+
119
+ class ExcelSecurityConfig {
120
+ +max_file_size_mb: int = 10
121
+ +max_rows: int = 100000
122
+ +max_columns: int = 50
123
+ +max_file_size_bytes: int
124
+ +from_action_config(action_config) ExcelSecurityConfig
125
+ }
126
+
127
+ class StepResult {
128
+ +success: bool
129
+ +data: dict
130
+ +error: str
131
+ +rollback_data: dict
132
+ +skipped: bool
133
+ +original_exception: Exception
134
+ +timestamp: datetime
135
+ }
136
+
137
+ %% Strategy Base Classes
138
+ class BaseValidationStrategy {
139
+ <<abstract>>
140
+ +validate_files(files, context) bool
141
+ +validate_security(file_path) bool
142
+ }
143
+
144
+ class BaseFileDiscoveryStrategy {
145
+ <<abstract>>
146
+ +discover_files(path, context) list
147
+ +organize_files(files, specs, context) list
148
+ }
149
+
150
+ class BaseMetadataStrategy {
151
+ <<abstract>>
152
+ +process_metadata(context) dict
153
+ +extract_metadata(file_path) dict
154
+ }
155
+
156
+ class BaseUploadStrategy {
157
+ <<abstract>>
158
+ +upload_files(files, context) list
159
+ +upload_batch(batch, context) list
160
+ }
161
+
162
+ class BaseDataUnitStrategy {
163
+ <<abstract>>
164
+ +generate_data_units(files, context) list
165
+ +create_data_unit_batch(batch, context) list
166
+ }
167
+
168
+ %% Workflow Steps
169
+ class InitializeStep {
170
+ +name = "initialize"
171
+ +progress_weight = 0.05
172
+ }
173
+
174
+ class ProcessMetadataStep {
175
+ +name = "process_metadata"
176
+ +progress_weight = 0.05
177
+ }
178
+
179
+ class AnalyzeCollectionStep {
180
+ +name = "analyze_collection"
181
+ +progress_weight = 0.05
182
+ }
183
+
184
+ class OrganizeFilesStep {
185
+ +name = "organize_files"
186
+ +progress_weight = 0.10
187
+ }
188
+
189
+ class ValidateFilesStep {
190
+ +name = "validate_files"
191
+ +progress_weight = 0.05
192
+ }
193
+
194
+ class UploadFilesStep {
195
+ +name = "upload_files"
196
+ +progress_weight = 0.30
197
+ }
198
+
199
+ class GenerateDataUnitsStep {
200
+ +name = "generate_data_units"
201
+ +progress_weight = 0.35
202
+ }
203
+
204
+ class CleanupStep {
205
+ +name = "cleanup"
206
+ +progress_weight = 0.05
207
+ }
208
+
209
+ %% Relationships
210
+ UploadAction --> UploadRun : uses
211
+ UploadAction --> UploadParams : validates with
212
+ UploadAction --> ExcelSecurityConfig : configures
213
+ UploadAction --> UploadOrchestrator : creates and executes
214
+ UploadAction --> StrategyFactory : configures strategies
215
+ UploadAction --> StepRegistry : manages workflow steps
216
+ UploadOrchestrator --> UploadContext : coordinates state
217
+ UploadOrchestrator --> StepRegistry : executes steps from
218
+ UploadOrchestrator --> BaseStep : executes
219
+ BaseStep --> StepResult : returns
220
+ UploadContext --> StepResult : updates with
221
+ StrategyFactory --> BaseValidationStrategy : creates
222
+ StrategyFactory --> BaseFileDiscoveryStrategy : creates
223
+ StrategyFactory --> BaseMetadataStrategy : creates
224
+ StrategyFactory --> BaseUploadStrategy : creates
225
+ StrategyFactory --> BaseDataUnitStrategy : creates
226
+ StepRegistry --> BaseStep : contains
227
+
228
+ %% Step inheritance
229
+ InitializeStep --|> BaseStep : extends
230
+ ProcessMetadataStep --|> BaseStep : extends
231
+ AnalyzeCollectionStep --|> BaseStep : extends
232
+ OrganizeFilesStep --|> BaseStep : extends
233
+ ValidateFilesStep --|> BaseStep : extends
234
+ UploadFilesStep --|> BaseStep : extends
235
+ GenerateDataUnitsStep --|> BaseStep : extends
236
+ CleanupStep --|> BaseStep : extends
237
+ ```
238
+
239
+ ### Step-Based Workflow Execution
240
+
241
+ The refactored architecture uses a step-based workflow coordinated by the UploadOrchestrator. Each step has a defined responsibility and progress weight.
242
+
243
+ #### Workflow Steps Overview
244
+
245
+ | Step | Name | Weight | Responsibility |
246
+ | ---- | ------------------- | ------ | -------------------------------------------- |
247
+ | 1 | Initialize | 5% | Setup storage, pathlib, and basic validation |
248
+ | 2 | Process Metadata | 5% | Handle Excel metadata if provided |
249
+ | 3 | Analyze Collection | 5% | Retrieve and validate data collection specs |
250
+ | 4 | Organize Files | 10% | Discover and organize files by type |
251
+ | 5 | Validate Files | 5% | Security and content validation |
252
+ | 6 | Upload Files | 30% | Upload files to storage |
253
+ | 7 | Generate Data Units | 35% | Create data units from uploaded files |
254
+ | 8 | Cleanup | 5% | Clean temporary resources |
255
+
256
+ #### Execution Flow
257
+
258
+ ```mermaid
259
+ flowchart TD
260
+ %% Start
261
+ A["🚀 Upload Action Started"] --> B["📋 Create UploadContext"]
262
+ B --> C["⚙️ Configure Strategies"]
263
+ C --> D["📝 Register Workflow Steps"]
264
+ D --> E["🎯 Create UploadOrchestrator"]
265
+
266
+ %% Strategy Injection
267
+ E --> F["💉 Inject Strategies into Context"]
268
+ F --> G["📊 Initialize Progress Tracking"]
269
+
270
+ %% Step Execution Loop
271
+ G --> H["🔄 Start Step Execution Loop"]
272
+ H --> I["📍 Get Next Step"]
273
+ I --> J{"🤔 Can Step be Skipped?"}
274
+ J -->|Yes| K["⏭️ Skip Step"]
275
+ J -->|No| L["▶️ Execute Step"]
276
+
277
+ %% Step Execution
278
+ L --> M{"✅ Step Successful?"}
279
+ M -->|Yes| N["📈 Update Progress"]
280
+ M -->|No| O["❌ Handle Step Failure"]
281
+
282
+ %% Success Path
283
+ N --> P["💾 Store Step Result"]
284
+ P --> Q["📝 Add to Executed Steps"]
285
+ Q --> R{"🏁 More Steps?"}
286
+ R -->|Yes| I
287
+ R -->|No| S["🎉 Workflow Complete"]
288
+
289
+ %% Skip Path
290
+ K --> T["📊 Update Progress (Skip)"]
291
+ T --> R
292
+
293
+ %% Error Handling
294
+ O --> U["🔙 Start Rollback Process"]
295
+ U --> V["⏪ Rollback Executed Steps"]
296
+ V --> W["📝 Log Rollback Results"]
297
+ W --> X["💥 Propagate Exception"]
298
+
299
+ %% Final Results
300
+ S --> Y["📊 Collect Final Metrics"]
301
+ Y --> Z["📋 Generate Result Summary"]
302
+ Z --> AA["🔄 Return to UploadAction"]
303
+
304
+ %% Apply styles - Light/Dark mode compatible
305
+ classDef startNode fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000000
306
+ classDef processNode fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000000
307
+ classDef decisionNode fill:#fff3e0,stroke:#f57c00,stroke-width:2px,color:#000000
308
+ classDef successNode fill:#e8f5e8,stroke:#388e3c,stroke-width:2px,color:#000000
309
+ classDef errorNode fill:#ffebee,stroke:#d32f2f,stroke-width:2px,color:#000000
310
+
311
+ class A,B,E startNode
312
+ class C,D,F,G,H,I,L,N,P,Q,T,Y,Z,AA processNode
313
+ class J,M,R decisionNode
314
+ class K,S successNode
315
+ class O,U,V,W,X errorNode
316
+ ```
317
+
318
+ #### Strategy Integration Points
319
+
320
+ Strategies are injected into the workflow at specific points:
321
+
322
+ - **Validation Strategy**: Used by ValidateFilesStep
323
+ - **File Discovery Strategy**: Used by OrganizeFilesStep
324
+ - **Metadata Strategy**: Used by ProcessMetadataStep
325
+ - **Upload Strategy**: Used by UploadFilesStep
326
+ - **Data Unit Strategy**: Used by GenerateDataUnitsStep
327
+
328
+ #### Error Handling and Rollback
329
+
330
+ The orchestrator provides automatic rollback functionality:
331
+
332
+ 1. **Exception Capture**: Preserves original exceptions for debugging
333
+ 2. **Rollback Execution**: Calls rollback() on all successfully executed steps in reverse order
334
+ 3. **Graceful Degradation**: Continues rollback even if individual step rollbacks fail
335
+ 4. **State Preservation**: Maintains execution state for post-failure analysis
336
+
337
+ ## Development Guide
338
+
339
+ This section provides comprehensive guidance for extending the upload action with custom strategies and workflow steps.
340
+
341
+ ### Creating Custom Strategies
342
+
343
+ Strategies implement specific behaviors for different aspects of the upload process. Each strategy type has a well-defined interface.
344
+
345
+ #### Custom Validation Strategy
346
+
347
+ ```python
348
+ from synapse_sdk.plugins.categories.upload.actions.upload.strategies.validation.base import BaseValidationStrategy
349
+ from synapse_sdk.plugins.categories.upload.actions.upload.context import UploadContext
350
+ from pathlib import Path
351
+ from typing import List
352
+
353
+ class CustomValidationStrategy(BaseValidationStrategy):
354
+ """Custom validation strategy with advanced security checks."""
355
+
356
+ def validate_files(self, files: List[Path], context: UploadContext) -> bool:
357
+ """Validate files using custom business rules."""
358
+ for file_path in files:
359
+ # Custom validation logic
360
+ if not self._validate_custom_rules(file_path):
361
+ return False
362
+
363
+ # Call security validation
364
+ if not self.validate_security(file_path):
365
+ return False
366
+ return True
367
+
368
+ def validate_security(self, file_path: Path) -> bool:
369
+ """Custom security validation."""
370
+ # Implement custom security checks
371
+ if file_path.suffix in ['.exe', '.bat', '.sh']:
372
+ return False
373
+
374
+ # Check file size
375
+ if file_path.stat().st_size > 100 * 1024 * 1024: # 100MB
376
+ return False
377
+
378
+ return True
379
+
380
+ def _validate_custom_rules(self, file_path: Path) -> bool:
381
+ """Implement domain-specific validation rules."""
382
+ # Custom business logic
383
+ return True
384
+ ```
385
+
386
+ #### Custom File Discovery Strategy
387
+
388
+ ```python
389
+ from synapse_sdk.plugins.categories.upload.actions.upload.strategies.file_discovery.base import BaseFileDiscoveryStrategy
390
+ from pathlib import Path
391
+ from typing import List, Dict, Any
392
+
393
+ class CustomFileDiscoveryStrategy(BaseFileDiscoveryStrategy):
394
+ """Custom file discovery with advanced filtering."""
395
+
396
+ def discover_files(self, path: Path, context: UploadContext) -> List[Path]:
397
+ """Discover files with custom filtering rules."""
398
+ files = []
399
+
400
+ if context.get_param('is_recursive', False):
401
+ files = list(path.rglob('*'))
402
+ else:
403
+ files = list(path.iterdir())
404
+
405
+ # Apply custom filtering
406
+ return self._apply_custom_filters(files, context)
407
+
408
+ def organize_files(self, files: List[Path], specs: Dict[str, Any], context: UploadContext) -> List[Dict[str, Any]]:
409
+ """Organize files using custom categorization."""
410
+ organized = []
411
+
412
+ for file_path in files:
413
+ if file_path.is_file():
414
+ category = self._determine_category(file_path)
415
+ organized.append({
416
+ 'file_path': file_path,
417
+ 'category': category,
418
+ 'metadata': self._extract_file_metadata(file_path)
419
+ })
420
+
421
+ return organized
422
+
423
+ def _apply_custom_filters(self, files: List[Path], context: UploadContext) -> List[Path]:
424
+ """Apply domain-specific file filters."""
425
+ filtered = []
426
+ for file_path in files:
427
+ if self._should_include_file(file_path):
428
+ filtered.append(file_path)
429
+ return filtered
430
+
431
+ def _determine_category(self, file_path: Path) -> str:
432
+ """Determine file category using custom logic."""
433
+ ext = file_path.suffix.lower()
434
+ if ext in ['.jpg', '.png', '.gif']:
435
+ return 'images'
436
+ elif ext in ['.pdf', '.doc', '.docx']:
437
+ return 'documents'
438
+ else:
439
+ return 'other'
440
+ ```
441
+
442
+ #### Custom Upload Strategy
443
+
444
+ ```python
445
+ from synapse_sdk.plugins.categories.upload.actions.upload.strategies.upload.base import BaseUploadStrategy
446
+ from typing import List, Dict, Any
447
+ import time
448
+
449
+ class CustomUploadStrategy(BaseUploadStrategy):
450
+ """Custom upload strategy with advanced retry logic."""
451
+
452
+ def upload_files(self, files: List[Dict[str, Any]], context: UploadContext) -> List[Dict[str, Any]]:
453
+ """Upload files with custom batching and retry logic."""
454
+ uploaded_files = []
455
+ batch_size = context.get_param('upload_batch_size', 10)
456
+
457
+ # Process in custom batches
458
+ for i in range(0, len(files), batch_size):
459
+ batch = files[i:i + batch_size]
460
+ batch_results = self.upload_batch(batch, context)
461
+ uploaded_files.extend(batch_results)
462
+
463
+ return uploaded_files
464
+
465
+ def upload_batch(self, batch: List[Dict[str, Any]], context: UploadContext) -> List[Dict[str, Any]]:
466
+ """Upload a batch of files with retry logic."""
467
+ results = []
468
+
469
+ for file_info in batch:
470
+ max_retries = 3
471
+ for attempt in range(max_retries):
472
+ try:
473
+ result = self._upload_single_file(file_info, context)
474
+ results.append(result)
475
+ break
476
+ except Exception as e:
477
+ if attempt == max_retries - 1:
478
+ # Final attempt failed
479
+ context.add_error(f"Failed to upload {file_info['file_path']}: {e}")
480
+ else:
481
+ # Wait before retry
482
+ time.sleep(2 ** attempt)
483
+
484
+ return results
485
+
486
+ def _upload_single_file(self, file_info: Dict[str, Any], context: UploadContext) -> Dict[str, Any]:
487
+ """Upload a single file with custom logic."""
488
+ file_path = file_info['file_path']
489
+ storage = context.storage
490
+
491
+ # Custom upload logic here
492
+ uploaded_file = {
493
+ 'file_path': str(file_path),
494
+ 'storage_path': f"uploads/{file_path.name}",
495
+ 'size': file_path.stat().st_size,
496
+ 'checksum': self._calculate_checksum(file_path)
497
+ }
498
+
499
+ return uploaded_file
500
+ ```
501
+
502
+ ### Creating Custom Workflow Steps
503
+
504
+ Custom workflow steps extend the base step class and implement the required interface.
505
+
506
+ #### Custom Processing Step
507
+
508
+ ```python
509
+ from synapse_sdk.plugins.categories.upload.actions.upload.steps.base import BaseStep
510
+ from synapse_sdk.plugins.categories.upload.actions.upload.context import UploadContext, StepResult
511
+ from pathlib import Path
512
+ from typing import List, Dict
513
+ from datetime import datetime
514
+
515
+ class CustomProcessingStep(BaseStep):
516
+ """Custom processing step for specialized file handling."""
517
+
518
+ @property
519
+ def name(self) -> str:
520
+ return 'custom_processing'
521
+
522
+ @property
523
+ def progress_weight(self) -> float:
524
+ return 0.15 # 15% of total workflow
525
+
526
+ def execute(self, context: UploadContext) -> StepResult:
527
+ """Execute custom processing logic."""
528
+ try:
529
+ # Custom processing logic
530
+ processed_files = self._process_files(context)
531
+
532
+ # Update context with results
533
+ return self.create_success_result({
534
+ 'processed_files': processed_files,
535
+ 'processing_stats': self._get_processing_stats()
536
+ })
537
+
538
+ except Exception as e:
539
+ return self.create_error_result(f'Custom processing failed: {str(e)}')
540
+
541
+ def can_skip(self, context: UploadContext) -> bool:
542
+ """Determine if step can be skipped."""
543
+ # Skip if no files to process
544
+ return len(context.organized_files) == 0
545
+
546
+ def rollback(self, context: UploadContext) -> None:
547
+ """Rollback custom processing operations."""
548
+ # Clean up any resources created during processing
549
+ self._cleanup_processing_resources(context)
550
+
551
+ def _process_files(self, context: UploadContext) -> List[Dict]:
552
+ """Implement custom file processing."""
553
+ processed = []
554
+
555
+ for file_info in context.organized_files:
556
+ # Custom processing logic
557
+ result = self._process_single_file(file_info)
558
+ processed.append(result)
559
+
560
+ return processed
561
+
562
+ def _process_single_file(self, file_info: Dict) -> Dict:
563
+ """Process a single file."""
564
+ return {
565
+ 'original': file_info,
566
+ 'processed': True,
567
+ 'timestamp': datetime.now()
568
+ }
569
+
570
+ def _get_processing_stats(self) -> Dict:
571
+ """Get processing statistics."""
572
+ return {}
573
+
574
+ def _cleanup_processing_resources(self, context: UploadContext) -> None:
575
+ """Clean up processing resources."""
576
+ pass
577
+ ```
578
+
579
+ ### Strategy Factory Extension
580
+
581
+ To make custom strategies available, extend the StrategyFactory:
582
+
583
+ ```python
584
+ from synapse_sdk.plugins.categories.upload.actions.upload.factory import StrategyFactory
585
+ from typing import Dict
586
+
587
+ class CustomStrategyFactory(StrategyFactory):
588
+ """Extended factory with custom strategies."""
589
+
590
+ def create_validation_strategy(self, params: Dict, context=None):
591
+ """Create validation strategy with custom options."""
592
+ validation_type = params.get('custom_validation_type', 'default')
593
+
594
+ if validation_type == 'strict':
595
+ return CustomValidationStrategy()
596
+ else:
597
+ return super().create_validation_strategy(params, context)
598
+
599
+ def create_file_discovery_strategy(self, params: Dict, context=None):
600
+ """Create file discovery strategy with custom options."""
601
+ discovery_mode = params.get('discovery_mode', 'default')
602
+
603
+ if discovery_mode == 'advanced':
604
+ return CustomFileDiscoveryStrategy()
605
+ else:
606
+ return super().create_file_discovery_strategy(params, context)
607
+ ```
608
+
609
+ ### Custom Upload Action
610
+
611
+ For comprehensive customization, extend the UploadAction itself:
612
+
613
+ ```python
614
+ from synapse_sdk.plugins.categories.upload.actions.upload.action import UploadAction
615
+ from synapse_sdk.plugins.categories.decorators import register_action
616
+ from typing import Dict, Any
617
+
618
+ @register_action
619
+ class CustomUploadAction(UploadAction):
620
+ """Custom upload action with extended workflow."""
621
+
622
+ name = 'custom_upload'
623
+
624
+ def __init__(self, *args, **kwargs):
625
+ super().__init__(*args, **kwargs)
626
+ # Use custom strategy factory
627
+ self.strategy_factory = CustomStrategyFactory()
628
+
629
+ def _configure_workflow(self) -> None:
630
+ """Configure custom workflow with additional steps."""
631
+ # Register standard steps
632
+ super()._configure_workflow()
633
+
634
+ # Add custom processing step
635
+ self.step_registry.register(CustomProcessingStep())
636
+
637
+ def _configure_strategies(self, context=None) -> Dict[str, Any]:
638
+ """Configure strategies with custom parameters."""
639
+ strategies = super()._configure_strategies(context)
640
+
641
+ # Add custom strategy
642
+ strategies['custom_processing'] = self._create_custom_processing_strategy()
643
+
644
+ return strategies
645
+
646
+ def _create_custom_processing_strategy(self):
647
+ """Create custom processing strategy."""
648
+ return CustomProcessingStrategy(self.params)
649
+ ```
650
+
651
+ ### Testing Custom Components
652
+
653
+ #### Testing Custom Strategies
654
+
655
+ ```python
656
+ import pytest
657
+ from unittest.mock import Mock
658
+ from pathlib import Path
659
+
660
+ class TestCustomValidationStrategy:
661
+
662
+ def setup_method(self):
663
+ self.strategy = CustomValidationStrategy()
664
+ self.context = Mock()
665
+
666
+ def test_validate_files_success(self):
667
+ """Test successful file validation."""
668
+ files = [Path('/test/file1.txt'), Path('/test/file2.jpg')]
669
+ result = self.strategy.validate_files(files, self.context)
670
+ assert result is True
671
+
672
+ def test_validate_files_security_failure(self):
673
+ """Test validation failure for security reasons."""
674
+ files = [Path('/test/malware.exe')]
675
+ result = self.strategy.validate_files(files, self.context)
676
+ assert result is False
677
+
678
+ def test_validate_large_file_failure(self):
679
+ """Test validation failure for large files."""
680
+ # Mock file stat to return large size
681
+ large_file = Mock(spec=Path)
682
+ large_file.suffix = '.txt'
683
+ large_file.stat.return_value.st_size = 200 * 1024 * 1024 # 200MB
684
+
685
+ result = self.strategy.validate_security(large_file)
686
+ assert result is False
687
+ ```
688
+
689
+ #### Testing Custom Steps
690
+
691
+ ```python
692
+ class TestCustomProcessingStep:
693
+
694
+ def setup_method(self):
695
+ self.step = CustomProcessingStep()
696
+ self.context = Mock()
697
+ self.context.organized_files = [
698
+ {'file_path': '/test/file1.txt'},
699
+ {'file_path': '/test/file2.jpg'}
700
+ ]
701
+
702
+ def test_execute_success(self):
703
+ """Test successful step execution."""
704
+ result = self.step.execute(self.context)
705
+
706
+ assert result.success is True
707
+ assert 'processed_files' in result.data
708
+ assert len(result.data['processed_files']) == 2
709
+
710
+ def test_can_skip_with_no_files(self):
711
+ """Test step skipping logic."""
712
+ self.context.organized_files = []
713
+ assert self.step.can_skip(self.context) is True
714
+
715
+ def test_rollback_cleanup(self):
716
+ """Test rollback cleanup."""
717
+ # This should not raise an exception
718
+ self.step.rollback(self.context)
719
+ ```
720
+
721
+ ## API Reference
722
+
723
+ ### Core Components
724
+
725
+ #### UploadAction
726
+
727
+ Main upload action class implementing Strategy and Facade patterns.
728
+
729
+ **Class Attributes:**
730
+
731
+ - `name = 'upload'` - Action identifier
732
+ - `category = PluginCategory.UPLOAD` - Plugin category
733
+ - `method = RunMethod.JOB` - Execution method
734
+ - `run_class = UploadRun` - Specialized run management
735
+ - `params_model = UploadParams` - Parameter validation model
736
+ - `strategy_factory: StrategyFactory` - Creates strategy implementations
737
+ - `step_registry: StepRegistry` - Manages workflow steps
738
+
739
+ **Key Methods:**
740
+
741
+ - `start() -> Dict[str, Any]` - Execute orchestrated upload workflow
742
+ - `get_workflow_summary() -> Dict[str, Any]` - Get configured workflow summary
743
+ - `_configure_workflow() -> None` - Register workflow steps
744
+ - `_configure_strategies(context=None) -> Dict[str, Any]` - Create strategy instances
745
+
746
+ #### UploadOrchestrator
747
+
748
+ Facade component coordinating the complete upload workflow with automatic rollback.
749
+
750
+ **Attributes:**
751
+
752
+ - `context: UploadContext` - Shared state
753
+ - `step_registry: StepRegistry` - Workflow steps
754
+ - `strategies: Dict[str, Any]` - Strategy implementations
755
+ - `executed_steps: List[BaseStep]` - Successfully executed steps
756
+ - `rollback_executed: bool` - Whether rollback was performed
757
+
758
+ **Key Methods:**
759
+
760
+ - `execute() -> Dict[str, Any]` - Execute complete workflow
761
+ - `get_workflow_summary() -> Dict[str, Any]` - Get execution summary
762
+ - `_execute_step(step: BaseStep) -> StepResult` - Execute individual step
763
+ - `_rollback_executed_steps() -> None` - Rollback in reverse order
764
+
765
+ #### UploadContext
766
+
767
+ Context object maintaining shared state between workflow components.
768
+
769
+ **State Attributes:**
770
+
771
+ - `params: Dict` - Upload parameters
772
+ - `storage: Any` - Storage configuration
773
+ - `metadata: Dict[str, Dict[str, Any]]` - File metadata
774
+ - `file_specifications: Dict[str, Any]` - Data collection specs
775
+ - `organized_files: List[Dict[str, Any]]` - Organized files
776
+ - `uploaded_files: List[Dict[str, Any]]` - Uploaded files
777
+ - `data_units: List[Dict[str, Any]]` - Generated data units
778
+
779
+ **Key Methods:**
780
+
781
+ - `update(result: StepResult) -> None` - Update with step results
782
+ - `get_result() -> Dict[str, Any]` - Generate final result
783
+ - `has_errors() -> bool` - Check for errors
784
+ - `update_metrics(category: str, metrics: Dict) -> None` - Update metrics
785
+
786
+ ### Workflow Steps
787
+
788
+ #### BaseStep (Abstract)
789
+
790
+ Base class for all workflow steps.
791
+
792
+ **Abstract Properties:**
793
+
794
+ - `name: str` - Unique step identifier
795
+ - `progress_weight: float` - Weight for progress calculation
796
+
797
+ **Abstract Methods:**
798
+
799
+ - `execute(context: UploadContext) -> StepResult` - Execute step logic
800
+ - `can_skip(context: UploadContext) -> bool` - Determine if skippable
801
+ - `rollback(context: UploadContext) -> None` - Rollback operations
802
+
803
+ **Utility Methods:**
804
+
805
+ - `create_success_result(data: Dict = None) -> StepResult`
806
+ - `create_error_result(error: str, exception: Exception = None) -> StepResult`
807
+ - `create_skip_result() -> StepResult`
808
+
809
+ #### Concrete Steps
810
+
811
+ **InitializeStep** (`name: "initialize"`, `weight: 0.05`)
812
+
813
+ - Sets up storage and working directory
814
+
815
+ **ProcessMetadataStep** (`name: "process_metadata"`, `weight: 0.05`)
816
+
817
+ - Processes Excel metadata if provided
818
+
819
+ **AnalyzeCollectionStep** (`name: "analyze_collection"`, `weight: 0.05`)
820
+
821
+ - Retrieves data collection specs
822
+
823
+ **OrganizeFilesStep** (`name: "organize_files"`, `weight: 0.10`)
824
+
825
+ - Discovers and organizes files by type
826
+
827
+ **ValidateFilesStep** (`name: "validate_files"`, `weight: 0.05`)
828
+
829
+ - Validates files using validation strategy
830
+
831
+ **UploadFilesStep** (`name: "upload_files"`, `weight: 0.30`)
832
+
833
+ - Uploads files using upload strategy
834
+
835
+ **GenerateDataUnitsStep** (`name: "generate_data_units"`, `weight: 0.35`)
836
+
837
+ - Creates data units using data unit strategy
838
+
839
+ **CleanupStep** (`name: "cleanup"`, `weight: 0.05`)
840
+
841
+ - Cleans temporary resources
842
+
843
+ ### Strategy Base Classes
844
+
845
+ #### BaseValidationStrategy (Abstract)
846
+
847
+ **Abstract Methods:**
848
+
849
+ - `validate_files(files: List[Path], context: UploadContext) -> bool`
850
+ - `validate_security(file_path: Path) -> bool`
851
+
852
+ #### BaseFileDiscoveryStrategy (Abstract)
853
+
854
+ **Abstract Methods:**
855
+
856
+ - `discover_files(path: Path, context: UploadContext) -> List[Path]`
857
+ - `organize_files(files: List[Path], specs: Dict, context: UploadContext) -> List[Dict]`
858
+
859
+ #### BaseMetadataStrategy (Abstract)
860
+
861
+ **Abstract Methods:**
862
+
863
+ - `process_metadata(context: UploadContext) -> Dict[str, Any]`
864
+ - `extract_metadata(file_path: Path) -> Dict[str, Any]`
865
+
866
+ #### BaseUploadStrategy (Abstract)
867
+
868
+ **Abstract Methods:**
869
+
870
+ - `upload_files(files: List[Dict], context: UploadContext) -> List[Dict]`
871
+ - `upload_batch(batch: List[Dict], context: UploadContext) -> List[Dict]`
872
+
873
+ #### BaseDataUnitStrategy (Abstract)
874
+
875
+ **Abstract Methods:**
876
+
877
+ - `generate_data_units(files: List[Dict], context: UploadContext) -> List[Dict]`
878
+ - `create_data_unit_batch(batch: List[Dict], context: UploadContext) -> List[Dict]`
879
+
880
+ ## Best Practices
881
+
882
+ ### Architecture Patterns
883
+
884
+ 1. **Strategy Selection**: Choose appropriate strategies based on requirements
885
+ 2. **Step Ordering**: Maintain logical step dependencies
886
+ 3. **Context Management**: Leverage UploadContext for state sharing
887
+
888
+ ### Performance Optimization
889
+
890
+ 1. **Batch Processing**: Configure optimal batch sizes
891
+ 2. **Async Operations**: Enable async for I/O-bound operations
892
+ 3. **Memory Management**: Monitor memory usage in custom strategies
893
+
894
+ ### Security Considerations
895
+
896
+ 1. **Input Validation**: Validate all parameters and file paths
897
+ 2. **File Content Security**: Implement content-based checks
898
+ 3. **Path Sanitization**: Validate and sanitize all paths
899
+
900
+ ### Error Handling and Recovery
901
+
902
+ 1. **Graceful Degradation**: Design for partial failure scenarios
903
+ 2. **Rollback Design**: Implement comprehensive rollback strategies
904
+ 3. **Detailed Logging**: Use structured logging for debugging
905
+
906
+ ## Migration From Legacy
907
+
908
+ The upload action maintains 100% backward compatibility. Existing code continues to work without changes.
909
+
910
+ ### Key Changes
911
+
912
+ **Before (Legacy):**
913
+
914
+ - Single 900+ line action class
915
+ - Hard-coded behaviors
916
+ - No extensibility
917
+
918
+ **After (Refactored):**
919
+
920
+ - Clean separation with 8 workflow steps
921
+ - Pluggable strategies
922
+ - Automatic rollback
923
+
924
+ ### Benefits
925
+
926
+ - Better error handling with automatic rollback
927
+ - Detailed progress tracking
928
+ - Extensibility with custom strategies
929
+ - Better testability
930
+
931
+ ## See Also
932
+
933
+ - [Upload Plugin Overview](./upload-plugin-overview.md) - User guide and configuration reference
934
+ - [BaseUploader Template Guide](./upload-plugin-template.md) - Plugin development using BaseUploader template