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
@@ -0,0 +1,200 @@
1
+ """Plugin local test command implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any, Literal
8
+
9
+ from rich.console import Console
10
+
11
+
12
+ @dataclass
13
+ class TestResult:
14
+ """Result of plugin test execution."""
15
+
16
+ action: str
17
+ plugin: str
18
+ result: Any
19
+ mode: str
20
+ job_id: str | None = None
21
+
22
+
23
+ def test_plugin(
24
+ action: str,
25
+ console: Console,
26
+ *,
27
+ path: Path,
28
+ params: dict[str, Any] | None = None,
29
+ mode: Literal['local', 'task', 'job'] = 'local',
30
+ ray_address: str = 'auto',
31
+ num_gpus: int | None = None,
32
+ num_cpus: int | None = None,
33
+ job_id: str | None = None,
34
+ env: dict[str, Any] | None = None,
35
+ include_sdk: bool = False,
36
+ ) -> TestResult:
37
+ """Execute plugin action for testing.
38
+
39
+ Args:
40
+ action: Action name to execute (e.g., test, train).
41
+ console: Rich console.
42
+ path: Plugin directory path.
43
+ params: Optional parameters to pass to the action.
44
+ mode: Execution mode:
45
+ - 'local': In-process execution (best for debugging)
46
+ - 'task': Ray Actor execution (no log streaming)
47
+ - 'job': Ray Jobs API with log streaming (recommended for remote)
48
+ ray_address: Ray cluster address (for 'task'/'job' modes).
49
+ num_gpus: Number of GPUs to request.
50
+ num_cpus: Number of CPUs to request.
51
+ job_id: Optional job identifier for tracking.
52
+ env: Environment variables to pass.
53
+
54
+ Returns:
55
+ TestResult with action result.
56
+
57
+ Raises:
58
+ ActionNotFoundError: If action doesn't exist.
59
+ ExecutionError: If execution fails.
60
+ """
61
+ import sys
62
+
63
+ from synapse_sdk.plugins.discovery import PluginDiscovery
64
+
65
+ # Add plugin directory to sys.path so entrypoints like 'plugin.test.TestAction' can be imported
66
+ path_str = str(path)
67
+ if path_str not in sys.path:
68
+ sys.path.insert(0, path_str)
69
+
70
+ # Discover plugin
71
+ discovery = PluginDiscovery.from_path(path)
72
+ config = discovery.config
73
+ plugin_code = config.code
74
+
75
+ # Merge env: CLI env takes precedence over config env
76
+ merged_env = {**config.env, **(env or {})}
77
+
78
+ console.print(f'[dim]Plugin:[/dim] {plugin_code}')
79
+ console.print(f'[dim]Action:[/dim] {action}')
80
+ console.print(f'[dim]Mode:[/dim] {mode}')
81
+
82
+ params = params or {}
83
+
84
+ if mode == 'local':
85
+ from synapse_sdk.plugins.executors.local import LocalExecutor
86
+
87
+ console.print()
88
+
89
+ # Get action class (local import works since we're in-process)
90
+ action_cls = discovery.get_action_class(action)
91
+
92
+ executor = LocalExecutor(env=merged_env, job_id=job_id)
93
+ result = executor.execute(action_cls, params)
94
+
95
+ return TestResult(
96
+ action=action,
97
+ plugin=plugin_code,
98
+ result=result,
99
+ mode=mode,
100
+ )
101
+
102
+ elif mode == 'task':
103
+ from synapse_sdk.plugins.executors.ray.task import RayActorExecutor
104
+
105
+ if num_gpus is not None:
106
+ console.print(f'[dim]GPUs:[/dim] {num_gpus}')
107
+ if num_cpus is not None:
108
+ console.print(f'[dim]CPUs:[/dim] {num_cpus}')
109
+ console.print()
110
+
111
+ # Get entrypoint string (don't load class locally - it runs on remote Ray worker)
112
+ entrypoint = discovery.get_action_entrypoint(action)
113
+
114
+ executor = RayActorExecutor(
115
+ working_dir=path,
116
+ ray_address=ray_address,
117
+ num_gpus=num_gpus,
118
+ num_cpus=num_cpus,
119
+ env=merged_env,
120
+ job_id=job_id,
121
+ include_sdk=include_sdk,
122
+ package_manager=config.package_manager,
123
+ package_manager_options=config.package_manager_options or None,
124
+ wheels_dir=config.wheels_dir,
125
+ runtime_env=config.runtime_env or None,
126
+ )
127
+
128
+ try:
129
+ result = executor.execute(entrypoint, params)
130
+ finally:
131
+ executor.shutdown()
132
+
133
+ return TestResult(
134
+ action=action,
135
+ plugin=plugin_code,
136
+ result=result,
137
+ mode=mode,
138
+ )
139
+
140
+ else: # mode == 'job' (or 'jobs-api' alias)
141
+ from synapse_sdk.plugins.executors.ray.jobs_api import RayJobsApiExecutor
142
+
143
+ # Derive dashboard address from ray_address
144
+ if ray_address.startswith('ray://'):
145
+ from urllib.parse import urlparse
146
+
147
+ parsed = urlparse(ray_address)
148
+ dashboard_addr = f'http://{parsed.hostname}:8265'
149
+ else:
150
+ dashboard_addr = 'http://localhost:8265'
151
+
152
+ console.print(f'[dim]Dashboard:[/dim] {dashboard_addr}')
153
+ if num_gpus is not None:
154
+ console.print(f'[dim]GPUs:[/dim] {num_gpus}')
155
+ if num_cpus is not None:
156
+ console.print(f'[dim]CPUs:[/dim] {num_cpus}')
157
+ console.print()
158
+
159
+ # Get entrypoint string (don't load class locally - it runs on remote Ray worker)
160
+ entrypoint = discovery.get_action_entrypoint(action)
161
+
162
+ executor = RayJobsApiExecutor(
163
+ dashboard_address=dashboard_addr,
164
+ working_dir=path,
165
+ num_gpus=num_gpus,
166
+ num_cpus=num_cpus,
167
+ env=merged_env,
168
+ include_sdk=include_sdk,
169
+ package_manager=config.package_manager,
170
+ package_manager_options=config.package_manager_options or None,
171
+ )
172
+
173
+ # Submit job via Ray Jobs API
174
+ submitted_job_id = executor.submit(entrypoint, params, job_id=job_id)
175
+
176
+ console.print(f'[dim]Job submitted:[/dim] {submitted_job_id}')
177
+ console.print('[dim]Streaming logs (Ctrl+C to stop watching)...[/dim]')
178
+ console.print()
179
+
180
+ # Stream logs in real-time
181
+ try:
182
+ for log_line in executor.stream_logs(submitted_job_id):
183
+ console.print(log_line, end='')
184
+ except KeyboardInterrupt:
185
+ console.print('\n[dim]Stopped watching logs.[/dim]')
186
+
187
+ # Get final result
188
+ result = executor.get_result(submitted_job_id, timeout=30)
189
+ console.print('\n[dim]Status:[/dim] SUCCEEDED')
190
+
191
+ return TestResult(
192
+ action=action,
193
+ plugin=plugin_code,
194
+ result=result,
195
+ mode=mode,
196
+ job_id=submitted_job_id,
197
+ )
198
+
199
+
200
+ __all__ = ['TestResult', 'test_plugin']
@@ -0,0 +1,239 @@
1
+ # Synapse SDK Clients
2
+
3
+ HTTP client module providing both synchronous (sync) and asynchronous (async) API clients.
4
+
5
+ ## Architecture Overview
6
+
7
+ ```
8
+ synapse_sdk/clients/
9
+ ├── __init__.py # Public API exports
10
+ ├── base.py # BaseClient, AsyncBaseClient
11
+ ├── protocols.py # ClientProtocol, AsyncClientProtocol
12
+ ├── utils.py # Shared utility functions
13
+ ├── validation.py # ValidationMixin
14
+ ├── exceptions.py # Exception re-exports
15
+ ├── _template.py # New client template
16
+ ├── agent/ # Agent API clients
17
+ │ ├── __init__.py # AgentClient, AsyncAgentClient
18
+ │ ├── ray.py # RayClientMixin
19
+ │ ├── async_ray.py # AsyncRayClientMixin
20
+ │ ├── container.py # ContainerClientMixin
21
+ │ └── plugin.py # PluginClientMixin
22
+ └── backend/ # Backend API clients
23
+ ├── __init__.py # BackendClient
24
+ ├── annotation.py # AnnotationClientMixin
25
+ ├── core.py # CoreClientMixin
26
+ ├── data_collection.py
27
+ ├── hitl.py
28
+ ├── integration.py
29
+ ├── ml.py
30
+ └── models.py # Pydantic models
31
+ ```
32
+
33
+ ## Module Structure
34
+
35
+ ### base.py
36
+
37
+ - `BaseClient`: Synchronous HTTP client based on requests
38
+ - `AsyncBaseClient`: Asynchronous HTTP client based on httpx
39
+ - Inherits `ValidationMixin` for Pydantic validation support
40
+
41
+ ### protocols.py
42
+
43
+ - `ClientProtocol`: Synchronous client protocol
44
+ - `AsyncClientProtocol`: Asynchronous client protocol
45
+ - `@runtime_checkable` decorator enables runtime type checking
46
+
47
+ ### utils.py
48
+
49
+ - `build_url()`: URL composition utility
50
+ - `extract_error_detail()`: Extract error details from response
51
+ - `parse_json_response()`: Parse JSON response
52
+
53
+ ### validation.py
54
+
55
+ - `ValidationMixin`: Provides Pydantic validation methods
56
+ - `_validate_response()`: Validate response data
57
+ - `_validate_request()`: Validate request data
58
+
59
+ ## Protocol-based Mixin Pattern
60
+
61
+ Use `ClientProtocol` as the `self` type hint in mixin classes:
62
+
63
+ ```python
64
+ from typing import TYPE_CHECKING
65
+
66
+ if TYPE_CHECKING:
67
+ from synapse_sdk.clients.protocols import ClientProtocol
68
+
69
+ class MyMixin:
70
+ def get_something(self: ClientProtocol) -> dict:
71
+ # IDE autocompletion supported
72
+ return self._get('something/')
73
+ ```
74
+
75
+ ### Why Use Protocol?
76
+
77
+ 1. **IDE Autocompletion**: Using `self: ClientProtocol` instead of `self: BaseClient` allows IDE to recognize methods like `_get`, `_post`, etc.
78
+ 2. **Avoid Circular Imports**: Import only within `TYPE_CHECKING` block to prevent runtime circular dependencies
79
+ 3. **Flexible Type Checking**: Supports structural typing without actual class inheritance
80
+
81
+ ## Exception Hierarchy
82
+
83
+ ```
84
+ ClientError (base)
85
+ ├── ClientConnectionError # Connection failure
86
+ ├── ClientTimeoutError # Timeout
87
+ ├── HTTPError # HTTP status code errors
88
+ │ ├── AuthenticationError (401)
89
+ │ ├── AuthorizationError (403)
90
+ │ ├── NotFoundError (404)
91
+ │ ├── ValidationError (400/422)
92
+ │ ├── RateLimitError (429)
93
+ │ └── ServerError (5xx)
94
+ └── StreamError # Streaming errors
95
+ ├── StreamLimitExceededError
96
+ └── WebSocketError
97
+ ```
98
+
99
+ ### Exception Usage Example
100
+
101
+ ```python
102
+ from synapse_sdk.exceptions import (
103
+ ClientError,
104
+ NotFoundError,
105
+ AuthenticationError,
106
+ )
107
+
108
+ try:
109
+ result = client.get_resource(123)
110
+ except NotFoundError:
111
+ print("Resource not found")
112
+ except AuthenticationError:
113
+ print("Authentication failed")
114
+ except ClientError as e:
115
+ print(f"Client error: {e.status_code}")
116
+ ```
117
+
118
+ ## Guide for Adding New Clients
119
+
120
+ ### 1. Copy Template
121
+
122
+ Create a new client by referencing `_template.py`:
123
+
124
+ ```python
125
+ from synapse_sdk.clients.base import BaseClient
126
+
127
+ class MyApiClient(BaseClient):
128
+ name = 'MyAPI'
129
+
130
+ def __init__(self, base_url: str, api_key: str, **kwargs):
131
+ super().__init__(base_url, **kwargs)
132
+ self.api_key = api_key
133
+
134
+ def _get_headers(self) -> dict[str, str]:
135
+ return {'X-API-Key': self.api_key}
136
+
137
+ def get_resource(self, resource_id: int) -> dict:
138
+ return self._get(f'resources/{resource_id}/')
139
+ ```
140
+
141
+ ### 2. Use Mixin Pattern (Optional)
142
+
143
+ For large APIs, separate functionality into mixins:
144
+
145
+ ```python
146
+ # my_api/users.py
147
+ from typing import TYPE_CHECKING
148
+
149
+ if TYPE_CHECKING:
150
+ from synapse_sdk.clients.protocols import ClientProtocol
151
+
152
+ class UsersMixin:
153
+ def list_users(self: ClientProtocol) -> list[dict]:
154
+ return self._get('users/')
155
+
156
+ def get_user(self: ClientProtocol, user_id: int) -> dict:
157
+ return self._get(f'users/{user_id}/')
158
+
159
+ # my_api/__init__.py
160
+ from synapse_sdk.clients.base import BaseClient
161
+ from my_api.users import UsersMixin
162
+
163
+ class MyApiClient(UsersMixin, BaseClient):
164
+ name = 'MyAPI'
165
+ # ...
166
+ ```
167
+
168
+ ### 3. Pydantic Model Validation
169
+
170
+ Use Pydantic models for request/response validation:
171
+
172
+ ```python
173
+ from pydantic import BaseModel
174
+
175
+ class UserCreate(BaseModel):
176
+ name: str
177
+ email: str
178
+
179
+ class UserResponse(BaseModel):
180
+ id: int
181
+ name: str
182
+ email: str
183
+
184
+ class MyApiClient(BaseClient):
185
+ def create_user(self, data: dict) -> dict:
186
+ return self._post(
187
+ 'users/',
188
+ request_model=UserCreate,
189
+ response_model=UserResponse,
190
+ data=data,
191
+ )
192
+ ```
193
+
194
+ ### 4. Async Client Implementation
195
+
196
+ ```python
197
+ from synapse_sdk.clients.base import AsyncBaseClient
198
+
199
+ class AsyncMyApiClient(AsyncBaseClient):
200
+ name = 'MyAPI'
201
+
202
+ def __init__(self, base_url: str, api_key: str, **kwargs):
203
+ super().__init__(base_url, **kwargs)
204
+ self.api_key = api_key
205
+
206
+ def _get_headers(self) -> dict[str, str]:
207
+ return {'X-API-Key': self.api_key}
208
+
209
+ async def get_resource(self, resource_id: int) -> dict:
210
+ return await self._get(f'resources/{resource_id}/')
211
+
212
+ # Usage
213
+ async with AsyncMyApiClient(base_url, api_key) as client:
214
+ result = await client.get_resource(123)
215
+ ```
216
+
217
+ ## Method Reference
218
+
219
+ ### BaseClient / AsyncBaseClient
220
+
221
+ | Method | Description |
222
+ | ---------------------- | ---------------------------- |
223
+ | `_get(path, ...)` | GET request |
224
+ | `_post(path, ...)` | POST request |
225
+ | `_put(path, ...)` | PUT request |
226
+ | `_patch(path, ...)` | PATCH request |
227
+ | `_delete(path, ...)` | DELETE request |
228
+ | `_list(path, ...)` | List with pagination support |
229
+ | `_validate_response()` | Pydantic response validation |
230
+ | `_validate_request()` | Pydantic request validation |
231
+
232
+ ### Utility Functions
233
+
234
+ | Function | Description |
235
+ | --------------------------------------- | ------------------------------ |
236
+ | `build_url(base, path, trailing_slash)` | URL composition |
237
+ | `extract_error_detail(response)` | Extract error details |
238
+ | `parse_json_response(response)` | Parse JSON response |
239
+ | `raise_for_status(status_code, detail)` | Raise exception by status code |
@@ -0,0 +1,5 @@
1
+ from synapse_sdk.clients.agent import AgentClient
2
+ from synapse_sdk.clients.base import BaseClient
3
+ from synapse_sdk.clients.exceptions import ClientError
4
+
5
+ __all__ = ['AgentClient', 'BaseClient', 'ClientError']
@@ -0,0 +1,266 @@
1
+ """Template for creating new HTTP clients.
2
+
3
+ This module provides template classes for building new sync and async
4
+ HTTP clients. Copy this file and modify it to create a new client.
5
+
6
+ Usage:
7
+ 1. Copy this file to a new module (e.g., my_api/__init__.py)
8
+ 2. Rename TemplateClient and AsyncTemplateClient
9
+ 3. Implement _get_headers() with your authentication
10
+ 4. Add domain-specific methods
11
+
12
+ Example:
13
+ >>> from synapse_sdk.clients.my_api import MyApiClient
14
+ >>> client = MyApiClient('https://api.example.com', api_key='secret')
15
+ >>> users = client.list_users()
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from typing import TYPE_CHECKING, Any
21
+
22
+ import httpx
23
+
24
+ from synapse_sdk.clients.base import AsyncBaseClient, BaseClient
25
+
26
+ if TYPE_CHECKING:
27
+ from pydantic import BaseModel
28
+
29
+
30
+ class TemplateClient(BaseClient):
31
+ """Synchronous HTTP client template.
32
+
33
+ This template demonstrates how to create a new sync client by
34
+ extending BaseClient with custom authentication and methods.
35
+
36
+ Attributes:
37
+ name: Client name for error messages.
38
+ api_key: API key for authentication.
39
+ """
40
+
41
+ name = 'TemplateAPI'
42
+
43
+ def __init__(
44
+ self,
45
+ base_url: str,
46
+ api_key: str,
47
+ *,
48
+ timeout: dict[str, int] | None = None,
49
+ ):
50
+ """Initialize the template client.
51
+
52
+ Args:
53
+ base_url: The base URL for all API requests.
54
+ api_key: API key for authentication.
55
+ timeout: Optional timeout configuration.
56
+ """
57
+ super().__init__(base_url, timeout=timeout)
58
+ self.api_key = api_key
59
+
60
+ def _get_headers(self) -> dict[str, str]:
61
+ """Return authentication headers.
62
+
63
+ Override this method to implement your authentication scheme.
64
+ Common patterns include:
65
+ - API key: {'X-API-Key': self.api_key}
66
+ - Bearer token: {'Authorization': f'Bearer {self.token}'}
67
+ - Basic auth: {'Authorization': f'Basic {base64_credentials}'}
68
+ """
69
+ return {'X-API-Key': self.api_key}
70
+
71
+ # -------------------------------------------------------------------------
72
+ # Example API Methods
73
+ # -------------------------------------------------------------------------
74
+
75
+ def list_resources(
76
+ self,
77
+ params: dict[str, Any] | None = None,
78
+ *,
79
+ list_all: bool = False,
80
+ ) -> dict[str, Any] | tuple[Any, int]:
81
+ """List resources with optional pagination.
82
+
83
+ Args:
84
+ params: Query parameters for filtering.
85
+ list_all: If True, returns (generator, count).
86
+
87
+ Returns:
88
+ Paginated list or (generator, count).
89
+ """
90
+ return self._list('resources/', params=params, list_all=list_all)
91
+
92
+ def get_resource(self, resource_id: int) -> dict[str, Any]:
93
+ """Get a resource by ID.
94
+
95
+ Args:
96
+ resource_id: The resource ID.
97
+
98
+ Returns:
99
+ Resource data.
100
+ """
101
+ return self._get(f'resources/{resource_id}/')
102
+
103
+ def create_resource(
104
+ self,
105
+ data: dict[str, Any],
106
+ request_model: type[BaseModel] | None = None,
107
+ response_model: type[BaseModel] | None = None,
108
+ ) -> dict[str, Any]:
109
+ """Create a new resource.
110
+
111
+ Args:
112
+ data: Resource data to create.
113
+ request_model: Optional Pydantic model for request validation.
114
+ response_model: Optional Pydantic model for response validation.
115
+
116
+ Returns:
117
+ Created resource data.
118
+ """
119
+ return self._post(
120
+ 'resources/',
121
+ request_model=request_model,
122
+ response_model=response_model,
123
+ data=data,
124
+ )
125
+
126
+ def update_resource(
127
+ self,
128
+ resource_id: int,
129
+ data: dict[str, Any],
130
+ ) -> dict[str, Any]:
131
+ """Update a resource.
132
+
133
+ Args:
134
+ resource_id: The resource ID to update.
135
+ data: Fields to update.
136
+
137
+ Returns:
138
+ Updated resource data.
139
+ """
140
+ return self._patch(f'resources/{resource_id}/', data=data)
141
+
142
+ def delete_resource(self, resource_id: int) -> None:
143
+ """Delete a resource.
144
+
145
+ Args:
146
+ resource_id: The resource ID to delete.
147
+ """
148
+ self._delete(f'resources/{resource_id}/')
149
+
150
+
151
+ class AsyncTemplateClient(AsyncBaseClient):
152
+ """Asynchronous HTTP client template.
153
+
154
+ This template demonstrates how to create a new async client by
155
+ extending AsyncBaseClient with custom authentication and methods.
156
+
157
+ Attributes:
158
+ name: Client name for error messages.
159
+ api_key: API key for authentication.
160
+ """
161
+
162
+ name = 'TemplateAPI'
163
+
164
+ def __init__(
165
+ self,
166
+ base_url: str,
167
+ api_key: str,
168
+ *,
169
+ timeout: float | httpx.Timeout | None = None,
170
+ ):
171
+ """Initialize the async template client.
172
+
173
+ Args:
174
+ base_url: The base URL for all API requests.
175
+ api_key: API key for authentication.
176
+ timeout: Optional timeout configuration.
177
+ """
178
+ super().__init__(base_url, timeout=timeout)
179
+ self.api_key = api_key
180
+
181
+ def _get_headers(self) -> dict[str, str]:
182
+ """Return authentication headers."""
183
+ return {'X-API-Key': self.api_key}
184
+
185
+ # -------------------------------------------------------------------------
186
+ # Example API Methods (Async versions)
187
+ # -------------------------------------------------------------------------
188
+
189
+ async def list_resources(
190
+ self,
191
+ params: dict[str, Any] | None = None,
192
+ *,
193
+ list_all: bool = False,
194
+ ) -> dict[str, Any] | tuple[Any, int]:
195
+ """List resources with optional pagination.
196
+
197
+ Args:
198
+ params: Query parameters for filtering.
199
+ list_all: If True, returns (generator, count).
200
+
201
+ Returns:
202
+ Paginated list or (generator, count).
203
+ """
204
+ return await self._list('resources/', params=params, list_all=list_all)
205
+
206
+ async def get_resource(self, resource_id: int) -> dict[str, Any]:
207
+ """Get a resource by ID.
208
+
209
+ Args:
210
+ resource_id: The resource ID.
211
+
212
+ Returns:
213
+ Resource data.
214
+ """
215
+ return await self._get(f'resources/{resource_id}/')
216
+
217
+ async def create_resource(
218
+ self,
219
+ data: dict[str, Any],
220
+ request_model: type[BaseModel] | None = None,
221
+ response_model: type[BaseModel] | None = None,
222
+ ) -> dict[str, Any]:
223
+ """Create a new resource.
224
+
225
+ Args:
226
+ data: Resource data to create.
227
+ request_model: Optional Pydantic model for request validation.
228
+ response_model: Optional Pydantic model for response validation.
229
+
230
+ Returns:
231
+ Created resource data.
232
+ """
233
+ return await self._post(
234
+ 'resources/',
235
+ request_model=request_model,
236
+ response_model=response_model,
237
+ json=data,
238
+ )
239
+
240
+ async def update_resource(
241
+ self,
242
+ resource_id: int,
243
+ data: dict[str, Any],
244
+ ) -> dict[str, Any]:
245
+ """Update a resource.
246
+
247
+ Args:
248
+ resource_id: The resource ID to update.
249
+ data: Fields to update.
250
+
251
+ Returns:
252
+ Updated resource data.
253
+ """
254
+ return await self._patch(f'resources/{resource_id}/', json=data)
255
+
256
+ async def delete_resource(self, resource_id: int) -> None:
257
+ """Delete a resource.
258
+
259
+ Args:
260
+ resource_id: The resource ID to delete.
261
+ """
262
+ await self._delete(f'resources/{resource_id}/')
263
+
264
+
265
+ # Note: This file is a template. Do not import or use these classes directly.
266
+ # Copy and modify them for your specific API client implementation.