synapse-sdk 1.0.0a11__py3-none-any.whl → 2026.1.1b2__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (261) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/__init__.py +9 -8
  3. synapse_sdk/cli/agent/__init__.py +25 -0
  4. synapse_sdk/cli/agent/config.py +104 -0
  5. synapse_sdk/cli/agent/select.py +197 -0
  6. synapse_sdk/cli/auth.py +104 -0
  7. synapse_sdk/cli/main.py +1025 -0
  8. synapse_sdk/cli/plugin/__init__.py +58 -0
  9. synapse_sdk/cli/plugin/create.py +566 -0
  10. synapse_sdk/cli/plugin/job.py +196 -0
  11. synapse_sdk/cli/plugin/publish.py +322 -0
  12. synapse_sdk/cli/plugin/run.py +131 -0
  13. synapse_sdk/cli/plugin/test.py +200 -0
  14. synapse_sdk/clients/README.md +239 -0
  15. synapse_sdk/clients/__init__.py +5 -0
  16. synapse_sdk/clients/_template.py +266 -0
  17. synapse_sdk/clients/agent/__init__.py +84 -29
  18. synapse_sdk/clients/agent/async_ray.py +289 -0
  19. synapse_sdk/clients/agent/container.py +83 -0
  20. synapse_sdk/clients/agent/plugin.py +101 -0
  21. synapse_sdk/clients/agent/ray.py +296 -39
  22. synapse_sdk/clients/backend/__init__.py +152 -12
  23. synapse_sdk/clients/backend/annotation.py +164 -22
  24. synapse_sdk/clients/backend/core.py +101 -0
  25. synapse_sdk/clients/backend/data_collection.py +292 -0
  26. synapse_sdk/clients/backend/hitl.py +87 -0
  27. synapse_sdk/clients/backend/integration.py +374 -46
  28. synapse_sdk/clients/backend/ml.py +134 -22
  29. synapse_sdk/clients/backend/models.py +247 -0
  30. synapse_sdk/clients/base.py +538 -59
  31. synapse_sdk/clients/exceptions.py +35 -7
  32. synapse_sdk/clients/pipeline/__init__.py +5 -0
  33. synapse_sdk/clients/pipeline/client.py +636 -0
  34. synapse_sdk/clients/protocols.py +178 -0
  35. synapse_sdk/clients/utils.py +86 -8
  36. synapse_sdk/clients/validation.py +58 -0
  37. synapse_sdk/enums.py +76 -0
  38. synapse_sdk/exceptions.py +168 -0
  39. synapse_sdk/integrations/__init__.py +74 -0
  40. synapse_sdk/integrations/_base.py +119 -0
  41. synapse_sdk/integrations/_context.py +53 -0
  42. synapse_sdk/integrations/ultralytics/__init__.py +78 -0
  43. synapse_sdk/integrations/ultralytics/_callbacks.py +126 -0
  44. synapse_sdk/integrations/ultralytics/_patches.py +124 -0
  45. synapse_sdk/loggers.py +476 -95
  46. synapse_sdk/mcp/MCP.md +69 -0
  47. synapse_sdk/mcp/__init__.py +48 -0
  48. synapse_sdk/mcp/__main__.py +6 -0
  49. synapse_sdk/mcp/config.py +349 -0
  50. synapse_sdk/mcp/prompts/__init__.py +4 -0
  51. synapse_sdk/mcp/resources/__init__.py +4 -0
  52. synapse_sdk/mcp/server.py +1352 -0
  53. synapse_sdk/mcp/tools/__init__.py +6 -0
  54. synapse_sdk/plugins/__init__.py +133 -9
  55. synapse_sdk/plugins/action.py +229 -0
  56. synapse_sdk/plugins/actions/__init__.py +82 -0
  57. synapse_sdk/plugins/actions/dataset/__init__.py +37 -0
  58. synapse_sdk/plugins/actions/dataset/action.py +471 -0
  59. synapse_sdk/plugins/actions/export/__init__.py +55 -0
  60. synapse_sdk/plugins/actions/export/action.py +183 -0
  61. synapse_sdk/plugins/actions/export/context.py +59 -0
  62. synapse_sdk/plugins/actions/inference/__init__.py +84 -0
  63. synapse_sdk/plugins/actions/inference/action.py +285 -0
  64. synapse_sdk/plugins/actions/inference/context.py +81 -0
  65. synapse_sdk/plugins/actions/inference/deployment.py +322 -0
  66. synapse_sdk/plugins/actions/inference/serve.py +252 -0
  67. synapse_sdk/plugins/actions/train/__init__.py +54 -0
  68. synapse_sdk/plugins/actions/train/action.py +326 -0
  69. synapse_sdk/plugins/actions/train/context.py +57 -0
  70. synapse_sdk/plugins/actions/upload/__init__.py +49 -0
  71. synapse_sdk/plugins/actions/upload/action.py +165 -0
  72. synapse_sdk/plugins/actions/upload/context.py +61 -0
  73. synapse_sdk/plugins/config.py +98 -0
  74. synapse_sdk/plugins/context/__init__.py +109 -0
  75. synapse_sdk/plugins/context/env.py +113 -0
  76. synapse_sdk/plugins/datasets/__init__.py +113 -0
  77. synapse_sdk/plugins/datasets/converters/__init__.py +76 -0
  78. synapse_sdk/plugins/datasets/converters/base.py +347 -0
  79. synapse_sdk/plugins/datasets/converters/yolo/__init__.py +9 -0
  80. synapse_sdk/plugins/datasets/converters/yolo/from_dm.py +468 -0
  81. synapse_sdk/plugins/datasets/converters/yolo/to_dm.py +381 -0
  82. synapse_sdk/plugins/datasets/formats/__init__.py +82 -0
  83. synapse_sdk/plugins/datasets/formats/dm.py +351 -0
  84. synapse_sdk/plugins/datasets/formats/yolo.py +240 -0
  85. synapse_sdk/plugins/decorators.py +83 -0
  86. synapse_sdk/plugins/discovery.py +790 -0
  87. synapse_sdk/plugins/docs/ACTION_DEV_GUIDE.md +933 -0
  88. synapse_sdk/plugins/docs/ARCHITECTURE.md +1225 -0
  89. synapse_sdk/plugins/docs/LOGGING_SYSTEM.md +683 -0
  90. synapse_sdk/plugins/docs/OVERVIEW.md +531 -0
  91. synapse_sdk/plugins/docs/PIPELINE_GUIDE.md +145 -0
  92. synapse_sdk/plugins/docs/README.md +513 -0
  93. synapse_sdk/plugins/docs/STEP.md +656 -0
  94. synapse_sdk/plugins/enums.py +70 -10
  95. synapse_sdk/plugins/errors.py +92 -0
  96. synapse_sdk/plugins/executors/__init__.py +43 -0
  97. synapse_sdk/plugins/executors/local.py +99 -0
  98. synapse_sdk/plugins/executors/ray/__init__.py +18 -0
  99. synapse_sdk/plugins/executors/ray/base.py +282 -0
  100. synapse_sdk/plugins/executors/ray/job.py +298 -0
  101. synapse_sdk/plugins/executors/ray/jobs_api.py +511 -0
  102. synapse_sdk/plugins/executors/ray/packaging.py +137 -0
  103. synapse_sdk/plugins/executors/ray/pipeline.py +792 -0
  104. synapse_sdk/plugins/executors/ray/task.py +257 -0
  105. synapse_sdk/plugins/models/__init__.py +26 -0
  106. synapse_sdk/plugins/models/logger.py +173 -0
  107. synapse_sdk/plugins/models/pipeline.py +25 -0
  108. synapse_sdk/plugins/pipelines/__init__.py +81 -0
  109. synapse_sdk/plugins/pipelines/action_pipeline.py +417 -0
  110. synapse_sdk/plugins/pipelines/context.py +107 -0
  111. synapse_sdk/plugins/pipelines/display.py +311 -0
  112. synapse_sdk/plugins/runner.py +114 -0
  113. synapse_sdk/plugins/schemas/__init__.py +19 -0
  114. synapse_sdk/plugins/schemas/results.py +152 -0
  115. synapse_sdk/plugins/steps/__init__.py +63 -0
  116. synapse_sdk/plugins/steps/base.py +128 -0
  117. synapse_sdk/plugins/steps/context.py +90 -0
  118. synapse_sdk/plugins/steps/orchestrator.py +128 -0
  119. synapse_sdk/plugins/steps/registry.py +103 -0
  120. synapse_sdk/plugins/steps/utils/__init__.py +20 -0
  121. synapse_sdk/plugins/steps/utils/logging.py +85 -0
  122. synapse_sdk/plugins/steps/utils/timing.py +71 -0
  123. synapse_sdk/plugins/steps/utils/validation.py +68 -0
  124. synapse_sdk/plugins/templates/__init__.py +50 -0
  125. synapse_sdk/plugins/templates/base/.gitignore.j2 +26 -0
  126. synapse_sdk/plugins/templates/base/.synapseignore.j2 +11 -0
  127. synapse_sdk/plugins/templates/base/README.md.j2 +26 -0
  128. synapse_sdk/plugins/templates/base/plugin/__init__.py.j2 +1 -0
  129. synapse_sdk/plugins/templates/base/pyproject.toml.j2 +14 -0
  130. synapse_sdk/plugins/templates/base/requirements.txt.j2 +1 -0
  131. synapse_sdk/plugins/templates/custom/plugin/main.py.j2 +18 -0
  132. synapse_sdk/plugins/templates/data_validation/plugin/validate.py.j2 +32 -0
  133. synapse_sdk/plugins/templates/export/plugin/export.py.j2 +36 -0
  134. synapse_sdk/plugins/templates/neural_net/plugin/inference.py.j2 +36 -0
  135. synapse_sdk/plugins/templates/neural_net/plugin/train.py.j2 +33 -0
  136. synapse_sdk/plugins/templates/post_annotation/plugin/post_annotate.py.j2 +32 -0
  137. synapse_sdk/plugins/templates/pre_annotation/plugin/pre_annotate.py.j2 +32 -0
  138. synapse_sdk/plugins/templates/smart_tool/plugin/auto_label.py.j2 +44 -0
  139. synapse_sdk/plugins/templates/upload/plugin/upload.py.j2 +35 -0
  140. synapse_sdk/plugins/testing/__init__.py +25 -0
  141. synapse_sdk/plugins/testing/sample_actions.py +98 -0
  142. synapse_sdk/plugins/types.py +206 -0
  143. synapse_sdk/plugins/upload.py +595 -64
  144. synapse_sdk/plugins/utils.py +325 -37
  145. synapse_sdk/shared/__init__.py +25 -0
  146. synapse_sdk/utils/__init__.py +1 -0
  147. synapse_sdk/utils/auth.py +74 -0
  148. synapse_sdk/utils/file/__init__.py +58 -0
  149. synapse_sdk/utils/file/archive.py +449 -0
  150. synapse_sdk/utils/file/checksum.py +167 -0
  151. synapse_sdk/utils/file/download.py +286 -0
  152. synapse_sdk/utils/file/io.py +129 -0
  153. synapse_sdk/utils/file/requirements.py +36 -0
  154. synapse_sdk/utils/network.py +168 -0
  155. synapse_sdk/utils/storage/__init__.py +238 -0
  156. synapse_sdk/utils/storage/config.py +188 -0
  157. synapse_sdk/utils/storage/errors.py +52 -0
  158. synapse_sdk/utils/storage/providers/__init__.py +13 -0
  159. synapse_sdk/utils/storage/providers/base.py +76 -0
  160. synapse_sdk/utils/storage/providers/gcs.py +168 -0
  161. synapse_sdk/utils/storage/providers/http.py +250 -0
  162. synapse_sdk/utils/storage/providers/local.py +126 -0
  163. synapse_sdk/utils/storage/providers/s3.py +177 -0
  164. synapse_sdk/utils/storage/providers/sftp.py +208 -0
  165. synapse_sdk/utils/storage/registry.py +125 -0
  166. synapse_sdk/utils/websocket.py +99 -0
  167. synapse_sdk-2026.1.1b2.dist-info/METADATA +715 -0
  168. synapse_sdk-2026.1.1b2.dist-info/RECORD +172 -0
  169. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/WHEEL +1 -1
  170. synapse_sdk-2026.1.1b2.dist-info/licenses/LICENSE +201 -0
  171. locale/en/LC_MESSAGES/messages.mo +0 -0
  172. locale/en/LC_MESSAGES/messages.po +0 -39
  173. locale/ko/LC_MESSAGES/messages.mo +0 -0
  174. locale/ko/LC_MESSAGES/messages.po +0 -34
  175. synapse_sdk/cli/create_plugin.py +0 -10
  176. synapse_sdk/clients/agent/core.py +0 -7
  177. synapse_sdk/clients/agent/service.py +0 -15
  178. synapse_sdk/clients/backend/dataset.py +0 -51
  179. synapse_sdk/clients/ray/__init__.py +0 -6
  180. synapse_sdk/clients/ray/core.py +0 -22
  181. synapse_sdk/clients/ray/serve.py +0 -20
  182. synapse_sdk/i18n.py +0 -35
  183. synapse_sdk/plugins/categories/__init__.py +0 -0
  184. synapse_sdk/plugins/categories/base.py +0 -235
  185. synapse_sdk/plugins/categories/data_validation/__init__.py +0 -0
  186. synapse_sdk/plugins/categories/data_validation/actions/__init__.py +0 -0
  187. synapse_sdk/plugins/categories/data_validation/actions/validation.py +0 -10
  188. synapse_sdk/plugins/categories/data_validation/templates/config.yaml +0 -3
  189. synapse_sdk/plugins/categories/data_validation/templates/plugin/__init__.py +0 -0
  190. synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +0 -5
  191. synapse_sdk/plugins/categories/decorators.py +0 -13
  192. synapse_sdk/plugins/categories/export/__init__.py +0 -0
  193. synapse_sdk/plugins/categories/export/actions/__init__.py +0 -0
  194. synapse_sdk/plugins/categories/export/actions/export.py +0 -10
  195. synapse_sdk/plugins/categories/import/__init__.py +0 -0
  196. synapse_sdk/plugins/categories/import/actions/__init__.py +0 -0
  197. synapse_sdk/plugins/categories/import/actions/import.py +0 -10
  198. synapse_sdk/plugins/categories/neural_net/__init__.py +0 -0
  199. synapse_sdk/plugins/categories/neural_net/actions/__init__.py +0 -0
  200. synapse_sdk/plugins/categories/neural_net/actions/deployment.py +0 -45
  201. synapse_sdk/plugins/categories/neural_net/actions/inference.py +0 -18
  202. synapse_sdk/plugins/categories/neural_net/actions/test.py +0 -10
  203. synapse_sdk/plugins/categories/neural_net/actions/train.py +0 -143
  204. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +0 -12
  205. synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py +0 -0
  206. synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +0 -4
  207. synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py +0 -2
  208. synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +0 -14
  209. synapse_sdk/plugins/categories/post_annotation/__init__.py +0 -0
  210. synapse_sdk/plugins/categories/post_annotation/actions/__init__.py +0 -0
  211. synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py +0 -10
  212. synapse_sdk/plugins/categories/post_annotation/templates/config.yaml +0 -3
  213. synapse_sdk/plugins/categories/post_annotation/templates/plugin/__init__.py +0 -0
  214. synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.py +0 -3
  215. synapse_sdk/plugins/categories/pre_annotation/__init__.py +0 -0
  216. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +0 -0
  217. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py +0 -10
  218. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +0 -3
  219. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py +0 -0
  220. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py +0 -3
  221. synapse_sdk/plugins/categories/registry.py +0 -16
  222. synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
  223. synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
  224. synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +0 -37
  225. synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +0 -7
  226. synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
  227. synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +0 -11
  228. synapse_sdk/plugins/categories/templates.py +0 -32
  229. synapse_sdk/plugins/cli/__init__.py +0 -21
  230. synapse_sdk/plugins/cli/publish.py +0 -37
  231. synapse_sdk/plugins/cli/run.py +0 -67
  232. synapse_sdk/plugins/exceptions.py +0 -22
  233. synapse_sdk/plugins/models.py +0 -121
  234. synapse_sdk/plugins/templates/cookiecutter.json +0 -11
  235. synapse_sdk/plugins/templates/hooks/post_gen_project.py +0 -3
  236. synapse_sdk/plugins/templates/hooks/pre_prompt.py +0 -21
  237. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
  238. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
  239. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.gitignore +0 -27
  240. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml +0 -7
  241. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md +0 -5
  242. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -6
  243. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
  244. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py +0 -0
  245. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml +0 -13
  246. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +0 -1
  247. synapse_sdk/shared/enums.py +0 -8
  248. synapse_sdk/utils/debug.py +0 -5
  249. synapse_sdk/utils/file.py +0 -87
  250. synapse_sdk/utils/module_loading.py +0 -29
  251. synapse_sdk/utils/pydantic/__init__.py +0 -0
  252. synapse_sdk/utils/pydantic/config.py +0 -4
  253. synapse_sdk/utils/pydantic/errors.py +0 -33
  254. synapse_sdk/utils/pydantic/validators.py +0 -7
  255. synapse_sdk/utils/storage.py +0 -91
  256. synapse_sdk/utils/string.py +0 -11
  257. synapse_sdk-1.0.0a11.dist-info/LICENSE +0 -21
  258. synapse_sdk-1.0.0a11.dist-info/METADATA +0 -43
  259. synapse_sdk-1.0.0a11.dist-info/RECORD +0 -111
  260. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/entry_points.txt +0 -0
  261. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,417 @@
1
+ """Action pipeline for chaining plugin actions with schema validation.
2
+
3
+ Provides a pipeline orchestrator that chains actions together like Unix pipes,
4
+ validating that the output schema of each action is compatible with the
5
+ input schema of the next action.
6
+
7
+ Example:
8
+ >>> from synapse_sdk.plugins.pipelines import ActionPipeline
9
+ >>>
10
+ >>> # Define compatible actions
11
+ >>> class DownloadAction(BaseAction[DownloadParams]):
12
+ ... result_model = DownloadResult
13
+ ...
14
+ >>> class ConvertAction(BaseAction[ConvertParams]): # ConvertParams compatible with DownloadResult
15
+ ... result_model = ConvertResult
16
+ ...
17
+ >>> # Create pipeline (validates schema compatibility at creation time)
18
+ >>> pipeline = ActionPipeline([DownloadAction, ConvertAction, TrainAction])
19
+ >>>
20
+ >>> # Execute pipeline (passes results as params)
21
+ >>> result = pipeline.execute(initial_params, ctx)
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import logging
27
+ from collections.abc import Callable
28
+ from typing import TYPE_CHECKING, Any
29
+
30
+ from pydantic import BaseModel
31
+
32
+ from synapse_sdk.plugins.action import BaseAction, NoResult, validate_result
33
+ from synapse_sdk.plugins.types import DataType
34
+
35
+ if TYPE_CHECKING:
36
+ from synapse_sdk.plugins.context import RuntimeContext
37
+
38
+ _logger = logging.getLogger(__name__)
39
+
40
+
41
+ class SchemaIncompatibleError(Exception):
42
+ """Raised when action schemas are incompatible in a pipeline."""
43
+
44
+ pass
45
+
46
+
47
+ class ActionPipeline:
48
+ """Pipeline that chains actions by validating input/output schema compatibility.
49
+
50
+ Actions are executed sequentially, with each action's result being merged
51
+ with the initial params to form the next action's params. This allows
52
+ downstream actions to use both the original params and upstream results.
53
+
54
+ Schema Compatibility:
55
+ For action A -> action B to be compatible:
56
+ - A.result_model must be defined (not NoResult)
57
+ - All required fields in B.params_model must be satisfiable by either:
58
+ - A.result_model fields, OR
59
+ - Initial params passed to pipeline.execute()
60
+
61
+ Example:
62
+ >>> # Unix pipe style: Download | Convert | Train
63
+ >>> pipeline = ActionPipeline([
64
+ ... DownloadDatasetAction,
65
+ ... ConvertDatasetAction,
66
+ ... TrainAction,
67
+ ... ])
68
+ >>>
69
+ >>> result = pipeline.execute(
70
+ ... params={'dataset_id': 123, 'target_format': 'yolo'},
71
+ ... ctx=runtime_ctx,
72
+ ... )
73
+ """
74
+
75
+ def __init__(
76
+ self,
77
+ actions: list[type[BaseAction]],
78
+ *,
79
+ validate_schemas: bool = True,
80
+ strict: bool = False,
81
+ ) -> None:
82
+ """Initialize pipeline with action sequence.
83
+
84
+ Args:
85
+ actions: Ordered list of action classes to execute.
86
+ validate_schemas: If True, validate schema compatibility at init.
87
+ strict: If True, raise on schema mismatch. If False, log warning.
88
+
89
+ Raises:
90
+ SchemaIncompatibleError: If strict=True and schemas are incompatible.
91
+ ValueError: If actions list is empty.
92
+ """
93
+ if not actions:
94
+ raise ValueError('Pipeline requires at least one action')
95
+
96
+ self._actions = actions
97
+ self._strict = strict
98
+
99
+ if validate_schemas:
100
+ self._validate_pipeline()
101
+
102
+ def _validate_pipeline(self) -> None:
103
+ """Validate type and schema compatibility between adjacent actions.
104
+
105
+ Checks:
106
+ 1. Semantic type compatibility (output_type -> input_type)
107
+ 2. Field compatibility (result_model fields -> params_model fields)
108
+
109
+ Raises:
110
+ SchemaIncompatibleError: If strict mode and types/schemas incompatible.
111
+ """
112
+ for i in range(len(self._actions) - 1):
113
+ source = self._actions[i]
114
+ target = self._actions[i + 1]
115
+
116
+ # Check semantic type compatibility
117
+ self._validate_types(source, target)
118
+
119
+ # Check schema field compatibility
120
+ self._validate_fields(source, target)
121
+
122
+ def _validate_types(
123
+ self,
124
+ source: type[BaseAction],
125
+ target: type[BaseAction],
126
+ ) -> None:
127
+ """Validate semantic type compatibility between actions.
128
+
129
+ Types are compatible if:
130
+ - Either is None (skip validation)
131
+ - They are the same DataType class
132
+ - One is a subclass of the other (e.g., YOLODataset is-a Dataset)
133
+
134
+ Args:
135
+ source: Source action class.
136
+ target: Target action class.
137
+
138
+ Raises:
139
+ SchemaIncompatibleError: If strict mode and types incompatible.
140
+ """
141
+ source_output: type[DataType] | None = getattr(source, 'output_type', None)
142
+ target_input: type[DataType] | None = getattr(target, 'input_type', None)
143
+
144
+ # If either is None, skip type validation (rely on schema validation)
145
+ if source_output is None or target_input is None:
146
+ return
147
+
148
+ # Check type compatibility using DataType.is_compatible_with
149
+ if not source_output.is_compatible_with(target_input):
150
+ msg = (
151
+ f"Type mismatch: '{source.__name__}' outputs {source_output.name!r} "
152
+ f"but '{target.__name__}' expects {target_input.name!r}."
153
+ )
154
+ if self._strict:
155
+ raise SchemaIncompatibleError(msg)
156
+ _logger.warning(msg)
157
+
158
+ def _validate_fields(
159
+ self,
160
+ source: type[BaseAction],
161
+ target: type[BaseAction],
162
+ ) -> None:
163
+ """Validate field compatibility between actions.
164
+
165
+ Args:
166
+ source: Source action class.
167
+ target: Target action class.
168
+
169
+ Raises:
170
+ SchemaIncompatibleError: If strict mode and fields incompatible.
171
+ """
172
+ # Check if source has result schema
173
+ if source.result_model is NoResult:
174
+ msg = (
175
+ f"Action '{source.__name__}' has no result_model. "
176
+ f"Cannot validate field compatibility with '{target.__name__}'."
177
+ )
178
+ if self._strict:
179
+ raise SchemaIncompatibleError(msg)
180
+ _logger.warning(msg)
181
+ return
182
+
183
+ # Check field compatibility
184
+ source_fields = self._get_model_fields(source.result_model)
185
+ target_required = self._get_required_fields(target.params_model)
186
+
187
+ missing = target_required - source_fields
188
+ if missing:
189
+ msg = (
190
+ f"Schema mismatch: '{source.__name__}' result missing fields "
191
+ f"required by '{target.__name__}' params: {missing}. "
192
+ f'These must be provided in initial params.'
193
+ )
194
+ if self._strict:
195
+ raise SchemaIncompatibleError(msg)
196
+ _logger.info(msg)
197
+
198
+ @staticmethod
199
+ def _get_model_fields(model: type[BaseModel]) -> set[str]:
200
+ """Get all field names from a Pydantic model."""
201
+ if not hasattr(model, 'model_fields'):
202
+ return set()
203
+ return set(model.model_fields.keys())
204
+
205
+ @staticmethod
206
+ def _get_required_fields(model: type[BaseModel]) -> set[str]:
207
+ """Get required field names (no default) from a Pydantic model."""
208
+ if not hasattr(model, 'model_fields'):
209
+ return set()
210
+ return {name for name, field in model.model_fields.items() if field.is_required()}
211
+
212
+ def execute(
213
+ self,
214
+ params: dict[str, Any] | BaseModel,
215
+ ctx: RuntimeContext,
216
+ *,
217
+ progress_callback: Callable[[int, int, str], None] | None = None,
218
+ ) -> Any:
219
+ """Execute the pipeline, chaining action results as params.
220
+
221
+ Args:
222
+ params: Initial parameters (dict or Pydantic model).
223
+ ctx: Runtime context for all actions.
224
+ progress_callback: Optional callback(current, total, action_name).
225
+
226
+ Returns:
227
+ Result from the final action in the pipeline.
228
+
229
+ Raises:
230
+ RuntimeError: If any action fails.
231
+ """
232
+ # Convert Pydantic model to dict if needed
233
+ if isinstance(params, BaseModel):
234
+ accumulated_params = params.model_dump()
235
+ else:
236
+ accumulated_params = dict(params)
237
+
238
+ total_actions = len(self._actions)
239
+ final_result = None
240
+
241
+ for i, action_cls in enumerate(self._actions):
242
+ action_name = action_cls.action_name or action_cls.__name__
243
+
244
+ # Report progress
245
+ if progress_callback:
246
+ progress_callback(i, total_actions, action_name)
247
+
248
+ # Validate params against action's params_model
249
+ try:
250
+ validated_params = action_cls.params_model.model_validate(accumulated_params)
251
+ except Exception as e:
252
+ raise RuntimeError(f"Failed to validate params for action '{action_name}': {e}") from e
253
+
254
+ # Create and execute action
255
+ action = action_cls(validated_params, ctx)
256
+
257
+ try:
258
+ if hasattr(action, 'run'):
259
+ result = action.run()
260
+ else:
261
+ result = action.execute()
262
+ except Exception as e:
263
+ raise RuntimeError(f"Action '{action_name}' failed: {e}") from e
264
+
265
+ # Validate result
266
+ result = validate_result(result, action_cls.result_model, _logger)
267
+
268
+ # Merge result into accumulated params for next action
269
+ if isinstance(result, BaseModel):
270
+ result_dict = result.model_dump()
271
+ elif isinstance(result, dict):
272
+ result_dict = result
273
+ else:
274
+ result_dict = {}
275
+
276
+ accumulated_params.update(result_dict)
277
+ final_result = result
278
+
279
+ # Final progress
280
+ if progress_callback:
281
+ progress_callback(total_actions, total_actions, 'complete')
282
+
283
+ return final_result
284
+
285
+ def validate_initial_params(
286
+ self,
287
+ params: dict[str, Any] | BaseModel,
288
+ ) -> list[str]:
289
+ """Check if initial params satisfy all required fields across pipeline.
290
+
291
+ Args:
292
+ params: Initial parameters to validate.
293
+
294
+ Returns:
295
+ List of missing required fields (empty if all satisfied).
296
+ """
297
+ if isinstance(params, BaseModel):
298
+ provided = set(params.model_dump().keys())
299
+ else:
300
+ provided = set(params.keys())
301
+
302
+ missing = []
303
+ accumulated = provided.copy()
304
+
305
+ for i, action_cls in enumerate(self._actions):
306
+ required = self._get_required_fields(action_cls.params_model)
307
+ action_missing = required - accumulated
308
+
309
+ if action_missing:
310
+ missing.extend(f'{action_cls.__name__}.{field}' for field in action_missing)
311
+
312
+ # Add result fields to accumulated
313
+ if action_cls.result_model is not NoResult:
314
+ accumulated.update(self._get_model_fields(action_cls.result_model))
315
+
316
+ return missing
317
+
318
+ def submit(
319
+ self,
320
+ params: dict[str, Any] | BaseModel,
321
+ executor: Any,
322
+ *,
323
+ name: str | None = None,
324
+ resume_from: str | None = None,
325
+ ) -> str:
326
+ """Submit the pipeline for remote execution (non-blocking).
327
+
328
+ This method submits the pipeline to a remote executor for asynchronous
329
+ execution. Use wait() or executor.get_progress() to monitor progress.
330
+
331
+ Args:
332
+ params: Initial parameters for the pipeline.
333
+ executor: Pipeline executor (e.g., RayPipelineExecutor).
334
+ name: Optional pipeline name for tracking.
335
+ resume_from: Run ID to resume from. If provided, the pipeline will
336
+ skip completed actions and restore accumulated params from the
337
+ latest checkpoint of that run.
338
+
339
+ Returns:
340
+ Run ID for tracking the execution.
341
+
342
+ Example:
343
+ >>> from synapse_sdk.plugins.executors.ray import RayPipelineExecutor
344
+ >>>
345
+ >>> executor = RayPipelineExecutor(
346
+ ... ray_address='auto',
347
+ ... pipeline_service_url='http://localhost:8100',
348
+ ... )
349
+ >>> run_id = pipeline.submit(params, executor)
350
+ >>> progress = executor.get_progress(run_id)
351
+ >>> result = pipeline.wait(run_id, executor)
352
+ >>>
353
+ >>> # Resume from a failed run
354
+ >>> new_run_id = pipeline.submit(params, executor, resume_from=run_id)
355
+ """
356
+ from synapse_sdk.plugins.executors.ray.pipeline import PipelineDefinition
357
+
358
+ # Convert Pydantic model to dict if needed
359
+ if isinstance(params, BaseModel):
360
+ params_dict = params.model_dump()
361
+ else:
362
+ params_dict = dict(params)
363
+
364
+ # Create pipeline definition
365
+ pipeline_name = name or self.__repr__()
366
+ pipeline_def = PipelineDefinition(
367
+ name=pipeline_name,
368
+ actions=self._actions,
369
+ )
370
+
371
+ # Submit to executor
372
+ return executor.submit(pipeline_def, params_dict, resume_from=resume_from)
373
+
374
+ def wait(
375
+ self,
376
+ run_id: str,
377
+ executor: Any,
378
+ *,
379
+ timeout_seconds: float = 3600,
380
+ poll_interval: float = 5.0,
381
+ ) -> Any:
382
+ """Wait for a submitted pipeline to complete.
383
+
384
+ Args:
385
+ run_id: Run ID from submit().
386
+ executor: The same executor used for submit().
387
+ timeout_seconds: Maximum time to wait.
388
+ poll_interval: Seconds between progress polls.
389
+
390
+ Returns:
391
+ Final pipeline result.
392
+
393
+ Raises:
394
+ ExecutionError: If pipeline fails or times out.
395
+
396
+ Example:
397
+ >>> run_id = pipeline.submit(params, executor)
398
+ >>> # ... do other work ...
399
+ >>> result = pipeline.wait(run_id, executor)
400
+ """
401
+ executor.wait(run_id, timeout_seconds, poll_interval)
402
+ return executor.get_result(run_id)
403
+
404
+ @property
405
+ def actions(self) -> list[type[BaseAction]]:
406
+ """Get list of action classes in the pipeline."""
407
+ return self._actions.copy()
408
+
409
+ def __len__(self) -> int:
410
+ return len(self._actions)
411
+
412
+ def __repr__(self) -> str:
413
+ action_names = ' | '.join(a.__name__ for a in self._actions)
414
+ return f'ActionPipeline({action_names})'
415
+
416
+
417
+ __all__ = ['ActionPipeline', 'SchemaIncompatibleError']
@@ -0,0 +1,107 @@
1
+ """Pipeline execution context for shared working directory."""
2
+
3
+ import uuid
4
+ from dataclasses import dataclass, field
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+
9
+ @dataclass
10
+ class PipelineContext:
11
+ """Shared context for all actions in a pipeline execution.
12
+
13
+ Provides a consistent working directory structure that persists
14
+ across all actions in the pipeline. Each pipeline run gets a unique
15
+ directory under the base path.
16
+
17
+ Attributes:
18
+ pipeline_id: Unique identifier for this pipeline run.
19
+ run_id: Unique identifier for this specific execution.
20
+ base_path: Base directory for all pipeline working directories.
21
+ metadata: Optional metadata for the pipeline.
22
+
23
+ Example:
24
+ >>> ctx = PipelineContext(pipeline_id="my-pipeline")
25
+ >>> ctx.work_dir
26
+ PosixPath('/tmp/synapse_pipelines/my-pipeline/abc123')
27
+ >>> ctx.datasets_dir
28
+ PosixPath('/tmp/synapse_pipelines/my-pipeline/abc123/datasets')
29
+ """
30
+
31
+ pipeline_id: str
32
+ run_id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
33
+ base_path: Path = field(default_factory=lambda: Path('/tmp/synapse_pipelines'))
34
+ metadata: dict[str, Any] = field(default_factory=dict)
35
+
36
+ def __post_init__(self) -> None:
37
+ """Ensure base path is a Path object and create work directory."""
38
+ if isinstance(self.base_path, str):
39
+ self.base_path = Path(self.base_path)
40
+ # Create work directory on initialization
41
+ self.work_dir.mkdir(parents=True, exist_ok=True)
42
+
43
+ @property
44
+ def work_dir(self) -> Path:
45
+ """Get the working directory for this pipeline run."""
46
+ return self.base_path / self.pipeline_id / self.run_id
47
+
48
+ @property
49
+ def datasets_dir(self) -> Path:
50
+ """Get the datasets directory (for downloaded/converted data)."""
51
+ path = self.work_dir / 'datasets'
52
+ path.mkdir(parents=True, exist_ok=True)
53
+ return path
54
+
55
+ @property
56
+ def models_dir(self) -> Path:
57
+ """Get the models directory (for trained models/weights)."""
58
+ path = self.work_dir / 'models'
59
+ path.mkdir(parents=True, exist_ok=True)
60
+ return path
61
+
62
+ @property
63
+ def artifacts_dir(self) -> Path:
64
+ """Get the artifacts directory (for outputs, exports, etc.)."""
65
+ path = self.work_dir / 'artifacts'
66
+ path.mkdir(parents=True, exist_ok=True)
67
+ return path
68
+
69
+ @property
70
+ def logs_dir(self) -> Path:
71
+ """Get the logs directory."""
72
+ path = self.work_dir / 'logs'
73
+ path.mkdir(parents=True, exist_ok=True)
74
+ return path
75
+
76
+ @property
77
+ def checkpoints_dir(self) -> Path:
78
+ """Get the checkpoints directory for pipeline resume."""
79
+ path = self.work_dir / 'checkpoints'
80
+ path.mkdir(parents=True, exist_ok=True)
81
+ return path
82
+
83
+ def get_action_dir(self, action_name: str) -> Path:
84
+ """Get a dedicated directory for a specific action.
85
+
86
+ Args:
87
+ action_name: Name of the action.
88
+
89
+ Returns:
90
+ Path to the action's dedicated directory.
91
+ """
92
+ path = self.work_dir / 'actions' / action_name
93
+ path.mkdir(parents=True, exist_ok=True)
94
+ return path
95
+
96
+ def cleanup(self) -> None:
97
+ """Remove all files in the working directory.
98
+
99
+ Use with caution - this deletes all pipeline artifacts.
100
+ """
101
+ import shutil
102
+
103
+ if self.work_dir.exists():
104
+ shutil.rmtree(self.work_dir)
105
+
106
+
107
+ __all__ = ['PipelineContext']