synapse-sdk 1.0.0b21__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.

@@ -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.