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.
- synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-action.md +934 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-overview.md +560 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-template.md +715 -0
- synapse_sdk/devtools/docs/docs/plugins/plugins.md +12 -5
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-action.md +934 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-overview.md +560 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-template.md +715 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current.json +16 -4
- synapse_sdk/devtools/docs/sidebars.ts +13 -1
- synapse_sdk/plugins/README.md +487 -80
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/action.py +10 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +145 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +97 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +250 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +284 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +87 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +127 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
- synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +2 -1
- synapse_sdk/plugins/categories/upload/actions/upload/models.py +134 -94
- synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +2 -2
- synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +106 -14
- synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +113 -36
- synapse_sdk/plugins/categories/upload/templates/README.md +365 -0
- {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.3.dist-info}/METADATA +1 -1
- {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.3.dist-info}/RECORD +40 -20
- synapse_sdk/devtools/docs/docs/plugins/developing-upload-template.md +0 -1463
- synapse_sdk/devtools/docs/docs/plugins/upload-plugins.md +0 -1964
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/developing-upload-template.md +0 -1463
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/upload-plugins.md +0 -2077
- {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.3.dist-info}/WHEEL +0 -0
- {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.3.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.3.dist-info}/licenses/LICENSE +0 -0
- {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
|