synapse-sdk 2025.10.1__py3-none-any.whl → 2025.10.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of synapse-sdk might be problematic. Click here for more details.

Files changed (54) hide show
  1. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/pre-annotation-plugin-overview.md +198 -0
  2. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-action-development.md +1645 -0
  3. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-overview.md +717 -0
  4. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-template-development.md +1380 -0
  5. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-action.md +934 -0
  6. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-overview.md +560 -0
  7. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-template.md +715 -0
  8. synapse_sdk/devtools/docs/docs/plugins/plugins.md +12 -5
  9. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/pre-annotation-plugin-overview.md +198 -0
  10. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-action-development.md +1645 -0
  11. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-overview.md +717 -0
  12. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-template-development.md +1380 -0
  13. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-action.md +934 -0
  14. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-overview.md +560 -0
  15. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-template.md +715 -0
  16. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current.json +16 -4
  17. synapse_sdk/devtools/docs/sidebars.ts +27 -1
  18. synapse_sdk/plugins/README.md +487 -80
  19. synapse_sdk/plugins/categories/export/actions/export/action.py +8 -3
  20. synapse_sdk/plugins/categories/export/actions/export/utils.py +108 -8
  21. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
  22. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
  23. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/action.py +10 -0
  24. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
  25. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +145 -0
  26. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
  27. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
  28. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
  29. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +97 -0
  30. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +250 -0
  31. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
  32. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
  33. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +284 -0
  34. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
  35. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
  36. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +87 -0
  37. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +127 -0
  38. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
  39. synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +2 -1
  40. synapse_sdk/plugins/categories/upload/actions/upload/models.py +134 -94
  41. synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +2 -2
  42. synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +106 -14
  43. synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +113 -36
  44. synapse_sdk/plugins/categories/upload/templates/README.md +365 -0
  45. {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.4.dist-info}/METADATA +1 -1
  46. {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.4.dist-info}/RECORD +50 -22
  47. synapse_sdk/devtools/docs/docs/plugins/developing-upload-template.md +0 -1463
  48. synapse_sdk/devtools/docs/docs/plugins/upload-plugins.md +0 -1964
  49. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/developing-upload-template.md +0 -1463
  50. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/upload-plugins.md +0 -2077
  51. {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.4.dist-info}/WHEEL +0 -0
  52. {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.4.dist-info}/entry_points.txt +0 -0
  53. {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.4.dist-info}/licenses/LICENSE +0 -0
  54. {synapse_sdk-2025.10.1.dist-info → synapse_sdk-2025.10.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,284 @@
1
+ """Annotation strategies for ToTask action."""
2
+
3
+ from typing import Any, Dict
4
+
5
+ from ..enums import AnnotateTaskDataStatus, LogCode
6
+ from .base import AnnotationStrategy, ToTaskContext
7
+
8
+
9
+ class FileAnnotationStrategy(AnnotationStrategy):
10
+ """Strategy for file-based annotation processing."""
11
+
12
+ def process_task(
13
+ self, context: ToTaskContext, task_id: int, task_data: Dict[str, Any], target_specification_name: str, **kwargs
14
+ ) -> Dict[str, Any]:
15
+ """Process a single task for file-based annotation.
16
+
17
+ Args:
18
+ context: Shared context for the action execution
19
+ task_id: The task ID to process
20
+ task_data: The task data dictionary
21
+ target_specification_name: The name of the target specification
22
+ **kwargs: Additional parameters
23
+
24
+ Returns:
25
+ Dict with 'success' boolean and optional 'error' message
26
+ """
27
+ try:
28
+ client = context.client
29
+ logger = context.logger
30
+
31
+ # Get data unit
32
+ data_unit = task_data.get('data_unit')
33
+ if not data_unit:
34
+ error_msg = 'Task does not have a data unit'
35
+ logger.log_annotate_task_event(LogCode.NO_DATA_UNIT, task_id)
36
+ logger.log_annotate_task_data({'task_id': task_id, 'error': error_msg}, AnnotateTaskDataStatus.FAILED)
37
+ return {'success': False, 'error': error_msg}
38
+
39
+ # Get data unit files
40
+ data_unit_files = data_unit.get('files', {})
41
+ if not data_unit_files:
42
+ error_msg = 'Data unit does not have files'
43
+ logger.log_annotate_task_event(LogCode.NO_DATA_UNIT_FILES, task_id)
44
+ logger.log_annotate_task_data({'task_id': task_id, 'error': error_msg}, AnnotateTaskDataStatus.FAILED)
45
+ return {'success': False, 'error': error_msg}
46
+
47
+ # Extract primary file URL from task data
48
+ primary_file_url, primary_file_original_name = self._extract_primary_file_url(task_data)
49
+ if not primary_file_url:
50
+ error_msg = 'Primary image URL not found in task data'
51
+ logger.log_annotate_task_event(LogCode.PRIMARY_IMAGE_URL_NOT_FOUND, task_id)
52
+ logger.log_annotate_task_data({'task_id': task_id, 'error': error_msg}, AnnotateTaskDataStatus.FAILED)
53
+ return {'success': False, 'error': error_msg}
54
+
55
+ # Get target specification file
56
+ target_file = data_unit_files.get(target_specification_name)
57
+ if not target_file:
58
+ error_msg = 'File specification not found'
59
+ logger.log_annotate_task_event(LogCode.FILE_SPEC_NOT_FOUND, task_id)
60
+ logger.log_annotate_task_data({'task_id': task_id, 'error': error_msg}, AnnotateTaskDataStatus.FAILED)
61
+ return {'success': False, 'error': error_msg}
62
+
63
+ # Get target file details
64
+ target_file_url = target_file.get('url')
65
+ target_file_original_name = target_file.get('file_name_original')
66
+
67
+ if not target_file_original_name:
68
+ error_msg = 'File original name not found'
69
+ logger.log_annotate_task_event(LogCode.FILE_ORIGINAL_NAME_NOT_FOUND, task_id)
70
+ logger.log_annotate_task_data({'task_id': task_id, 'error': error_msg}, AnnotateTaskDataStatus.FAILED)
71
+ return {'success': False, 'error': error_msg}
72
+
73
+ if not target_file_url:
74
+ error_msg = 'URL not found'
75
+ logger.log_annotate_task_event(LogCode.URL_NOT_FOUND, task_id)
76
+ logger.log_annotate_task_data({'task_id': task_id, 'error': error_msg}, AnnotateTaskDataStatus.FAILED)
77
+ return {'success': False, 'error': error_msg}
78
+
79
+ # Fetch and process the data using template
80
+ try:
81
+ # Convert data to task object using action's entrypoint
82
+ annotation_to_task = context.entrypoint(logger)
83
+ converted_data = annotation_to_task.convert_data_from_file(
84
+ primary_file_url, primary_file_original_name, target_file_url, target_file_original_name
85
+ )
86
+ except Exception as e:
87
+ if 'requests' in str(type(e)):
88
+ error_msg = f'Failed to fetch data from URL: {str(e)}'
89
+ logger.log_annotate_task_event(LogCode.FETCH_DATA_FAILED, target_file_url, task_id)
90
+ else:
91
+ error_msg = f'Failed to convert data to task object: {str(e)}'
92
+ logger.log_annotate_task_event(LogCode.CONVERT_DATA_FAILED, str(e), task_id)
93
+ logger.log_annotate_task_data({'task_id': task_id, 'error': error_msg}, AnnotateTaskDataStatus.FAILED)
94
+ return {'success': False, 'error': error_msg}
95
+
96
+ # Submit annotation data
97
+ try:
98
+ client.annotate_task_data(task_id, data={'action': 'submit', 'data': converted_data})
99
+ logger.log_annotate_task_data(
100
+ {'task_id': task_id, 'target_spec': target_specification_name}, AnnotateTaskDataStatus.SUCCESS
101
+ )
102
+ return {'success': True}
103
+ except Exception as e:
104
+ error_msg = f'Failed to submit annotation data: {str(e)}'
105
+ logger.log_annotate_task_event(LogCode.ANNOTATION_SUBMISSION_FAILED, task_id, str(e))
106
+ logger.log_annotate_task_data({'task_id': task_id, 'error': error_msg}, AnnotateTaskDataStatus.FAILED)
107
+ return {'success': False, 'error': error_msg}
108
+
109
+ except Exception as e:
110
+ error_msg = f'Failed to process file annotation for task {task_id}: {str(e)}'
111
+ context.logger.log_annotate_task_event(LogCode.TASK_PROCESSING_FAILED, task_id, str(e))
112
+ context.logger.log_annotate_task_data(
113
+ {'task_id': task_id, 'error': error_msg}, AnnotateTaskDataStatus.FAILED
114
+ )
115
+ return {'success': False, 'error': error_msg}
116
+
117
+ def _extract_primary_file_url(self, task_data: Dict[str, Any]) -> tuple:
118
+ """Extract the primary file URL from task data.
119
+
120
+ Args:
121
+ task_data: The task data dictionary
122
+
123
+ Returns:
124
+ Tuple of (primary_file_url, primary_file_original_name)
125
+ """
126
+ data_unit = task_data.get('data_unit', {})
127
+ files = data_unit.get('files', {})
128
+
129
+ for file_info in files.values():
130
+ if isinstance(file_info, dict) and file_info.get('is_primary') and file_info.get('url'):
131
+ return file_info['url'], file_info.get('file_name_original')
132
+
133
+ return None, None
134
+
135
+
136
+ class InferenceAnnotationStrategy(AnnotationStrategy):
137
+ """Strategy for inference-based annotation processing."""
138
+
139
+ def process_task(self, context: ToTaskContext, task_id: int, task_data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
140
+ """Process a single task for inference-based annotation.
141
+
142
+ Args:
143
+ context: Shared context for the action execution
144
+ task_id: The task ID to process
145
+ task_data: The task data dictionary
146
+ **kwargs: Additional parameters
147
+
148
+ Returns:
149
+ Dict with 'success' boolean and optional 'error' message
150
+ """
151
+ try:
152
+ client = context.client
153
+ logger = context.logger
154
+
155
+ # Get pre-processor ID from parameters
156
+ pre_processor_id = context.params.get('pre_processor')
157
+ if not pre_processor_id:
158
+ error_msg = 'Pre-processor ID is required for inference annotation method'
159
+ logger.log_annotate_task_event(LogCode.NO_PREPROCESSOR_ID, task_id)
160
+ logger.log_annotate_task_data({'task_id': task_id, 'error': error_msg}, AnnotateTaskDataStatus.FAILED)
161
+ return {'success': False, 'error': error_msg}
162
+
163
+ # Get pre-processor info using factory-created strategy
164
+ from ..factory import ToTaskStrategyFactory
165
+
166
+ factory = ToTaskStrategyFactory()
167
+ preprocessor_strategy = factory.create_preprocessor_strategy()
168
+
169
+ pre_processor_info = preprocessor_strategy.get_preprocessor_info(context, pre_processor_id)
170
+ if not pre_processor_info['success']:
171
+ error_msg = pre_processor_info.get('error', 'Failed to get pre-processor info')
172
+ logger.log_annotate_task_event(LogCode.INFERENCE_PREPROCESSOR_FAILED, task_id, error_msg)
173
+ return pre_processor_info
174
+
175
+ pre_processor_code = pre_processor_info['code']
176
+ pre_processor_version = pre_processor_info['version']
177
+
178
+ # Ensure pre-processor is running
179
+ pre_processor_status = preprocessor_strategy.ensure_preprocessor_running(context, pre_processor_code)
180
+ if not pre_processor_status['success']:
181
+ error_msg = pre_processor_status.get('error', 'Failed to ensure pre-processor running')
182
+ logger.log_annotate_task_event(LogCode.INFERENCE_PREPROCESSOR_FAILED, task_id, error_msg)
183
+ return pre_processor_status
184
+
185
+ # Extract primary file URL using factory-created strategy
186
+ extraction_strategy = factory.create_extraction_strategy(context.annotation_method)
187
+ primary_file_url, _ = extraction_strategy.extract_data(context, task_data)
188
+ if not primary_file_url:
189
+ error_msg = 'Primary image URL not found in task data'
190
+ logger.log_annotate_task_event(LogCode.PRIMARY_IMAGE_URL_NOT_FOUND, task_id)
191
+ logger.log_annotate_task_data({'task_id': task_id, 'error': error_msg}, AnnotateTaskDataStatus.FAILED)
192
+ return {'success': False, 'error': error_msg}
193
+
194
+ # Run inference
195
+ inference_result = self._run_inference(
196
+ client,
197
+ pre_processor_code,
198
+ pre_processor_version,
199
+ primary_file_url,
200
+ context.params['agent'],
201
+ context.params['model'],
202
+ pre_processor_params=context.params.get('pre_processor_params', {}),
203
+ )
204
+ if not inference_result['success']:
205
+ error_msg = inference_result.get('error', 'Failed to run inference')
206
+ logger.log_annotate_task_event(LogCode.INFERENCE_PREPROCESSOR_FAILED, task_id, error_msg)
207
+ return inference_result
208
+
209
+ # Convert and submit inference data
210
+ try:
211
+ # This would need to be injected or configured based on the action's entrypoint
212
+ # For now, we'll assume the conversion is done externally
213
+ converted_result = inference_result['data'] # Simplified for refactoring
214
+
215
+ client.annotate_task_data(task_id, data={'action': 'submit', 'data': converted_result})
216
+ logger.log_annotate_task_data(
217
+ {'task_id': task_id, 'pre_processor_id': pre_processor_id}, AnnotateTaskDataStatus.SUCCESS
218
+ )
219
+ return {'success': True, 'pre_processor_id': pre_processor_id}
220
+
221
+ except Exception as e:
222
+ error_msg = f'Failed to convert/submit inference data: {str(e)}'
223
+ logger.log_annotate_task_event(LogCode.INFERENCE_PREPROCESSOR_FAILED, task_id, error_msg)
224
+ return {'success': False, 'error': error_msg}
225
+
226
+ except Exception as e:
227
+ error_msg = f'Failed to process inference for task {task_id}: {str(e)}'
228
+ context.logger.log_message_with_code(LogCode.INFERENCE_PROCESSING_FAILED, task_id, str(e))
229
+ context.logger.log_annotate_task_data(
230
+ {'task_id': task_id, 'error': error_msg}, AnnotateTaskDataStatus.FAILED
231
+ )
232
+ return {'success': False, 'error': error_msg}
233
+
234
+ def _run_inference(
235
+ self,
236
+ client: Any,
237
+ pre_processor_code: str,
238
+ pre_processor_version: str,
239
+ primary_file_url: str,
240
+ agent: int,
241
+ model: int,
242
+ pre_processor_params: Dict[str, Any] = {},
243
+ ) -> Dict[str, Any]:
244
+ """Run inference using the pre-processor.
245
+
246
+ Args:
247
+ client: Backend client instance
248
+ pre_processor_code: Pre-processor code
249
+ pre_processor_version: Pre-processor version
250
+ primary_file_url: URL of the primary file to process
251
+ agent: Agent id for inference
252
+ model: Model id for inference
253
+ pre_processor_params: Additional parameters for the pre-processor
254
+
255
+ Returns:
256
+ Dict with inference results or error
257
+ """
258
+ try:
259
+ if not agent or not model:
260
+ return {'success': False, 'error': 'Parameters not available'}
261
+
262
+ pre_processor_params['image_path'] = primary_file_url
263
+
264
+ inference_payload = {
265
+ 'agent': agent,
266
+ 'action': 'inference',
267
+ 'version': pre_processor_version,
268
+ 'params': {
269
+ 'model': model,
270
+ 'method': 'post',
271
+ 'json': pre_processor_params,
272
+ },
273
+ }
274
+
275
+ inference_data = client.run_plugin(pre_processor_code, inference_payload)
276
+
277
+ # Every inference api should return None if failed to inference.
278
+ if inference_data is None:
279
+ return {'success': False, 'error': 'Inference data is None'}
280
+
281
+ return {'success': True, 'data': inference_data}
282
+
283
+ except Exception as e:
284
+ return {'success': False, 'error': f'Failed to run inference: {str(e)}'}
@@ -0,0 +1,170 @@
1
+ """Abstract base classes for ToTask strategies."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, Dict, List, Optional, Tuple
5
+
6
+ from synapse_sdk.clients.backend import BackendClient
7
+
8
+ from ..enums import AnnotationMethod
9
+ from ..models import MetricsRecord
10
+
11
+
12
+ class ToTaskContext:
13
+ """Shared context for ToTask action execution."""
14
+
15
+ def __init__(
16
+ self,
17
+ params: Dict[str, Any],
18
+ client: BackendClient,
19
+ logger: Any,
20
+ entrypoint: Any = None,
21
+ config: Optional[Dict[str, Any]] = None,
22
+ plugin_config: Optional[Dict[str, Any]] = None,
23
+ job_id: Optional[str] = None,
24
+ progress_categories: Optional[Dict[str, Any]] = None,
25
+ metrics_categories: Optional[Dict[str, Any]] = None,
26
+ project: Optional[Dict[str, Any]] = None,
27
+ data_collection: Optional[Dict[str, Any]] = None,
28
+ task_ids: Optional[List[int]] = None,
29
+ metrics: Optional[MetricsRecord] = None,
30
+ annotation_method: Optional[AnnotationMethod] = None,
31
+ ):
32
+ self.params = params
33
+ self.client = client
34
+ self.logger = logger
35
+ self.entrypoint = entrypoint
36
+ self.config = config
37
+ self.plugin_config = plugin_config
38
+ self.job_id = job_id
39
+ self.progress_categories = progress_categories
40
+ self.metrics_categories = metrics_categories
41
+ self.project = project
42
+ self.data_collection = data_collection
43
+ self.task_ids = task_ids or []
44
+ self.metrics = metrics or MetricsRecord(stand_by=0, failed=0, success=0)
45
+ self.annotation_method = annotation_method
46
+ self.temp_files: List[str] = []
47
+ self.rollback_actions: List[callable] = []
48
+
49
+ def add_temp_file(self, file_path: str):
50
+ """Track temporary files for cleanup."""
51
+ self.temp_files.append(file_path)
52
+
53
+ def add_rollback_action(self, action: callable):
54
+ """Add rollback action for error recovery."""
55
+ self.rollback_actions.append(action)
56
+
57
+ def update_metrics(self, success_count: int, failed_count: int, total_count: int):
58
+ """Update execution metrics."""
59
+ self.metrics = MetricsRecord(
60
+ stand_by=total_count - success_count - failed_count, failed=failed_count, success=success_count
61
+ )
62
+
63
+
64
+ class ValidationStrategy(ABC):
65
+ """Abstract base class for validation strategies."""
66
+
67
+ @abstractmethod
68
+ def validate(self, context: ToTaskContext) -> Dict[str, Any]:
69
+ """Validate specific aspects of the ToTask execution.
70
+
71
+ Args:
72
+ context: Shared context for the action execution
73
+
74
+ Returns:
75
+ Dict with 'success' boolean and optional 'error' message
76
+ """
77
+ pass
78
+
79
+
80
+ class AnnotationStrategy(ABC):
81
+ """Abstract base class for annotation strategies."""
82
+
83
+ @abstractmethod
84
+ def process_task(self, context: ToTaskContext, task_id: int, task_data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
85
+ """Process a single task for annotation.
86
+
87
+ Args:
88
+ context: Shared context for the action execution
89
+ task_id: The task ID to process
90
+ task_data: The task data dictionary
91
+ **kwargs: Additional method-specific parameters
92
+
93
+ Returns:
94
+ Dict with 'success' boolean and optional 'error' message
95
+ """
96
+ pass
97
+
98
+
99
+ class PreProcessorStrategy(ABC):
100
+ """Abstract base class for pre-processor management strategies."""
101
+
102
+ @abstractmethod
103
+ def get_preprocessor_info(self, context: ToTaskContext, preprocessor_id: int) -> Dict[str, Any]:
104
+ """Get pre-processor information.
105
+
106
+ Args:
107
+ context: Shared context for the action execution
108
+ preprocessor_id: The pre-processor ID
109
+
110
+ Returns:
111
+ Dict with pre-processor info or error
112
+ """
113
+ pass
114
+
115
+ @abstractmethod
116
+ def ensure_preprocessor_running(self, context: ToTaskContext, preprocessor_code: str) -> Dict[str, Any]:
117
+ """Ensure pre-processor is running.
118
+
119
+ Args:
120
+ context: Shared context for the action execution
121
+ preprocessor_code: The pre-processor code
122
+
123
+ Returns:
124
+ Dict indicating success or failure
125
+ """
126
+ pass
127
+
128
+
129
+ class DataExtractionStrategy(ABC):
130
+ """Abstract base class for data extraction strategies."""
131
+
132
+ @abstractmethod
133
+ def extract_data(self, context: ToTaskContext, task_data: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]:
134
+ """Extract required data from task.
135
+
136
+ Args:
137
+ context: Shared context for the action execution
138
+ task_data: The task data dictionary
139
+
140
+ Returns:
141
+ Tuple of extracted data values
142
+ """
143
+ pass
144
+
145
+
146
+ class MetricsStrategy(ABC):
147
+ """Abstract base class for metrics strategies."""
148
+
149
+ @abstractmethod
150
+ def update_progress(self, context: ToTaskContext, current: int, total: int):
151
+ """Update progress tracking.
152
+
153
+ Args:
154
+ context: Shared context for the action execution
155
+ current: Current progress count
156
+ total: Total items to process
157
+ """
158
+ pass
159
+
160
+ @abstractmethod
161
+ def record_task_result(self, context: ToTaskContext, task_id: int, success: bool, error: Optional[str] = None):
162
+ """Record the result of processing a single task.
163
+
164
+ Args:
165
+ context: Shared context for the action execution
166
+ task_id: The task ID that was processed
167
+ success: Whether the task processing was successful
168
+ error: Error message if unsuccessful
169
+ """
170
+ pass
@@ -0,0 +1,83 @@
1
+ """Data extraction strategies for ToTask action."""
2
+
3
+ from typing import Any, Dict, Optional, Tuple
4
+
5
+ from .base import DataExtractionStrategy, ToTaskContext
6
+
7
+
8
+ class FileUrlExtractionStrategy(DataExtractionStrategy):
9
+ """Strategy for extracting file URLs from task data."""
10
+
11
+ def extract_data(self, context: ToTaskContext, task_data: Dict[str, Any]) -> str | None:
12
+ """Extract primary file URL from task data.
13
+
14
+ Args:
15
+ context: Shared context for the action execution
16
+ task_data: The task data dictionary
17
+
18
+ Returns:
19
+ str: The primary file URL or None if not found
20
+ """
21
+ try:
22
+ # This implementation follows the original _extract_primary_file_url logic
23
+ data_unit = task_data.get('data_unit')
24
+ if not data_unit:
25
+ return None
26
+
27
+ data_unit_files = data_unit.get('files', {})
28
+ if not data_unit_files:
29
+ return None
30
+
31
+ # Find primary file URL
32
+ for key in data_unit_files:
33
+ if data_unit_files[key]['is_primary']:
34
+ return data_unit_files[key]['url']
35
+
36
+ return None
37
+
38
+ except Exception as e:
39
+ context.logger.log_message_with_code(
40
+ context.logger.LogCode.DATA_EXTRACTION_FAILED
41
+ if hasattr(context.logger, 'LogCode')
42
+ else 'DATA_EXTRACTION_FAILED',
43
+ str(e),
44
+ )
45
+ return None
46
+
47
+
48
+ class InferenceDataExtractionStrategy(DataExtractionStrategy):
49
+ """Strategy for extracting inference data from task data."""
50
+
51
+ def extract_data(self, context: ToTaskContext, task_data: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]:
52
+ """Extract data needed for inference processing.
53
+
54
+ Args:
55
+ context: Shared context for the action execution
56
+ task_data: The task data dictionary
57
+
58
+ Returns:
59
+ Tuple of (primary_file_url, inference_metadata)
60
+ """
61
+ try:
62
+ # Reuse the file URL extraction logic
63
+ file_strategy = FileUrlExtractionStrategy()
64
+ primary_url = file_strategy.extract_data(context, task_data)
65
+
66
+ # Extract additional inference-specific metadata
67
+ data_unit = task_data.get('data_unit', {})
68
+ inference_metadata = {
69
+ 'data_unit_id': data_unit.get('id'),
70
+ 'task_id': task_data.get('id'),
71
+ 'additional_params': context.params.get('inference_params', {}),
72
+ }
73
+
74
+ return primary_url, str(inference_metadata) if inference_metadata else None
75
+
76
+ except Exception as e:
77
+ context.logger.log_message_with_code(
78
+ context.logger.LogCode.DATA_EXTRACTION_FAILED
79
+ if hasattr(context.logger, 'LogCode')
80
+ else 'DATA_EXTRACTION_FAILED',
81
+ str(e),
82
+ )
83
+ return None, None
@@ -0,0 +1,87 @@
1
+ """Metrics and progress tracking strategies for ToTask action."""
2
+
3
+ from typing import Optional
4
+
5
+ from ..enums import AnnotateTaskDataStatus, LogCode
6
+ from .base import MetricsStrategy, ToTaskContext
7
+
8
+
9
+ class ProgressTrackingStrategy(MetricsStrategy):
10
+ """Strategy for tracking progress and metrics during task processing."""
11
+
12
+ def update_progress(self, context: ToTaskContext, current: int, total: int):
13
+ """Update progress tracking.
14
+
15
+ Args:
16
+ context: Shared context for the action execution
17
+ current: Current progress count
18
+ total: Total items to process
19
+ """
20
+ try:
21
+ context.logger.set_progress(current, total, category='annotate_task_data')
22
+ except Exception as e:
23
+ context.logger.log_message_with_code(LogCode.PROGRESS_UPDATE_FAILED, str(e))
24
+
25
+ def record_task_result(self, context: ToTaskContext, task_id: int, success: bool, error: Optional[str] = None):
26
+ """Record the result of processing a single task.
27
+
28
+ Args:
29
+ context: Shared context for the action execution
30
+ task_id: The task ID that was processed
31
+ success: Whether the task processing was successful
32
+ error: Error message if unsuccessful
33
+ """
34
+ try:
35
+ if success:
36
+ status = AnnotateTaskDataStatus.SUCCESS
37
+ log_data = {'task_id': task_id}
38
+ else:
39
+ status = AnnotateTaskDataStatus.FAILED
40
+ log_data = {'task_id': task_id, 'error': error or 'Unknown error'}
41
+
42
+ context.logger.log_annotate_task_data(log_data, status)
43
+
44
+ except Exception as e:
45
+ context.logger.log_message_with_code(LogCode.METRICS_RECORDING_FAILED, str(e))
46
+
47
+ def update_metrics(self, context: ToTaskContext, total_tasks: int, success_count: int, failed_count: int):
48
+ """Update execution metrics.
49
+
50
+ Args:
51
+ context: Shared context for the action execution
52
+ total_tasks: Total number of tasks
53
+ success_count: Number of successful tasks
54
+ failed_count: Number of failed tasks
55
+ """
56
+ try:
57
+ stand_by_count = total_tasks - success_count - failed_count
58
+ context.update_metrics(success_count, failed_count, total_tasks)
59
+
60
+ # Update metrics in the logger
61
+ metrics_data = {
62
+ 'stand_by': stand_by_count,
63
+ 'failed': failed_count,
64
+ 'success': success_count,
65
+ }
66
+ context.logger.log_metrics(metrics_data, 'annotate_task_data')
67
+
68
+ except Exception as e:
69
+ context.logger.log_message_with_code(LogCode.METRICS_UPDATE_FAILED, str(e))
70
+
71
+ def finalize_metrics(self, context: ToTaskContext):
72
+ """Finalize metrics at the end of processing.
73
+
74
+ Args:
75
+ context: Shared context for the action execution
76
+ """
77
+ try:
78
+ metrics = context.metrics
79
+ total_tasks = len(context.task_ids)
80
+
81
+ context.logger.log_message_with_code(LogCode.ANNOTATION_COMPLETED, metrics.success, metrics.failed)
82
+
83
+ # Final progress update
84
+ self.update_progress(context, total_tasks, total_tasks)
85
+
86
+ except Exception as e:
87
+ context.logger.log_message_with_code(LogCode.METRICS_FINALIZATION_FAILED, str(e))