synapse-sdk 1.0.0b5__py3-none-any.whl → 2025.12.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/code_server.py +305 -33
  3. synapse_sdk/clients/agent/__init__.py +2 -1
  4. synapse_sdk/clients/agent/container.py +143 -0
  5. synapse_sdk/clients/agent/ray.py +296 -38
  6. synapse_sdk/clients/backend/annotation.py +1 -1
  7. synapse_sdk/clients/backend/core.py +31 -4
  8. synapse_sdk/clients/backend/data_collection.py +82 -7
  9. synapse_sdk/clients/backend/hitl.py +1 -1
  10. synapse_sdk/clients/backend/ml.py +1 -1
  11. synapse_sdk/clients/base.py +211 -61
  12. synapse_sdk/loggers.py +46 -0
  13. synapse_sdk/plugins/README.md +1340 -0
  14. synapse_sdk/plugins/categories/base.py +59 -9
  15. synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
  16. synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
  17. synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
  18. synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
  19. synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
  20. synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
  21. synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
  22. synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
  23. synapse_sdk/plugins/categories/export/templates/config.yaml +19 -1
  24. synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
  25. synapse_sdk/plugins/categories/export/templates/plugin/export.py +153 -177
  26. synapse_sdk/plugins/categories/neural_net/actions/train.py +1130 -32
  27. synapse_sdk/plugins/categories/neural_net/actions/tune.py +157 -4
  28. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +7 -4
  29. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
  30. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
  31. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/action.py +10 -0
  32. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
  33. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +148 -0
  34. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
  35. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
  36. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
  37. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +100 -0
  38. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +248 -0
  39. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
  40. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
  41. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +265 -0
  42. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
  43. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
  44. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +92 -0
  45. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +243 -0
  46. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
  47. synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
  48. synapse_sdk/plugins/categories/upload/actions/upload/action.py +236 -0
  49. synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
  50. synapse_sdk/plugins/categories/upload/actions/upload/enums.py +493 -0
  51. synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
  52. synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
  53. synapse_sdk/plugins/categories/upload/actions/upload/models.py +214 -0
  54. synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
  55. synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
  56. synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
  57. synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
  58. synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
  59. synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
  60. synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
  61. synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +91 -0
  62. synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
  63. synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
  64. synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +201 -0
  65. synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +104 -0
  66. synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
  67. synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
  68. synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
  69. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
  70. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
  71. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
  72. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
  73. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +300 -0
  74. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +287 -0
  75. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
  76. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
  77. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
  78. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
  79. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
  80. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
  81. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
  82. synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
  83. synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
  84. synapse_sdk/plugins/categories/upload/templates/config.yaml +28 -2
  85. synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +310 -0
  86. synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +82 -20
  87. synapse_sdk/plugins/models.py +111 -9
  88. synapse_sdk/plugins/templates/plugin-config-schema.json +7 -0
  89. synapse_sdk/plugins/templates/schema.json +7 -0
  90. synapse_sdk/plugins/utils/__init__.py +3 -0
  91. synapse_sdk/plugins/utils/ray_gcs.py +66 -0
  92. synapse_sdk/shared/__init__.py +25 -0
  93. synapse_sdk/utils/converters/dm/__init__.py +42 -41
  94. synapse_sdk/utils/converters/dm/base.py +137 -0
  95. synapse_sdk/utils/converters/dm/from_v1.py +208 -562
  96. synapse_sdk/utils/converters/dm/to_v1.py +258 -304
  97. synapse_sdk/utils/converters/dm/tools/__init__.py +214 -0
  98. synapse_sdk/utils/converters/dm/tools/answer.py +95 -0
  99. synapse_sdk/utils/converters/dm/tools/bounding_box.py +132 -0
  100. synapse_sdk/utils/converters/dm/tools/bounding_box_3d.py +121 -0
  101. synapse_sdk/utils/converters/dm/tools/classification.py +75 -0
  102. synapse_sdk/utils/converters/dm/tools/keypoint.py +117 -0
  103. synapse_sdk/utils/converters/dm/tools/named_entity.py +111 -0
  104. synapse_sdk/utils/converters/dm/tools/polygon.py +122 -0
  105. synapse_sdk/utils/converters/dm/tools/polyline.py +124 -0
  106. synapse_sdk/utils/converters/dm/tools/prompt.py +94 -0
  107. synapse_sdk/utils/converters/dm/tools/relation.py +86 -0
  108. synapse_sdk/utils/converters/dm/tools/segmentation.py +141 -0
  109. synapse_sdk/utils/converters/dm/tools/segmentation_3d.py +83 -0
  110. synapse_sdk/utils/converters/dm/types.py +168 -0
  111. synapse_sdk/utils/converters/dm/utils.py +162 -0
  112. synapse_sdk/utils/converters/dm_legacy/__init__.py +56 -0
  113. synapse_sdk/utils/converters/dm_legacy/from_v1.py +627 -0
  114. synapse_sdk/utils/converters/dm_legacy/to_v1.py +367 -0
  115. synapse_sdk/utils/file/__init__.py +58 -0
  116. synapse_sdk/utils/file/archive.py +32 -0
  117. synapse_sdk/utils/file/checksum.py +56 -0
  118. synapse_sdk/utils/file/chunking.py +31 -0
  119. synapse_sdk/utils/file/download.py +385 -0
  120. synapse_sdk/utils/file/encoding.py +40 -0
  121. synapse_sdk/utils/file/io.py +22 -0
  122. synapse_sdk/utils/file/upload.py +165 -0
  123. synapse_sdk/utils/file/video/__init__.py +29 -0
  124. synapse_sdk/utils/file/video/transcode.py +307 -0
  125. synapse_sdk/utils/{file.py → file.py.backup} +77 -0
  126. synapse_sdk/utils/network.py +272 -0
  127. synapse_sdk/utils/storage/__init__.py +6 -2
  128. synapse_sdk/utils/storage/providers/file_system.py +6 -0
  129. {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/METADATA +19 -2
  130. {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/RECORD +134 -74
  131. synapse_sdk/devtools/docs/.gitignore +0 -20
  132. synapse_sdk/devtools/docs/README.md +0 -41
  133. synapse_sdk/devtools/docs/blog/2019-05-28-first-blog-post.md +0 -12
  134. synapse_sdk/devtools/docs/blog/2019-05-29-long-blog-post.md +0 -44
  135. synapse_sdk/devtools/docs/blog/2021-08-01-mdx-blog-post.mdx +0 -24
  136. synapse_sdk/devtools/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  137. synapse_sdk/devtools/docs/blog/2021-08-26-welcome/index.md +0 -29
  138. synapse_sdk/devtools/docs/blog/authors.yml +0 -25
  139. synapse_sdk/devtools/docs/blog/tags.yml +0 -19
  140. synapse_sdk/devtools/docs/docusaurus.config.ts +0 -138
  141. synapse_sdk/devtools/docs/package-lock.json +0 -17455
  142. synapse_sdk/devtools/docs/package.json +0 -47
  143. synapse_sdk/devtools/docs/sidebars.ts +0 -44
  144. synapse_sdk/devtools/docs/src/components/HomepageFeatures/index.tsx +0 -71
  145. synapse_sdk/devtools/docs/src/components/HomepageFeatures/styles.module.css +0 -11
  146. synapse_sdk/devtools/docs/src/css/custom.css +0 -30
  147. synapse_sdk/devtools/docs/src/pages/index.module.css +0 -23
  148. synapse_sdk/devtools/docs/src/pages/index.tsx +0 -21
  149. synapse_sdk/devtools/docs/src/pages/markdown-page.md +0 -7
  150. synapse_sdk/devtools/docs/static/.nojekyll +0 -0
  151. synapse_sdk/devtools/docs/static/img/docusaurus-social-card.jpg +0 -0
  152. synapse_sdk/devtools/docs/static/img/docusaurus.png +0 -0
  153. synapse_sdk/devtools/docs/static/img/favicon.ico +0 -0
  154. synapse_sdk/devtools/docs/static/img/logo.png +0 -0
  155. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
  156. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_react.svg +0 -170
  157. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  158. synapse_sdk/devtools/docs/tsconfig.json +0 -8
  159. synapse_sdk/plugins/categories/export/actions/export.py +0 -346
  160. synapse_sdk/plugins/categories/export/enums.py +0 -7
  161. synapse_sdk/plugins/categories/neural_net/actions/gradio.py +0 -151
  162. synapse_sdk/plugins/categories/pre_annotation/actions/to_task.py +0 -943
  163. synapse_sdk/plugins/categories/upload/actions/upload.py +0 -954
  164. {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/WHEEL +0 -0
  165. {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/entry_points.txt +0 -0
  166. {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/licenses/LICENSE +0 -0
  167. {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,248 @@
1
+ """Orchestrator for coordinating ToTask action workflow using Facade pattern."""
2
+
3
+ from typing import Any, Dict
4
+
5
+ from synapse_sdk.clients.backend.models import JobStatus
6
+
7
+ from .enums import AnnotationMethod, LogCode
8
+ from .exceptions import CriticalError, PreAnnotationToTaskFailed
9
+ from .factory import ToTaskStrategyFactory
10
+ from .models import ToTaskResult
11
+ from .strategies.base import ToTaskContext
12
+
13
+
14
+ class ToTaskOrchestrator:
15
+ """Facade that orchestrates the complete ToTask annotation workflow."""
16
+
17
+ def __init__(self, context: ToTaskContext):
18
+ """Initialize orchestrator with context and strategies.
19
+
20
+ Args:
21
+ context: Shared context for the action execution
22
+ """
23
+ self.context = context
24
+ self.factory = ToTaskStrategyFactory()
25
+ self.steps_completed = []
26
+
27
+ # Initialize strategies
28
+ self.project_validation = self.factory.create_validation_strategy('project')
29
+ self.task_validation = self.factory.create_validation_strategy('task')
30
+ self.target_spec_validation = self.factory.create_validation_strategy('target_spec')
31
+ self.metrics_strategy = self.factory.create_metrics_strategy()
32
+
33
+ def execute_workflow(self) -> Dict[str, Any]:
34
+ """Execute the complete ToTask workflow with rollback support.
35
+
36
+ Returns:
37
+ Dict containing the workflow result
38
+ """
39
+ try:
40
+ # Step 1: Project and data collection validation
41
+ self._execute_step('project_validation', self._validate_project)
42
+
43
+ # Step 2: Task discovery and validation
44
+ self._execute_step('task_validation', self._validate_tasks)
45
+
46
+ # Step 3: Determine annotation method
47
+ self._execute_step('method_determination', self._determine_annotation_method)
48
+
49
+ # Step 4: Method-specific validation
50
+ self._execute_step('method_validation', self._validate_annotation_method)
51
+
52
+ # Step 5: Initialize processing
53
+ self._execute_step('processing_initialization', self._initialize_processing)
54
+
55
+ # Step 6: Process all tasks
56
+ self._execute_step('task_processing', self._process_all_tasks)
57
+
58
+ # Step 7: Finalize metrics and progress
59
+ self._execute_step('finalization', self._finalize_processing)
60
+
61
+ # Return success result
62
+ result = ToTaskResult(status=JobStatus.SUCCEEDED, message='Pre-annotation to task completed successfully')
63
+ return result.model_dump()
64
+
65
+ except Exception as e:
66
+ # Mark progress as failed with elapsed time
67
+ self.context.logger.set_progress_failed(category='annotate_task_data')
68
+ self._rollback_completed_steps()
69
+ if isinstance(e, PreAnnotationToTaskFailed):
70
+ raise e
71
+ raise PreAnnotationToTaskFailed(f'Workflow failed at step {len(self.steps_completed)}: {e}')
72
+
73
+ def _execute_step(self, step_name: str, step_func: callable):
74
+ """Execute a workflow step with error handling and progress tracking.
75
+
76
+ Args:
77
+ step_name: Name of the step for logging
78
+ step_func: Function to execute for this step
79
+
80
+ Returns:
81
+ Result of the step function
82
+ """
83
+ self.context.logger.log_message_with_code(LogCode.STEP_STARTED, step_name)
84
+
85
+ try:
86
+ result = step_func()
87
+ self.steps_completed.append(step_name)
88
+ self.context.logger.log_message_with_code(LogCode.STEP_COMPLETED, step_name)
89
+ return result
90
+ except Exception as e:
91
+ self.context.logger.log_message_with_code(LogCode.STEP_FAILED, step_name, str(e))
92
+ raise
93
+
94
+ def _validate_project(self):
95
+ """Step 1: Validate project and data collection."""
96
+ result = self.project_validation.validate(self.context)
97
+ if not result['success']:
98
+ error_msg = result.get('error', 'Project validation failed')
99
+ raise PreAnnotationToTaskFailed(error_msg)
100
+
101
+ def _validate_tasks(self):
102
+ """Step 2: Discover and validate tasks."""
103
+ result = self.task_validation.validate(self.context)
104
+ if not result['success']:
105
+ error_msg = result.get('error', 'Task validation failed')
106
+ raise PreAnnotationToTaskFailed(error_msg)
107
+
108
+ def _determine_annotation_method(self):
109
+ """Step 3: Determine annotation method from parameters."""
110
+ method = self.context.params.get('method')
111
+ if method == AnnotationMethod.FILE:
112
+ self.context.annotation_method = AnnotationMethod.FILE
113
+ elif method == AnnotationMethod.INFERENCE:
114
+ self.context.annotation_method = AnnotationMethod.INFERENCE
115
+ else:
116
+ self.context.logger.log_message_with_code(LogCode.UNSUPPORTED_METHOD, method)
117
+ raise PreAnnotationToTaskFailed(f'Unsupported annotation method: {method}')
118
+
119
+ def _validate_annotation_method(self):
120
+ """Step 4: Validate method-specific requirements."""
121
+ if self.context.annotation_method == AnnotationMethod.FILE:
122
+ result = self.target_spec_validation.validate(self.context)
123
+ if not result['success']:
124
+ error_msg = result.get('error', 'Target specification validation failed')
125
+ raise PreAnnotationToTaskFailed(error_msg)
126
+
127
+ def _initialize_processing(self):
128
+ """Step 5: Initialize processing metrics and progress."""
129
+ total_tasks = len(self.context.task_ids)
130
+ self.context.update_metrics(0, 0, total_tasks)
131
+ self.metrics_strategy.update_progress(self.context, 0, total_tasks)
132
+ self.context.logger.log_message_with_code(LogCode.ANNOTATING_DATA)
133
+
134
+ def _process_all_tasks(self):
135
+ """Step 6: Process all tasks using appropriate annotation strategy."""
136
+ annotation_strategy = self.factory.create_annotation_strategy(self.context.annotation_method)
137
+
138
+ total_tasks = len(self.context.task_ids)
139
+ success_count = 0
140
+ failed_count = 0
141
+ current_progress = 0
142
+
143
+ # Get task parameters
144
+ task_params = {
145
+ 'fields': 'id,data,data_unit',
146
+ 'expand': 'data_unit',
147
+ }
148
+
149
+ # Process each task
150
+ for task_id in self.context.task_ids:
151
+ try:
152
+ # Get task data
153
+ task_response = self.context.client.get_task(task_id, params=task_params)
154
+ if isinstance(task_response, str):
155
+ error_msg = 'Invalid task response'
156
+ self.context.logger.log_annotate_task_event(LogCode.INVALID_TASK_RESPONSE, task_id)
157
+ self.metrics_strategy.record_task_result(self.context, task_id, False, error_msg)
158
+ failed_count += 1
159
+ continue
160
+
161
+ task_data: Dict[str, Any] = task_response
162
+
163
+ # Process task using annotation strategy
164
+ if self.context.annotation_method == AnnotationMethod.FILE:
165
+ target_spec_name = self.context.params.get('target_specification_name')
166
+ result = annotation_strategy.process_task(
167
+ self.context, task_id, task_data, target_specification_name=target_spec_name
168
+ )
169
+ else:
170
+ result = annotation_strategy.process_task(self.context, task_id, task_data)
171
+
172
+ # Record result
173
+ if result['success']:
174
+ success_count += 1
175
+ self.metrics_strategy.record_task_result(self.context, task_id, True)
176
+ else:
177
+ failed_count += 1
178
+ error_msg = result.get('error', 'Unknown error')
179
+ self.metrics_strategy.record_task_result(self.context, task_id, False, error_msg)
180
+
181
+ # Update progress
182
+ current_progress += 1
183
+ self.context.update_metrics(success_count, failed_count, total_tasks)
184
+ self.metrics_strategy.update_progress(self.context, current_progress, total_tasks)
185
+ self.metrics_strategy.update_metrics(self.context, total_tasks, success_count, failed_count)
186
+
187
+ except CriticalError:
188
+ self.context.logger.log_message_with_code(LogCode.CRITICAL_ERROR)
189
+ raise PreAnnotationToTaskFailed('Critical error occurred during task processing')
190
+
191
+ except Exception as e:
192
+ self.context.logger.log_annotate_task_event(LogCode.TASK_PROCESSING_FAILED, task_id, str(e))
193
+ self.metrics_strategy.record_task_result(self.context, task_id, False, str(e))
194
+ failed_count += 1
195
+ current_progress += 1
196
+ self.context.update_metrics(success_count, failed_count, total_tasks)
197
+ self.metrics_strategy.update_progress(self.context, current_progress, total_tasks)
198
+ self.metrics_strategy.update_metrics(self.context, total_tasks, success_count, failed_count)
199
+
200
+ def _finalize_processing(self):
201
+ """Step 7: Finalize metrics."""
202
+ # Finalize metrics
203
+ self.metrics_strategy.finalize_metrics(self.context)
204
+
205
+ def _rollback_completed_steps(self):
206
+ """Rollback completed steps in reverse order."""
207
+ for step in reversed(self.steps_completed):
208
+ try:
209
+ rollback_method = getattr(self, f'_rollback_{step}', None)
210
+ if rollback_method:
211
+ rollback_method()
212
+ except Exception as e:
213
+ self.context.logger.log_message_with_code(LogCode.ROLLBACK_FAILED, step, str(e))
214
+
215
+ # Execute any additional rollback actions
216
+ for action in reversed(self.context.rollback_actions):
217
+ try:
218
+ action()
219
+ except Exception as e:
220
+ self.context.logger.log_message_with_code(LogCode.ROLLBACK_ACTION_FAILED, str(e))
221
+
222
+ def _rollback_project_validation(self):
223
+ """Rollback project validation step."""
224
+ # Clear cached project and data collection data
225
+ self.context.project = None
226
+ self.context.data_collection = None
227
+
228
+ def _rollback_task_validation(self):
229
+ """Rollback task validation step."""
230
+ # Clear cached task data
231
+ self.context.task_ids = []
232
+
233
+ def _rollback_processing_initialization(self):
234
+ """Rollback processing initialization step."""
235
+ # Reset metrics
236
+ self.context.update_metrics(0, 0, 0)
237
+
238
+ def _rollback_task_processing(self):
239
+ """Rollback task processing step."""
240
+ # Clean up any temporary files
241
+ for temp_file in self.context.temp_files:
242
+ try:
243
+ import os
244
+
245
+ if os.path.exists(temp_file):
246
+ os.remove(temp_file)
247
+ except Exception:
248
+ pass # Best effort cleanup
@@ -0,0 +1,64 @@
1
+ import json
2
+ from datetime import datetime
3
+ from typing import Any, Dict, Optional
4
+
5
+ from synapse_sdk.plugins.models import Run
6
+ from synapse_sdk.shared.enums import Context
7
+
8
+ from .enums import LOG_MESSAGES, AnnotateTaskDataStatus, LogCode
9
+ from .models import AnnotateTaskDataLog, AnnotateTaskEventLog, MetricsRecord
10
+
11
+
12
+ class ToTaskRun(Run):
13
+ def log_message_with_code(self, code: LogCode, *args, level: Optional[Context] = None):
14
+ """Log message using predefined code and optional level override."""
15
+ if code not in LOG_MESSAGES:
16
+ self.log_message(f'Unknown log code: {code}')
17
+ return
18
+
19
+ log_config = LOG_MESSAGES[code]
20
+ message = log_config['message'].format(*args) if args else log_config['message']
21
+ log_level = level or log_config['level']
22
+
23
+ if log_level:
24
+ self.log_message(message, context=log_level.value)
25
+ else:
26
+ self.log_message(message, context=Context.INFO.value)
27
+
28
+ def log_annotate_task_event(self, code: LogCode, *args, level: Optional[Context] = None):
29
+ """Log annotate task event using predefined code."""
30
+ if code not in LOG_MESSAGES:
31
+ now = datetime.now().isoformat()
32
+ self.log(
33
+ 'annotate_task_event',
34
+ AnnotateTaskEventLog(info=f'Unknown log code: {code}', status=Context.DANGER, created=now).model_dump(),
35
+ )
36
+ return
37
+
38
+ log_config = LOG_MESSAGES[code]
39
+ message = log_config['message'].format(*args) if args else log_config['message']
40
+ log_level = level or log_config['level'] or Context.INFO
41
+
42
+ now = datetime.now().isoformat()
43
+ self.log(
44
+ 'annotate_task_event',
45
+ AnnotateTaskEventLog(info=message, status=log_level, created=now).model_dump(),
46
+ )
47
+
48
+ def log_annotate_task_data(self, task_info: Dict[str, Any], status: AnnotateTaskDataStatus):
49
+ """Log annotate task data."""
50
+ now = datetime.now().isoformat()
51
+ self.log(
52
+ 'annotate_task_data',
53
+ AnnotateTaskDataLog(task_info=json.dumps(task_info), status=status, created=now).model_dump(),
54
+ )
55
+
56
+ def log_metrics(self, record: MetricsRecord, category: str):
57
+ """Log FileToTask metrics.
58
+
59
+ Args:
60
+ record (MetricsRecord): The metrics record to log.
61
+ category (str): The category of the metrics.
62
+ """
63
+ record = MetricsRecord.model_validate(record)
64
+ self.set_metrics(value=record.model_dump(), category=category)
@@ -0,0 +1,17 @@
1
+ """Strategy classes for ToTask action refactoring."""
2
+
3
+ from .base import (
4
+ AnnotationStrategy,
5
+ DataExtractionStrategy,
6
+ MetricsStrategy,
7
+ PreProcessorStrategy,
8
+ ValidationStrategy,
9
+ )
10
+
11
+ __all__ = [
12
+ 'AnnotationStrategy',
13
+ 'DataExtractionStrategy',
14
+ 'MetricsStrategy',
15
+ 'PreProcessorStrategy',
16
+ 'ValidationStrategy',
17
+ ]
@@ -0,0 +1,265 @@
1
+ """Annotation strategies for ToTask action."""
2
+
3
+ from typing import Any, Dict
4
+
5
+ from ..enums import 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
+ return {'success': False, 'error': error_msg}
37
+
38
+ # Get data unit files
39
+ data_unit_files = data_unit.get('files', {})
40
+ if not data_unit_files:
41
+ error_msg = 'Data unit does not have files'
42
+ logger.log_annotate_task_event(LogCode.NO_DATA_UNIT_FILES, task_id)
43
+ return {'success': False, 'error': error_msg}
44
+
45
+ # Extract primary file URL from task data
46
+ primary_file_url, primary_file_original_name = self._extract_primary_file_url(task_data)
47
+ if not primary_file_url:
48
+ error_msg = 'Primary image URL not found in task data'
49
+ logger.log_annotate_task_event(LogCode.PRIMARY_IMAGE_URL_NOT_FOUND, task_id)
50
+ return {'success': False, 'error': error_msg}
51
+
52
+ # Get target specification file
53
+ target_file = data_unit_files.get(target_specification_name)
54
+ if not target_file:
55
+ error_msg = 'File specification not found'
56
+ logger.log_annotate_task_event(LogCode.FILE_SPEC_NOT_FOUND, task_id)
57
+ return {'success': False, 'error': error_msg}
58
+
59
+ # Get target file details
60
+ target_file_url = target_file.get('url')
61
+ target_file_original_name = target_file.get('file_name_original')
62
+
63
+ if not target_file_original_name:
64
+ error_msg = 'File original name not found'
65
+ logger.log_annotate_task_event(LogCode.FILE_ORIGINAL_NAME_NOT_FOUND, task_id)
66
+ return {'success': False, 'error': error_msg}
67
+
68
+ if not target_file_url:
69
+ error_msg = 'URL not found'
70
+ logger.log_annotate_task_event(LogCode.URL_NOT_FOUND, task_id)
71
+ return {'success': False, 'error': error_msg}
72
+
73
+ # Fetch and process the data using template
74
+ try:
75
+ # Convert data to task object using action's entrypoint
76
+ annotation_to_task = context.entrypoint(logger)
77
+ converted_data = annotation_to_task.convert_data_from_file(
78
+ primary_file_url, primary_file_original_name, target_file_url, target_file_original_name
79
+ )
80
+ except Exception as e:
81
+ if 'requests' in str(type(e)):
82
+ error_msg = f'Failed to fetch data from URL: {str(e)}'
83
+ logger.log_annotate_task_event(LogCode.FETCH_DATA_FAILED, target_file_url, task_id)
84
+ else:
85
+ error_msg = f'Failed to convert data to task object: {str(e)}'
86
+ logger.log_annotate_task_event(LogCode.CONVERT_DATA_FAILED, str(e), task_id)
87
+ return {'success': False, 'error': error_msg}
88
+
89
+ # Submit annotation data
90
+ try:
91
+ client.annotate_task_data(task_id, data={'action': 'submit', 'data': converted_data})
92
+ return {'success': True}
93
+ except Exception as e:
94
+ error_msg = f'Failed to submit annotation data: {str(e)}'
95
+ logger.log_annotate_task_event(LogCode.ANNOTATION_SUBMISSION_FAILED, task_id, str(e))
96
+ return {'success': False, 'error': error_msg}
97
+
98
+ except Exception as e:
99
+ error_msg = f'Failed to process file annotation for task {task_id}: {str(e)}'
100
+ context.logger.log_annotate_task_event(LogCode.TASK_PROCESSING_FAILED, task_id, str(e))
101
+ return {'success': False, 'error': error_msg}
102
+
103
+ def _extract_primary_file_url(self, task_data: Dict[str, Any]) -> tuple:
104
+ """Extract the primary file URL from task data.
105
+
106
+ Args:
107
+ task_data: The task data dictionary
108
+
109
+ Returns:
110
+ Tuple of (primary_file_url, primary_file_original_name)
111
+ """
112
+ data_unit = task_data.get('data_unit', {})
113
+ files = data_unit.get('files', {})
114
+
115
+ for file_info in files.values():
116
+ if isinstance(file_info, dict) and file_info.get('is_primary') and file_info.get('url'):
117
+ return file_info['url'], file_info.get('file_name_original')
118
+
119
+ return None, None
120
+
121
+
122
+ class InferenceAnnotationStrategy(AnnotationStrategy):
123
+ """Strategy for inference-based annotation processing."""
124
+
125
+ def process_task(self, context: ToTaskContext, task_id: int, task_data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
126
+ """Process a single task for inference-based annotation.
127
+
128
+ Args:
129
+ context: Shared context for the action execution
130
+ task_id: The task ID to process
131
+ task_data: The task data dictionary
132
+ **kwargs: Additional parameters
133
+
134
+ Returns:
135
+ Dict with 'success' boolean and optional 'error' message
136
+ """
137
+ try:
138
+ client = context.client
139
+ logger = context.logger
140
+
141
+ # Get pre-processor ID from parameters
142
+ pre_processor_id = context.params.get('pre_processor')
143
+ if not pre_processor_id:
144
+ error_msg = 'Pre-processor ID is required for inference annotation method'
145
+ logger.log_annotate_task_event(LogCode.NO_PREPROCESSOR_ID, task_id)
146
+ return {'success': False, 'error': error_msg}
147
+
148
+ # Get pre-processor info using factory-created strategy
149
+ from ..factory import ToTaskStrategyFactory
150
+
151
+ factory = ToTaskStrategyFactory()
152
+ preprocessor_strategy = factory.create_preprocessor_strategy()
153
+
154
+ pre_processor_info = preprocessor_strategy.get_preprocessor_info(context, pre_processor_id)
155
+ if not pre_processor_info['success']:
156
+ error_msg = pre_processor_info.get('error', 'Failed to get pre-processor info')
157
+ logger.log_annotate_task_event(LogCode.INFERENCE_PREPROCESSOR_FAILED, task_id, error_msg)
158
+ return pre_processor_info
159
+
160
+ pre_processor_code = pre_processor_info['code']
161
+ pre_processor_version = pre_processor_info['version']
162
+
163
+ # Ensure pre-processor is running
164
+ pre_processor_status = preprocessor_strategy.ensure_preprocessor_running(context, pre_processor_code)
165
+ if not pre_processor_status['success']:
166
+ error_msg = pre_processor_status.get('error', 'Failed to ensure pre-processor running')
167
+ logger.log_annotate_task_event(LogCode.INFERENCE_PREPROCESSOR_FAILED, task_id, error_msg)
168
+ return pre_processor_status
169
+
170
+ # Extract primary file URL using factory-created strategy
171
+ extraction_strategy = factory.create_extraction_strategy(context.annotation_method)
172
+ primary_file_url, _ = extraction_strategy.extract_data(context, task_data)
173
+ if not primary_file_url:
174
+ error_msg = 'Primary image URL not found in task data'
175
+ logger.log_annotate_task_event(LogCode.PRIMARY_IMAGE_URL_NOT_FOUND, task_id)
176
+ return {'success': False, 'error': error_msg}
177
+
178
+ # Run inference
179
+ inference_result = self._run_inference(
180
+ client,
181
+ pre_processor_code,
182
+ pre_processor_version,
183
+ primary_file_url,
184
+ context.params['agent'],
185
+ context.params['model'],
186
+ pre_processor_params=context.params.get('pre_processor_params', {}),
187
+ )
188
+ if not inference_result['success']:
189
+ error_msg = inference_result.get('error', 'Failed to run inference')
190
+ logger.log_annotate_task_event(LogCode.INFERENCE_PREPROCESSOR_FAILED, task_id, error_msg)
191
+ return inference_result
192
+
193
+ # Convert and submit inference data
194
+ try:
195
+ # This would need to be injected or configured based on the action's entrypoint
196
+ # For now, we'll assume the conversion is done externally
197
+ inference_data = inference_result['data'] # Simplified for refactoring
198
+
199
+ annotation_to_task = context.entrypoint(logger)
200
+ converted_result = annotation_to_task.convert_data_from_inference(inference_data)
201
+
202
+ client.annotate_task_data(task_id, data={'action': 'submit', 'data': converted_result})
203
+ return {'success': True, 'pre_processor_id': pre_processor_id}
204
+
205
+ except Exception as e:
206
+ error_msg = f'Failed to convert/submit inference data: {str(e)}'
207
+ logger.log_annotate_task_event(LogCode.INFERENCE_PREPROCESSOR_FAILED, task_id, error_msg)
208
+ return {'success': False, 'error': error_msg}
209
+
210
+ except Exception as e:
211
+ error_msg = f'Failed to process inference for task {task_id}: {str(e)}'
212
+ context.logger.log_message_with_code(LogCode.INFERENCE_PROCESSING_FAILED, task_id, str(e))
213
+ return {'success': False, 'error': error_msg}
214
+
215
+ def _run_inference(
216
+ self,
217
+ client: Any,
218
+ pre_processor_code: str,
219
+ pre_processor_version: str,
220
+ primary_file_url: str,
221
+ agent: int,
222
+ model: int,
223
+ pre_processor_params: Dict[str, Any] = {},
224
+ ) -> Dict[str, Any]:
225
+ """Run inference using the pre-processor.
226
+
227
+ Args:
228
+ client: Backend client instance
229
+ pre_processor_code: Pre-processor code
230
+ pre_processor_version: Pre-processor version
231
+ primary_file_url: URL of the primary file to process
232
+ agent: Agent id for inference
233
+ model: Model id for inference
234
+ pre_processor_params: Additional parameters for the pre-processor
235
+
236
+ Returns:
237
+ Dict with inference results or error
238
+ """
239
+ try:
240
+ if not agent or not model:
241
+ return {'success': False, 'error': 'Parameters not available'}
242
+
243
+ pre_processor_params['image_path'] = primary_file_url
244
+
245
+ inference_payload = {
246
+ 'agent': agent,
247
+ 'action': 'inference',
248
+ 'version': pre_processor_version,
249
+ 'params': {
250
+ 'model': model,
251
+ 'method': 'post',
252
+ 'json': pre_processor_params,
253
+ },
254
+ }
255
+
256
+ inference_data = client.run_plugin(pre_processor_code, inference_payload)
257
+
258
+ # Every inference api should return None if failed to inference.
259
+ if inference_data is None:
260
+ return {'success': False, 'error': 'Inference data is None'}
261
+
262
+ return {'success': True, 'data': inference_data}
263
+
264
+ except Exception as e:
265
+ return {'success': False, 'error': f'Failed to run inference: {str(e)}'}