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
synapse_sdk/clients/agent/ray.py
CHANGED
|
@@ -1,54 +1,311 @@
|
|
|
1
|
-
from
|
|
2
|
-
from synapse_sdk.clients.exceptions import ClientError
|
|
1
|
+
from __future__ import annotations
|
|
3
2
|
|
|
3
|
+
import json
|
|
4
|
+
from typing import TYPE_CHECKING, Generator, Literal
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
from synapse_sdk.exceptions import ClientError
|
|
7
|
+
from synapse_sdk.utils.network import (
|
|
8
|
+
StreamLimits,
|
|
9
|
+
http_to_websocket_url,
|
|
10
|
+
sanitize_error_message,
|
|
11
|
+
validate_resource_id,
|
|
12
|
+
validate_timeout,
|
|
13
|
+
)
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return self._get(path)
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from synapse_sdk.clients.protocols import ClientProtocol
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
path = f'jobs/{pk}/logs/'
|
|
16
|
-
return self._get(path)
|
|
18
|
+
StreamProtocol = Literal['websocket', 'http', 'auto']
|
|
17
19
|
|
|
18
|
-
def tail_job_logs(self, pk):
|
|
19
|
-
if self.long_poll_handler:
|
|
20
|
-
raise ClientError(400, '"tail_job_logs" does not support long polling')
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
class RayClientMixin:
|
|
22
|
+
"""Mixin for Ray cluster management endpoints."""
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
_stream_limits: StreamLimits | None = None
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def stream_limits(self) -> StreamLimits:
|
|
28
|
+
"""Get stream limits configuration."""
|
|
29
|
+
if self._stream_limits is None:
|
|
30
|
+
self._stream_limits = StreamLimits()
|
|
31
|
+
return self._stream_limits
|
|
32
|
+
|
|
33
|
+
@stream_limits.setter
|
|
34
|
+
def stream_limits(self, value: StreamLimits) -> None:
|
|
35
|
+
"""Set stream limits configuration."""
|
|
36
|
+
self._stream_limits = value
|
|
37
|
+
|
|
38
|
+
# -------------------------------------------------------------------------
|
|
39
|
+
# Jobs
|
|
40
|
+
# -------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
def list_jobs(self: ClientProtocol) -> list[dict]:
|
|
43
|
+
"""List all Ray jobs."""
|
|
44
|
+
return self._get('ray/jobs/')
|
|
45
|
+
|
|
46
|
+
def get_job(self: ClientProtocol, job_id: str) -> dict:
|
|
47
|
+
"""Get a Ray job by ID."""
|
|
48
|
+
return self._get(f'ray/jobs/{job_id}')
|
|
49
|
+
|
|
50
|
+
def get_job_logs(self: ClientProtocol, job_id: str) -> str:
|
|
51
|
+
"""Get all logs for a job (non-streaming)."""
|
|
52
|
+
return self._get(f'ray/jobs/{job_id}/logs')
|
|
53
|
+
|
|
54
|
+
def stop_job(self: ClientProtocol, job_id: str) -> dict:
|
|
55
|
+
"""Stop a running job."""
|
|
56
|
+
return self._post(f'ray/jobs/{job_id}/stop/')
|
|
57
|
+
|
|
58
|
+
def websocket_tail_job_logs(
|
|
59
|
+
self: ClientProtocol,
|
|
60
|
+
job_id: str,
|
|
61
|
+
timeout: float = 30.0,
|
|
62
|
+
) -> Generator[str, None, None]:
|
|
63
|
+
"""Stream job logs via WebSocket protocol.
|
|
64
|
+
|
|
65
|
+
Establishes a WebSocket connection for real-time log streaming.
|
|
66
|
+
Preferred method for low-latency log monitoring.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
job_id: The Ray job ID to tail logs for.
|
|
70
|
+
timeout: Connection and read timeout in seconds.
|
|
71
|
+
|
|
72
|
+
Yields:
|
|
73
|
+
Log message strings.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
ClientError: On connection, protocol, or validation errors.
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
>>> for line in client.websocket_tail_job_logs('raysubmit_abc123'):
|
|
80
|
+
... print(line)
|
|
81
|
+
"""
|
|
82
|
+
validated_id = validate_resource_id(job_id, 'job')
|
|
83
|
+
validated_timeout = validate_timeout(timeout)
|
|
84
|
+
|
|
85
|
+
url = self._get_url(f'ray/jobs/{validated_id}/logs/')
|
|
86
|
+
ws_url = http_to_websocket_url(url)
|
|
25
87
|
headers = self._get_headers()
|
|
26
88
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
89
|
+
try:
|
|
90
|
+
import websocket
|
|
91
|
+
except ImportError:
|
|
92
|
+
raise ClientError(500, 'websocket-client package required for WebSocket streaming')
|
|
93
|
+
|
|
94
|
+
header_list = [f'{k}: {v}' for k, v in headers.items()]
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
ws = websocket.create_connection(
|
|
98
|
+
ws_url,
|
|
99
|
+
header=header_list,
|
|
100
|
+
timeout=validated_timeout,
|
|
101
|
+
)
|
|
102
|
+
except websocket.WebSocketException as e:
|
|
103
|
+
raise ClientError(503, f'WebSocket connection failed: {e}')
|
|
104
|
+
except Exception as e:
|
|
105
|
+
raise ClientError(503, sanitize_error_message(str(e), 'connection error'))
|
|
106
|
+
|
|
107
|
+
limits = self.stream_limits
|
|
108
|
+
message_count = 0
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
while True:
|
|
112
|
+
try:
|
|
113
|
+
data = ws.recv()
|
|
114
|
+
except websocket.WebSocketTimeoutException:
|
|
115
|
+
break
|
|
116
|
+
except websocket.WebSocketConnectionClosedException:
|
|
117
|
+
break
|
|
118
|
+
|
|
119
|
+
if not data:
|
|
120
|
+
break
|
|
121
|
+
|
|
122
|
+
message_count += 1
|
|
123
|
+
if message_count > limits.max_messages:
|
|
124
|
+
raise ClientError(429, 'Stream message limit exceeded')
|
|
125
|
+
|
|
126
|
+
# Skip oversized messages
|
|
127
|
+
if len(data) > limits.max_message_size:
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
# Parse JSON event format
|
|
131
|
+
try:
|
|
132
|
+
event = json.loads(data)
|
|
133
|
+
except json.JSONDecodeError:
|
|
134
|
+
event = {'message': data}
|
|
135
|
+
|
|
136
|
+
# Handle event types
|
|
137
|
+
event_type = event.get('type')
|
|
138
|
+
if event_type == 'error':
|
|
139
|
+
raise ClientError(500, event.get('message', 'Unknown error'))
|
|
140
|
+
elif event_type == 'complete':
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
if msg := event.get('message'):
|
|
144
|
+
yield msg
|
|
145
|
+
finally:
|
|
146
|
+
ws.close()
|
|
147
|
+
|
|
148
|
+
def stream_tail_job_logs(
|
|
149
|
+
self: ClientProtocol,
|
|
150
|
+
job_id: str,
|
|
151
|
+
timeout: float = 30.0,
|
|
152
|
+
) -> Generator[str, None, None]:
|
|
153
|
+
"""Stream job logs via HTTP chunked transfer encoding.
|
|
154
|
+
|
|
155
|
+
Uses HTTP streaming as an alternative when WebSocket is unavailable.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
job_id: The Ray job ID to tail logs for.
|
|
159
|
+
timeout: Connection timeout in seconds (read timeout is infinite).
|
|
160
|
+
|
|
161
|
+
Yields:
|
|
162
|
+
Log lines as strings.
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
ClientError: On connection, protocol, or validation errors.
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
>>> for line in client.stream_tail_job_logs('raysubmit_abc123'):
|
|
169
|
+
... print(line)
|
|
170
|
+
"""
|
|
171
|
+
validated_id = validate_resource_id(job_id, 'job')
|
|
172
|
+
validated_timeout = validate_timeout(timeout)
|
|
173
|
+
|
|
174
|
+
url = self._get_url(f'ray/jobs/{validated_id}/logs/')
|
|
175
|
+
headers = self._get_headers()
|
|
176
|
+
|
|
177
|
+
response = None
|
|
178
|
+
try:
|
|
179
|
+
response = self.requests_session.get(
|
|
180
|
+
url,
|
|
181
|
+
headers=headers,
|
|
182
|
+
stream=True,
|
|
183
|
+
timeout=(validated_timeout, None), # No read timeout for streaming
|
|
184
|
+
)
|
|
185
|
+
response.raise_for_status()
|
|
186
|
+
|
|
187
|
+
limits = self.stream_limits
|
|
188
|
+
line_count = 0
|
|
189
|
+
total_bytes = 0
|
|
190
|
+
|
|
191
|
+
for line in response.iter_lines(decode_unicode=True):
|
|
192
|
+
if line:
|
|
193
|
+
line_count += 1
|
|
194
|
+
total_bytes += len(line.encode('utf-8'))
|
|
195
|
+
|
|
196
|
+
if line_count > limits.max_lines:
|
|
197
|
+
raise ClientError(429, 'Stream line limit exceeded')
|
|
198
|
+
|
|
199
|
+
if total_bytes > limits.max_bytes:
|
|
200
|
+
raise ClientError(429, 'Stream size limit exceeded')
|
|
201
|
+
|
|
202
|
+
if len(line) > limits.max_message_size:
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
yield line
|
|
206
|
+
|
|
207
|
+
except ClientError:
|
|
208
|
+
raise
|
|
209
|
+
except Exception as e:
|
|
210
|
+
raise ClientError(503, sanitize_error_message(str(e), 'HTTP streaming error'))
|
|
211
|
+
finally:
|
|
212
|
+
if response is not None:
|
|
213
|
+
response.close()
|
|
214
|
+
|
|
215
|
+
def tail_job_logs(
|
|
216
|
+
self: ClientProtocol,
|
|
217
|
+
job_id: str,
|
|
218
|
+
timeout: float = 30.0,
|
|
219
|
+
*,
|
|
220
|
+
protocol: StreamProtocol = 'auto',
|
|
221
|
+
) -> Generator[str, None, None]:
|
|
222
|
+
"""Stream job logs with automatic protocol selection.
|
|
223
|
+
|
|
224
|
+
Unified method that supports WebSocket, HTTP, and auto-selection.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
job_id: The Ray job ID to tail logs for.
|
|
228
|
+
timeout: Connection timeout in seconds.
|
|
229
|
+
protocol: Protocol to use:
|
|
230
|
+
- 'websocket': Use WebSocket only
|
|
231
|
+
- 'http': Use HTTP streaming only
|
|
232
|
+
- 'auto': Try WebSocket, fall back to HTTP on connection failure
|
|
233
|
+
|
|
234
|
+
Yields:
|
|
235
|
+
Log message strings.
|
|
236
|
+
|
|
237
|
+
Raises:
|
|
238
|
+
ClientError: On connection, protocol, or validation errors.
|
|
239
|
+
|
|
240
|
+
Example:
|
|
241
|
+
>>> # Auto protocol selection (recommended)
|
|
242
|
+
>>> for line in client.tail_job_logs('raysubmit_abc123'):
|
|
243
|
+
... print(line)
|
|
244
|
+
|
|
245
|
+
>>> # Explicit WebSocket
|
|
246
|
+
>>> for line in client.tail_job_logs('raysubmit_abc123', protocol='websocket'):
|
|
247
|
+
... print(line)
|
|
248
|
+
|
|
249
|
+
>>> # Explicit HTTP streaming
|
|
250
|
+
>>> for line in client.tail_job_logs('raysubmit_abc123', protocol='http'):
|
|
251
|
+
... print(line)
|
|
252
|
+
"""
|
|
253
|
+
# Pre-validate to fail fast
|
|
254
|
+
validate_resource_id(job_id, 'job')
|
|
255
|
+
validate_timeout(timeout)
|
|
256
|
+
|
|
257
|
+
if protocol == 'websocket':
|
|
258
|
+
yield from self.websocket_tail_job_logs(job_id, timeout)
|
|
259
|
+
elif protocol == 'http':
|
|
260
|
+
yield from self.stream_tail_job_logs(job_id, timeout)
|
|
261
|
+
elif protocol == 'auto':
|
|
262
|
+
try:
|
|
263
|
+
yield from self.websocket_tail_job_logs(job_id, timeout)
|
|
264
|
+
except ClientError as e:
|
|
265
|
+
# Fall back to HTTP on WebSocket failures
|
|
266
|
+
if e.status_code in (500, 503):
|
|
267
|
+
yield from self.stream_tail_job_logs(job_id, timeout)
|
|
268
|
+
else:
|
|
269
|
+
raise
|
|
270
|
+
else:
|
|
271
|
+
raise ClientError(400, f'Invalid protocol: {protocol}')
|
|
272
|
+
|
|
273
|
+
# -------------------------------------------------------------------------
|
|
274
|
+
# Nodes
|
|
275
|
+
# -------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
def list_nodes(self: ClientProtocol) -> list[dict]:
|
|
278
|
+
"""List all Ray nodes."""
|
|
279
|
+
return self._get('ray/nodes/')
|
|
280
|
+
|
|
281
|
+
def get_node(self: ClientProtocol, node_id: str) -> dict:
|
|
282
|
+
"""Get a Ray node by ID."""
|
|
283
|
+
return self._get(f'ray/nodes/{node_id}')
|
|
284
|
+
|
|
285
|
+
# -------------------------------------------------------------------------
|
|
286
|
+
# Tasks
|
|
287
|
+
# -------------------------------------------------------------------------
|
|
31
288
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
return self._get(
|
|
289
|
+
def list_tasks(self: ClientProtocol) -> list[dict]:
|
|
290
|
+
"""List all Ray tasks."""
|
|
291
|
+
return self._get('ray/tasks/')
|
|
35
292
|
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
return self._get(
|
|
293
|
+
def get_task(self: ClientProtocol, task_id: str) -> dict:
|
|
294
|
+
"""Get a Ray task by ID."""
|
|
295
|
+
return self._get(f'ray/tasks/{task_id}')
|
|
39
296
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
297
|
+
# -------------------------------------------------------------------------
|
|
298
|
+
# Serve Applications
|
|
299
|
+
# -------------------------------------------------------------------------
|
|
43
300
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
return self._get(
|
|
301
|
+
def list_serve_applications(self: ClientProtocol) -> list[dict]:
|
|
302
|
+
"""List all Ray Serve applications."""
|
|
303
|
+
return self._get('ray/serve_applications/')
|
|
47
304
|
|
|
48
|
-
def get_serve_application(self,
|
|
49
|
-
|
|
50
|
-
return self._get(
|
|
305
|
+
def get_serve_application(self: ClientProtocol, name: str) -> dict:
|
|
306
|
+
"""Get a Ray Serve application by name."""
|
|
307
|
+
return self._get(f'ray/serve_applications/{name}')
|
|
51
308
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
309
|
+
def delete_serve_application(self: ClientProtocol, name: str) -> None:
|
|
310
|
+
"""Delete a Ray Serve application."""
|
|
311
|
+
self._delete(f'ray/serve_applications/{name}')
|
|
@@ -1,23 +1,163 @@
|
|
|
1
|
+
"""Backend API client for synapse-backend.
|
|
2
|
+
|
|
3
|
+
This module provides the BackendClient for interacting with the synapse-backend API.
|
|
4
|
+
It composes functionality from multiple mixins for different API domains.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from synapse_sdk.clients.backend import BackendClient
|
|
8
|
+
>>>
|
|
9
|
+
>>> client = BackendClient(
|
|
10
|
+
... 'https://api.example.com',
|
|
11
|
+
... access_token='your_token',
|
|
12
|
+
... tenant='your_tenant',
|
|
13
|
+
... )
|
|
14
|
+
>>>
|
|
15
|
+
>>> # Get a project
|
|
16
|
+
>>> project = client.get_project(123)
|
|
17
|
+
>>>
|
|
18
|
+
>>> # Upload data collection
|
|
19
|
+
>>> client.upload_data_collection(456, data, project_id=789)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
1
24
|
from synapse_sdk.clients.backend.annotation import AnnotationClientMixin
|
|
2
|
-
from synapse_sdk.clients.backend.
|
|
25
|
+
from synapse_sdk.clients.backend.bulk_upload import BulkUploadClientMixin
|
|
26
|
+
from synapse_sdk.clients.backend.core import CoreClientMixin
|
|
27
|
+
from synapse_sdk.clients.backend.data_collection import DataCollectionClientMixin
|
|
28
|
+
from synapse_sdk.clients.backend.hitl import HITLClientMixin
|
|
3
29
|
from synapse_sdk.clients.backend.integration import IntegrationClientMixin
|
|
4
30
|
from synapse_sdk.clients.backend.ml import MLClientMixin
|
|
31
|
+
from synapse_sdk.clients.base import BaseClient
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BackendClient(
|
|
35
|
+
AnnotationClientMixin,
|
|
36
|
+
BulkUploadClientMixin,
|
|
37
|
+
CoreClientMixin,
|
|
38
|
+
DataCollectionClientMixin,
|
|
39
|
+
HITLClientMixin,
|
|
40
|
+
IntegrationClientMixin,
|
|
41
|
+
MLClientMixin,
|
|
42
|
+
BaseClient,
|
|
43
|
+
):
|
|
44
|
+
"""Synchronous client for synapse-backend API.
|
|
5
45
|
|
|
46
|
+
Composes functionality from multiple mixins:
|
|
47
|
+
- AnnotationClientMixin: Project and task operations
|
|
48
|
+
- BulkUploadClientMixin: High-performance presigned URL uploads
|
|
49
|
+
- CoreClientMixin: Chunked file upload
|
|
50
|
+
- DataCollectionClientMixin: Data collection management
|
|
51
|
+
- HITLClientMixin: Assignment operations
|
|
52
|
+
- IntegrationClientMixin: Plugin, job, and storage operations
|
|
53
|
+
- MLClientMixin: Model and ground truth operations
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
base_url: Backend API base URL.
|
|
57
|
+
access_token: API access token.
|
|
58
|
+
authorization_token: Optional authorization token (legacy).
|
|
59
|
+
tenant: Optional tenant identifier for multi-tenancy.
|
|
60
|
+
agent_token: Optional agent token for agent-initiated requests.
|
|
61
|
+
timeout: Request timeout dict with 'connect' and 'read' keys.
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
>>> client = BackendClient(
|
|
65
|
+
... 'https://api.example.com',
|
|
66
|
+
... access_token='abc123',
|
|
67
|
+
... tenant='my-tenant',
|
|
68
|
+
... )
|
|
69
|
+
>>> project = client.get_project(1)
|
|
70
|
+
"""
|
|
6
71
|
|
|
7
|
-
class BackendClient(AnnotationClientMixin, DatasetClientMixin, IntegrationClientMixin, MLClientMixin):
|
|
8
72
|
name = 'Backend'
|
|
9
|
-
token = None
|
|
10
|
-
tenant = None
|
|
11
73
|
|
|
12
|
-
def __init__(
|
|
13
|
-
|
|
14
|
-
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
base_url: str,
|
|
77
|
+
access_token: str | None = None,
|
|
78
|
+
*,
|
|
79
|
+
authorization_token: str | None = None,
|
|
80
|
+
tenant: str | None = None,
|
|
81
|
+
agent_token: str | None = None,
|
|
82
|
+
timeout: dict[str, int] | None = None,
|
|
83
|
+
):
|
|
84
|
+
"""Initialize the backend client.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
base_url: Backend API base URL.
|
|
88
|
+
access_token: API access token for authentication.
|
|
89
|
+
authorization_token: Legacy auth token (deprecated, use access_token).
|
|
90
|
+
tenant: Tenant code for multi-tenant deployments.
|
|
91
|
+
agent_token: Agent token for agent-initiated requests.
|
|
92
|
+
timeout: Request timeout configuration.
|
|
93
|
+
"""
|
|
94
|
+
super().__init__(base_url, timeout=timeout)
|
|
95
|
+
self.access_token = access_token
|
|
96
|
+
self.authorization_token = authorization_token
|
|
15
97
|
self.tenant = tenant
|
|
98
|
+
self.agent_token = agent_token
|
|
99
|
+
|
|
100
|
+
def _get_headers(self) -> dict[str, str]:
|
|
101
|
+
"""Return authentication headers.
|
|
102
|
+
|
|
103
|
+
Multiple authentication methods are supported:
|
|
104
|
+
- Synapse-Access-Token: Primary authentication
|
|
105
|
+
- Authorization: Legacy token authentication
|
|
106
|
+
- Synapse-Tenant: Multi-tenant identifier
|
|
107
|
+
- SYNAPSE-Agent: Agent-initiated request identifier
|
|
108
|
+
"""
|
|
109
|
+
headers: dict[str, str] = {}
|
|
110
|
+
|
|
111
|
+
if self.access_token:
|
|
112
|
+
headers['Synapse-Access-Token'] = f'Token {self.access_token}'
|
|
113
|
+
|
|
114
|
+
if self.authorization_token:
|
|
115
|
+
headers['Authorization'] = f'Token {self.authorization_token}'
|
|
16
116
|
|
|
17
|
-
def _get_headers(self):
|
|
18
|
-
headers = {}
|
|
19
|
-
if self.token:
|
|
20
|
-
headers = {'Authorization': f'Token {self.token}'}
|
|
21
117
|
if self.tenant:
|
|
22
|
-
headers['
|
|
118
|
+
headers['Synapse-Tenant'] = f'Token {self.tenant}'
|
|
119
|
+
|
|
120
|
+
if self.agent_token:
|
|
121
|
+
headers['SYNAPSE-Agent'] = f'Token {self.agent_token}'
|
|
122
|
+
|
|
23
123
|
return headers
|
|
124
|
+
|
|
125
|
+
def close(self) -> None:
|
|
126
|
+
"""Close the HTTP session.
|
|
127
|
+
|
|
128
|
+
Call this when done with the client to release resources.
|
|
129
|
+
"""
|
|
130
|
+
if self._session is not None:
|
|
131
|
+
self._session.close()
|
|
132
|
+
self._session = None
|
|
133
|
+
|
|
134
|
+
def __enter__(self) -> BackendClient:
|
|
135
|
+
"""Context manager entry."""
|
|
136
|
+
return self
|
|
137
|
+
|
|
138
|
+
def __exit__(self, *args) -> None:
|
|
139
|
+
"""Context manager exit - closes session."""
|
|
140
|
+
self.close()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# Re-export models for convenience
|
|
144
|
+
from synapse_sdk.clients.backend.models import ( # noqa: E402
|
|
145
|
+
Agent,
|
|
146
|
+
JobStatus,
|
|
147
|
+
Storage,
|
|
148
|
+
StorageCategory,
|
|
149
|
+
StorageProvider,
|
|
150
|
+
UpdateJobRequest,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
__all__ = [
|
|
154
|
+
# Client
|
|
155
|
+
'BackendClient',
|
|
156
|
+
# Models
|
|
157
|
+
'Agent',
|
|
158
|
+
'JobStatus',
|
|
159
|
+
'Storage',
|
|
160
|
+
'StorageCategory',
|
|
161
|
+
'StorageProvider',
|
|
162
|
+
'UpdateJobRequest',
|
|
163
|
+
]
|