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.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/__init__.py +9 -8
- synapse_sdk/cli/agent/__init__.py +25 -0
- synapse_sdk/cli/agent/config.py +104 -0
- synapse_sdk/cli/agent/select.py +197 -0
- synapse_sdk/cli/auth.py +104 -0
- synapse_sdk/cli/main.py +1025 -0
- synapse_sdk/cli/plugin/__init__.py +58 -0
- synapse_sdk/cli/plugin/create.py +566 -0
- synapse_sdk/cli/plugin/job.py +196 -0
- synapse_sdk/cli/plugin/publish.py +322 -0
- synapse_sdk/cli/plugin/run.py +131 -0
- synapse_sdk/cli/plugin/test.py +200 -0
- synapse_sdk/clients/README.md +239 -0
- synapse_sdk/clients/__init__.py +5 -0
- synapse_sdk/clients/_template.py +266 -0
- synapse_sdk/clients/agent/__init__.py +84 -29
- synapse_sdk/clients/agent/async_ray.py +289 -0
- synapse_sdk/clients/agent/container.py +83 -0
- synapse_sdk/clients/agent/plugin.py +101 -0
- synapse_sdk/clients/agent/ray.py +296 -39
- synapse_sdk/clients/backend/__init__.py +152 -12
- synapse_sdk/clients/backend/annotation.py +164 -22
- synapse_sdk/clients/backend/core.py +101 -0
- synapse_sdk/clients/backend/data_collection.py +292 -0
- synapse_sdk/clients/backend/hitl.py +87 -0
- synapse_sdk/clients/backend/integration.py +374 -46
- synapse_sdk/clients/backend/ml.py +134 -22
- synapse_sdk/clients/backend/models.py +247 -0
- synapse_sdk/clients/base.py +538 -59
- synapse_sdk/clients/exceptions.py +35 -7
- synapse_sdk/clients/pipeline/__init__.py +5 -0
- synapse_sdk/clients/pipeline/client.py +636 -0
- synapse_sdk/clients/protocols.py +178 -0
- synapse_sdk/clients/utils.py +86 -8
- synapse_sdk/clients/validation.py +58 -0
- synapse_sdk/enums.py +76 -0
- synapse_sdk/exceptions.py +168 -0
- synapse_sdk/integrations/__init__.py +74 -0
- synapse_sdk/integrations/_base.py +119 -0
- synapse_sdk/integrations/_context.py +53 -0
- synapse_sdk/integrations/ultralytics/__init__.py +78 -0
- synapse_sdk/integrations/ultralytics/_callbacks.py +126 -0
- synapse_sdk/integrations/ultralytics/_patches.py +124 -0
- synapse_sdk/loggers.py +476 -95
- synapse_sdk/mcp/MCP.md +69 -0
- synapse_sdk/mcp/__init__.py +48 -0
- synapse_sdk/mcp/__main__.py +6 -0
- synapse_sdk/mcp/config.py +349 -0
- synapse_sdk/mcp/prompts/__init__.py +4 -0
- synapse_sdk/mcp/resources/__init__.py +4 -0
- synapse_sdk/mcp/server.py +1352 -0
- synapse_sdk/mcp/tools/__init__.py +6 -0
- synapse_sdk/plugins/__init__.py +133 -9
- synapse_sdk/plugins/action.py +229 -0
- synapse_sdk/plugins/actions/__init__.py +82 -0
- synapse_sdk/plugins/actions/dataset/__init__.py +37 -0
- synapse_sdk/plugins/actions/dataset/action.py +471 -0
- synapse_sdk/plugins/actions/export/__init__.py +55 -0
- synapse_sdk/plugins/actions/export/action.py +183 -0
- synapse_sdk/plugins/actions/export/context.py +59 -0
- synapse_sdk/plugins/actions/inference/__init__.py +84 -0
- synapse_sdk/plugins/actions/inference/action.py +285 -0
- synapse_sdk/plugins/actions/inference/context.py +81 -0
- synapse_sdk/plugins/actions/inference/deployment.py +322 -0
- synapse_sdk/plugins/actions/inference/serve.py +252 -0
- synapse_sdk/plugins/actions/train/__init__.py +54 -0
- synapse_sdk/plugins/actions/train/action.py +326 -0
- synapse_sdk/plugins/actions/train/context.py +57 -0
- synapse_sdk/plugins/actions/upload/__init__.py +49 -0
- synapse_sdk/plugins/actions/upload/action.py +165 -0
- synapse_sdk/plugins/actions/upload/context.py +61 -0
- synapse_sdk/plugins/config.py +98 -0
- synapse_sdk/plugins/context/__init__.py +109 -0
- synapse_sdk/plugins/context/env.py +113 -0
- synapse_sdk/plugins/datasets/__init__.py +113 -0
- synapse_sdk/plugins/datasets/converters/__init__.py +76 -0
- synapse_sdk/plugins/datasets/converters/base.py +347 -0
- synapse_sdk/plugins/datasets/converters/yolo/__init__.py +9 -0
- synapse_sdk/plugins/datasets/converters/yolo/from_dm.py +468 -0
- synapse_sdk/plugins/datasets/converters/yolo/to_dm.py +381 -0
- synapse_sdk/plugins/datasets/formats/__init__.py +82 -0
- synapse_sdk/plugins/datasets/formats/dm.py +351 -0
- synapse_sdk/plugins/datasets/formats/yolo.py +240 -0
- synapse_sdk/plugins/decorators.py +83 -0
- synapse_sdk/plugins/discovery.py +790 -0
- synapse_sdk/plugins/docs/ACTION_DEV_GUIDE.md +933 -0
- synapse_sdk/plugins/docs/ARCHITECTURE.md +1225 -0
- synapse_sdk/plugins/docs/LOGGING_SYSTEM.md +683 -0
- synapse_sdk/plugins/docs/OVERVIEW.md +531 -0
- synapse_sdk/plugins/docs/PIPELINE_GUIDE.md +145 -0
- synapse_sdk/plugins/docs/README.md +513 -0
- synapse_sdk/plugins/docs/STEP.md +656 -0
- synapse_sdk/plugins/enums.py +70 -10
- synapse_sdk/plugins/errors.py +92 -0
- synapse_sdk/plugins/executors/__init__.py +43 -0
- synapse_sdk/plugins/executors/local.py +99 -0
- synapse_sdk/plugins/executors/ray/__init__.py +18 -0
- synapse_sdk/plugins/executors/ray/base.py +282 -0
- synapse_sdk/plugins/executors/ray/job.py +298 -0
- synapse_sdk/plugins/executors/ray/jobs_api.py +511 -0
- synapse_sdk/plugins/executors/ray/packaging.py +137 -0
- synapse_sdk/plugins/executors/ray/pipeline.py +792 -0
- synapse_sdk/plugins/executors/ray/task.py +257 -0
- synapse_sdk/plugins/models/__init__.py +26 -0
- synapse_sdk/plugins/models/logger.py +173 -0
- synapse_sdk/plugins/models/pipeline.py +25 -0
- synapse_sdk/plugins/pipelines/__init__.py +81 -0
- synapse_sdk/plugins/pipelines/action_pipeline.py +417 -0
- synapse_sdk/plugins/pipelines/context.py +107 -0
- synapse_sdk/plugins/pipelines/display.py +311 -0
- synapse_sdk/plugins/runner.py +114 -0
- synapse_sdk/plugins/schemas/__init__.py +19 -0
- synapse_sdk/plugins/schemas/results.py +152 -0
- synapse_sdk/plugins/steps/__init__.py +63 -0
- synapse_sdk/plugins/steps/base.py +128 -0
- synapse_sdk/plugins/steps/context.py +90 -0
- synapse_sdk/plugins/steps/orchestrator.py +128 -0
- synapse_sdk/plugins/steps/registry.py +103 -0
- synapse_sdk/plugins/steps/utils/__init__.py +20 -0
- synapse_sdk/plugins/steps/utils/logging.py +85 -0
- synapse_sdk/plugins/steps/utils/timing.py +71 -0
- synapse_sdk/plugins/steps/utils/validation.py +68 -0
- synapse_sdk/plugins/templates/__init__.py +50 -0
- synapse_sdk/plugins/templates/base/.gitignore.j2 +26 -0
- synapse_sdk/plugins/templates/base/.synapseignore.j2 +11 -0
- synapse_sdk/plugins/templates/base/README.md.j2 +26 -0
- synapse_sdk/plugins/templates/base/plugin/__init__.py.j2 +1 -0
- synapse_sdk/plugins/templates/base/pyproject.toml.j2 +14 -0
- synapse_sdk/plugins/templates/base/requirements.txt.j2 +1 -0
- synapse_sdk/plugins/templates/custom/plugin/main.py.j2 +18 -0
- synapse_sdk/plugins/templates/data_validation/plugin/validate.py.j2 +32 -0
- synapse_sdk/plugins/templates/export/plugin/export.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/inference.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/train.py.j2 +33 -0
- synapse_sdk/plugins/templates/post_annotation/plugin/post_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/pre_annotation/plugin/pre_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/smart_tool/plugin/auto_label.py.j2 +44 -0
- synapse_sdk/plugins/templates/upload/plugin/upload.py.j2 +35 -0
- synapse_sdk/plugins/testing/__init__.py +25 -0
- synapse_sdk/plugins/testing/sample_actions.py +98 -0
- synapse_sdk/plugins/types.py +206 -0
- synapse_sdk/plugins/upload.py +595 -64
- synapse_sdk/plugins/utils.py +325 -37
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/utils/__init__.py +1 -0
- synapse_sdk/utils/auth.py +74 -0
- synapse_sdk/utils/file/__init__.py +58 -0
- synapse_sdk/utils/file/archive.py +449 -0
- synapse_sdk/utils/file/checksum.py +167 -0
- synapse_sdk/utils/file/download.py +286 -0
- synapse_sdk/utils/file/io.py +129 -0
- synapse_sdk/utils/file/requirements.py +36 -0
- synapse_sdk/utils/network.py +168 -0
- synapse_sdk/utils/storage/__init__.py +238 -0
- synapse_sdk/utils/storage/config.py +188 -0
- synapse_sdk/utils/storage/errors.py +52 -0
- synapse_sdk/utils/storage/providers/__init__.py +13 -0
- synapse_sdk/utils/storage/providers/base.py +76 -0
- synapse_sdk/utils/storage/providers/gcs.py +168 -0
- synapse_sdk/utils/storage/providers/http.py +250 -0
- synapse_sdk/utils/storage/providers/local.py +126 -0
- synapse_sdk/utils/storage/providers/s3.py +177 -0
- synapse_sdk/utils/storage/providers/sftp.py +208 -0
- synapse_sdk/utils/storage/registry.py +125 -0
- synapse_sdk/utils/websocket.py +99 -0
- synapse_sdk-2026.1.1b2.dist-info/METADATA +715 -0
- synapse_sdk-2026.1.1b2.dist-info/RECORD +172 -0
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/WHEEL +1 -1
- synapse_sdk-2026.1.1b2.dist-info/licenses/LICENSE +201 -0
- locale/en/LC_MESSAGES/messages.mo +0 -0
- locale/en/LC_MESSAGES/messages.po +0 -39
- locale/ko/LC_MESSAGES/messages.mo +0 -0
- locale/ko/LC_MESSAGES/messages.po +0 -34
- synapse_sdk/cli/create_plugin.py +0 -10
- synapse_sdk/clients/agent/core.py +0 -7
- synapse_sdk/clients/agent/service.py +0 -15
- synapse_sdk/clients/backend/dataset.py +0 -51
- synapse_sdk/clients/ray/__init__.py +0 -6
- synapse_sdk/clients/ray/core.py +0 -22
- synapse_sdk/clients/ray/serve.py +0 -20
- synapse_sdk/i18n.py +0 -35
- synapse_sdk/plugins/categories/__init__.py +0 -0
- synapse_sdk/plugins/categories/base.py +0 -235
- synapse_sdk/plugins/categories/data_validation/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/validation.py +0 -10
- synapse_sdk/plugins/categories/data_validation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/data_validation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +0 -5
- synapse_sdk/plugins/categories/decorators.py +0 -13
- synapse_sdk/plugins/categories/export/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/export.py +0 -10
- synapse_sdk/plugins/categories/import/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/import.py +0 -10
- synapse_sdk/plugins/categories/neural_net/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/deployment.py +0 -45
- synapse_sdk/plugins/categories/neural_net/actions/inference.py +0 -18
- synapse_sdk/plugins/categories/neural_net/actions/test.py +0 -10
- synapse_sdk/plugins/categories/neural_net/actions/train.py +0 -143
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +0 -12
- synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +0 -4
- synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py +0 -2
- synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +0 -14
- synapse_sdk/plugins/categories/post_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py +0 -10
- synapse_sdk/plugins/categories/post_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.py +0 -3
- synapse_sdk/plugins/categories/pre_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py +0 -10
- synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py +0 -3
- synapse_sdk/plugins/categories/registry.py +0 -16
- synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +0 -37
- synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +0 -7
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +0 -11
- synapse_sdk/plugins/categories/templates.py +0 -32
- synapse_sdk/plugins/cli/__init__.py +0 -21
- synapse_sdk/plugins/cli/publish.py +0 -37
- synapse_sdk/plugins/cli/run.py +0 -67
- synapse_sdk/plugins/exceptions.py +0 -22
- synapse_sdk/plugins/models.py +0 -121
- synapse_sdk/plugins/templates/cookiecutter.json +0 -11
- synapse_sdk/plugins/templates/hooks/post_gen_project.py +0 -3
- synapse_sdk/plugins/templates/hooks/pre_prompt.py +0 -21
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.gitignore +0 -27
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml +0 -7
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md +0 -5
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -6
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py +0 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml +0 -13
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +0 -1
- synapse_sdk/shared/enums.py +0 -8
- synapse_sdk/utils/debug.py +0 -5
- synapse_sdk/utils/file.py +0 -87
- synapse_sdk/utils/module_loading.py +0 -29
- synapse_sdk/utils/pydantic/__init__.py +0 -0
- synapse_sdk/utils/pydantic/config.py +0 -4
- synapse_sdk/utils/pydantic/errors.py +0 -33
- synapse_sdk/utils/pydantic/validators.py +0 -7
- synapse_sdk/utils/storage.py +0 -91
- synapse_sdk/utils/string.py +0 -11
- synapse_sdk-1.0.0a11.dist-info/LICENSE +0 -21
- synapse_sdk-1.0.0a11.dist-info/METADATA +0 -43
- synapse_sdk-1.0.0a11.dist-info/RECORD +0 -111
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/entry_points.txt +0 -0
- {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']
|