synapse-sdk 1.0.0a23__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 (228) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/__init__.py +310 -5
  3. synapse_sdk/cli/alias/__init__.py +22 -0
  4. synapse_sdk/cli/alias/create.py +36 -0
  5. synapse_sdk/cli/alias/dataclass.py +31 -0
  6. synapse_sdk/cli/alias/default.py +16 -0
  7. synapse_sdk/cli/alias/delete.py +15 -0
  8. synapse_sdk/cli/alias/list.py +19 -0
  9. synapse_sdk/cli/alias/read.py +15 -0
  10. synapse_sdk/cli/alias/update.py +17 -0
  11. synapse_sdk/cli/alias/utils.py +61 -0
  12. synapse_sdk/cli/code_server.py +687 -0
  13. synapse_sdk/cli/config.py +440 -0
  14. synapse_sdk/cli/devtools.py +90 -0
  15. synapse_sdk/cli/plugin/__init__.py +33 -0
  16. synapse_sdk/cli/{create_plugin.py → plugin/create.py} +2 -2
  17. synapse_sdk/{plugins/cli → cli/plugin}/publish.py +23 -15
  18. synapse_sdk/clients/agent/__init__.py +9 -3
  19. synapse_sdk/clients/agent/container.py +143 -0
  20. synapse_sdk/clients/agent/core.py +19 -0
  21. synapse_sdk/clients/agent/ray.py +298 -9
  22. synapse_sdk/clients/backend/__init__.py +30 -12
  23. synapse_sdk/clients/backend/annotation.py +13 -5
  24. synapse_sdk/clients/backend/core.py +31 -4
  25. synapse_sdk/clients/backend/data_collection.py +186 -0
  26. synapse_sdk/clients/backend/hitl.py +17 -0
  27. synapse_sdk/clients/backend/integration.py +16 -1
  28. synapse_sdk/clients/backend/ml.py +5 -1
  29. synapse_sdk/clients/backend/models.py +78 -0
  30. synapse_sdk/clients/base.py +384 -41
  31. synapse_sdk/clients/ray/serve.py +2 -0
  32. synapse_sdk/clients/validators/collections.py +31 -0
  33. synapse_sdk/devtools/config.py +94 -0
  34. synapse_sdk/devtools/server.py +41 -0
  35. synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
  36. synapse_sdk/devtools/streamlit_app/app.py +128 -0
  37. synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
  38. synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
  39. synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
  40. synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
  41. synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
  42. synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
  43. synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
  44. synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
  45. synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
  46. synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
  47. synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
  48. synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
  49. synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
  50. synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
  51. synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
  52. synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
  53. synapse_sdk/devtools/streamlit_app.py +10 -0
  54. synapse_sdk/loggers.py +120 -9
  55. synapse_sdk/plugins/README.md +1340 -0
  56. synapse_sdk/plugins/__init__.py +0 -13
  57. synapse_sdk/plugins/categories/base.py +117 -11
  58. synapse_sdk/plugins/categories/data_validation/actions/validation.py +72 -0
  59. synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +33 -5
  60. synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
  61. synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
  62. synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
  63. synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
  64. synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
  65. synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
  66. synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
  67. synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
  68. synapse_sdk/plugins/categories/export/templates/config.yaml +21 -0
  69. synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
  70. synapse_sdk/plugins/categories/export/templates/plugin/export.py +160 -0
  71. synapse_sdk/plugins/categories/neural_net/actions/deployment.py +13 -12
  72. synapse_sdk/plugins/categories/neural_net/actions/train.py +1134 -31
  73. synapse_sdk/plugins/categories/neural_net/actions/tune.py +534 -0
  74. synapse_sdk/plugins/categories/neural_net/base/inference.py +1 -1
  75. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +32 -4
  76. synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +26 -10
  77. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
  78. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
  79. synapse_sdk/plugins/categories/{export/actions/export.py → pre_annotation/actions/pre_annotation/action.py} +4 -4
  80. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
  81. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +148 -0
  82. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
  83. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
  84. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
  85. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +100 -0
  86. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +248 -0
  87. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
  88. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
  89. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +265 -0
  90. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
  91. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
  92. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +92 -0
  93. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +243 -0
  94. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
  95. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +19 -0
  96. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/to_task.py +40 -0
  97. synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +2 -0
  98. synapse_sdk/plugins/categories/upload/__init__.py +0 -0
  99. synapse_sdk/plugins/categories/upload/actions/__init__.py +0 -0
  100. synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
  101. synapse_sdk/plugins/categories/upload/actions/upload/action.py +236 -0
  102. synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
  103. synapse_sdk/plugins/categories/upload/actions/upload/enums.py +493 -0
  104. synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
  105. synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
  106. synapse_sdk/plugins/categories/upload/actions/upload/models.py +214 -0
  107. synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
  108. synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
  109. synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
  110. synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
  111. synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
  112. synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
  113. synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
  114. synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +91 -0
  115. synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
  116. synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
  117. synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +201 -0
  118. synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +104 -0
  119. synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
  120. synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
  121. synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
  122. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
  123. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
  124. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
  125. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
  126. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +300 -0
  127. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +287 -0
  128. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
  129. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
  130. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
  131. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
  132. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
  133. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
  134. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
  135. synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
  136. synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
  137. synapse_sdk/plugins/categories/upload/templates/config.yaml +33 -0
  138. synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +310 -0
  139. synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +102 -0
  140. synapse_sdk/plugins/enums.py +3 -1
  141. synapse_sdk/plugins/models.py +148 -11
  142. synapse_sdk/plugins/templates/plugin-config-schema.json +406 -0
  143. synapse_sdk/plugins/templates/schema.json +491 -0
  144. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +1 -0
  145. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +1 -1
  146. synapse_sdk/plugins/utils/__init__.py +46 -0
  147. synapse_sdk/plugins/utils/actions.py +119 -0
  148. synapse_sdk/plugins/utils/config.py +203 -0
  149. synapse_sdk/plugins/{utils.py → utils/legacy.py} +26 -46
  150. synapse_sdk/plugins/utils/ray_gcs.py +66 -0
  151. synapse_sdk/plugins/utils/registry.py +58 -0
  152. synapse_sdk/shared/__init__.py +25 -0
  153. synapse_sdk/shared/enums.py +93 -0
  154. synapse_sdk/types.py +19 -0
  155. synapse_sdk/utils/converters/__init__.py +240 -0
  156. synapse_sdk/utils/converters/coco/__init__.py +0 -0
  157. synapse_sdk/utils/converters/coco/from_dm.py +322 -0
  158. synapse_sdk/utils/converters/coco/to_dm.py +215 -0
  159. synapse_sdk/utils/converters/dm/__init__.py +57 -0
  160. synapse_sdk/utils/converters/dm/base.py +137 -0
  161. synapse_sdk/utils/converters/dm/from_v1.py +273 -0
  162. synapse_sdk/utils/converters/dm/to_v1.py +321 -0
  163. synapse_sdk/utils/converters/dm/tools/__init__.py +214 -0
  164. synapse_sdk/utils/converters/dm/tools/answer.py +95 -0
  165. synapse_sdk/utils/converters/dm/tools/bounding_box.py +132 -0
  166. synapse_sdk/utils/converters/dm/tools/bounding_box_3d.py +121 -0
  167. synapse_sdk/utils/converters/dm/tools/classification.py +75 -0
  168. synapse_sdk/utils/converters/dm/tools/keypoint.py +117 -0
  169. synapse_sdk/utils/converters/dm/tools/named_entity.py +111 -0
  170. synapse_sdk/utils/converters/dm/tools/polygon.py +122 -0
  171. synapse_sdk/utils/converters/dm/tools/polyline.py +124 -0
  172. synapse_sdk/utils/converters/dm/tools/prompt.py +94 -0
  173. synapse_sdk/utils/converters/dm/tools/relation.py +86 -0
  174. synapse_sdk/utils/converters/dm/tools/segmentation.py +141 -0
  175. synapse_sdk/utils/converters/dm/tools/segmentation_3d.py +83 -0
  176. synapse_sdk/utils/converters/dm/types.py +168 -0
  177. synapse_sdk/utils/converters/dm/utils.py +162 -0
  178. synapse_sdk/utils/converters/dm_legacy/__init__.py +56 -0
  179. synapse_sdk/utils/converters/dm_legacy/from_v1.py +627 -0
  180. synapse_sdk/utils/converters/dm_legacy/to_v1.py +367 -0
  181. synapse_sdk/utils/converters/pascal/__init__.py +0 -0
  182. synapse_sdk/utils/converters/pascal/from_dm.py +244 -0
  183. synapse_sdk/utils/converters/pascal/to_dm.py +214 -0
  184. synapse_sdk/utils/converters/yolo/__init__.py +0 -0
  185. synapse_sdk/utils/converters/yolo/from_dm.py +384 -0
  186. synapse_sdk/utils/converters/yolo/to_dm.py +267 -0
  187. synapse_sdk/utils/dataset.py +46 -0
  188. synapse_sdk/utils/encryption.py +158 -0
  189. synapse_sdk/utils/file/__init__.py +58 -0
  190. synapse_sdk/utils/file/archive.py +32 -0
  191. synapse_sdk/utils/file/checksum.py +56 -0
  192. synapse_sdk/utils/file/chunking.py +31 -0
  193. synapse_sdk/utils/file/download.py +385 -0
  194. synapse_sdk/utils/file/encoding.py +40 -0
  195. synapse_sdk/utils/file/io.py +22 -0
  196. synapse_sdk/utils/file/upload.py +165 -0
  197. synapse_sdk/utils/file/video/__init__.py +29 -0
  198. synapse_sdk/utils/file/video/transcode.py +307 -0
  199. synapse_sdk/utils/file.py.backup +301 -0
  200. synapse_sdk/utils/http.py +138 -0
  201. synapse_sdk/utils/network.py +309 -0
  202. synapse_sdk/utils/storage/__init__.py +72 -0
  203. synapse_sdk/utils/storage/providers/__init__.py +183 -0
  204. synapse_sdk/utils/storage/providers/file_system.py +134 -0
  205. synapse_sdk/utils/storage/providers/gcp.py +13 -0
  206. synapse_sdk/utils/storage/providers/http.py +190 -0
  207. synapse_sdk/utils/storage/providers/s3.py +91 -0
  208. synapse_sdk/utils/storage/providers/sftp.py +47 -0
  209. synapse_sdk/utils/storage/registry.py +17 -0
  210. synapse_sdk-2025.12.3.dist-info/METADATA +123 -0
  211. synapse_sdk-2025.12.3.dist-info/RECORD +279 -0
  212. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/WHEEL +1 -1
  213. synapse_sdk/clients/backend/dataset.py +0 -51
  214. synapse_sdk/plugins/categories/import/actions/import.py +0 -10
  215. synapse_sdk/plugins/cli/__init__.py +0 -21
  216. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
  217. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
  218. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
  219. synapse_sdk/utils/file.py +0 -168
  220. synapse_sdk/utils/storage.py +0 -91
  221. synapse_sdk-1.0.0a23.dist-info/METADATA +0 -44
  222. synapse_sdk-1.0.0a23.dist-info/RECORD +0 -114
  223. /synapse_sdk/{plugins/cli → cli/plugin}/run.py +0 -0
  224. /synapse_sdk/{plugins/categories/import → clients/validators}/__init__.py +0 -0
  225. /synapse_sdk/{plugins/categories/import/actions → devtools}/__init__.py +0 -0
  226. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/entry_points.txt +0 -0
  227. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info/licenses}/LICENSE +0 -0
  228. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,107 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, Dict, Optional
3
+
4
+ from ..context import StepResult, UploadContext
5
+ from ..enums import LogCode
6
+
7
+
8
+ class BaseStep(ABC):
9
+ """Abstract base class for all workflow steps."""
10
+
11
+ @property
12
+ @abstractmethod
13
+ def name(self) -> str:
14
+ """Step name for logging and tracking."""
15
+ pass
16
+
17
+ @property
18
+ @abstractmethod
19
+ def progress_weight(self) -> float:
20
+ """Relative weight for progress calculation (0.0 to 1.0)."""
21
+ pass
22
+
23
+ @abstractmethod
24
+ def execute(self, context: UploadContext) -> StepResult:
25
+ """Execute the step logic."""
26
+ pass
27
+
28
+ @abstractmethod
29
+ def can_skip(self, context: UploadContext) -> bool:
30
+ """Determine if this step can be skipped based on context."""
31
+ pass
32
+
33
+ @abstractmethod
34
+ def rollback(self, context: UploadContext) -> None:
35
+ """Rollback changes made by this step."""
36
+ pass
37
+
38
+ def validate_prerequisites(self, context: UploadContext) -> None:
39
+ """Validate step prerequisites. Raises exception if not met."""
40
+ pass
41
+
42
+ def log_step_start(self, context: UploadContext) -> None:
43
+ """Log step start."""
44
+ context.run.log_message_with_code(LogCode.STEP_STARTING, self.name)
45
+
46
+ def log_step_complete(self, context: UploadContext) -> None:
47
+ """Log step completion."""
48
+ context.run.log_message_with_code(LogCode.STEP_COMPLETED, self.name)
49
+
50
+ def log_step_skipped(self, context: UploadContext) -> None:
51
+ """Log step skipped."""
52
+ context.run.log_message_with_code(LogCode.STEP_SKIPPED, self.name)
53
+
54
+ def log_step_error(self, context: UploadContext, error: str) -> None:
55
+ """Log step error."""
56
+ context.run.log_message_with_code(LogCode.STEP_ERROR, self.name, error)
57
+
58
+ def create_success_result(
59
+ self,
60
+ data: Optional[Dict[str, Any]] = None,
61
+ rollback_data: Optional[Dict[str, Any]] = None,
62
+ skipped: bool = False,
63
+ ) -> StepResult:
64
+ """Create a successful step result."""
65
+ rollback_data = rollback_data or {}
66
+ rollback_data['step_name'] = self.name
67
+ return StepResult(success=True, data=data or {}, rollback_data=rollback_data, skipped=skipped)
68
+
69
+ def create_error_result(
70
+ self, error: str, rollback_data: Optional[Dict[str, Any]] = None, original_exception: Optional[Exception] = None
71
+ ) -> StepResult:
72
+ """Create an error step result."""
73
+ rollback_data = rollback_data or {}
74
+ rollback_data['step_name'] = self.name
75
+ return StepResult(
76
+ success=False, error=error, rollback_data=rollback_data, original_exception=original_exception
77
+ )
78
+
79
+ def safe_execute(self, context: UploadContext) -> StepResult:
80
+ """Execute step with error handling and logging."""
81
+ try:
82
+ self.validate_prerequisites(context)
83
+
84
+ if self.can_skip(context):
85
+ self.log_step_skipped(context)
86
+ return self.create_success_result(skipped=True)
87
+
88
+ self.log_step_start(context)
89
+ result = self.execute(context)
90
+
91
+ if result.success:
92
+ self.log_step_complete(context)
93
+ else:
94
+ self.log_step_error(context, result.error or 'Unknown error')
95
+
96
+ return result
97
+
98
+ except Exception as e:
99
+ error_msg = f'Exception in step {self.name}: {str(e)}'
100
+ self.log_step_error(context, error_msg)
101
+ return self.create_error_result(error_msg, original_exception=e)
102
+
103
+ def __str__(self):
104
+ return f'{self.__class__.__name__}(name={self.name})'
105
+
106
+ def __repr__(self):
107
+ return f"{self.__class__.__name__}(name='{self.name}', weight={self.progress_weight})"
@@ -0,0 +1,62 @@
1
+ import os
2
+ import shutil
3
+ from pathlib import Path
4
+
5
+ from ..context import StepResult, UploadContext
6
+ from ..enums import LogCode
7
+ from .base import BaseStep
8
+
9
+
10
+ class CleanupStep(BaseStep):
11
+ """Cleanup temporary resources and finalize workflow."""
12
+
13
+ @property
14
+ def name(self) -> str:
15
+ return 'cleanup'
16
+
17
+ @property
18
+ def progress_weight(self) -> float:
19
+ return 0.05
20
+
21
+ def execute(self, context: UploadContext) -> StepResult:
22
+ """Execute cleanup step."""
23
+ try:
24
+ # Cleanup temporary directory - commented out because duplicated process with ray cleanup process
25
+ # self._cleanup_temp_directory(context)
26
+
27
+ # Log completion
28
+ context.run.log_message_with_code(LogCode.IMPORT_COMPLETED)
29
+
30
+ return self.create_success_result(data={'cleanup_completed': True}, rollback_data={'temp_cleaned': True})
31
+
32
+ except Exception as e:
33
+ # Cleanup failures shouldn't stop the workflow
34
+ context.run.log_message_with_code(LogCode.CLEANUP_WARNING, str(e))
35
+ return self.create_success_result(
36
+ data={'cleanup_completed': False}, rollback_data={'cleanup_error': str(e)}
37
+ )
38
+
39
+ def can_skip(self, context: UploadContext) -> bool:
40
+ """Cleanup step can be skipped if disabled."""
41
+ return context.get_param('skip_cleanup', False)
42
+
43
+ def rollback(self, context: UploadContext) -> None:
44
+ """Rollback cleanup (nothing to rollback for cleanup)."""
45
+ context.run.log_message_with_code(LogCode.ROLLBACK_CLEANUP)
46
+
47
+ def _cleanup_temp_directory(self, context: UploadContext, temp_path: Path = None) -> None:
48
+ """Clean up temporary directory."""
49
+ if temp_path is None:
50
+ try:
51
+ temp_path = Path(os.getcwd()) / 'temp'
52
+ except (FileNotFoundError, OSError):
53
+ return
54
+
55
+ if not temp_path.exists():
56
+ return
57
+
58
+ try:
59
+ shutil.rmtree(temp_path, ignore_errors=True)
60
+ context.run.log_message_with_code(LogCode.CLEANUP_TEMP_DIR_SUCCESS, temp_path)
61
+ except Exception as e:
62
+ context.run.log_message_with_code(LogCode.CLEANUP_TEMP_DIR_FAILED, str(e))
@@ -0,0 +1,63 @@
1
+ from synapse_sdk.plugins.exceptions import ActionError
2
+
3
+ from ..context import StepResult, UploadContext
4
+ from ..enums import LogCode
5
+ from .base import BaseStep
6
+
7
+
8
+ class AnalyzeCollectionStep(BaseStep):
9
+ """Analyze data collection to get file specifications."""
10
+
11
+ @property
12
+ def name(self) -> str:
13
+ return 'analyze_collection'
14
+
15
+ @property
16
+ def progress_weight(self) -> float:
17
+ return 0.05
18
+
19
+ def execute(self, context: UploadContext) -> StepResult:
20
+ """Execute collection analysis step."""
21
+ collection_id = context.get_param('data_collection')
22
+ if collection_id is None:
23
+ return self.create_error_result('Data collection parameter is required')
24
+
25
+ try:
26
+ # Set initial progress
27
+ context.run.set_progress(0, 2, category='analyze_collection')
28
+
29
+ # Get collection from client
30
+ collection = context.client.get_data_collection(collection_id)
31
+ context.run.set_progress(1, 2, category='analyze_collection')
32
+
33
+ # Extract file specifications
34
+ file_specifications = collection.get('file_specifications', [])
35
+ context.set_file_specifications(file_specifications)
36
+
37
+ # Complete progress
38
+ context.run.set_progress(2, 2, category='analyze_collection')
39
+
40
+ return self.create_success_result(
41
+ data={'file_specifications': file_specifications}, rollback_data={'collection_id': collection_id}
42
+ )
43
+
44
+ except Exception as e:
45
+ return self.create_error_result(f'Failed to analyze collection {collection_id}: {str(e)}')
46
+
47
+ def can_skip(self, context: UploadContext) -> bool:
48
+ """Collection analysis cannot be skipped."""
49
+ return False
50
+
51
+ def rollback(self, context: UploadContext) -> None:
52
+ """Rollback collection analysis."""
53
+ # Clear file specifications
54
+ context.file_specifications.clear()
55
+ context.run.log_message_with_code(LogCode.ROLLBACK_COLLECTION_ANALYSIS)
56
+
57
+ def validate_prerequisites(self, context: UploadContext) -> None:
58
+ """Validate prerequisites for collection analysis."""
59
+ if context.client is None:
60
+ raise ActionError('Client is required for collection analysis')
61
+
62
+ if context.get_param('data_collection') is None:
63
+ raise ActionError('Data collection parameter is required')
@@ -0,0 +1,91 @@
1
+ from ..context import StepResult, UploadContext
2
+ from ..enums import LogCode, UploadStatus
3
+ from .base import BaseStep
4
+
5
+
6
+ class GenerateDataUnitsStep(BaseStep):
7
+ """Generate data units from uploaded files."""
8
+
9
+ @property
10
+ def name(self) -> str:
11
+ return 'generate_data_units'
12
+
13
+ @property
14
+ def progress_weight(self) -> float:
15
+ return 0.20
16
+
17
+ def execute(self, context: UploadContext) -> StepResult:
18
+ """Execute data unit generation step."""
19
+ data_unit_strategy = context.strategies.get('data_unit')
20
+ if not data_unit_strategy:
21
+ return self.create_error_result('Data unit strategy not found')
22
+
23
+ if not context.uploaded_files:
24
+ context.run.log_message_with_code(LogCode.NO_DATA_UNITS_GENERATED)
25
+ return self.create_error_result('No uploaded files to generate data units from')
26
+
27
+ try:
28
+ # Setup progress tracking
29
+ upload_result_count = len(context.uploaded_files)
30
+ context.run.set_progress(0, upload_result_count, category='generate_data_units')
31
+ context.run.log_message_with_code(LogCode.GENERATING_DATA_UNITS)
32
+
33
+ # Initialize metrics
34
+ initial_metrics = {'stand_by': upload_result_count, 'success': 0, 'failed': 0}
35
+ context.update_metrics('data_units', initial_metrics)
36
+ context.run.set_metrics(initial_metrics, category='data_units')
37
+
38
+ # Get batch size from parameters
39
+ batch_size = context.get_param('creating_data_unit_batch_size', 1)
40
+
41
+ # Generate data units using strategy
42
+ generated_data_units = data_unit_strategy.generate(context.uploaded_files, batch_size)
43
+
44
+ # Update context
45
+ context.add_data_units(generated_data_units)
46
+
47
+ # Log data unit results
48
+ for data_unit in generated_data_units:
49
+ context.run.log_data_unit(
50
+ data_unit.get('id'), UploadStatus.SUCCESS, data_unit_meta=data_unit.get('meta')
51
+ )
52
+
53
+ # Update final metrics
54
+ final_metrics = {'stand_by': 0, 'success': len(generated_data_units), 'failed': 0}
55
+ context.update_metrics('data_units', final_metrics)
56
+ context.run.set_metrics(final_metrics, category='data_units')
57
+
58
+ # Handle success vs failure cases
59
+ if generated_data_units:
60
+ # Success: Set completion progress with elapsed time
61
+ context.run.set_progress(upload_result_count, upload_result_count, category='generate_data_units')
62
+ return self.create_success_result(
63
+ data={'generated_data_units': generated_data_units},
64
+ rollback_data={'data_units_count': len(generated_data_units), 'batch_size': batch_size},
65
+ )
66
+ else:
67
+ # Failure: Mark as failed with elapsed time but no completion
68
+ context.run.set_progress_failed(category='generate_data_units')
69
+ return self.create_error_result('No data units were successfully generated')
70
+
71
+ except Exception as e:
72
+ # Exception: Mark as failed with elapsed time
73
+ context.run.set_progress_failed(category='generate_data_units')
74
+ context.run.log_message_with_code(LogCode.DATA_UNIT_BATCH_FAILED, str(e))
75
+ return self.create_error_result(f'Data unit generation failed: {str(e)}')
76
+
77
+ def can_skip(self, context: UploadContext) -> bool:
78
+ """Data unit generation cannot be skipped."""
79
+ return False
80
+
81
+ def rollback(self, context: UploadContext) -> None:
82
+ """Rollback data unit generation."""
83
+ # In a real implementation, this would delete generated data units
84
+ # For now, just clear the data units list and log
85
+ context.data_units.clear()
86
+ context.run.log_message_with_code(LogCode.ROLLBACK_DATA_UNIT_GENERATION)
87
+
88
+ def validate_prerequisites(self, context: UploadContext) -> None:
89
+ """Validate prerequisites for data unit generation."""
90
+ if not context.uploaded_files:
91
+ raise ValueError('No uploaded files available for data unit generation')
@@ -0,0 +1,82 @@
1
+ from synapse_sdk.plugins.exceptions import ActionError
2
+ from synapse_sdk.utils.storage import get_pathlib
3
+
4
+ from ..context import StepResult, UploadContext
5
+ from ..enums import LogCode
6
+ from .base import BaseStep
7
+
8
+
9
+ class InitializeStep(BaseStep):
10
+ """Initialize upload workflow by setting up storage and paths."""
11
+
12
+ @property
13
+ def name(self) -> str:
14
+ return 'initialize'
15
+
16
+ @property
17
+ def progress_weight(self) -> float:
18
+ return 0.05
19
+
20
+ def execute(self, context: UploadContext) -> StepResult:
21
+ """Execute initialization step."""
22
+ # Get and validate storage
23
+ storage_id = context.get_param('storage')
24
+ if storage_id is None:
25
+ return self.create_error_result('Storage parameter is required')
26
+
27
+ try:
28
+ storage = context.client.get_storage(storage_id)
29
+ context.set_storage(storage)
30
+ except Exception as e:
31
+ return self.create_error_result(f'Failed to get storage {storage_id}: {str(e)}')
32
+
33
+ # Check if we're in multi-path mode
34
+ use_single_path = context.get_param('use_single_path', True)
35
+
36
+ # Get and validate path (only required in single-path mode)
37
+ path = context.get_param('path')
38
+ pathlib_cwd = None
39
+
40
+ if use_single_path:
41
+ # Single-path mode: global path is required
42
+ if path is None:
43
+ return self.create_error_result('Path parameter is required in single-path mode')
44
+
45
+ try:
46
+ pathlib_cwd = get_pathlib(storage, path)
47
+ context.set_pathlib_cwd(pathlib_cwd)
48
+ except Exception as e:
49
+ return self.create_error_result(f'Failed to get path {path}: {str(e)}')
50
+ else:
51
+ # Multi-path mode: global path is optional (each asset has its own path)
52
+ if path:
53
+ try:
54
+ pathlib_cwd = get_pathlib(storage, path)
55
+ context.set_pathlib_cwd(pathlib_cwd)
56
+ except Exception as e:
57
+ return self.create_error_result(f'Failed to get path {path}: {str(e)}')
58
+
59
+ # Return success with rollback data
60
+ rollback_data = {'storage_id': storage_id, 'path': path, 'use_single_path': use_single_path}
61
+
62
+ return self.create_success_result(
63
+ data={'storage': storage, 'pathlib_cwd': pathlib_cwd}, rollback_data=rollback_data
64
+ )
65
+
66
+ def can_skip(self, context: UploadContext) -> bool:
67
+ """Initialize step cannot be skipped."""
68
+ return False
69
+
70
+ def rollback(self, context: UploadContext) -> None:
71
+ """Rollback initialization (cleanup if needed)."""
72
+ # For initialization, there's typically nothing to rollback
73
+ # But we could log the rollback action
74
+ context.run.log_message_with_code(LogCode.ROLLBACK_INITIALIZATION)
75
+
76
+ def validate_prerequisites(self, context: UploadContext) -> None:
77
+ """Validate prerequisites for initialization."""
78
+ if context.client is None:
79
+ raise ActionError('Client is required for initialization')
80
+
81
+ if context.run is None:
82
+ raise ActionError('Run instance is required for initialization')
@@ -0,0 +1,235 @@
1
+ from pathlib import Path
2
+
3
+ from synapse_sdk.utils.storage import get_pathlib
4
+
5
+ from ..context import StepResult, UploadContext
6
+ from ..enums import LogCode
7
+ from ..exceptions import ExcelParsingError, ExcelSecurityError
8
+ from .base import BaseStep
9
+
10
+
11
+ class ProcessMetadataStep(BaseStep):
12
+ """Process metadata from Excel files or other sources.
13
+
14
+ This step handles Excel metadata file processing, including path resolution,
15
+ file extraction, and validation. Supports multiple path resolution strategies:
16
+ absolute paths, storage-relative paths, and working directory-relative paths.
17
+ """
18
+
19
+ @property
20
+ def name(self) -> str:
21
+ return 'process_metadata'
22
+
23
+ @property
24
+ def progress_weight(self) -> float:
25
+ return 0.10
26
+
27
+ def execute(self, context: UploadContext) -> StepResult:
28
+ """Execute metadata processing step.
29
+
30
+ Processes Excel metadata files by resolving the file path, extracting
31
+ metadata, and validating the extracted data. Supports multiple path
32
+ resolution strategies and handles both explicit metadata paths and
33
+ default metadata file discovery.
34
+
35
+ Args:
36
+ context: Upload context containing parameters and state information.
37
+
38
+ Returns:
39
+ StepResult containing:
40
+ - success: True if metadata processing succeeded
41
+ - data: Dictionary with 'metadata' key containing extracted metadata
42
+ - rollback_data: Dictionary with 'metadata_processed' flag
43
+
44
+ Note:
45
+ If no metadata strategy is configured, the step completes successfully
46
+ with empty metadata. If excel_metadata_path is specified but the file
47
+ is not found, the step logs a warning and continues with empty metadata.
48
+ """
49
+ metadata_strategy = context.strategies.get('metadata')
50
+ if not metadata_strategy:
51
+ context.run.log_message_with_code(LogCode.NO_METADATA_STRATEGY)
52
+ return self.create_success_result(data={'metadata': {}})
53
+
54
+ excel_metadata = {}
55
+ temp_file_to_cleanup = None
56
+
57
+ try:
58
+ # Check if Excel metadata path is specified
59
+ excel_metadata_path_config = context.get_param('excel_metadata_path')
60
+
61
+ if excel_metadata_path_config:
62
+ # Path-based approach (only supported method)
63
+ excel_path = self._resolve_excel_path_from_string(excel_metadata_path_config, context)
64
+
65
+ if not excel_path or not excel_path.exists():
66
+ context.run.log_message_with_code(LogCode.EXCEL_FILE_NOT_FOUND_PATH)
67
+ return self.create_success_result(data={'metadata': {}})
68
+
69
+ excel_metadata = metadata_strategy.extract(excel_path)
70
+ else:
71
+ # Look for default metadata files (meta.xlsx, meta.xls)
72
+ # Only possible in single-path mode where pathlib_cwd is set
73
+ if context.pathlib_cwd:
74
+ excel_path = self._find_excel_metadata_file(context.pathlib_cwd)
75
+ if excel_path:
76
+ excel_metadata = metadata_strategy.extract(excel_path)
77
+ else:
78
+ context.run.log_message_with_code(LogCode.NO_METADATA_STRATEGY)
79
+
80
+ # Validate extracted metadata
81
+ if excel_metadata:
82
+ validation_result = metadata_strategy.validate(excel_metadata)
83
+ if not validation_result.valid:
84
+ error_msg = f'Metadata validation failed: {", ".join(validation_result.errors)}'
85
+ return self.create_error_result(error_msg)
86
+ context.run.log_message_with_code(LogCode.EXCEL_METADATA_LOADED, len(excel_metadata))
87
+
88
+ return self.create_success_result(
89
+ data={'metadata': excel_metadata}, rollback_data={'metadata_processed': len(excel_metadata) > 0}
90
+ )
91
+
92
+ except ExcelSecurityError as e:
93
+ context.run.log_message_with_code(LogCode.EXCEL_SECURITY_VIOLATION, str(e))
94
+ return self.create_error_result(f'Excel security violation: {str(e)}')
95
+
96
+ except ExcelParsingError as e:
97
+ # If excel_metadata_path was specified, this is an error
98
+ # If we were just looking for default files, it's not an error
99
+ if context.get_param('excel_metadata_path'):
100
+ context.run.log_message_with_code(LogCode.EXCEL_PARSING_ERROR, str(e))
101
+ return self.create_error_result(f'Excel parsing error: {str(e)}')
102
+ else:
103
+ context.run.log_message_with_code(LogCode.EXCEL_PARSING_ERROR, str(e))
104
+ return self.create_success_result(data={'metadata': {}})
105
+
106
+ except Exception as e:
107
+ return self.create_error_result(f'Unexpected error processing metadata: {str(e)}')
108
+
109
+ finally:
110
+ # Clean up temporary file if it was created from base64
111
+ if temp_file_to_cleanup and temp_file_to_cleanup.exists():
112
+ try:
113
+ temp_file_to_cleanup.unlink()
114
+ context.run.log_message_with_code(LogCode.METADATA_TEMP_FILE_CLEANUP, temp_file_to_cleanup)
115
+ except Exception as e:
116
+ context.run.log_message_with_code(
117
+ LogCode.METADATA_TEMP_FILE_CLEANUP_FAILED, temp_file_to_cleanup, str(e)
118
+ )
119
+
120
+ def can_skip(self, context: UploadContext) -> bool:
121
+ """Check if metadata step can be skipped.
122
+
123
+ Args:
124
+ context: Upload context containing strategy configuration.
125
+
126
+ Returns:
127
+ True if no metadata strategy is configured, False otherwise.
128
+ """
129
+ return 'metadata' not in context.strategies
130
+
131
+ def rollback(self, context: UploadContext) -> None:
132
+ """Rollback metadata processing.
133
+
134
+ Clears any loaded metadata from the context to restore the state
135
+ before this step was executed.
136
+
137
+ Args:
138
+ context: Upload context containing metadata to clear.
139
+ """
140
+ # Clear any loaded metadata
141
+ context.metadata.clear()
142
+
143
+ def _resolve_excel_path_from_string(self, excel_path_str: str, context: UploadContext) -> Path | None:
144
+ """Resolve Excel metadata path from a string path.
145
+
146
+ Attempts to resolve the Excel metadata file path using multiple strategies
147
+ in the following order:
148
+ 1. Absolute filesystem path
149
+ 2. Relative to storage default path (if storage is available)
150
+ 3. Relative to working directory (if pathlib_cwd is set in single-path mode)
151
+
152
+ Args:
153
+ excel_path_str: File path string to the Excel metadata file.
154
+ context: Upload context containing storage configuration and working
155
+ directory information for path resolution.
156
+
157
+ Returns:
158
+ Path object pointing to the Excel file if found, None otherwise.
159
+
160
+ Examples:
161
+ Absolute path:
162
+
163
+ >>> path = self._resolve_excel_path_from_string(
164
+ ... "/data/meta.xlsx", context
165
+ ... )
166
+
167
+ Storage-relative path:
168
+
169
+ >>> path = self._resolve_excel_path_from_string(
170
+ ... "metadata/meta.xlsx", context
171
+ ... )
172
+
173
+ Note:
174
+ When resolving storage-relative paths, the method logs the resolved
175
+ path for debugging purposes. Failed storage path resolution is logged
176
+ but does not raise an exception.
177
+ """
178
+ # Try absolute path first
179
+ path = Path(excel_path_str)
180
+ if path.exists() and path.is_file():
181
+ return path
182
+
183
+ # Try relative to storage default path (if storage is available)
184
+ if context.storage:
185
+ try:
186
+ storage_base_path = get_pathlib(context.storage, excel_path_str)
187
+ if storage_base_path.exists() and storage_base_path.is_file():
188
+ context.run.log_message_with_code(LogCode.EXCEL_PATH_RESOLVED_STORAGE, str(storage_base_path))
189
+ return storage_base_path
190
+ except (FileNotFoundError, PermissionError) as e:
191
+ # Expected errors when path doesn't exist or no permissions
192
+ context.run.log_message_with_code(LogCode.EXCEL_PATH_RESOLUTION_FAILED, type(e).__name__, str(e))
193
+ except Exception as e:
194
+ # Unexpected errors should be logged with more detail for debugging
195
+ context.run.log_message_with_code(LogCode.EXCEL_PATH_RESOLUTION_ERROR, type(e).__name__, str(e))
196
+
197
+ # Try relative to cwd (only if pathlib_cwd is set - single-path mode)
198
+ if context.pathlib_cwd:
199
+ path = context.pathlib_cwd / excel_path_str
200
+ if path.exists() and path.is_file():
201
+ return path
202
+
203
+ # Could not resolve path
204
+ return None
205
+
206
+ def _find_excel_metadata_file(self, pathlib_cwd: Path) -> Path:
207
+ """Find default Excel metadata file in working directory.
208
+
209
+ Searches for standard Excel metadata filenames (meta.xlsx, meta.xls)
210
+ in the specified working directory. Prioritizes .xlsx format over .xls.
211
+
212
+ Args:
213
+ pathlib_cwd: Working directory path to search in. Must not be None.
214
+
215
+ Returns:
216
+ Path object to the Excel metadata file if found, None otherwise.
217
+
218
+ Note:
219
+ This method only searches the immediate working directory, not
220
+ subdirectories. The search order is: meta.xlsx, then meta.xls.
221
+ """
222
+ if not pathlib_cwd:
223
+ return None
224
+
225
+ # Check .xlsx first as it's more common
226
+ excel_path = pathlib_cwd / 'meta.xlsx'
227
+ if excel_path.exists():
228
+ return excel_path
229
+
230
+ # Fallback to .xls
231
+ excel_path = pathlib_cwd / 'meta.xls'
232
+ if excel_path.exists():
233
+ return excel_path
234
+
235
+ return None