synapse-sdk 1.0.0b22__py3-none-any.whl → 1.0.0b23__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/upload-plugins.md +680 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/upload-plugins.md +897 -0
- synapse_sdk/devtools/docs/sidebars.ts +1 -0
- synapse_sdk/plugins/README.md +934 -0
- synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +20 -0
- synapse_sdk/plugins/categories/upload/actions/upload/action.py +623 -0
- synapse_sdk/plugins/categories/upload/actions/upload/enums.py +221 -0
- synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
- synapse_sdk/plugins/categories/upload/actions/upload/models.py +149 -0
- synapse_sdk/plugins/categories/upload/actions/upload/run.py +178 -0
- synapse_sdk/plugins/categories/upload/actions/upload/utils.py +139 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +6 -1
- synapse_sdk/plugins/models.py +13 -7
- {synapse_sdk-1.0.0b22.dist-info → synapse_sdk-1.0.0b23.dist-info}/METADATA +1 -1
- {synapse_sdk-1.0.0b22.dist-info → synapse_sdk-1.0.0b23.dist-info}/RECORD +19 -10
- synapse_sdk/plugins/categories/upload/actions/upload.py +0 -1368
- {synapse_sdk-1.0.0b22.dist-info → synapse_sdk-1.0.0b23.dist-info}/WHEEL +0 -0
- {synapse_sdk-1.0.0b22.dist-info → synapse_sdk-1.0.0b23.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0b22.dist-info → synapse_sdk-1.0.0b23.dist-info}/licenses/LICENSE +0 -0
- {synapse_sdk-1.0.0b22.dist-info → synapse_sdk-1.0.0b23.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,934 @@
|
|
|
1
|
+
# Synapse SDK Plugin System - Developer Reference
|
|
2
|
+
|
|
3
|
+
This document provides comprehensive guidance for developers working on the Synapse SDK plugin system architecture, internal APIs, and core infrastructure.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Synapse SDK plugin system is a modular framework that enables distributed execution of ML operations across different categories and execution methods. The system is built around the concept of **actions** - discrete operations that can be packaged, distributed, and executed in various environments.
|
|
8
|
+
|
|
9
|
+
### Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
synapse_sdk/plugins/
|
|
13
|
+
├── categories/ # Plugin category implementations
|
|
14
|
+
│ ├── base.py # Action base class
|
|
15
|
+
│ ├── decorators.py # Registration decorators
|
|
16
|
+
│ ├── registry.py # Action registry
|
|
17
|
+
│ ├── neural_net/ # Neural network actions
|
|
18
|
+
│ ├── export/ # Data export actions
|
|
19
|
+
│ ├── upload/ # File upload actions
|
|
20
|
+
│ ├── smart_tool/ # AI-powered tools
|
|
21
|
+
│ ├── pre_annotation/ # Pre-processing actions
|
|
22
|
+
│ ├── post_annotation/ # Post-processing actions
|
|
23
|
+
│ └── data_validation/ # Validation actions
|
|
24
|
+
├── templates/ # Cookiecutter templates
|
|
25
|
+
├── utils/ # Utility functions
|
|
26
|
+
├── models.py # Core plugin models
|
|
27
|
+
├── enums.py # Plugin enums
|
|
28
|
+
└── exceptions.py # Plugin exceptions
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Key Features
|
|
32
|
+
|
|
33
|
+
- **🔌 Modular Architecture**: Self-contained plugins with isolated dependencies
|
|
34
|
+
- **⚡ Multiple Execution Methods**: Jobs, Tasks, and REST API endpoints
|
|
35
|
+
- **📦 Distributed Execution**: Ray-based scalable computing
|
|
36
|
+
- **🛠️ Template System**: Cookiecutter-based scaffolding
|
|
37
|
+
- **📊 Progress Tracking**: Built-in logging, metrics, and progress monitoring
|
|
38
|
+
- **🔄 Dynamic Loading**: Runtime plugin discovery and registration
|
|
39
|
+
|
|
40
|
+
## Core Components
|
|
41
|
+
|
|
42
|
+
### Action Base Class
|
|
43
|
+
|
|
44
|
+
The `Action` class (`synapse_sdk/plugins/categories/base.py`) provides the unified interface for all plugin actions:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
class Action:
|
|
48
|
+
"""Base class for all plugin actions.
|
|
49
|
+
|
|
50
|
+
Class Variables:
|
|
51
|
+
name (str): Action identifier
|
|
52
|
+
category (PluginCategory): Plugin category
|
|
53
|
+
method (RunMethod): Execution method
|
|
54
|
+
run_class (Run): Run management class
|
|
55
|
+
params_model (BaseModel): Parameter validation model
|
|
56
|
+
progress_categories (Dict): Progress tracking categories
|
|
57
|
+
metrics_categories (Dict): Metrics collection categories
|
|
58
|
+
|
|
59
|
+
Instance Variables:
|
|
60
|
+
params (Dict): Validated action parameters
|
|
61
|
+
plugin_config (Dict): Plugin configuration
|
|
62
|
+
plugin_release (PluginRelease): Plugin metadata
|
|
63
|
+
client: Backend API client
|
|
64
|
+
run (Run): Execution instance
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
# Class configuration
|
|
68
|
+
name = None
|
|
69
|
+
category = None
|
|
70
|
+
method = None
|
|
71
|
+
run_class = Run
|
|
72
|
+
params_model = None
|
|
73
|
+
progress_categories = None
|
|
74
|
+
metrics_categories = None
|
|
75
|
+
|
|
76
|
+
def start(self):
|
|
77
|
+
"""Main action logic - implement in subclasses."""
|
|
78
|
+
raise NotImplementedError
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Plugin Categories
|
|
82
|
+
|
|
83
|
+
The system supports seven main categories defined in `enums.py`:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
class PluginCategory(Enum):
|
|
87
|
+
NEURAL_NET = 'neural_net' # ML training and inference
|
|
88
|
+
EXPORT = 'export' # Data export operations
|
|
89
|
+
UPLOAD = 'upload' # File upload functionality
|
|
90
|
+
SMART_TOOL = 'smart_tool' # AI-powered automation
|
|
91
|
+
POST_ANNOTATION = 'post_annotation' # Post-processing
|
|
92
|
+
PRE_ANNOTATION = 'pre_annotation' # Pre-processing
|
|
93
|
+
DATA_VALIDATION = 'data_validation' # Quality checks
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Execution Methods
|
|
97
|
+
|
|
98
|
+
Three execution methods are supported:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
class RunMethod(Enum):
|
|
102
|
+
JOB = 'job' # Long-running distributed tasks
|
|
103
|
+
TASK = 'task' # Simple operations
|
|
104
|
+
RESTAPI = 'restapi' # HTTP endpoints
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Run Management
|
|
108
|
+
|
|
109
|
+
The `Run` class (`models.py`) manages action execution:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
class Run(BaseModel):
|
|
113
|
+
"""Manages plugin execution lifecycle.
|
|
114
|
+
|
|
115
|
+
Key Methods:
|
|
116
|
+
log_message(message, context): Log execution messages
|
|
117
|
+
set_progress(current, total, category): Update progress
|
|
118
|
+
set_metrics(metrics, category): Record metrics
|
|
119
|
+
log(log_type, data): Structured logging
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def log_message(self, message: str, context: str = 'INFO'):
|
|
123
|
+
"""Log execution messages with context."""
|
|
124
|
+
|
|
125
|
+
def set_progress(self, current: int, total: int, category: str = None):
|
|
126
|
+
"""Update progress tracking."""
|
|
127
|
+
|
|
128
|
+
def set_metrics(self, metrics: dict, category: str):
|
|
129
|
+
"""Record execution metrics."""
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Creating Plugin Categories
|
|
133
|
+
|
|
134
|
+
### 1. Define Category Structure
|
|
135
|
+
|
|
136
|
+
Create a new category directory:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
synapse_sdk/plugins/categories/my_category/
|
|
140
|
+
├── __init__.py
|
|
141
|
+
├── actions/
|
|
142
|
+
│ ├── __init__.py
|
|
143
|
+
│ └── my_action.py
|
|
144
|
+
└── templates/
|
|
145
|
+
└── plugin/
|
|
146
|
+
├── __init__.py
|
|
147
|
+
└── my_action.py
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 2. Implement Base Action
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
# synapse_sdk/plugins/categories/my_category/actions/my_action.py
|
|
154
|
+
from synapse_sdk.plugins.categories.base import Action
|
|
155
|
+
from synapse_sdk.plugins.categories.decorators import register_action
|
|
156
|
+
from synapse_sdk.plugins.enums import PluginCategory, RunMethod
|
|
157
|
+
from pydantic import BaseModel
|
|
158
|
+
|
|
159
|
+
class MyActionParams(BaseModel):
|
|
160
|
+
"""Parameter model for validation."""
|
|
161
|
+
input_path: str
|
|
162
|
+
output_path: str
|
|
163
|
+
config: dict = {}
|
|
164
|
+
|
|
165
|
+
@register_action
|
|
166
|
+
class MyAction(Action):
|
|
167
|
+
"""Base implementation for my_category actions."""
|
|
168
|
+
|
|
169
|
+
name = 'my_action'
|
|
170
|
+
category = PluginCategory.MY_CATEGORY
|
|
171
|
+
method = RunMethod.JOB
|
|
172
|
+
params_model = MyActionParams
|
|
173
|
+
|
|
174
|
+
progress_categories = {
|
|
175
|
+
'preprocessing': {'proportion': 20},
|
|
176
|
+
'processing': {'proportion': 60},
|
|
177
|
+
'postprocessing': {'proportion': 20}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
metrics_categories = {
|
|
181
|
+
'performance': {
|
|
182
|
+
'throughput': 0,
|
|
183
|
+
'latency': 0,
|
|
184
|
+
'accuracy': 0
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
def start(self):
|
|
189
|
+
"""Main execution logic."""
|
|
190
|
+
self.run.log_message("Starting my action...")
|
|
191
|
+
|
|
192
|
+
# Access validated parameters
|
|
193
|
+
input_path = self.params['input_path']
|
|
194
|
+
output_path = self.params['output_path']
|
|
195
|
+
|
|
196
|
+
# Update progress
|
|
197
|
+
self.run.set_progress(0, 100, 'preprocessing')
|
|
198
|
+
|
|
199
|
+
# Your implementation here
|
|
200
|
+
result = self.process_data(input_path, output_path)
|
|
201
|
+
|
|
202
|
+
# Record metrics
|
|
203
|
+
self.run.set_metrics({
|
|
204
|
+
'throughput': result['throughput'],
|
|
205
|
+
'items_processed': result['count']
|
|
206
|
+
}, 'performance')
|
|
207
|
+
|
|
208
|
+
self.run.log_message("Action completed successfully")
|
|
209
|
+
return result
|
|
210
|
+
|
|
211
|
+
def process_data(self, input_path, output_path):
|
|
212
|
+
"""Implement category-specific logic."""
|
|
213
|
+
raise NotImplementedError("Subclasses must implement process_data")
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 3. Create Template
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
# synapse_sdk/plugins/categories/my_category/templates/plugin/my_action.py
|
|
220
|
+
from synapse_sdk.plugins.categories.my_category import MyAction as BaseMyAction
|
|
221
|
+
|
|
222
|
+
class MyAction(BaseMyAction):
|
|
223
|
+
"""Custom implementation of my_action."""
|
|
224
|
+
|
|
225
|
+
def process_data(self, input_path, output_path):
|
|
226
|
+
"""Custom data processing logic."""
|
|
227
|
+
# Plugin developer implements this
|
|
228
|
+
return {"status": "success", "items_processed": 100}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 4. Register Category
|
|
232
|
+
|
|
233
|
+
Update `enums.py`:
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
class PluginCategory(Enum):
|
|
237
|
+
# ... existing categories
|
|
238
|
+
MY_CATEGORY = 'my_category'
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Action Implementation Examples
|
|
242
|
+
|
|
243
|
+
### Upload Action Architecture
|
|
244
|
+
|
|
245
|
+
The upload action demonstrates modular action architecture:
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
# Structure after SYN-5306 refactoring
|
|
249
|
+
synapse_sdk/plugins/categories/upload/actions/upload/
|
|
250
|
+
├── __init__.py # Public API exports
|
|
251
|
+
├── action.py # Main UploadAction class
|
|
252
|
+
├── run.py # UploadRun execution management
|
|
253
|
+
├── models.py # UploadParams validation
|
|
254
|
+
├── enums.py # LogCode and LOG_MESSAGES
|
|
255
|
+
├── exceptions.py # Custom exceptions
|
|
256
|
+
└── utils.py # Utility classes
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Key Implementation Details:**
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
# upload/action.py
|
|
263
|
+
@register_action
|
|
264
|
+
class UploadAction(Action):
|
|
265
|
+
name = 'upload'
|
|
266
|
+
category = PluginCategory.UPLOAD
|
|
267
|
+
method = RunMethod.JOB
|
|
268
|
+
run_class = UploadRun
|
|
269
|
+
|
|
270
|
+
def start(self):
|
|
271
|
+
# Comprehensive upload workflow
|
|
272
|
+
storage_id = self.params.get('storage')
|
|
273
|
+
path = self.params.get('path')
|
|
274
|
+
|
|
275
|
+
# Setup and validation
|
|
276
|
+
storage = self.client.get_storage(storage_id)
|
|
277
|
+
pathlib_cwd = get_pathlib(storage, path)
|
|
278
|
+
|
|
279
|
+
# Excel metadata processing
|
|
280
|
+
excel_metadata = self._read_excel_metadata(pathlib_cwd)
|
|
281
|
+
|
|
282
|
+
# File organization and upload
|
|
283
|
+
file_specification = self._analyze_collection()
|
|
284
|
+
organized_files = self._organize_files(pathlib_cwd, file_specification, excel_metadata)
|
|
285
|
+
|
|
286
|
+
# Async or sync upload based on configuration
|
|
287
|
+
if self.params.get('use_async_upload', False):
|
|
288
|
+
uploaded_files = self.run_async(self._upload_files_async(organized_files, 10))
|
|
289
|
+
else:
|
|
290
|
+
uploaded_files = self._upload_files(organized_files)
|
|
291
|
+
|
|
292
|
+
# Data unit generation
|
|
293
|
+
generated_data_units = self._generate_data_units(uploaded_files, batch_size)
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
'uploaded_files_count': len(uploaded_files),
|
|
297
|
+
'generated_data_units_count': len(generated_data_units)
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Plugin Action Structure Guidelines
|
|
302
|
+
|
|
303
|
+
For complex actions that require multiple components, follow the modular structure pattern established by the refactored upload action. This approach improves maintainability, testability, and code organization.
|
|
304
|
+
|
|
305
|
+
### Recommended File Structure
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
synapse_sdk/plugins/categories/{category}/actions/{action}/
|
|
309
|
+
├── __init__.py # Public API exports
|
|
310
|
+
├── action.py # Main action implementation
|
|
311
|
+
├── run.py # Execution and logging management
|
|
312
|
+
├── models.py # Pydantic parameter models
|
|
313
|
+
├── enums.py # Enums and message constants
|
|
314
|
+
├── exceptions.py # Custom exception classes
|
|
315
|
+
├── utils.py # Helper utilities and configurations
|
|
316
|
+
└── README.md # Action-specific documentation
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Module Responsibilities
|
|
320
|
+
|
|
321
|
+
#### 1. `__init__.py` - Public API
|
|
322
|
+
|
|
323
|
+
Defines the public interface and maintains backward compatibility:
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
# Export all public classes for backward compatibility
|
|
327
|
+
from .action import UploadAction
|
|
328
|
+
from .enums import LogCode, LOG_MESSAGES, UploadStatus
|
|
329
|
+
from .exceptions import ExcelParsingError, ExcelSecurityError
|
|
330
|
+
from .models import UploadParams
|
|
331
|
+
from .run import UploadRun
|
|
332
|
+
from .utils import ExcelMetadataUtils, ExcelSecurityConfig, PathAwareJSONEncoder
|
|
333
|
+
|
|
334
|
+
__all__ = [
|
|
335
|
+
'UploadAction',
|
|
336
|
+
'UploadRun',
|
|
337
|
+
'UploadParams',
|
|
338
|
+
'UploadStatus',
|
|
339
|
+
'LogCode',
|
|
340
|
+
'LOG_MESSAGES',
|
|
341
|
+
'ExcelSecurityError',
|
|
342
|
+
'ExcelParsingError',
|
|
343
|
+
'PathAwareJSONEncoder',
|
|
344
|
+
'ExcelSecurityConfig',
|
|
345
|
+
'ExcelMetadataUtils',
|
|
346
|
+
]
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
#### 2. `action.py` - Main Implementation
|
|
350
|
+
|
|
351
|
+
Contains the core action logic, inheriting from the base `Action` class:
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
from synapse_sdk.plugins.categories.base import Action
|
|
355
|
+
from synapse_sdk.plugins.enums import PluginCategory, RunMethod
|
|
356
|
+
|
|
357
|
+
from .enums import LogCode
|
|
358
|
+
from .models import UploadParams
|
|
359
|
+
from .run import UploadRun
|
|
360
|
+
|
|
361
|
+
class UploadAction(Action):
|
|
362
|
+
"""Main upload action implementation."""
|
|
363
|
+
|
|
364
|
+
name = 'upload'
|
|
365
|
+
category = PluginCategory.UPLOAD
|
|
366
|
+
method = RunMethod.JOB
|
|
367
|
+
run_class = UploadRun
|
|
368
|
+
params_model = UploadParams
|
|
369
|
+
|
|
370
|
+
def start(self):
|
|
371
|
+
"""Main action logic."""
|
|
372
|
+
# Validate parameters
|
|
373
|
+
self.validate_params()
|
|
374
|
+
|
|
375
|
+
# Log start
|
|
376
|
+
self.run.log_message_with_code(LogCode.UPLOAD_STARTED)
|
|
377
|
+
|
|
378
|
+
# Execute main logic
|
|
379
|
+
result = self._process_upload()
|
|
380
|
+
|
|
381
|
+
# Log completion
|
|
382
|
+
self.run.log_message_with_code(LogCode.UPLOAD_COMPLETED)
|
|
383
|
+
|
|
384
|
+
return result
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
#### 3. `models.py` - Parameter Validation
|
|
388
|
+
|
|
389
|
+
Defines Pydantic models for type-safe parameter validation:
|
|
390
|
+
|
|
391
|
+
```python
|
|
392
|
+
from typing import Annotated
|
|
393
|
+
from pydantic import AfterValidator, BaseModel, field_validator
|
|
394
|
+
from synapse_sdk.utils.pydantic.validators import non_blank
|
|
395
|
+
|
|
396
|
+
class UploadParams(BaseModel):
|
|
397
|
+
"""Upload action parameters with validation."""
|
|
398
|
+
|
|
399
|
+
name: Annotated[str, AfterValidator(non_blank)]
|
|
400
|
+
description: str | None = None
|
|
401
|
+
path: str
|
|
402
|
+
storage: int
|
|
403
|
+
collection: int
|
|
404
|
+
project: int | None = None
|
|
405
|
+
is_recursive: bool = False
|
|
406
|
+
max_file_size_mb: int = 50
|
|
407
|
+
use_async_upload: bool = True
|
|
408
|
+
|
|
409
|
+
@field_validator('storage', mode='before')
|
|
410
|
+
@classmethod
|
|
411
|
+
def check_storage_exists(cls, value: str, info) -> str:
|
|
412
|
+
"""Validate storage exists via API."""
|
|
413
|
+
action = info.context['action']
|
|
414
|
+
client = action.client
|
|
415
|
+
try:
|
|
416
|
+
client.get_storage(value)
|
|
417
|
+
except ClientError:
|
|
418
|
+
raise PydanticCustomError('client_error', 'Storage not found')
|
|
419
|
+
return value
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
#### 4. `enums.py` - Constants and Enums
|
|
423
|
+
|
|
424
|
+
Centralizes all enum definitions and constant values:
|
|
425
|
+
|
|
426
|
+
```python
|
|
427
|
+
from enum import Enum
|
|
428
|
+
from synapse_sdk.plugins.enums import Context
|
|
429
|
+
|
|
430
|
+
class UploadStatus(str, Enum):
|
|
431
|
+
"""Upload processing status."""
|
|
432
|
+
PENDING = 'pending'
|
|
433
|
+
PROCESSING = 'processing'
|
|
434
|
+
COMPLETED = 'completed'
|
|
435
|
+
FAILED = 'failed'
|
|
436
|
+
|
|
437
|
+
class LogCode(str, Enum):
|
|
438
|
+
"""Type-safe logging codes."""
|
|
439
|
+
UPLOAD_STARTED = 'UPLOAD_STARTED'
|
|
440
|
+
VALIDATION_FAILED = 'VALIDATION_FAILED'
|
|
441
|
+
NO_FILES_FOUND = 'NO_FILES_FOUND'
|
|
442
|
+
UPLOAD_COMPLETED = 'UPLOAD_COMPLETED'
|
|
443
|
+
# ... additional codes
|
|
444
|
+
|
|
445
|
+
LOG_MESSAGES = {
|
|
446
|
+
LogCode.UPLOAD_STARTED: {
|
|
447
|
+
'message': 'Upload process started.',
|
|
448
|
+
'level': Context.INFO,
|
|
449
|
+
},
|
|
450
|
+
LogCode.VALIDATION_FAILED: {
|
|
451
|
+
'message': 'Validation failed: {}',
|
|
452
|
+
'level': Context.DANGER,
|
|
453
|
+
},
|
|
454
|
+
# ... message configurations
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
#### 5. `run.py` - Execution Management
|
|
459
|
+
|
|
460
|
+
Handles execution flow, progress tracking, and specialized logging:
|
|
461
|
+
|
|
462
|
+
```python
|
|
463
|
+
from typing import Optional
|
|
464
|
+
from synapse_sdk.plugins.models import Run
|
|
465
|
+
from synapse_sdk.plugins.enums import Context
|
|
466
|
+
|
|
467
|
+
from .enums import LogCode, LOG_MESSAGES
|
|
468
|
+
|
|
469
|
+
class UploadRun(Run):
|
|
470
|
+
"""Specialized run management for upload actions."""
|
|
471
|
+
|
|
472
|
+
def log_message_with_code(self, code: LogCode, *args, level: Optional[Context] = None):
|
|
473
|
+
"""Type-safe logging with predefined messages."""
|
|
474
|
+
if code not in LOG_MESSAGES:
|
|
475
|
+
self.log_message(f'Unknown log code: {code}')
|
|
476
|
+
return
|
|
477
|
+
|
|
478
|
+
log_config = LOG_MESSAGES[code]
|
|
479
|
+
message = log_config['message'].format(*args) if args else log_config['message']
|
|
480
|
+
log_level = level or log_config['level']
|
|
481
|
+
|
|
482
|
+
self.log_message(message, context=log_level.value)
|
|
483
|
+
|
|
484
|
+
def log_upload_event(self, code: LogCode, *args, level: Optional[Context] = None):
|
|
485
|
+
"""Log upload-specific events with metrics."""
|
|
486
|
+
self.log_message_with_code(code, *args, level)
|
|
487
|
+
# Additional upload-specific logging logic
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
#### 6. `exceptions.py` - Custom Exceptions
|
|
491
|
+
|
|
492
|
+
Defines action-specific exception classes:
|
|
493
|
+
|
|
494
|
+
```python
|
|
495
|
+
class ExcelSecurityError(Exception):
|
|
496
|
+
"""Raised when Excel file security validation fails."""
|
|
497
|
+
pass
|
|
498
|
+
|
|
499
|
+
class ExcelParsingError(Exception):
|
|
500
|
+
"""Raised when Excel file parsing encounters errors."""
|
|
501
|
+
pass
|
|
502
|
+
|
|
503
|
+
class UploadValidationError(Exception):
|
|
504
|
+
"""Raised when upload parameter validation fails."""
|
|
505
|
+
pass
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
#### 7. `utils.py` - Helper Utilities
|
|
509
|
+
|
|
510
|
+
Contains utility classes and helper functions:
|
|
511
|
+
|
|
512
|
+
```python
|
|
513
|
+
import json
|
|
514
|
+
import os
|
|
515
|
+
from pathlib import Path
|
|
516
|
+
|
|
517
|
+
class PathAwareJSONEncoder(json.JSONEncoder):
|
|
518
|
+
"""JSON encoder that handles Path objects."""
|
|
519
|
+
|
|
520
|
+
def default(self, obj):
|
|
521
|
+
if hasattr(obj, '__fspath__') or hasattr(obj, 'as_posix'):
|
|
522
|
+
return str(obj)
|
|
523
|
+
elif hasattr(obj, 'isoformat'):
|
|
524
|
+
return obj.isoformat()
|
|
525
|
+
return super().default(obj)
|
|
526
|
+
|
|
527
|
+
class ExcelSecurityConfig:
|
|
528
|
+
"""Configuration for Excel file security limits."""
|
|
529
|
+
|
|
530
|
+
def __init__(self):
|
|
531
|
+
self.MAX_FILE_SIZE_MB = int(os.getenv('EXCEL_MAX_FILE_SIZE_MB', '10'))
|
|
532
|
+
self.MAX_ROWS = int(os.getenv('EXCEL_MAX_ROWS', '10000'))
|
|
533
|
+
self.MAX_COLUMNS = int(os.getenv('EXCEL_MAX_COLUMNS', '50'))
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Migration Guide
|
|
537
|
+
|
|
538
|
+
#### From Monolithic to Modular Structure
|
|
539
|
+
|
|
540
|
+
1. **Identify Components**: Break down the monolithic action into logical components
|
|
541
|
+
2. **Extract Models**: Move parameter validation to `models.py`
|
|
542
|
+
3. **Separate Enums**: Move constants and enums to `enums.py`
|
|
543
|
+
4. **Create Utilities**: Extract helper functions to `utils.py`
|
|
544
|
+
5. **Update Imports**: Ensure backward compatibility through `__init__.py`
|
|
545
|
+
|
|
546
|
+
#### Example Migration Steps
|
|
547
|
+
|
|
548
|
+
```python
|
|
549
|
+
# Before: Single upload.py file (1362 lines)
|
|
550
|
+
class UploadAction(Action):
|
|
551
|
+
# All code in one file...
|
|
552
|
+
|
|
553
|
+
# After: Modular structure
|
|
554
|
+
# action.py - Main logic (546 lines)
|
|
555
|
+
# models.py - Parameter validation (98 lines)
|
|
556
|
+
# enums.py - Constants and logging codes (156 lines)
|
|
557
|
+
# run.py - Execution management (134 lines)
|
|
558
|
+
# utils.py - Helper utilities (89 lines)
|
|
559
|
+
# exceptions.py - Custom exceptions (6 lines)
|
|
560
|
+
# __init__.py - Public API (20 lines)
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### Benefits of Modular Structure
|
|
564
|
+
|
|
565
|
+
- **Maintainability**: Each file has a single responsibility
|
|
566
|
+
- **Testability**: Individual components can be tested in isolation
|
|
567
|
+
- **Reusability**: Utilities and models can be shared across actions
|
|
568
|
+
- **Type Safety**: Enum-based logging and strong parameter validation
|
|
569
|
+
- **Backward Compatibility**: Public API remains unchanged
|
|
570
|
+
|
|
571
|
+
**Logging System with Enums:**
|
|
572
|
+
|
|
573
|
+
```python
|
|
574
|
+
# upload/enums.py
|
|
575
|
+
class LogCode(str, Enum):
|
|
576
|
+
VALIDATION_FAILED = 'VALIDATION_FAILED'
|
|
577
|
+
NO_FILES_FOUND = 'NO_FILES_FOUND'
|
|
578
|
+
# ... 36 total log codes
|
|
579
|
+
|
|
580
|
+
LOG_MESSAGES = {
|
|
581
|
+
LogCode.VALIDATION_FAILED: {
|
|
582
|
+
'message': 'Validation failed.',
|
|
583
|
+
'level': Context.DANGER,
|
|
584
|
+
},
|
|
585
|
+
# ... message configurations
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
# upload/run.py
|
|
589
|
+
class UploadRun(Run):
|
|
590
|
+
def log_message_with_code(self, code: LogCode, *args, level: Optional[Context] = None):
|
|
591
|
+
"""Type-safe logging with predefined messages."""
|
|
592
|
+
if code not in LOG_MESSAGES:
|
|
593
|
+
self.log_message(f'Unknown log code: {code}')
|
|
594
|
+
return
|
|
595
|
+
|
|
596
|
+
log_config = LOG_MESSAGES[code]
|
|
597
|
+
message = log_config['message'].format(*args) if args else log_config['message']
|
|
598
|
+
log_level = level or log_config['level'] or Context.INFO
|
|
599
|
+
|
|
600
|
+
if log_level == Context.INFO.value:
|
|
601
|
+
self.log_message(message, context=log_level.value)
|
|
602
|
+
else:
|
|
603
|
+
self.log_upload_event(code, *args, level)
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
## Development Workflow
|
|
607
|
+
|
|
608
|
+
### 1. Local Development Setup
|
|
609
|
+
|
|
610
|
+
```bash
|
|
611
|
+
# Set up development environment
|
|
612
|
+
cd synapse_sdk/plugins/categories/my_category
|
|
613
|
+
python -m pip install -e .
|
|
614
|
+
|
|
615
|
+
# Create test plugin
|
|
616
|
+
synapse plugin create --category my_category --debug
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### 2. Action Testing
|
|
620
|
+
|
|
621
|
+
```python
|
|
622
|
+
# Test action implementation
|
|
623
|
+
from synapse_sdk.plugins.utils import get_action_class
|
|
624
|
+
|
|
625
|
+
# Get action class
|
|
626
|
+
ActionClass = get_action_class("my_category", "my_action")
|
|
627
|
+
|
|
628
|
+
# Create test instance
|
|
629
|
+
action = ActionClass(
|
|
630
|
+
params={"input_path": "/test/data", "output_path": "/test/output"},
|
|
631
|
+
plugin_config={"debug": True},
|
|
632
|
+
envs={"TEST_MODE": "true"}
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
# Run action
|
|
636
|
+
result = action.run_action()
|
|
637
|
+
assert result["status"] == "success"
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### 3. Integration Testing
|
|
641
|
+
|
|
642
|
+
```python
|
|
643
|
+
# Test with Ray backend
|
|
644
|
+
import ray
|
|
645
|
+
from synapse_sdk.clients.ray import RayClient
|
|
646
|
+
|
|
647
|
+
# Initialize Ray
|
|
648
|
+
ray.init()
|
|
649
|
+
client = RayClient()
|
|
650
|
+
|
|
651
|
+
# Test distributed execution
|
|
652
|
+
job_result = client.submit_job(
|
|
653
|
+
entrypoint="python action.py",
|
|
654
|
+
runtime_env=action.get_runtime_env()
|
|
655
|
+
)
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
## Advanced Features
|
|
659
|
+
|
|
660
|
+
### Custom Progress Categories
|
|
661
|
+
|
|
662
|
+
```python
|
|
663
|
+
class MyAction(Action):
|
|
664
|
+
progress_categories = {
|
|
665
|
+
'data_loading': {
|
|
666
|
+
'proportion': 10,
|
|
667
|
+
'description': 'Loading input data'
|
|
668
|
+
},
|
|
669
|
+
'feature_extraction': {
|
|
670
|
+
'proportion': 30,
|
|
671
|
+
'description': 'Extracting features'
|
|
672
|
+
},
|
|
673
|
+
'model_training': {
|
|
674
|
+
'proportion': 50,
|
|
675
|
+
'description': 'Training model'
|
|
676
|
+
},
|
|
677
|
+
'evaluation': {
|
|
678
|
+
'proportion': 10,
|
|
679
|
+
'description': 'Evaluating results'
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
def start(self):
|
|
684
|
+
# Update specific progress categories
|
|
685
|
+
self.run.set_progress(50, 100, 'data_loading')
|
|
686
|
+
self.run.set_progress(25, 100, 'feature_extraction')
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### Runtime Environment Customization
|
|
690
|
+
|
|
691
|
+
```python
|
|
692
|
+
def get_runtime_env(self):
|
|
693
|
+
"""Customize execution environment."""
|
|
694
|
+
env = super().get_runtime_env()
|
|
695
|
+
|
|
696
|
+
# Add custom packages
|
|
697
|
+
env['pip']['packages'].extend([
|
|
698
|
+
'custom-ml-library==2.0.0',
|
|
699
|
+
'specialized-tool>=1.5.0'
|
|
700
|
+
])
|
|
701
|
+
|
|
702
|
+
# Set environment variables
|
|
703
|
+
env['env_vars'].update({
|
|
704
|
+
'CUDA_VISIBLE_DEVICES': '0,1',
|
|
705
|
+
'OMP_NUM_THREADS': '8',
|
|
706
|
+
'CUSTOM_CONFIG_PATH': '/app/config'
|
|
707
|
+
})
|
|
708
|
+
|
|
709
|
+
# Add working directory files
|
|
710
|
+
env['working_dir_files'] = {
|
|
711
|
+
'config.yaml': 'path/to/local/config.yaml',
|
|
712
|
+
'model_weights.pth': 'path/to/weights.pth'
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return env
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
### Parameter Validation Patterns
|
|
719
|
+
|
|
720
|
+
```python
|
|
721
|
+
from pydantic import BaseModel, validator, Field
|
|
722
|
+
from typing import Literal, Optional, List
|
|
723
|
+
|
|
724
|
+
class AdvancedParams(BaseModel):
|
|
725
|
+
"""Advanced parameter validation."""
|
|
726
|
+
|
|
727
|
+
# Enum-like validation
|
|
728
|
+
model_type: Literal["cnn", "transformer", "resnet"]
|
|
729
|
+
|
|
730
|
+
# Range validation
|
|
731
|
+
learning_rate: float = Field(gt=0, le=1, default=0.001)
|
|
732
|
+
batch_size: int = Field(ge=1, le=1024, default=32)
|
|
733
|
+
|
|
734
|
+
# File path validation
|
|
735
|
+
data_path: str
|
|
736
|
+
output_path: Optional[str] = None
|
|
737
|
+
|
|
738
|
+
# Complex validation
|
|
739
|
+
layers: List[int] = Field(min_items=1, max_items=10)
|
|
740
|
+
|
|
741
|
+
@validator('data_path')
|
|
742
|
+
def validate_data_path(cls, v):
|
|
743
|
+
if not os.path.exists(v):
|
|
744
|
+
raise ValueError(f'Data path does not exist: {v}')
|
|
745
|
+
return v
|
|
746
|
+
|
|
747
|
+
@validator('output_path')
|
|
748
|
+
def validate_output_path(cls, v, values):
|
|
749
|
+
if v is None:
|
|
750
|
+
# Auto-generate from data_path
|
|
751
|
+
data_path = values.get('data_path', '')
|
|
752
|
+
return f"{data_path}_output"
|
|
753
|
+
return v
|
|
754
|
+
|
|
755
|
+
@validator('layers')
|
|
756
|
+
def validate_layers(cls, v):
|
|
757
|
+
if len(v) < 2:
|
|
758
|
+
raise ValueError('Must specify at least 2 layers')
|
|
759
|
+
if v[0] <= 0 or v[-1] <= 0:
|
|
760
|
+
raise ValueError('Input and output layers must be positive')
|
|
761
|
+
return v
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
## Best Practices
|
|
765
|
+
|
|
766
|
+
### 1. Action Design
|
|
767
|
+
|
|
768
|
+
- **Single Responsibility**: Each action should have one clear purpose
|
|
769
|
+
- **Parameterization**: Make actions configurable through well-defined parameters
|
|
770
|
+
- **Error Handling**: Implement comprehensive error handling and validation
|
|
771
|
+
- **Progress Reporting**: Provide meaningful progress updates for long operations
|
|
772
|
+
|
|
773
|
+
### 2. Code Organization
|
|
774
|
+
|
|
775
|
+
```python
|
|
776
|
+
# Good: Modular structure
|
|
777
|
+
class UploadAction(Action):
|
|
778
|
+
def start(self):
|
|
779
|
+
self._validate_inputs()
|
|
780
|
+
files = self._discover_files()
|
|
781
|
+
processed_files = self._process_files(files)
|
|
782
|
+
return self._generate_output(processed_files)
|
|
783
|
+
|
|
784
|
+
def _validate_inputs(self):
|
|
785
|
+
"""Separate validation logic."""
|
|
786
|
+
pass
|
|
787
|
+
|
|
788
|
+
def _discover_files(self):
|
|
789
|
+
"""Separate file discovery logic."""
|
|
790
|
+
pass
|
|
791
|
+
|
|
792
|
+
# Good: Use of enums for constants
|
|
793
|
+
class LogCode(str, Enum):
|
|
794
|
+
VALIDATION_FAILED = 'VALIDATION_FAILED'
|
|
795
|
+
FILE_NOT_FOUND = 'FILE_NOT_FOUND'
|
|
796
|
+
|
|
797
|
+
# Good: Type hints and documentation
|
|
798
|
+
def process_batch(self, items: List[Dict[str, Any]], batch_size: int = 100) -> List[Dict[str, Any]]:
|
|
799
|
+
"""Process items in batches for memory efficiency.
|
|
800
|
+
|
|
801
|
+
Args:
|
|
802
|
+
items: List of items to process
|
|
803
|
+
batch_size: Number of items per batch
|
|
804
|
+
|
|
805
|
+
Returns:
|
|
806
|
+
List of processed items
|
|
807
|
+
"""
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
### 3. Performance Optimization
|
|
811
|
+
|
|
812
|
+
```python
|
|
813
|
+
# Use async for I/O-bound operations
|
|
814
|
+
async def _upload_files_async(self, files: List[Path], max_concurrent: int = 10):
|
|
815
|
+
semaphore = asyncio.Semaphore(max_concurrent)
|
|
816
|
+
|
|
817
|
+
async def upload_single_file(file_path):
|
|
818
|
+
async with semaphore:
|
|
819
|
+
return await self._upload_file(file_path)
|
|
820
|
+
|
|
821
|
+
tasks = [upload_single_file(f) for f in files]
|
|
822
|
+
return await asyncio.gather(*tasks, return_exceptions=True)
|
|
823
|
+
|
|
824
|
+
# Use generators for memory efficiency
|
|
825
|
+
def _process_large_dataset(self, data_source):
|
|
826
|
+
"""Process data in chunks to avoid memory issues."""
|
|
827
|
+
for chunk in self._chunk_data(data_source, chunk_size=1000):
|
|
828
|
+
processed_chunk = self._process_chunk(chunk)
|
|
829
|
+
yield processed_chunk
|
|
830
|
+
|
|
831
|
+
# Update progress
|
|
832
|
+
self.run.set_progress(self.processed_count, self.total_count, 'processing')
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
### 4. Error Handling
|
|
836
|
+
|
|
837
|
+
```python
|
|
838
|
+
from synapse_sdk.plugins.exceptions import ActionError
|
|
839
|
+
|
|
840
|
+
class MyAction(Action):
|
|
841
|
+
def start(self):
|
|
842
|
+
try:
|
|
843
|
+
return self._execute_main_logic()
|
|
844
|
+
except ValidationError as e:
|
|
845
|
+
self.run.log_message(f"Validation error: {e}", "ERROR")
|
|
846
|
+
raise ActionError(f"Parameter validation failed: {e}")
|
|
847
|
+
except FileNotFoundError as e:
|
|
848
|
+
self.run.log_message(f"File not found: {e}", "ERROR")
|
|
849
|
+
raise ActionError(f"Required file missing: {e}")
|
|
850
|
+
except Exception as e:
|
|
851
|
+
self.run.log_message(f"Unexpected error: {e}", "ERROR")
|
|
852
|
+
raise ActionError(f"Action execution failed: {e}")
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
### 5. Security Considerations
|
|
856
|
+
|
|
857
|
+
```python
|
|
858
|
+
# Good: Validate file paths
|
|
859
|
+
def _validate_file_path(self, file_path: str) -> Path:
|
|
860
|
+
"""Validate and sanitize file paths."""
|
|
861
|
+
path = Path(file_path).resolve()
|
|
862
|
+
|
|
863
|
+
# Prevent directory traversal
|
|
864
|
+
if not str(path).startswith(str(self.workspace_root)):
|
|
865
|
+
raise ActionError(f"File path outside workspace: {path}")
|
|
866
|
+
|
|
867
|
+
return path
|
|
868
|
+
|
|
869
|
+
# Good: Sanitize user inputs
|
|
870
|
+
def _sanitize_filename(self, filename: str) -> str:
|
|
871
|
+
"""Remove unsafe characters from filename."""
|
|
872
|
+
import re
|
|
873
|
+
# Remove path separators and control characters
|
|
874
|
+
safe_name = re.sub(r'[<>:"/\\|?*\x00-\x1f]', '_', filename)
|
|
875
|
+
return safe_name[:255] # Limit length
|
|
876
|
+
|
|
877
|
+
# Good: Validate data sizes
|
|
878
|
+
def _validate_data_size(self, data: bytes) -> None:
|
|
879
|
+
"""Check data size limits."""
|
|
880
|
+
max_size = 100 * 1024 * 1024 # 100MB
|
|
881
|
+
if len(data) > max_size:
|
|
882
|
+
raise ActionError(f"Data too large: {len(data)} bytes (max: {max_size})")
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
## API Reference
|
|
886
|
+
|
|
887
|
+
### Core Classes
|
|
888
|
+
|
|
889
|
+
#### Action
|
|
890
|
+
Base class for all plugin actions.
|
|
891
|
+
|
|
892
|
+
**Methods:**
|
|
893
|
+
- `start()`: Main execution method (abstract)
|
|
894
|
+
- `run_action()`: Execute action with error handling
|
|
895
|
+
- `get_runtime_env()`: Get execution environment configuration
|
|
896
|
+
- `validate_params()`: Validate action parameters
|
|
897
|
+
|
|
898
|
+
#### Run
|
|
899
|
+
Manages action execution lifecycle.
|
|
900
|
+
|
|
901
|
+
**Methods:**
|
|
902
|
+
- `log_message(message, context)`: Log execution messages
|
|
903
|
+
- `set_progress(current, total, category)`: Update progress
|
|
904
|
+
- `set_metrics(metrics, category)`: Record metrics
|
|
905
|
+
- `log(log_type, data)`: Structured logging
|
|
906
|
+
|
|
907
|
+
#### PluginRelease
|
|
908
|
+
Manages plugin metadata and configuration.
|
|
909
|
+
|
|
910
|
+
**Attributes:**
|
|
911
|
+
- `code`: Plugin identifier
|
|
912
|
+
- `name`: Human-readable name
|
|
913
|
+
- `version`: Semantic version
|
|
914
|
+
- `category`: Plugin category
|
|
915
|
+
- `config`: Plugin configuration
|
|
916
|
+
|
|
917
|
+
### Utility Functions
|
|
918
|
+
|
|
919
|
+
```python
|
|
920
|
+
# synapse_sdk/plugins/utils/
|
|
921
|
+
from synapse_sdk.plugins.utils import (
|
|
922
|
+
get_action_class, # Get action class by category/name
|
|
923
|
+
load_plugin_config, # Load plugin configuration
|
|
924
|
+
validate_plugin, # Validate plugin structure
|
|
925
|
+
register_plugin, # Register plugin in system
|
|
926
|
+
)
|
|
927
|
+
|
|
928
|
+
# Usage examples
|
|
929
|
+
ActionClass = get_action_class("upload", "upload")
|
|
930
|
+
config = load_plugin_config("/path/to/plugin")
|
|
931
|
+
is_valid = validate_plugin("/path/to/plugin")
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
This README provides the foundation for developing and extending the Synapse SDK plugin system. For specific implementation examples, refer to the existing plugin categories and their respective documentation.
|