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

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

Potentially problematic release.


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

Files changed (261) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/__init__.py +9 -8
  3. synapse_sdk/cli/agent/__init__.py +25 -0
  4. synapse_sdk/cli/agent/config.py +104 -0
  5. synapse_sdk/cli/agent/select.py +197 -0
  6. synapse_sdk/cli/auth.py +104 -0
  7. synapse_sdk/cli/main.py +1025 -0
  8. synapse_sdk/cli/plugin/__init__.py +58 -0
  9. synapse_sdk/cli/plugin/create.py +566 -0
  10. synapse_sdk/cli/plugin/job.py +196 -0
  11. synapse_sdk/cli/plugin/publish.py +322 -0
  12. synapse_sdk/cli/plugin/run.py +131 -0
  13. synapse_sdk/cli/plugin/test.py +200 -0
  14. synapse_sdk/clients/README.md +239 -0
  15. synapse_sdk/clients/__init__.py +5 -0
  16. synapse_sdk/clients/_template.py +266 -0
  17. synapse_sdk/clients/agent/__init__.py +84 -29
  18. synapse_sdk/clients/agent/async_ray.py +289 -0
  19. synapse_sdk/clients/agent/container.py +83 -0
  20. synapse_sdk/clients/agent/plugin.py +101 -0
  21. synapse_sdk/clients/agent/ray.py +296 -39
  22. synapse_sdk/clients/backend/__init__.py +152 -12
  23. synapse_sdk/clients/backend/annotation.py +164 -22
  24. synapse_sdk/clients/backend/core.py +101 -0
  25. synapse_sdk/clients/backend/data_collection.py +292 -0
  26. synapse_sdk/clients/backend/hitl.py +87 -0
  27. synapse_sdk/clients/backend/integration.py +374 -46
  28. synapse_sdk/clients/backend/ml.py +134 -22
  29. synapse_sdk/clients/backend/models.py +247 -0
  30. synapse_sdk/clients/base.py +538 -59
  31. synapse_sdk/clients/exceptions.py +35 -7
  32. synapse_sdk/clients/pipeline/__init__.py +5 -0
  33. synapse_sdk/clients/pipeline/client.py +636 -0
  34. synapse_sdk/clients/protocols.py +178 -0
  35. synapse_sdk/clients/utils.py +86 -8
  36. synapse_sdk/clients/validation.py +58 -0
  37. synapse_sdk/enums.py +76 -0
  38. synapse_sdk/exceptions.py +168 -0
  39. synapse_sdk/integrations/__init__.py +74 -0
  40. synapse_sdk/integrations/_base.py +119 -0
  41. synapse_sdk/integrations/_context.py +53 -0
  42. synapse_sdk/integrations/ultralytics/__init__.py +78 -0
  43. synapse_sdk/integrations/ultralytics/_callbacks.py +126 -0
  44. synapse_sdk/integrations/ultralytics/_patches.py +124 -0
  45. synapse_sdk/loggers.py +476 -95
  46. synapse_sdk/mcp/MCP.md +69 -0
  47. synapse_sdk/mcp/__init__.py +48 -0
  48. synapse_sdk/mcp/__main__.py +6 -0
  49. synapse_sdk/mcp/config.py +349 -0
  50. synapse_sdk/mcp/prompts/__init__.py +4 -0
  51. synapse_sdk/mcp/resources/__init__.py +4 -0
  52. synapse_sdk/mcp/server.py +1352 -0
  53. synapse_sdk/mcp/tools/__init__.py +6 -0
  54. synapse_sdk/plugins/__init__.py +133 -9
  55. synapse_sdk/plugins/action.py +229 -0
  56. synapse_sdk/plugins/actions/__init__.py +82 -0
  57. synapse_sdk/plugins/actions/dataset/__init__.py +37 -0
  58. synapse_sdk/plugins/actions/dataset/action.py +471 -0
  59. synapse_sdk/plugins/actions/export/__init__.py +55 -0
  60. synapse_sdk/plugins/actions/export/action.py +183 -0
  61. synapse_sdk/plugins/actions/export/context.py +59 -0
  62. synapse_sdk/plugins/actions/inference/__init__.py +84 -0
  63. synapse_sdk/plugins/actions/inference/action.py +285 -0
  64. synapse_sdk/plugins/actions/inference/context.py +81 -0
  65. synapse_sdk/plugins/actions/inference/deployment.py +322 -0
  66. synapse_sdk/plugins/actions/inference/serve.py +252 -0
  67. synapse_sdk/plugins/actions/train/__init__.py +54 -0
  68. synapse_sdk/plugins/actions/train/action.py +326 -0
  69. synapse_sdk/plugins/actions/train/context.py +57 -0
  70. synapse_sdk/plugins/actions/upload/__init__.py +49 -0
  71. synapse_sdk/plugins/actions/upload/action.py +165 -0
  72. synapse_sdk/plugins/actions/upload/context.py +61 -0
  73. synapse_sdk/plugins/config.py +98 -0
  74. synapse_sdk/plugins/context/__init__.py +109 -0
  75. synapse_sdk/plugins/context/env.py +113 -0
  76. synapse_sdk/plugins/datasets/__init__.py +113 -0
  77. synapse_sdk/plugins/datasets/converters/__init__.py +76 -0
  78. synapse_sdk/plugins/datasets/converters/base.py +347 -0
  79. synapse_sdk/plugins/datasets/converters/yolo/__init__.py +9 -0
  80. synapse_sdk/plugins/datasets/converters/yolo/from_dm.py +468 -0
  81. synapse_sdk/plugins/datasets/converters/yolo/to_dm.py +381 -0
  82. synapse_sdk/plugins/datasets/formats/__init__.py +82 -0
  83. synapse_sdk/plugins/datasets/formats/dm.py +351 -0
  84. synapse_sdk/plugins/datasets/formats/yolo.py +240 -0
  85. synapse_sdk/plugins/decorators.py +83 -0
  86. synapse_sdk/plugins/discovery.py +790 -0
  87. synapse_sdk/plugins/docs/ACTION_DEV_GUIDE.md +933 -0
  88. synapse_sdk/plugins/docs/ARCHITECTURE.md +1225 -0
  89. synapse_sdk/plugins/docs/LOGGING_SYSTEM.md +683 -0
  90. synapse_sdk/plugins/docs/OVERVIEW.md +531 -0
  91. synapse_sdk/plugins/docs/PIPELINE_GUIDE.md +145 -0
  92. synapse_sdk/plugins/docs/README.md +513 -0
  93. synapse_sdk/plugins/docs/STEP.md +656 -0
  94. synapse_sdk/plugins/enums.py +70 -10
  95. synapse_sdk/plugins/errors.py +92 -0
  96. synapse_sdk/plugins/executors/__init__.py +43 -0
  97. synapse_sdk/plugins/executors/local.py +99 -0
  98. synapse_sdk/plugins/executors/ray/__init__.py +18 -0
  99. synapse_sdk/plugins/executors/ray/base.py +282 -0
  100. synapse_sdk/plugins/executors/ray/job.py +298 -0
  101. synapse_sdk/plugins/executors/ray/jobs_api.py +511 -0
  102. synapse_sdk/plugins/executors/ray/packaging.py +137 -0
  103. synapse_sdk/plugins/executors/ray/pipeline.py +792 -0
  104. synapse_sdk/plugins/executors/ray/task.py +257 -0
  105. synapse_sdk/plugins/models/__init__.py +26 -0
  106. synapse_sdk/plugins/models/logger.py +173 -0
  107. synapse_sdk/plugins/models/pipeline.py +25 -0
  108. synapse_sdk/plugins/pipelines/__init__.py +81 -0
  109. synapse_sdk/plugins/pipelines/action_pipeline.py +417 -0
  110. synapse_sdk/plugins/pipelines/context.py +107 -0
  111. synapse_sdk/plugins/pipelines/display.py +311 -0
  112. synapse_sdk/plugins/runner.py +114 -0
  113. synapse_sdk/plugins/schemas/__init__.py +19 -0
  114. synapse_sdk/plugins/schemas/results.py +152 -0
  115. synapse_sdk/plugins/steps/__init__.py +63 -0
  116. synapse_sdk/plugins/steps/base.py +128 -0
  117. synapse_sdk/plugins/steps/context.py +90 -0
  118. synapse_sdk/plugins/steps/orchestrator.py +128 -0
  119. synapse_sdk/plugins/steps/registry.py +103 -0
  120. synapse_sdk/plugins/steps/utils/__init__.py +20 -0
  121. synapse_sdk/plugins/steps/utils/logging.py +85 -0
  122. synapse_sdk/plugins/steps/utils/timing.py +71 -0
  123. synapse_sdk/plugins/steps/utils/validation.py +68 -0
  124. synapse_sdk/plugins/templates/__init__.py +50 -0
  125. synapse_sdk/plugins/templates/base/.gitignore.j2 +26 -0
  126. synapse_sdk/plugins/templates/base/.synapseignore.j2 +11 -0
  127. synapse_sdk/plugins/templates/base/README.md.j2 +26 -0
  128. synapse_sdk/plugins/templates/base/plugin/__init__.py.j2 +1 -0
  129. synapse_sdk/plugins/templates/base/pyproject.toml.j2 +14 -0
  130. synapse_sdk/plugins/templates/base/requirements.txt.j2 +1 -0
  131. synapse_sdk/plugins/templates/custom/plugin/main.py.j2 +18 -0
  132. synapse_sdk/plugins/templates/data_validation/plugin/validate.py.j2 +32 -0
  133. synapse_sdk/plugins/templates/export/plugin/export.py.j2 +36 -0
  134. synapse_sdk/plugins/templates/neural_net/plugin/inference.py.j2 +36 -0
  135. synapse_sdk/plugins/templates/neural_net/plugin/train.py.j2 +33 -0
  136. synapse_sdk/plugins/templates/post_annotation/plugin/post_annotate.py.j2 +32 -0
  137. synapse_sdk/plugins/templates/pre_annotation/plugin/pre_annotate.py.j2 +32 -0
  138. synapse_sdk/plugins/templates/smart_tool/plugin/auto_label.py.j2 +44 -0
  139. synapse_sdk/plugins/templates/upload/plugin/upload.py.j2 +35 -0
  140. synapse_sdk/plugins/testing/__init__.py +25 -0
  141. synapse_sdk/plugins/testing/sample_actions.py +98 -0
  142. synapse_sdk/plugins/types.py +206 -0
  143. synapse_sdk/plugins/upload.py +595 -64
  144. synapse_sdk/plugins/utils.py +325 -37
  145. synapse_sdk/shared/__init__.py +25 -0
  146. synapse_sdk/utils/__init__.py +1 -0
  147. synapse_sdk/utils/auth.py +74 -0
  148. synapse_sdk/utils/file/__init__.py +58 -0
  149. synapse_sdk/utils/file/archive.py +449 -0
  150. synapse_sdk/utils/file/checksum.py +167 -0
  151. synapse_sdk/utils/file/download.py +286 -0
  152. synapse_sdk/utils/file/io.py +129 -0
  153. synapse_sdk/utils/file/requirements.py +36 -0
  154. synapse_sdk/utils/network.py +168 -0
  155. synapse_sdk/utils/storage/__init__.py +238 -0
  156. synapse_sdk/utils/storage/config.py +188 -0
  157. synapse_sdk/utils/storage/errors.py +52 -0
  158. synapse_sdk/utils/storage/providers/__init__.py +13 -0
  159. synapse_sdk/utils/storage/providers/base.py +76 -0
  160. synapse_sdk/utils/storage/providers/gcs.py +168 -0
  161. synapse_sdk/utils/storage/providers/http.py +250 -0
  162. synapse_sdk/utils/storage/providers/local.py +126 -0
  163. synapse_sdk/utils/storage/providers/s3.py +177 -0
  164. synapse_sdk/utils/storage/providers/sftp.py +208 -0
  165. synapse_sdk/utils/storage/registry.py +125 -0
  166. synapse_sdk/utils/websocket.py +99 -0
  167. synapse_sdk-2026.1.1b2.dist-info/METADATA +715 -0
  168. synapse_sdk-2026.1.1b2.dist-info/RECORD +172 -0
  169. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/WHEEL +1 -1
  170. synapse_sdk-2026.1.1b2.dist-info/licenses/LICENSE +201 -0
  171. locale/en/LC_MESSAGES/messages.mo +0 -0
  172. locale/en/LC_MESSAGES/messages.po +0 -39
  173. locale/ko/LC_MESSAGES/messages.mo +0 -0
  174. locale/ko/LC_MESSAGES/messages.po +0 -34
  175. synapse_sdk/cli/create_plugin.py +0 -10
  176. synapse_sdk/clients/agent/core.py +0 -7
  177. synapse_sdk/clients/agent/service.py +0 -15
  178. synapse_sdk/clients/backend/dataset.py +0 -51
  179. synapse_sdk/clients/ray/__init__.py +0 -6
  180. synapse_sdk/clients/ray/core.py +0 -22
  181. synapse_sdk/clients/ray/serve.py +0 -20
  182. synapse_sdk/i18n.py +0 -35
  183. synapse_sdk/plugins/categories/__init__.py +0 -0
  184. synapse_sdk/plugins/categories/base.py +0 -235
  185. synapse_sdk/plugins/categories/data_validation/__init__.py +0 -0
  186. synapse_sdk/plugins/categories/data_validation/actions/__init__.py +0 -0
  187. synapse_sdk/plugins/categories/data_validation/actions/validation.py +0 -10
  188. synapse_sdk/plugins/categories/data_validation/templates/config.yaml +0 -3
  189. synapse_sdk/plugins/categories/data_validation/templates/plugin/__init__.py +0 -0
  190. synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +0 -5
  191. synapse_sdk/plugins/categories/decorators.py +0 -13
  192. synapse_sdk/plugins/categories/export/__init__.py +0 -0
  193. synapse_sdk/plugins/categories/export/actions/__init__.py +0 -0
  194. synapse_sdk/plugins/categories/export/actions/export.py +0 -10
  195. synapse_sdk/plugins/categories/import/__init__.py +0 -0
  196. synapse_sdk/plugins/categories/import/actions/__init__.py +0 -0
  197. synapse_sdk/plugins/categories/import/actions/import.py +0 -10
  198. synapse_sdk/plugins/categories/neural_net/__init__.py +0 -0
  199. synapse_sdk/plugins/categories/neural_net/actions/__init__.py +0 -0
  200. synapse_sdk/plugins/categories/neural_net/actions/deployment.py +0 -45
  201. synapse_sdk/plugins/categories/neural_net/actions/inference.py +0 -18
  202. synapse_sdk/plugins/categories/neural_net/actions/test.py +0 -10
  203. synapse_sdk/plugins/categories/neural_net/actions/train.py +0 -143
  204. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +0 -12
  205. synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py +0 -0
  206. synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +0 -4
  207. synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py +0 -2
  208. synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +0 -14
  209. synapse_sdk/plugins/categories/post_annotation/__init__.py +0 -0
  210. synapse_sdk/plugins/categories/post_annotation/actions/__init__.py +0 -0
  211. synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py +0 -10
  212. synapse_sdk/plugins/categories/post_annotation/templates/config.yaml +0 -3
  213. synapse_sdk/plugins/categories/post_annotation/templates/plugin/__init__.py +0 -0
  214. synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.py +0 -3
  215. synapse_sdk/plugins/categories/pre_annotation/__init__.py +0 -0
  216. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +0 -0
  217. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py +0 -10
  218. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +0 -3
  219. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py +0 -0
  220. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py +0 -3
  221. synapse_sdk/plugins/categories/registry.py +0 -16
  222. synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
  223. synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
  224. synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +0 -37
  225. synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +0 -7
  226. synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
  227. synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +0 -11
  228. synapse_sdk/plugins/categories/templates.py +0 -32
  229. synapse_sdk/plugins/cli/__init__.py +0 -21
  230. synapse_sdk/plugins/cli/publish.py +0 -37
  231. synapse_sdk/plugins/cli/run.py +0 -67
  232. synapse_sdk/plugins/exceptions.py +0 -22
  233. synapse_sdk/plugins/models.py +0 -121
  234. synapse_sdk/plugins/templates/cookiecutter.json +0 -11
  235. synapse_sdk/plugins/templates/hooks/post_gen_project.py +0 -3
  236. synapse_sdk/plugins/templates/hooks/pre_prompt.py +0 -21
  237. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
  238. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
  239. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.gitignore +0 -27
  240. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml +0 -7
  241. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md +0 -5
  242. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -6
  243. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
  244. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py +0 -0
  245. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml +0 -13
  246. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +0 -1
  247. synapse_sdk/shared/enums.py +0 -8
  248. synapse_sdk/utils/debug.py +0 -5
  249. synapse_sdk/utils/file.py +0 -87
  250. synapse_sdk/utils/module_loading.py +0 -29
  251. synapse_sdk/utils/pydantic/__init__.py +0 -0
  252. synapse_sdk/utils/pydantic/config.py +0 -4
  253. synapse_sdk/utils/pydantic/errors.py +0 -33
  254. synapse_sdk/utils/pydantic/validators.py +0 -7
  255. synapse_sdk/utils/storage.py +0 -91
  256. synapse_sdk/utils/string.py +0 -11
  257. synapse_sdk-1.0.0a11.dist-info/LICENSE +0 -21
  258. synapse_sdk-1.0.0a11.dist-info/METADATA +0 -43
  259. synapse_sdk-1.0.0a11.dist-info/RECORD +0 -111
  260. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/entry_points.txt +0 -0
  261. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/top_level.txt +0 -0
@@ -1,54 +1,311 @@
1
- from synapse_sdk.clients.base import BaseClient
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
- class RayClientMixin(BaseClient):
6
- def get_job(self, pk):
7
- path = f'jobs/{pk}/'
8
- return self._get(path)
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
- def list_jobs(self):
11
- path = 'jobs/'
12
- return self._get(path)
15
+ if TYPE_CHECKING:
16
+ from synapse_sdk.clients.protocols import ClientProtocol
13
17
 
14
- def list_job_logs(self, pk):
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
- path = f'jobs/{pk}/tail_logs/'
21
+ class RayClientMixin:
22
+ """Mixin for Ray cluster management endpoints."""
23
23
 
24
- url = self._get_url(path)
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
- response = self.requests_session.get(url, headers=headers, stream=True)
28
- for line in response.iter_lines(decode_unicode=True):
29
- if line:
30
- yield f'{line}\n'
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 get_node(self, pk):
33
- path = f'nodes/{pk}/'
34
- return self._get(path)
289
+ def list_tasks(self: ClientProtocol) -> list[dict]:
290
+ """List all Ray tasks."""
291
+ return self._get('ray/tasks/')
35
292
 
36
- def list_nodes(self):
37
- path = 'nodes/'
38
- return self._get(path)
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
- def get_task(self, pk):
41
- path = f'tasks/{pk}/'
42
- return self._get(path)
297
+ # -------------------------------------------------------------------------
298
+ # Serve Applications
299
+ # -------------------------------------------------------------------------
43
300
 
44
- def list_tasks(self):
45
- path = 'tasks/'
46
- return self._get(path)
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, pk):
49
- path = f'serve_application/{pk}/'
50
- return self._get(path)
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 list_serve_applications(self):
53
- path = 'serve_applications/'
54
- return self._get(path)
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.dataset import DatasetClientMixin
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__(self, base_url, token=None, tenant=None):
13
- super().__init__(base_url)
14
- self.token = token
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['SYNAPSE-Tenant'] = f'Token {self.tenant}'
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
+ ]