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,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,92 @@
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 - only set completion if some tasks succeeded
84
+ if metrics.success > 0:
85
+ # Success: Set completion progress
86
+ self.update_progress(context, total_tasks, total_tasks)
87
+ else:
88
+ # Failure: Mark as failed (all tasks failed)
89
+ context.logger.set_progress_failed(category='annotate_task_data')
90
+
91
+ except Exception as e:
92
+ context.logger.log_message_with_code(LogCode.METRICS_FINALIZATION_FAILED, str(e))
@@ -0,0 +1,243 @@
1
+ """Pre-processor management strategies for ToTask action."""
2
+
3
+ import time
4
+ from typing import Any, Dict, List
5
+
6
+ from .base import PreProcessorStrategy, ToTaskContext
7
+
8
+
9
+ class PreProcessorManagementStrategy(PreProcessorStrategy):
10
+ """Strategy for managing pre-processor lifecycle."""
11
+
12
+ def _get_preprocessor_config(self, context: ToTaskContext, preprocessor_id: int) -> Dict[str, Any]:
13
+ """Retrieve pre-processor configuration from the backend.
14
+
15
+ Args:
16
+ context: Shared context for the action execution
17
+ preprocessor_id: The pre-processor ID
18
+
19
+ Returns:
20
+ Dict with 'success', 'config', 'version', and optional 'error'
21
+ """
22
+ try:
23
+ client = context.client
24
+ pre_processor_response = client.get_plugin_release(preprocessor_id)
25
+
26
+ if isinstance(pre_processor_response, str):
27
+ return {'success': False, 'error': 'Invalid pre-processor response received'}
28
+
29
+ if not isinstance(pre_processor_response, dict):
30
+ return {'success': False, 'error': 'Unexpected pre-processor response format'}
31
+
32
+ config = pre_processor_response.get('config', {})
33
+ version = pre_processor_response.get('version')
34
+
35
+ return {'success': True, 'config': config, 'version': version}
36
+
37
+ except Exception as e:
38
+ return {'success': False, 'error': f'Failed to get pre-processor config: {str(e)}'}
39
+
40
+ def get_preprocessor_info(self, context: ToTaskContext, preprocessor_id: int) -> Dict[str, Any]:
41
+ """Get pre-processor information from the backend.
42
+
43
+ Args:
44
+ context: Shared context for the action execution
45
+ preprocessor_id: The pre-processor ID
46
+
47
+ Returns:
48
+ Dict with pre-processor info or error
49
+ """
50
+ config_result = self._get_preprocessor_config(context, preprocessor_id)
51
+ if not config_result['success']:
52
+ return config_result
53
+
54
+ config = config_result['config']
55
+ code = config.get('code')
56
+ version = config_result['version']
57
+
58
+ if not code or not version:
59
+ return {'success': False, 'error': 'Invalid pre-processor configuration'}
60
+
61
+ return {'success': True, 'code': code, 'version': version}
62
+
63
+ def _get_running_serve_apps(self, context: ToTaskContext, preprocessor_code: str) -> Dict[str, Any]:
64
+ """Get list of running serve applications for a preprocessor.
65
+
66
+ Args:
67
+ context: Shared context for the action execution
68
+ preprocessor_code: The pre-processor code
69
+
70
+ Returns:
71
+ Dict with 'success', 'running_apps' (list), and optional 'error'
72
+ """
73
+ try:
74
+ client = context.client
75
+ list_serve_applications_params = {
76
+ 'plugin_code': preprocessor_code,
77
+ 'job__agent': context.params.get('agent') if context.params else None,
78
+ }
79
+
80
+ serve_applications_response = client.list_serve_applications(params=list_serve_applications_params)
81
+ if isinstance(serve_applications_response, str):
82
+ return {'success': False, 'running_apps': [], 'error': 'Invalid serve applications response'}
83
+
84
+ if not isinstance(serve_applications_response, dict):
85
+ return {'success': False, 'running_apps': [], 'error': 'Unexpected serve applications response format'}
86
+
87
+ results = serve_applications_response.get('results', [])
88
+ running_apps: List[Dict[str, Any]] = [
89
+ app for app in results if isinstance(app, dict) and app.get('status') == 'RUNNING'
90
+ ]
91
+
92
+ return {'success': True, 'running_apps': running_apps}
93
+
94
+ except Exception as e:
95
+ return {'success': False, 'running_apps': [], 'error': f'Failed to get running serve apps: {str(e)}'}
96
+
97
+ def ensure_preprocessor_running(self, context: ToTaskContext, preprocessor_code: str) -> Dict[str, Any]:
98
+ """Ensure the pre-processor is running, restart if necessary.
99
+
100
+ Args:
101
+ context: Shared context for the action execution
102
+ preprocessor_code: The pre-processor code
103
+
104
+ Returns:
105
+ Dict indicating success or failure
106
+ """
107
+ try:
108
+ # Check if pre-processor is already running
109
+ result = self._get_running_serve_apps(context, preprocessor_code)
110
+ if not result['success']:
111
+ return {'success': False, 'error': result.get('error', 'Failed to check running apps')}
112
+
113
+ if result['running_apps']:
114
+ return {'success': True}
115
+
116
+ # If not running, restart the pre-processor
117
+ restart_result = self._restart_preprocessor(context, preprocessor_code)
118
+ if not restart_result['success']:
119
+ return restart_result
120
+
121
+ return {'success': True}
122
+
123
+ except Exception as e:
124
+ return {'success': False, 'error': f'Failed to ensure pre-processor running: {str(e)}'}
125
+
126
+ def _restart_preprocessor(self, context: ToTaskContext, preprocessor_code: str) -> Dict[str, Any]:
127
+ """Restart the pre-processor and wait for it to be running.
128
+
129
+ Starts the pre-processor deployment and polls for up to 3 minutes
130
+ to verify it is running, logging progress messages during the wait.
131
+
132
+ Args:
133
+ context: Shared context for the action execution
134
+ preprocessor_code: The pre-processor code
135
+
136
+ Returns:
137
+ Dict indicating success or failure
138
+ """
139
+ MAX_WAIT_SECONDS = 180 # 3 minutes
140
+ POLL_INTERVAL_SECONDS = 10
141
+
142
+ try:
143
+ # Start deployment
144
+ start_result = self._start_preprocessor_deployment(context, preprocessor_code)
145
+ if not start_result['success']:
146
+ return start_result
147
+
148
+ context.logger.log_message('Pre-processor deployment started, waiting for it to be ready...')
149
+
150
+ # Poll for running status with logging
151
+ return self._wait_for_preprocessor_ready(
152
+ context, preprocessor_code, MAX_WAIT_SECONDS, POLL_INTERVAL_SECONDS
153
+ )
154
+
155
+ except Exception as e:
156
+ return {'success': False, 'error': f'Failed to restart pre-processor: {str(e)}'}
157
+
158
+ def _start_preprocessor_deployment(self, context: ToTaskContext, preprocessor_code: str) -> Dict[str, Any]:
159
+ """Start the pre-processor deployment.
160
+
161
+ Args:
162
+ context: Shared context for the action execution
163
+ preprocessor_code: The pre-processor code
164
+
165
+ Returns:
166
+ Dict indicating success or failure with optional job_id
167
+ """
168
+ try:
169
+ # Retrieve Pre-Processor Configuration
170
+ pre_processor_id = context.params.get('pre_processor')
171
+ if not pre_processor_id:
172
+ return {'success': False, 'error': 'No pre-processor ID provided'}
173
+
174
+ config_result = self._get_preprocessor_config(context, pre_processor_id)
175
+ if not config_result['success']:
176
+ return config_result
177
+
178
+ config = config_result['config']
179
+ inference_config = config.get('actions', {}).get('inference', {})
180
+ required_resources = inference_config.get('required_resources', {})
181
+
182
+ # Build deployment payload
183
+ serve_application_deployment_payload = {
184
+ 'agent': context.params.get('agent') if context.params else None,
185
+ 'action': 'deployment',
186
+ 'params': {
187
+ 'num_cpus': required_resources.get('required_cpu_count', 1),
188
+ 'num_gpus': required_resources.get('required_gpu_count', 0.1),
189
+ },
190
+ 'debug': True,
191
+ }
192
+
193
+ deployment_result = context.client.run_plugin(
194
+ preprocessor_code,
195
+ serve_application_deployment_payload,
196
+ )
197
+
198
+ deployment_job_id = deployment_result.get('job_id')
199
+ if not deployment_job_id:
200
+ return {'success': False, 'error': 'No deployment job ID returned'}
201
+
202
+ return {'success': True, 'job_id': deployment_job_id}
203
+
204
+ except Exception as e:
205
+ return {'success': False, 'error': f'Failed to start deployment: {str(e)}'}
206
+
207
+ def _wait_for_preprocessor_ready(
208
+ self,
209
+ context: ToTaskContext,
210
+ preprocessor_code: str,
211
+ max_wait_seconds: int,
212
+ poll_interval_seconds: int,
213
+ ) -> Dict[str, Any]:
214
+ """Wait for the pre-processor to be running with polling and logging.
215
+
216
+ Args:
217
+ context: Shared context for the action execution
218
+ preprocessor_code: The pre-processor code
219
+ max_wait_seconds: Maximum time to wait in seconds
220
+ poll_interval_seconds: Interval between polling attempts in seconds
221
+
222
+ Returns:
223
+ Dict indicating success or failure
224
+ """
225
+ max_attempts = max_wait_seconds // poll_interval_seconds
226
+
227
+ for attempt in range(max_attempts):
228
+ elapsed_seconds = attempt * poll_interval_seconds
229
+ context.logger.log_message(
230
+ f'Waiting for pre-processor to start... ({elapsed_seconds}s / {max_wait_seconds}s)'
231
+ )
232
+
233
+ time.sleep(poll_interval_seconds)
234
+
235
+ result = self._get_running_serve_apps(context, preprocessor_code)
236
+ if not result['success']:
237
+ continue # Keep trying on transient errors
238
+
239
+ if result['running_apps']:
240
+ context.logger.log_message('Pre-processor started successfully')
241
+ return {'success': True}
242
+
243
+ return {'success': False, 'error': f'Pre-processor failed to start within {max_wait_seconds} seconds'}