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,128 @@
|
|
|
1
|
+
"""Workflow step base class and result dataclass.
|
|
2
|
+
|
|
3
|
+
Provides the foundation for defining workflow steps
|
|
4
|
+
with execution, skip conditions, and rollback support.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from synapse_sdk.plugins.steps.context import BaseStepContext
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class StepResult:
|
|
20
|
+
"""Result of a workflow step execution.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
success: Whether the step completed successfully.
|
|
24
|
+
data: Output data from the step.
|
|
25
|
+
error: Error message if step failed.
|
|
26
|
+
rollback_data: Data needed for rollback on failure.
|
|
27
|
+
skipped: Whether the step was skipped.
|
|
28
|
+
timestamp: When the step completed.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> result = StepResult(success=True, data={'files': 10})
|
|
32
|
+
>>> if not result.success:
|
|
33
|
+
... print(f"Failed: {result.error}")
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
success: bool = True
|
|
37
|
+
data: dict[str, Any] = field(default_factory=dict)
|
|
38
|
+
error: str | None = None
|
|
39
|
+
rollback_data: dict[str, Any] = field(default_factory=dict)
|
|
40
|
+
skipped: bool = False
|
|
41
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BaseStep[C: BaseStepContext](ABC):
|
|
45
|
+
"""Abstract base class for workflow steps.
|
|
46
|
+
|
|
47
|
+
Type parameter C is the context type (must extend BaseStepContext).
|
|
48
|
+
Implement this class to define custom workflow steps with
|
|
49
|
+
execution, skip conditions, and rollback support.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
name: Unique identifier for the step.
|
|
53
|
+
progress_weight: Relative weight (0.0-1.0) for progress calculation.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
>>> class ValidateStep(BaseStep[MyContext]):
|
|
57
|
+
... @property
|
|
58
|
+
... def name(self) -> str:
|
|
59
|
+
... return 'validate'
|
|
60
|
+
...
|
|
61
|
+
... @property
|
|
62
|
+
... def progress_weight(self) -> float:
|
|
63
|
+
... return 0.1
|
|
64
|
+
...
|
|
65
|
+
... def execute(self, context: MyContext) -> StepResult:
|
|
66
|
+
... if not context.data:
|
|
67
|
+
... return StepResult(success=False, error='No data')
|
|
68
|
+
... return StepResult(success=True)
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def name(self) -> str:
|
|
74
|
+
"""Step identifier.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Unique name for this step.
|
|
78
|
+
"""
|
|
79
|
+
...
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def progress_weight(self) -> float:
|
|
84
|
+
"""Relative weight for progress calculation.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Float between 0.0 and 1.0 representing this step's
|
|
88
|
+
portion of total workflow progress.
|
|
89
|
+
"""
|
|
90
|
+
...
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def execute(self, context: C) -> StepResult:
|
|
94
|
+
"""Execute the step.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
context: Shared context with params and state.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
StepResult indicating success/failure and any output data.
|
|
101
|
+
"""
|
|
102
|
+
...
|
|
103
|
+
|
|
104
|
+
def can_skip(self, context: C) -> bool:
|
|
105
|
+
"""Check if step can be skipped.
|
|
106
|
+
|
|
107
|
+
Override to implement conditional step execution.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
context: Shared context.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
True if step should be skipped, False otherwise.
|
|
114
|
+
Default: False.
|
|
115
|
+
"""
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
def rollback(self, context: C, result: StepResult) -> None:
|
|
119
|
+
"""Rollback step on workflow failure.
|
|
120
|
+
|
|
121
|
+
Override to implement cleanup when a later step fails.
|
|
122
|
+
Called in reverse order for all executed steps.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
context: Shared context.
|
|
126
|
+
result: The result from this step's execution.
|
|
127
|
+
"""
|
|
128
|
+
pass
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Base context for step-based workflows.
|
|
2
|
+
|
|
3
|
+
Provides the abstract base class for sharing state between workflow steps.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from synapse_sdk.plugins.context import RuntimeContext
|
|
13
|
+
from synapse_sdk.plugins.steps.base import StepResult
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class BaseStepContext:
|
|
18
|
+
"""Abstract base context for step-based workflows.
|
|
19
|
+
|
|
20
|
+
Provides the common interface for step contexts. Subclass this
|
|
21
|
+
to add action-specific state fields.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
runtime_ctx: Parent RuntimeContext with logger, env, client.
|
|
25
|
+
step_results: Results from each executed step.
|
|
26
|
+
errors: Accumulated error messages.
|
|
27
|
+
current_step: Name of the currently executing step (set by Orchestrator).
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
>>> @dataclass
|
|
31
|
+
... class UploadContext(BaseStepContext):
|
|
32
|
+
... params: dict[str, Any] = field(default_factory=dict)
|
|
33
|
+
... uploaded_files: list[str] = field(default_factory=list)
|
|
34
|
+
>>>
|
|
35
|
+
>>> ctx = UploadContext(runtime_ctx=runtime_ctx)
|
|
36
|
+
>>> ctx.log('upload_start', {'count': 10})
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
runtime_ctx: RuntimeContext
|
|
40
|
+
step_results: list[StepResult] = field(default_factory=list)
|
|
41
|
+
errors: list[str] = field(default_factory=list)
|
|
42
|
+
current_step: str | None = field(default=None, init=False)
|
|
43
|
+
|
|
44
|
+
def _set_current_step(self, step_name: str | None) -> None:
|
|
45
|
+
"""Set the currently executing step name (internal use by Orchestrator).
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
step_name: Name of the step being executed, or None to clear.
|
|
49
|
+
"""
|
|
50
|
+
self.current_step = step_name
|
|
51
|
+
|
|
52
|
+
def log(self, event: str, data: dict[str, Any], file: str | None = None) -> None:
|
|
53
|
+
"""Log an event via runtime context.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
event: Event name/type.
|
|
57
|
+
data: Dictionary of event data.
|
|
58
|
+
file: Optional file path associated with the event.
|
|
59
|
+
"""
|
|
60
|
+
self.runtime_ctx.log(event, data, file)
|
|
61
|
+
|
|
62
|
+
def set_progress(self, current: int, total: int, category: str | None = None) -> None:
|
|
63
|
+
"""Set progress via runtime context.
|
|
64
|
+
|
|
65
|
+
If category is not provided, uses current_step as the category.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
current: Current progress value.
|
|
69
|
+
total: Total progress value.
|
|
70
|
+
category: Optional category name. Defaults to current_step if not provided.
|
|
71
|
+
"""
|
|
72
|
+
effective_category = category if category is not None else self.current_step
|
|
73
|
+
self.runtime_ctx.set_progress(current, total, effective_category)
|
|
74
|
+
|
|
75
|
+
def set_metrics(self, value: dict[str, Any], category: str | None = None) -> None:
|
|
76
|
+
"""Set metrics via runtime context.
|
|
77
|
+
|
|
78
|
+
If category is not provided, uses current_step as the category.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
value: Dictionary of metric values.
|
|
82
|
+
category: Category name. Defaults to current_step if not provided.
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
ValueError: If category is not provided and current_step is None.
|
|
86
|
+
"""
|
|
87
|
+
effective_category = category if category is not None else self.current_step
|
|
88
|
+
if effective_category is None:
|
|
89
|
+
raise ValueError('category must be provided when not executing within a step (current_step is None)')
|
|
90
|
+
self.runtime_ctx.set_metrics(value, effective_category)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Orchestrator for executing workflow steps with rollback support."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
from synapse_sdk.plugins.steps.base import BaseStep, StepResult
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from synapse_sdk.plugins.steps.context import BaseStepContext
|
|
12
|
+
from synapse_sdk.plugins.steps.registry import StepRegistry
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Orchestrator[C: BaseStepContext]:
|
|
16
|
+
"""Executes workflow steps with progress tracking and rollback.
|
|
17
|
+
|
|
18
|
+
Type parameter C is the context type shared between steps.
|
|
19
|
+
Runs steps in order, tracking progress based on step weights.
|
|
20
|
+
On failure, automatically rolls back executed steps in reverse order.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
registry: StepRegistry containing ordered steps.
|
|
24
|
+
context: Shared context for step communication.
|
|
25
|
+
progress_callback: Optional callback for progress updates.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
>>> registry = StepRegistry[MyContext]()
|
|
29
|
+
>>> registry.register(InitStep())
|
|
30
|
+
>>> registry.register(ProcessStep())
|
|
31
|
+
>>> context = MyContext(runtime_ctx=runtime_ctx)
|
|
32
|
+
>>> orchestrator = Orchestrator(registry, context)
|
|
33
|
+
>>> result = orchestrator.execute()
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
registry: StepRegistry[C],
|
|
39
|
+
context: C,
|
|
40
|
+
progress_callback: Callable[[int, int], None] | None = None,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Initialize orchestrator.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
registry: StepRegistry with steps to execute.
|
|
46
|
+
context: Shared context for steps.
|
|
47
|
+
progress_callback: Optional callback(current, total) for progress.
|
|
48
|
+
"""
|
|
49
|
+
self._registry = registry
|
|
50
|
+
self._context = context
|
|
51
|
+
self._progress_callback = progress_callback
|
|
52
|
+
self._executed_steps: list[tuple[BaseStep[C], StepResult]] = []
|
|
53
|
+
|
|
54
|
+
def execute(self) -> dict[str, Any]:
|
|
55
|
+
"""Execute all steps in order with rollback on failure.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Dict with success status and step count.
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
RuntimeError: If any step fails (after rollback).
|
|
62
|
+
"""
|
|
63
|
+
steps = self._registry.get_steps()
|
|
64
|
+
total_weight = self._registry.total_weight
|
|
65
|
+
completed_weight = 0.0
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
for step in steps:
|
|
69
|
+
# Set current step name for auto-category support
|
|
70
|
+
self._context._set_current_step(step.name)
|
|
71
|
+
|
|
72
|
+
# Check skip condition
|
|
73
|
+
if step.can_skip(self._context):
|
|
74
|
+
result = StepResult(success=True, skipped=True)
|
|
75
|
+
self._context.step_results.append(result)
|
|
76
|
+
completed_weight += step.progress_weight
|
|
77
|
+
self._update_progress(completed_weight, total_weight)
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
# Execute step
|
|
81
|
+
try:
|
|
82
|
+
result = step.execute(self._context)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
result = StepResult(success=False, error=str(e))
|
|
85
|
+
|
|
86
|
+
self._context.step_results.append(result)
|
|
87
|
+
self._executed_steps.append((step, result))
|
|
88
|
+
|
|
89
|
+
if not result.success:
|
|
90
|
+
self._rollback()
|
|
91
|
+
raise RuntimeError(f"Step '{step.name}' failed: {result.error}")
|
|
92
|
+
|
|
93
|
+
# Update progress
|
|
94
|
+
completed_weight += step.progress_weight
|
|
95
|
+
self._update_progress(completed_weight, total_weight)
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
'success': True,
|
|
99
|
+
'steps_executed': len(self._executed_steps),
|
|
100
|
+
'steps_total': len(steps),
|
|
101
|
+
}
|
|
102
|
+
finally:
|
|
103
|
+
# Clear current step after execution completes or fails
|
|
104
|
+
self._context._set_current_step(None)
|
|
105
|
+
|
|
106
|
+
def _update_progress(self, completed: float, total: float) -> None:
|
|
107
|
+
"""Update progress via callback.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
completed: Completed weight sum.
|
|
111
|
+
total: Total weight sum.
|
|
112
|
+
"""
|
|
113
|
+
if self._progress_callback and total > 0:
|
|
114
|
+
progress_pct = int((completed / total) * 100)
|
|
115
|
+
self._progress_callback(progress_pct, 100)
|
|
116
|
+
|
|
117
|
+
def _rollback(self) -> None:
|
|
118
|
+
"""Rollback executed steps in reverse order.
|
|
119
|
+
|
|
120
|
+
Best-effort rollback - errors during rollback are logged but
|
|
121
|
+
do not prevent other steps from rolling back.
|
|
122
|
+
"""
|
|
123
|
+
for step, result in reversed(self._executed_steps):
|
|
124
|
+
try:
|
|
125
|
+
step.rollback(self._context, result)
|
|
126
|
+
except Exception:
|
|
127
|
+
# Best effort rollback - log but continue
|
|
128
|
+
self._context.errors.append(f"Rollback failed for step '{step.name}'")
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Step registry for managing ordered workflow steps."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from synapse_sdk.plugins.steps.base import BaseStep
|
|
9
|
+
from synapse_sdk.plugins.steps.context import BaseStepContext
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StepRegistry[C: BaseStepContext]:
|
|
13
|
+
"""Registry for managing ordered workflow steps.
|
|
14
|
+
|
|
15
|
+
Type parameter C is the context type for steps in this registry.
|
|
16
|
+
Maintains an ordered list of steps and provides methods for
|
|
17
|
+
registration, removal, and insertion at specific positions.
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
>>> registry = StepRegistry[MyContext]()
|
|
21
|
+
>>> registry.register(InitStep())
|
|
22
|
+
>>> registry.register(ProcessStep())
|
|
23
|
+
>>> registry.insert_before('process', ValidateStep())
|
|
24
|
+
>>> for step in registry.get_steps():
|
|
25
|
+
... print(step.name) # init, validate, process
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self) -> None:
|
|
29
|
+
"""Initialize empty registry."""
|
|
30
|
+
self._steps: list[BaseStep[C]] = []
|
|
31
|
+
|
|
32
|
+
def register(self, step: BaseStep[C]) -> None:
|
|
33
|
+
"""Add step to end of workflow.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
step: Step instance to register.
|
|
37
|
+
"""
|
|
38
|
+
self._steps.append(step)
|
|
39
|
+
|
|
40
|
+
def unregister(self, name: str) -> None:
|
|
41
|
+
"""Remove step by name.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
name: Name of step to remove.
|
|
45
|
+
"""
|
|
46
|
+
self._steps = [s for s in self._steps if s.name != name]
|
|
47
|
+
|
|
48
|
+
def get_steps(self) -> list[BaseStep[C]]:
|
|
49
|
+
"""Get ordered list of steps.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Copy of the step list in execution order.
|
|
53
|
+
"""
|
|
54
|
+
return list(self._steps)
|
|
55
|
+
|
|
56
|
+
def insert_after(self, after_name: str, step: BaseStep[C]) -> None:
|
|
57
|
+
"""Insert step after another step.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
after_name: Name of existing step to insert after.
|
|
61
|
+
step: Step instance to insert.
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
ValueError: If step with after_name not found.
|
|
65
|
+
"""
|
|
66
|
+
for i, s in enumerate(self._steps):
|
|
67
|
+
if s.name == after_name:
|
|
68
|
+
self._steps.insert(i + 1, step)
|
|
69
|
+
return
|
|
70
|
+
raise ValueError(f"Step '{after_name}' not found")
|
|
71
|
+
|
|
72
|
+
def insert_before(self, before_name: str, step: BaseStep[C]) -> None:
|
|
73
|
+
"""Insert step before another step.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
before_name: Name of existing step to insert before.
|
|
77
|
+
step: Step instance to insert.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
ValueError: If step with before_name not found.
|
|
81
|
+
"""
|
|
82
|
+
for i, s in enumerate(self._steps):
|
|
83
|
+
if s.name == before_name:
|
|
84
|
+
self._steps.insert(i, step)
|
|
85
|
+
return
|
|
86
|
+
raise ValueError(f"Step '{before_name}' not found")
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def total_weight(self) -> float:
|
|
90
|
+
"""Sum of all step weights.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Total progress weight across all registered steps.
|
|
94
|
+
"""
|
|
95
|
+
return sum(s.progress_weight for s in self._steps)
|
|
96
|
+
|
|
97
|
+
def __len__(self) -> int:
|
|
98
|
+
"""Return number of registered steps."""
|
|
99
|
+
return len(self._steps)
|
|
100
|
+
|
|
101
|
+
def __bool__(self) -> bool:
|
|
102
|
+
"""Return True if any steps are registered."""
|
|
103
|
+
return bool(self._steps)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Utility step wrappers for Synapse SDK.
|
|
2
|
+
|
|
3
|
+
This module provides utility step wrappers that enhance step execution
|
|
4
|
+
with additional functionality such as logging, timing, and validation.
|
|
5
|
+
|
|
6
|
+
Classes:
|
|
7
|
+
LoggingStep: Wrapper that adds logging to step execution.
|
|
8
|
+
TimingStep: Wrapper that adds timing measurement to step execution.
|
|
9
|
+
ValidationStep: Wrapper that adds pre-execution validation to steps.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from synapse_sdk.plugins.steps.utils.logging import LoggingStep
|
|
13
|
+
from synapse_sdk.plugins.steps.utils.timing import TimingStep
|
|
14
|
+
from synapse_sdk.plugins.steps.utils.validation import ValidationStep
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
'LoggingStep',
|
|
18
|
+
'TimingStep',
|
|
19
|
+
'ValidationStep',
|
|
20
|
+
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Logging step wrapper for workflow steps."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from synapse_sdk.plugins.steps.base import BaseStep, StepResult
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from synapse_sdk.plugins.steps.context import BaseStepContext
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LoggingStep[C: BaseStepContext](BaseStep[C]):
|
|
15
|
+
"""Wraps a step with start/end logging including timing.
|
|
16
|
+
|
|
17
|
+
Logs step_start event before execution and step_end event after,
|
|
18
|
+
including elapsed time in seconds.
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
>>> logged_step = LoggingStep(MyProcessStep())
|
|
22
|
+
>>> registry.register(logged_step)
|
|
23
|
+
>>> # Logs: step_start {'step': 'process'}
|
|
24
|
+
>>> # Logs: step_end {'step': 'process', 'elapsed': 1.23, 'success': True}
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, step: BaseStep[C]) -> None:
|
|
28
|
+
"""Initialize with step to wrap.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
step: The step to wrap with logging.
|
|
32
|
+
"""
|
|
33
|
+
self._wrapped = step
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def name(self) -> str:
|
|
37
|
+
"""Return wrapped step name with 'logged_' prefix."""
|
|
38
|
+
return f'logged_{self._wrapped.name}'
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def progress_weight(self) -> float:
|
|
42
|
+
"""Return wrapped step's progress weight."""
|
|
43
|
+
return self._wrapped.progress_weight
|
|
44
|
+
|
|
45
|
+
def execute(self, context: C) -> StepResult:
|
|
46
|
+
"""Execute wrapped step with logging.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
context: Shared context.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Result from wrapped step execution.
|
|
53
|
+
"""
|
|
54
|
+
step_name = self._wrapped.name
|
|
55
|
+
context.log('step_start', {'step': step_name})
|
|
56
|
+
|
|
57
|
+
start = time.perf_counter()
|
|
58
|
+
result = self._wrapped.execute(context)
|
|
59
|
+
elapsed = time.perf_counter() - start
|
|
60
|
+
|
|
61
|
+
context.log(
|
|
62
|
+
'step_end',
|
|
63
|
+
{
|
|
64
|
+
'step': step_name,
|
|
65
|
+
'elapsed': round(elapsed, 3),
|
|
66
|
+
'success': result.success,
|
|
67
|
+
'skipped': result.skipped,
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return result
|
|
72
|
+
|
|
73
|
+
def can_skip(self, context: C) -> bool:
|
|
74
|
+
"""Delegate to wrapped step."""
|
|
75
|
+
return self._wrapped.can_skip(context)
|
|
76
|
+
|
|
77
|
+
def rollback(self, context: C, result: StepResult) -> None:
|
|
78
|
+
"""Delegate rollback to wrapped step with logging.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
context: Shared context.
|
|
82
|
+
result: Result from this step's execution.
|
|
83
|
+
"""
|
|
84
|
+
context.log('step_rollback', {'step': self._wrapped.name})
|
|
85
|
+
self._wrapped.rollback(context, result)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Timing step wrapper for workflow steps."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from synapse_sdk.plugins.steps.base import BaseStep, StepResult
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from synapse_sdk.plugins.steps.context import BaseStepContext
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TimingStep[C: BaseStepContext](BaseStep[C]):
|
|
15
|
+
"""Wraps a step with duration measurement.
|
|
16
|
+
|
|
17
|
+
Measures execution time and adds 'duration_seconds' to result data.
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
>>> timed_step = TimingStep(MyProcessStep())
|
|
21
|
+
>>> registry.register(timed_step)
|
|
22
|
+
>>> result = orchestrator.execute()
|
|
23
|
+
>>> print(result.data['duration_seconds']) # 1.234
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, step: BaseStep[C]) -> None:
|
|
27
|
+
"""Initialize with step to wrap.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
step: The step to wrap with timing.
|
|
31
|
+
"""
|
|
32
|
+
self._wrapped = step
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def name(self) -> str:
|
|
36
|
+
"""Return wrapped step name with 'timed_' prefix."""
|
|
37
|
+
return f'timed_{self._wrapped.name}'
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def progress_weight(self) -> float:
|
|
41
|
+
"""Return wrapped step's progress weight."""
|
|
42
|
+
return self._wrapped.progress_weight
|
|
43
|
+
|
|
44
|
+
def execute(self, context: C) -> StepResult:
|
|
45
|
+
"""Execute wrapped step with timing.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
context: Shared context.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Result from wrapped step with duration_seconds added.
|
|
52
|
+
"""
|
|
53
|
+
start = time.perf_counter()
|
|
54
|
+
result = self._wrapped.execute(context)
|
|
55
|
+
elapsed = time.perf_counter() - start
|
|
56
|
+
|
|
57
|
+
result.data['duration_seconds'] = round(elapsed, 6)
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
def can_skip(self, context: C) -> bool:
|
|
61
|
+
"""Delegate to wrapped step."""
|
|
62
|
+
return self._wrapped.can_skip(context)
|
|
63
|
+
|
|
64
|
+
def rollback(self, context: C, result: StepResult) -> None:
|
|
65
|
+
"""Delegate rollback to wrapped step.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
context: Shared context.
|
|
69
|
+
result: Result from this step's execution.
|
|
70
|
+
"""
|
|
71
|
+
self._wrapped.rollback(context, result)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Validation step for checking context state."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from synapse_sdk.plugins.steps.base import BaseStep, StepResult
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from synapse_sdk.plugins.steps.context import BaseStepContext
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ValidationStep[C: BaseStepContext](BaseStep[C]):
|
|
15
|
+
"""Validates context state before proceeding.
|
|
16
|
+
|
|
17
|
+
Takes a validator function that checks context and returns
|
|
18
|
+
(is_valid, error_message). Fails the step if validation fails.
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
>>> def check_data(ctx: MyContext) -> tuple[bool, str | None]:
|
|
22
|
+
... if not ctx.data:
|
|
23
|
+
... return False, 'No data loaded'
|
|
24
|
+
... return True, None
|
|
25
|
+
>>>
|
|
26
|
+
>>> registry.register(ValidationStep(check_data, name='validate_data'))
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
validator: Callable[[C], tuple[bool, str | None]],
|
|
32
|
+
name: str = 'validate',
|
|
33
|
+
progress_weight: float = 0.05,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Initialize validation step.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
validator: Function that takes context and returns (is_valid, error).
|
|
39
|
+
name: Step name (default: 'validate').
|
|
40
|
+
progress_weight: Progress weight (default: 0.05).
|
|
41
|
+
"""
|
|
42
|
+
self._validator = validator
|
|
43
|
+
self._name = name
|
|
44
|
+
self._progress_weight = progress_weight
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def name(self) -> str:
|
|
48
|
+
"""Return step name."""
|
|
49
|
+
return self._name
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def progress_weight(self) -> float:
|
|
53
|
+
"""Return progress weight."""
|
|
54
|
+
return self._progress_weight
|
|
55
|
+
|
|
56
|
+
def execute(self, context: C) -> StepResult:
|
|
57
|
+
"""Execute validation.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
context: Shared context to validate.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
StepResult with success=False if validation fails.
|
|
64
|
+
"""
|
|
65
|
+
is_valid, error = self._validator(context)
|
|
66
|
+
if not is_valid:
|
|
67
|
+
return StepResult(success=False, error=error)
|
|
68
|
+
return StepResult(success=True)
|