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,24 +1,42 @@
1
- from synapse_sdk.clients.agent.core import CoreClientMixin
1
+ from __future__ import annotations
2
+
3
+ from synapse_sdk.clients.agent.async_ray import AsyncRayClientMixin
4
+ from synapse_sdk.clients.agent.container import ContainerClientMixin
5
+ from synapse_sdk.clients.agent.plugin import PluginClientMixin
2
6
  from synapse_sdk.clients.agent.ray import RayClientMixin
3
- from synapse_sdk.clients.agent.service import ServiceClientMixin
4
- from synapse_sdk.clients.exceptions import ClientError
7
+ from synapse_sdk.clients.base import AsyncBaseClient, BaseClient
8
+
5
9
 
10
+ class AgentClient(ContainerClientMixin, PluginClientMixin, RayClientMixin, BaseClient):
11
+ """Sync client for synapse-agent API."""
6
12
 
7
- class AgentClient(CoreClientMixin, RayClientMixin, ServiceClientMixin):
8
13
  name = 'Agent'
9
- agent_token = None
10
- user_token = None
11
- tenant = None
12
- long_poll_handler = None
13
14
 
14
- def __init__(self, base_url, agent_token, user_token=None, tenant=None, long_poll_handler=None):
15
- super().__init__(base_url)
15
+ def __init__(
16
+ self,
17
+ base_url: str,
18
+ agent_token: str,
19
+ *,
20
+ user_token: str | None = None,
21
+ tenant: str | None = None,
22
+ timeout: dict[str, int] | None = None,
23
+ ):
24
+ """Initialize the agent client.
25
+
26
+ Args:
27
+ base_url: Agent API base URL.
28
+ agent_token: Agent authentication token.
29
+ user_token: Optional user token for impersonation.
30
+ tenant: Optional tenant identifier.
31
+ timeout: Request timeout dict with 'connect' and 'read' keys.
32
+ """
33
+ super().__init__(base_url, timeout=timeout or {'connect': 3, 'read': 10})
16
34
  self.agent_token = agent_token
17
35
  self.user_token = user_token
18
36
  self.tenant = tenant
19
- self.long_poll_handler = long_poll_handler
20
37
 
21
- def _get_headers(self):
38
+ def _get_headers(self) -> dict[str, str]:
39
+ """Return authentication headers."""
22
40
  headers = {'Authorization': self.agent_token}
23
41
  if self.user_token:
24
42
  headers['SYNAPSE-User'] = f'Token {self.user_token}'
@@ -26,26 +44,63 @@ class AgentClient(CoreClientMixin, RayClientMixin, ServiceClientMixin):
26
44
  headers['SYNAPSE-Tenant'] = f'Token {self.tenant}'
27
45
  return headers
28
46
 
29
- def _request(self, method, path, **kwargs):
30
- if self.long_poll_handler:
31
- return self._request_long_poll(method, path, **kwargs)
32
- return super()._request(method, path, **kwargs)
47
+ def health_check(self) -> dict:
48
+ """Check agent health."""
49
+ path = 'health/'
50
+ return self._get(path)
51
+
52
+
53
+ class AsyncAgentClient(AsyncRayClientMixin, AsyncBaseClient):
54
+ """Async client for synapse-agent API.
55
+
56
+ Provides async/await interface for all agent operations including
57
+ WebSocket and HTTP streaming for job log tailing.
58
+
59
+ Example:
60
+ >>> async with AsyncAgentClient(base_url, agent_token) as client:
61
+ ... jobs = await client.list_jobs()
62
+ ... async for line in client.tail_job_logs('job-123'):
63
+ ... print(line)
64
+ """
65
+
66
+ name = 'Agent'
33
67
 
34
- def _request_long_poll(self, method, path, **kwargs):
35
- headers = self._get_headers()
68
+ def __init__(
69
+ self,
70
+ base_url: str,
71
+ agent_token: str,
72
+ *,
73
+ user_token: str | None = None,
74
+ tenant: str | None = None,
75
+ timeout: float | None = None,
76
+ ):
77
+ """Initialize the async agent client.
36
78
 
37
- if kwargs.get('files'):
38
- raise ClientError(400, 'file is not allowed when long polling')
79
+ Args:
80
+ base_url: Agent API base URL.
81
+ agent_token: Agent authentication token.
82
+ user_token: Optional user token for impersonation.
83
+ tenant: Optional tenant identifier.
84
+ timeout: Request timeout in seconds.
85
+ """
86
+ super().__init__(base_url, timeout=timeout)
87
+ self.agent_token = agent_token
88
+ self.user_token = user_token
89
+ self.tenant = tenant
39
90
 
40
- headers['Content-Type'] = 'application/json'
91
+ def _get_headers(self) -> dict[str, str]:
92
+ """Return authentication headers."""
93
+ headers = {'Authorization': self.agent_token}
94
+ if self.user_token:
95
+ headers['SYNAPSE-User'] = f'Token {self.user_token}'
96
+ if self.tenant:
97
+ headers['SYNAPSE-Tenant'] = f'Token {self.tenant}'
98
+ return headers
41
99
 
42
- request_id = self.long_poll_handler.set_request({'method': method, 'path': path, 'headers': headers, **kwargs})
43
- try:
44
- response = self.long_poll_handler.get_response(request_id)
45
- except TimeoutError:
46
- raise ClientError(408, f'{self.name} is not responding')
100
+ async def health_check(self) -> dict:
101
+ """Check agent health."""
102
+ path = 'health/'
103
+ return await self._get(path)
47
104
 
48
- if 400 <= response['status'] < 600:
49
- raise ClientError(response['status'], response.json() if response['status'] == 400 else response['reason'])
50
105
 
51
- return response['data']
106
+ __all__ = ['AgentClient', 'AsyncAgentClient']
@@ -0,0 +1,289 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import TYPE_CHECKING, AsyncGenerator, Literal
5
+
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
+ )
14
+
15
+ if TYPE_CHECKING:
16
+ from synapse_sdk.clients.protocols import AsyncClientProtocol
17
+
18
+ StreamProtocol = Literal['websocket', 'http', 'auto']
19
+
20
+
21
+ class AsyncRayClientMixin:
22
+ """Async mixin for Ray cluster management endpoints."""
23
+
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
+ async def list_jobs(self: AsyncClientProtocol) -> list[dict]:
43
+ """List all Ray jobs."""
44
+ return await self._get('ray/jobs/')
45
+
46
+ async def get_job(self: AsyncClientProtocol, job_id: str) -> dict:
47
+ """Get a Ray job by ID."""
48
+ return await self._get(f'ray/jobs/{job_id}')
49
+
50
+ async def get_job_logs(self: AsyncClientProtocol, job_id: str) -> str:
51
+ """Get all logs for a job (non-streaming)."""
52
+ return await self._get(f'ray/jobs/{job_id}/logs')
53
+
54
+ async def stop_job(self: AsyncClientProtocol, job_id: str) -> dict:
55
+ """Stop a running job."""
56
+ return await self._post(f'ray/jobs/{job_id}/stop/')
57
+
58
+ async def websocket_tail_job_logs(
59
+ self: AsyncClientProtocol,
60
+ job_id: str,
61
+ timeout: float = 30.0,
62
+ ) -> AsyncGenerator[str, None]:
63
+ """Stream job logs via WebSocket protocol (async).
64
+
65
+ Establishes an async WebSocket connection for real-time log streaming.
66
+
67
+ Args:
68
+ job_id: The Ray job ID to tail logs for.
69
+ timeout: Connection and read timeout in seconds.
70
+
71
+ Yields:
72
+ Log message strings.
73
+
74
+ Raises:
75
+ ClientError: On connection, protocol, or validation errors.
76
+
77
+ Example:
78
+ >>> async for line in client.websocket_tail_job_logs('raysubmit_abc123'):
79
+ ... print(line)
80
+ """
81
+ validated_id = validate_resource_id(job_id, 'job')
82
+ validated_timeout = validate_timeout(timeout)
83
+
84
+ url = self._get_url(f'ray/jobs/{validated_id}/logs/')
85
+ ws_url = http_to_websocket_url(f'{self.base_url}/{url}')
86
+ headers = self._get_headers()
87
+
88
+ try:
89
+ import websockets
90
+ except ImportError:
91
+ raise ClientError(500, 'websockets package required for async WebSocket streaming')
92
+
93
+ limits = self.stream_limits
94
+ message_count = 0
95
+
96
+ try:
97
+ async with websockets.connect(
98
+ ws_url,
99
+ additional_headers=headers,
100
+ close_timeout=validated_timeout,
101
+ ping_timeout=validated_timeout,
102
+ ) as ws:
103
+ async for data in ws:
104
+ if not data:
105
+ break
106
+
107
+ message_count += 1
108
+ if message_count > limits.max_messages:
109
+ raise ClientError(429, 'Stream message limit exceeded')
110
+
111
+ if len(str(data)) > limits.max_message_size:
112
+ continue
113
+
114
+ try:
115
+ event = json.loads(data)
116
+ except json.JSONDecodeError:
117
+ event = {'message': data}
118
+
119
+ event_type = event.get('type')
120
+ if event_type == 'error':
121
+ raise ClientError(500, event.get('message', 'Unknown error'))
122
+ elif event_type == 'complete':
123
+ return
124
+
125
+ if msg := event.get('message'):
126
+ yield msg
127
+
128
+ except ClientError:
129
+ raise
130
+ except Exception as e:
131
+ if 'ConnectionClosed' in type(e).__name__:
132
+ return # Normal close
133
+ raise ClientError(503, sanitize_error_message(str(e), 'WebSocket error'))
134
+
135
+ async def stream_tail_job_logs(
136
+ self: AsyncClientProtocol,
137
+ job_id: str,
138
+ timeout: float = 30.0,
139
+ ) -> AsyncGenerator[str, None]:
140
+ """Stream job logs via HTTP chunked transfer (async).
141
+
142
+ Uses HTTP streaming as an alternative when WebSocket is unavailable.
143
+
144
+ Args:
145
+ job_id: The Ray job ID to tail logs for.
146
+ timeout: Connection timeout in seconds.
147
+
148
+ Yields:
149
+ Log lines as strings.
150
+
151
+ Raises:
152
+ ClientError: On connection, protocol, or validation errors.
153
+
154
+ Example:
155
+ >>> async for line in client.stream_tail_job_logs('raysubmit_abc123'):
156
+ ... print(line)
157
+ """
158
+ validated_id = validate_resource_id(job_id, 'job')
159
+ validated_timeout = validate_timeout(timeout)
160
+
161
+ url = self._get_url(f'ray/jobs/{validated_id}/logs/')
162
+ headers = self._get_headers()
163
+
164
+ client = await self._get_client()
165
+ limits = self.stream_limits
166
+ line_count = 0
167
+ total_bytes = 0
168
+
169
+ try:
170
+ async with client.stream(
171
+ 'GET',
172
+ url,
173
+ headers=headers,
174
+ timeout=validated_timeout,
175
+ ) as response:
176
+ if not response.is_success:
177
+ raise ClientError(response.status_code, 'HTTP streaming failed')
178
+
179
+ async for line in response.aiter_lines():
180
+ if line:
181
+ line_count += 1
182
+ total_bytes += len(line.encode('utf-8'))
183
+
184
+ if line_count > limits.max_lines:
185
+ raise ClientError(429, 'Stream line limit exceeded')
186
+
187
+ if total_bytes > limits.max_bytes:
188
+ raise ClientError(429, 'Stream size limit exceeded')
189
+
190
+ if len(line) > limits.max_message_size:
191
+ continue
192
+
193
+ yield line
194
+
195
+ except ClientError:
196
+ raise
197
+ except Exception as e:
198
+ raise ClientError(503, sanitize_error_message(str(e), 'HTTP streaming error'))
199
+
200
+ async def tail_job_logs(
201
+ self: AsyncClientProtocol,
202
+ job_id: str,
203
+ timeout: float = 30.0,
204
+ *,
205
+ protocol: StreamProtocol = 'auto',
206
+ ) -> AsyncGenerator[str, None]:
207
+ """Stream job logs with automatic protocol selection (async).
208
+
209
+ Unified method that supports WebSocket, HTTP, and auto-selection.
210
+
211
+ Args:
212
+ job_id: The Ray job ID to tail logs for.
213
+ timeout: Connection timeout in seconds.
214
+ protocol: Protocol to use:
215
+ - 'websocket': Use WebSocket only
216
+ - 'http': Use HTTP streaming only
217
+ - 'auto': Try WebSocket, fall back to HTTP on connection failure
218
+
219
+ Yields:
220
+ Log message strings.
221
+
222
+ Raises:
223
+ ClientError: On connection, protocol, or validation errors.
224
+
225
+ Example:
226
+ >>> async for line in client.tail_job_logs('raysubmit_abc123'):
227
+ ... print(line)
228
+ """
229
+ validate_resource_id(job_id, 'job')
230
+ validate_timeout(timeout)
231
+
232
+ if protocol == 'websocket':
233
+ async for msg in self.websocket_tail_job_logs(job_id, timeout):
234
+ yield msg
235
+ elif protocol == 'http':
236
+ async for msg in self.stream_tail_job_logs(job_id, timeout):
237
+ yield msg
238
+ elif protocol == 'auto':
239
+ try:
240
+ async for msg in self.websocket_tail_job_logs(job_id, timeout):
241
+ yield msg
242
+ except ClientError as e:
243
+ if e.status_code in (500, 503):
244
+ async for msg in self.stream_tail_job_logs(job_id, timeout):
245
+ yield msg
246
+ else:
247
+ raise
248
+ else:
249
+ raise ClientError(400, f'Invalid protocol: {protocol}')
250
+
251
+ # -------------------------------------------------------------------------
252
+ # Nodes
253
+ # -------------------------------------------------------------------------
254
+
255
+ async def list_nodes(self: AsyncClientProtocol) -> list[dict]:
256
+ """List all Ray nodes."""
257
+ return await self._get('ray/nodes/')
258
+
259
+ async def get_node(self: AsyncClientProtocol, node_id: str) -> dict:
260
+ """Get a Ray node by ID."""
261
+ return await self._get(f'ray/nodes/{node_id}')
262
+
263
+ # -------------------------------------------------------------------------
264
+ # Tasks
265
+ # -------------------------------------------------------------------------
266
+
267
+ async def list_tasks(self: AsyncClientProtocol) -> list[dict]:
268
+ """List all Ray tasks."""
269
+ return await self._get('ray/tasks/')
270
+
271
+ async def get_task(self: AsyncClientProtocol, task_id: str) -> dict:
272
+ """Get a Ray task by ID."""
273
+ return await self._get(f'ray/tasks/{task_id}')
274
+
275
+ # -------------------------------------------------------------------------
276
+ # Serve Applications
277
+ # -------------------------------------------------------------------------
278
+
279
+ async def list_serve_applications(self: AsyncClientProtocol) -> list[dict]:
280
+ """List all Ray Serve applications."""
281
+ return await self._get('ray/serve_applications/')
282
+
283
+ async def get_serve_application(self: AsyncClientProtocol, name: str) -> dict:
284
+ """Get a Ray Serve application by name."""
285
+ return await self._get(f'ray/serve_applications/{name}')
286
+
287
+ async def delete_serve_application(self: AsyncClientProtocol, name: str) -> None:
288
+ """Delete a Ray Serve application."""
289
+ await self._delete(f'ray/serve_applications/{name}')
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ if TYPE_CHECKING:
6
+ from synapse_sdk.clients.protocols import ClientProtocol
7
+
8
+
9
+ class ContainerClientMixin:
10
+ """Mixin for container management endpoints."""
11
+
12
+ # Docker containers
13
+ def list_docker_containers(self: ClientProtocol) -> list[dict]:
14
+ """List all Docker containers on the host."""
15
+ return self._get('containers/docker/')
16
+
17
+ def get_docker_container(self: ClientProtocol, container_id: str) -> dict:
18
+ """Get a specific Docker container by ID."""
19
+ return self._get(f'containers/docker/{container_id}')
20
+
21
+ def create_docker_container(
22
+ self: ClientProtocol,
23
+ plugin_release: str,
24
+ *,
25
+ params: dict[str, Any] | None = None,
26
+ envs: dict[str, str] | None = None,
27
+ metadata: dict[str, Any] | None = None,
28
+ labels: list[str] | None = None,
29
+ ) -> dict:
30
+ """Build and run a Docker container for a plugin.
31
+
32
+ Args:
33
+ plugin_release: Plugin identifier (e.g., "plugin_code@version").
34
+ params: Parameters forwarded to the plugin.
35
+ envs: Environment variables injected into the container.
36
+ metadata: Additional metadata stored with the container record.
37
+ labels: Container labels for display or filtering.
38
+ """
39
+ data = {'plugin_release': plugin_release}
40
+ if params is not None:
41
+ data['params'] = params
42
+ if envs is not None:
43
+ data['envs'] = envs
44
+ if metadata is not None:
45
+ data['metadata'] = metadata
46
+ if labels is not None:
47
+ data['labels'] = labels
48
+
49
+ return self._post('containers/docker/', data=data)
50
+
51
+ def delete_docker_container(self: ClientProtocol, container_id: str) -> None:
52
+ """Stop and remove a Docker container."""
53
+ self._delete(f'containers/docker/{container_id}')
54
+
55
+ # Database container records
56
+ def list_containers(
57
+ self: ClientProtocol,
58
+ params: dict | None = None,
59
+ *,
60
+ list_all: bool = False,
61
+ ) -> dict | tuple[Any, int]:
62
+ """List tracked containers from database."""
63
+ return self._list('containers/', params=params, list_all=list_all)
64
+
65
+ def get_container(self: ClientProtocol, container_id: int) -> dict:
66
+ """Get a tracked container by database ID."""
67
+ return self._get(f'containers/{container_id}')
68
+
69
+ def update_container(
70
+ self: ClientProtocol,
71
+ container_id: int,
72
+ *,
73
+ status: str | None = None,
74
+ ) -> dict:
75
+ """Update a tracked container's status."""
76
+ data = {}
77
+ if status is not None:
78
+ data['status'] = status
79
+ return self._patch(f'containers/{container_id}', data=data)
80
+
81
+ def delete_container(self: ClientProtocol, container_id: int) -> None:
82
+ """Delete a tracked container (stops Docker container too)."""
83
+ self._delete(f'containers/{container_id}')
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ if TYPE_CHECKING:
6
+ from synapse_sdk.clients.protocols import ClientProtocol
7
+
8
+
9
+ class PluginClientMixin:
10
+ """Mixin for plugin release endpoints."""
11
+
12
+ def list_plugin_releases(
13
+ self: ClientProtocol,
14
+ params: dict | None = None,
15
+ *,
16
+ list_all: bool = False,
17
+ ) -> dict | tuple[Any, int]:
18
+ """List all plugin releases."""
19
+ return self._list('plugin_releases/', params=params, list_all=list_all)
20
+
21
+ def get_plugin_release(self: ClientProtocol, lookup: str) -> dict:
22
+ """Get a plugin release by ID or code@version."""
23
+ return self._get(f'plugin_releases/{lookup}')
24
+
25
+ def create_plugin_release(
26
+ self: ClientProtocol,
27
+ plugin: str,
28
+ version: str,
29
+ ) -> dict:
30
+ """Fetch and cache a plugin release."""
31
+ return self._post('plugin_releases/', data={'plugin': plugin, 'version': version})
32
+
33
+ def delete_plugin_release(self: ClientProtocol, lookup: str) -> None:
34
+ """Delete a plugin release."""
35
+ self._delete(f'plugin_releases/{lookup}')
36
+
37
+ def run_plugin_release(
38
+ self: ClientProtocol,
39
+ lookup: str,
40
+ action: str,
41
+ params: dict[str, Any] | None = None,
42
+ *,
43
+ requirements: list[str] | None = None,
44
+ job_id: str | None = None,
45
+ ) -> Any:
46
+ """Run a plugin release action.
47
+
48
+ Args:
49
+ lookup: Plugin identifier (ID or "plugin@version").
50
+ action: Action name to execute.
51
+ params: Parameters to pass to the action.
52
+ requirements: Additional pip requirements.
53
+ job_id: Optional job ID for tracking.
54
+ """
55
+ data: dict[str, Any] = {'action': action}
56
+ if params is not None:
57
+ data['params'] = params
58
+ if requirements is not None:
59
+ data['requirements'] = requirements
60
+ if job_id is not None:
61
+ data['job_id'] = job_id
62
+
63
+ return self._post(f'plugin_releases/{lookup}/run/', data=data)
64
+
65
+ def run_debug_plugin_release(
66
+ self: ClientProtocol,
67
+ action: str,
68
+ params: dict[str, Any] | None = None,
69
+ *,
70
+ plugin_path: str | None = None,
71
+ config: dict[str, Any] | None = None,
72
+ modules: dict[str, str] | None = None,
73
+ requirements: list[str] | None = None,
74
+ job_id: str | None = None,
75
+ ) -> Any:
76
+ """Run a plugin in debug mode (from source path).
77
+
78
+ Args:
79
+ action: Action name to execute.
80
+ params: Parameters to pass to the action.
81
+ plugin_path: Path to the plugin source directory.
82
+ config: Plugin configuration override.
83
+ modules: Module source code mapping.
84
+ requirements: Additional pip requirements.
85
+ job_id: Optional job ID for tracking.
86
+ """
87
+ data: dict[str, Any] = {'action': action}
88
+ if params is not None:
89
+ data['params'] = params
90
+ if plugin_path is not None:
91
+ data['plugin_path'] = plugin_path
92
+ if config is not None:
93
+ data['config'] = config
94
+ if modules is not None:
95
+ data['modules'] = modules
96
+ if requirements is not None:
97
+ data['requirements'] = requirements
98
+ if job_id is not None:
99
+ data['job_id'] = job_id
100
+
101
+ return self._post('plugin_releases/run_debug/', data=data)