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
synapse_sdk/loggers.py CHANGED
@@ -1,116 +1,497 @@
1
- import datetime
1
+ from __future__ import annotations
2
+
3
+ import logging
2
4
  import time
5
+ import warnings
6
+ from abc import ABC, abstractmethod
7
+ from collections.abc import Mapping
8
+ from dataclasses import dataclass, field
9
+ from enum import Enum
10
+ from typing import TYPE_CHECKING, Any, Protocol
11
+
12
+ if TYPE_CHECKING:
13
+ from synapse_sdk.plugins.models.logger import LogLevel
14
+
15
+ # Module-level logger for SDK output
16
+ _logger = logging.getLogger('synapse_sdk.loggers')
17
+
18
+ # LogLevel string value → Python logging level mapping
19
+ _LOG_LEVEL_MAP: dict[str, int] = {
20
+ 'debug': logging.DEBUG,
21
+ 'info': logging.INFO,
22
+ 'warning': logging.WARNING,
23
+ 'error': logging.ERROR,
24
+ 'critical': logging.CRITICAL,
25
+ }
26
+
27
+
28
+ class LoggerBackend(Protocol):
29
+ """Protocol for logger backends that handle data synchronization."""
30
+
31
+ def publish_progress(self, job_id: str, progress: 'ProgressData') -> None: ...
32
+ def publish_metrics(self, job_id: str, metrics: dict[str, Any]) -> None: ...
33
+ def publish_log(self, job_id: str, log_entry: 'LogEntry') -> None: ...
34
+
35
+
36
+ @dataclass
37
+ class ProgressData:
38
+ """Immutable progress data snapshot."""
39
+
40
+ percent: float
41
+ time_remaining: float | None = None
42
+ elapsed_time: float | None = None
43
+ status: str = 'running'
44
+
45
+
46
+ @dataclass
47
+ class LogEntry:
48
+ """Single log entry."""
49
+
50
+ event: str
51
+ data: dict[str, Any]
52
+ timestamp: float = field(default_factory=time.time)
53
+ file: str | None = None
54
+ step: str | None = None # 신규: 로그가 발생한 step
55
+ level: Any = None # 신규: 로그 레벨 (LogLevel enum, 순환 import 방지를 위해 Any)
56
+
57
+ def to_dict(self) -> dict[str, Any]:
58
+ """Convert to dictionary for API serialization."""
59
+ return {
60
+ 'event': self.event,
61
+ 'data': self.data,
62
+ 'timestamp': self.timestamp,
63
+ 'file': self.file,
64
+ 'step': self.step,
65
+ 'level': self.level.value if self.level else None,
66
+ }
67
+
68
+
69
+ class BaseLogger(ABC):
70
+ """Base class for logging progress, metrics, and events.
71
+
72
+ All state is instance-level to prevent cross-instance contamination.
73
+ Uses composition over inheritance for backend communication.
74
+ """
75
+
76
+ _start_time: float
77
+ _progress: dict[str, ProgressData]
78
+ _metrics: dict[str, dict[str, Any]]
79
+ _category_start_times: dict[str, float]
80
+ _current_step: str | None
81
+ _is_finished: bool
82
+
83
+ def __init__(self) -> None:
84
+ self._start_time = time.monotonic()
85
+ self._progress = {}
86
+ self._metrics = {}
87
+ self._category_start_times = {}
88
+ self._current_step = None
89
+ self._is_finished = False
90
+
91
+ def set_step(self, step: str | None) -> None:
92
+ """Set the current step for logging context.
93
+
94
+ Args:
95
+ step: The step name, or None to clear.
96
+ """
97
+ self._current_step = step
98
+
99
+ def get_step(self) -> str | None:
100
+ """Get the current step.
101
+
102
+ Returns:
103
+ The current step name, or None if not set.
104
+ """
105
+ return self._current_step
106
+
107
+ def _raise_if_finished(self) -> None:
108
+ if self._is_finished:
109
+ raise RuntimeError('Cannot log to a finished logger')
110
+
111
+ def log(
112
+ self,
113
+ level: LogLevel,
114
+ event: str,
115
+ data: dict[str, Any],
116
+ file: str | None = None,
117
+ step: str | None = None,
118
+ ) -> None:
119
+ """Log an event with data.
120
+
121
+ Args:
122
+ level: Log level (LogLevel enum).
123
+ event: Event name/type.
124
+ data: Dictionary of event data.
125
+ file: Optional file path associated with the event.
126
+ step: Optional step name. Uses current step if not specified.
127
+
128
+ Raises:
129
+ TypeError: If level is not a LogLevel enum or data is not a dictionary.
130
+ RuntimeError: If logger is already finished.
131
+ """
132
+ self._raise_if_finished()
133
+
134
+ # Validate level is a LogLevel enum (duck typing check to avoid circular import)
135
+ if not (isinstance(level, Enum) and hasattr(level, 'value') and level.value in _LOG_LEVEL_MAP):
136
+ raise TypeError(f'level must be a LogLevel enum, got {type(level).__name__}')
137
+
138
+ if not isinstance(data, Mapping):
139
+ raise TypeError(f'data must be a dict, got {type(data).__name__}')
140
+
141
+ data = dict(data) # Copy to avoid mutating input
142
+ # Use explicit step or fall back to current step
143
+ effective_step = step if step is not None else self._current_step
144
+ self._log_impl(event, data, file, effective_step, level)
145
+
146
+ def info(self, message: str) -> None:
147
+ """Log an info message."""
148
+ from synapse_sdk.plugins.models.logger import LogLevel
149
+
150
+ self.log(LogLevel.INFO, 'info', {'message': message})
151
+
152
+ def debug(self, message: str) -> None:
153
+ """Log a debug message."""
154
+ from synapse_sdk.plugins.models.logger import LogLevel
155
+
156
+ self.log(LogLevel.DEBUG, 'debug', {'message': message})
157
+
158
+ def warning(self, message: str) -> None:
159
+ """Log a warning message."""
160
+ from synapse_sdk.plugins.models.logger import LogLevel
161
+
162
+ self.log(LogLevel.WARNING, 'warning', {'message': message})
163
+
164
+ def error(self, message: str) -> None:
165
+ """Log an error message."""
166
+ from synapse_sdk.plugins.models.logger import LogLevel
167
+
168
+ self.log(LogLevel.ERROR, 'error', {'message': message})
169
+
170
+ def critical(self, message: str) -> None:
171
+ """Log a critical message."""
172
+ from synapse_sdk.plugins.models.logger import LogLevel
173
+
174
+ self.log(LogLevel.CRITICAL, 'critical', {'message': message})
175
+
176
+ def set_progress(
177
+ self,
178
+ current: int,
179
+ total: int,
180
+ step: str | None = None,
181
+ category: str | None = None,
182
+ ) -> None:
183
+ """Set progress for the current operation.
184
+
185
+ Args:
186
+ current: Current progress value (0 to total).
187
+ total: Total progress value.
188
+ step: Optional step name. Uses current step if not specified.
189
+ category: Deprecated. Use step instead.
3
190
 
4
- from synapse_sdk.clients.exceptions import ClientError
191
+ Raises:
192
+ ValueError: If current/total values are invalid.
193
+ RuntimeError: If logger is already finished.
194
+ """
195
+ self._raise_if_finished()
5
196
 
197
+ if total <= 0:
198
+ raise ValueError(f'total must be > 0, got {total}')
199
+ if not 0 <= current <= total:
200
+ raise ValueError(f'current must be between 0 and {total}, got {current}')
6
201
 
7
- class BaseLogger:
8
- progress_record = {}
9
- progress_categories = None
10
- current_category = None
11
- time_begin_per_category = {}
202
+ # Priority: explicit step > current step > category (deprecated)
203
+ effective_key: str | None = None
204
+ if step is not None:
205
+ effective_key = step
206
+ if category is not None:
207
+ warnings.warn(
208
+ "The 'category' parameter is deprecated. Use 'step' instead. "
209
+ "'step' takes precedence when both are provided.",
210
+ DeprecationWarning,
211
+ stacklevel=2,
212
+ )
213
+ elif self._current_step is not None:
214
+ # Use current step if available
215
+ effective_key = self._current_step
216
+ if category is not None:
217
+ warnings.warn(
218
+ "The 'category' parameter is deprecated. Use 'step' instead. "
219
+ 'Current step takes precedence over category.',
220
+ DeprecationWarning,
221
+ stacklevel=2,
222
+ )
223
+ elif category is not None:
224
+ warnings.warn(
225
+ "The 'category' parameter is deprecated. Use 'step' instead.",
226
+ DeprecationWarning,
227
+ stacklevel=2,
228
+ )
229
+ effective_key = category
12
230
 
13
- def __init__(self, progress_categories=None):
14
- self.progress_categories = progress_categories
15
- if progress_categories:
16
- self.progress_record['categories'] = progress_categories
231
+ key = effective_key or '__default__'
232
+ now = time.monotonic()
17
233
 
18
- def set_progress(self, current, total, category=None):
19
- assert 0 <= current <= total and total > 0
20
- assert category is not None or 'categories' not in self.progress_record
234
+ # Initialize start time on first call for this category
235
+ if key not in self._category_start_times or current == 0:
236
+ self._category_start_times[key] = now
21
237
 
22
- percent = (current / total) * 100
23
- percent = round(percent, 2)
24
- # TODO current 0 으로 시작하지 않아도 작동되도록 수정
25
- if current == 0:
26
- self.time_begin_per_category[category] = time.time()
27
- time_remaining = None
28
- else:
29
- seconds_per_item = (time.time() - self.time_begin_per_category[category]) / current
30
- time_remaining = round(seconds_per_item * (total - current), 2)
238
+ elapsed = now - self._category_start_times[key]
239
+ percent = round((current / total) * 100, 2)
31
240
 
32
- current_progress = {'percent': percent, 'time_remaining': time_remaining}
241
+ # Calculate time remaining
242
+ time_remaining = None
243
+ if current > 0:
244
+ rate = elapsed / current
245
+ time_remaining = round(rate * (total - current), 2)
33
246
 
247
+ progress = ProgressData(
248
+ percent=percent,
249
+ time_remaining=time_remaining,
250
+ elapsed_time=round(elapsed, 2),
251
+ )
252
+
253
+ self._progress[key] = progress
254
+ self._on_progress(progress, effective_key)
255
+
256
+ def set_progress_failed(self, category: str | None = None) -> None:
257
+ """Mark progress as failed.
258
+
259
+ Args:
260
+ category: Optional category name.
261
+
262
+ Raises:
263
+ RuntimeError: If logger is already finished.
264
+ """
265
+ self._raise_if_finished()
266
+
267
+ key = category or '__default__'
268
+ elapsed = None
269
+
270
+ if key in self._category_start_times:
271
+ elapsed = round(time.monotonic() - self._category_start_times[key], 2)
272
+
273
+ progress = ProgressData(
274
+ percent=0.0,
275
+ time_remaining=None,
276
+ elapsed_time=elapsed,
277
+ status='failed',
278
+ )
279
+
280
+ self._progress[key] = progress
281
+ self._on_progress(progress, category)
282
+
283
+ def set_metrics(
284
+ self,
285
+ value: dict[str, Any],
286
+ step: str | None = None,
287
+ category: str | None = None,
288
+ ) -> None:
289
+ """Set metrics for a step.
290
+
291
+ Args:
292
+ value: Dictionary of metric values.
293
+ step: Optional step name. Uses current step if not specified.
294
+ category: Deprecated. Use step instead.
295
+
296
+ Raises:
297
+ ValueError: If no step/category is available.
298
+ TypeError: If value is not a dictionary.
299
+ RuntimeError: If logger is already finished.
300
+ """
301
+ self._raise_if_finished()
302
+
303
+ if not isinstance(value, Mapping):
304
+ raise TypeError(f'value must be a dict, got {type(value).__name__}')
305
+
306
+ # Priority: explicit step > current step > category (deprecated)
307
+ effective_key: str | None = None
308
+ if step is not None:
309
+ effective_key = step
310
+ if category is not None:
311
+ warnings.warn(
312
+ "The 'category' parameter is deprecated. Use 'step' instead. "
313
+ "'step' takes precedence when both are provided.",
314
+ DeprecationWarning,
315
+ stacklevel=2,
316
+ )
317
+ elif self._current_step is not None:
318
+ # Use current step if available
319
+ effective_key = self._current_step
320
+ if category is not None:
321
+ warnings.warn(
322
+ "The 'category' parameter is deprecated. Use 'step' instead. "
323
+ 'Current step takes precedence over category.',
324
+ DeprecationWarning,
325
+ stacklevel=2,
326
+ )
327
+ elif category is not None:
328
+ warnings.warn(
329
+ "The 'category' parameter is deprecated. Use 'step' instead.",
330
+ DeprecationWarning,
331
+ stacklevel=2,
332
+ )
333
+ effective_key = category
334
+
335
+ if not effective_key:
336
+ raise ValueError('step must be specified or set via set_step()')
337
+
338
+ data = dict(value) # Copy
339
+
340
+ if effective_key not in self._metrics:
341
+ self._metrics[effective_key] = {}
342
+ self._metrics[effective_key].update(data)
343
+
344
+ self._on_metrics(effective_key, self._metrics[effective_key])
345
+
346
+ def get_progress(self, category: str | None = None) -> ProgressData | None:
347
+ """Get progress for a category."""
348
+ key = category or '__default__'
349
+ return self._progress.get(key)
350
+
351
+ def get_metrics(self, category: str | None = None) -> dict[str, Any]:
352
+ """Get metrics, optionally filtered by category."""
34
353
  if category:
35
- self.current_category = category
36
- self.progress_record['categories'][category].update(current_progress)
37
- else:
38
- self.progress_record.update(current_progress)
39
-
40
- def get_current_progress(self):
41
- categories = self.progress_record.get('categories')
42
-
43
- if categories:
44
- category_progress = None
45
-
46
- overall = 0
47
- for category, category_record in categories.items():
48
- if category == self.current_category:
49
- break
50
- overall += category_record['proportion']
51
-
52
- category_record = categories[self.current_category]
53
- category_percent = category_record.get('percent', 0)
54
- if not category_progress and 'percent' in category_record:
55
- category_progress = {
56
- 'category': self.current_category,
57
- 'percent': category_percent,
58
- 'time_remaining': category_record.get('time_remaining'),
59
- }
60
- if category_percent > 0:
61
- overall += round(category_record['proportion'] / 100 * category_percent, 2)
62
- progress = {'overall': overall, **category_progress}
63
- else:
64
- progress = {
65
- 'overall': self.progress_record.get('percent'),
66
- 'time_remaining': self.progress_record.get('time_remaining'),
67
- }
68
-
69
- return progress
354
+ return dict(self._metrics.get(category, {}))
355
+ return {k: dict(v) for k, v in self._metrics.items()}
356
+
357
+ def finish(self) -> None:
358
+ """Mark the logger as finished. No further logging is allowed."""
359
+ self._is_finished = True
360
+ self._on_finish()
361
+
362
+ @abstractmethod
363
+ def _log_impl(
364
+ self,
365
+ event: str,
366
+ data: dict[str, Any],
367
+ file: str | None,
368
+ step: str | None,
369
+ level: LogLevel | None = None,
370
+ ) -> None:
371
+ """Implementation-specific log handling."""
372
+ ...
373
+
374
+ def _on_progress(self, progress: ProgressData, category: str | None) -> None:
375
+ """Hook called when progress is updated. Override in subclasses."""
376
+ pass
377
+
378
+ def _on_metrics(self, category: str, metrics: dict[str, Any]) -> None:
379
+ """Hook called when metrics are updated. Override in subclasses."""
380
+ pass
381
+
382
+ def _on_finish(self) -> None:
383
+ """Hook called when logger is finished. Override in subclasses."""
384
+ pass
70
385
 
71
386
 
72
387
  class ConsoleLogger(BaseLogger):
73
- def set_progress(self, current, total, category=None):
74
- super().set_progress(current, total, category=category)
75
- print(self.get_current_progress())
388
+ """Logger that prints to console using Python logging module."""
389
+
390
+ def _log_impl(
391
+ self,
392
+ event: str,
393
+ data: dict[str, Any],
394
+ file: str | None,
395
+ step: str | None,
396
+ level: LogLevel | None = None,
397
+ ) -> None:
398
+ prefix = f'[{step}] ' if step else ''
399
+ level_str = level.value.upper() if level else 'INFO'
400
+ message = f'{level_str}: {prefix}{event} {data}'
76
401
 
77
- def log(self, action, data):
78
- print(action, data)
402
+ # Use print with flush=True for immediate output
403
+ # This ensures logs are visible in Ray remote workers
404
+ print(message, flush=True)
405
+
406
+ def _on_progress(self, progress: ProgressData, category: str | None) -> None:
407
+ prefix = f'[{category}] ' if category else ''
408
+ print(f'INFO: {prefix}Progress: {progress.percent}% | ETA: {progress.time_remaining}s', flush=True)
409
+
410
+ def _on_metrics(self, category: str, metrics: dict[str, Any]) -> None:
411
+ print(f'INFO: [{category}] Metrics: {metrics}', flush=True)
79
412
 
80
413
 
81
414
  class BackendLogger(BaseLogger):
82
- logs_queue = []
83
- client = None
84
- job_id = None
415
+ """Logger that syncs with a remote backend.
416
+
417
+ Uses a backend interface for decoupled communication.
418
+ """
85
419
 
86
- def __init__(self, client, job_id, **kwargs):
87
- super().__init__(**kwargs)
88
- self.client = client
89
- self.job_id = job_id
420
+ _backend: LoggerBackend | None
421
+ _job_id: str
422
+ _log_queue: list[LogEntry]
423
+
424
+ def __init__(self, backend: LoggerBackend | None, job_id: str) -> None:
425
+ super().__init__()
426
+ self._backend = backend
427
+ self._job_id = job_id
428
+ self._log_queue = []
429
+
430
+ def _log_impl(
431
+ self,
432
+ event: str,
433
+ data: dict[str, Any],
434
+ file: str | None,
435
+ step: str | None,
436
+ level: LogLevel | None = None,
437
+ ) -> None:
438
+ entry = LogEntry(event=event, data=data, file=file, step=step, level=level)
439
+ self._log_queue.append(entry)
440
+ self._flush_logs()
441
+
442
+ def _on_progress(self, progress: ProgressData, category: str | None) -> None:
443
+ if self._backend is None:
444
+ return
90
445
 
91
- def set_progress(self, current, total, category=None):
92
- super().set_progress(current, total, category=category)
93
446
  try:
94
- progress_record = {
95
- 'record': self.progress_record,
96
- 'current_progress': self.get_current_progress(),
97
- }
98
- self.client.update_job(self.job_id, data={'progress_record': progress_record})
99
- except ClientError:
100
- pass
101
-
102
- def log(self, event, data):
103
- print(event, data)
104
-
105
- log = {
106
- 'event': event,
107
- 'data': data,
108
- 'datetime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'),
109
- 'job': self.job_id,
110
- }
111
- self.logs_queue.append(log)
447
+ self._backend.publish_progress(self._job_id, progress)
448
+ except Exception as e:
449
+ _logger.error(f'Failed to publish progress: {e}')
450
+
451
+ def _on_metrics(self, category: str, metrics: dict[str, Any]) -> None:
452
+ if self._backend is None:
453
+ return
454
+
112
455
  try:
113
- self.client.create_logs(self.logs_queue)
114
- self.logs_queue.clear()
115
- except ClientError as e:
116
- print(e)
456
+ self._backend.publish_metrics(self._job_id, {category: metrics})
457
+ except Exception as e:
458
+ _logger.error(f'Failed to publish metrics: {e}')
459
+
460
+ def _flush_logs(self) -> None:
461
+ if self._backend is None or not self._log_queue:
462
+ return
463
+
464
+ try:
465
+ for entry in self._log_queue:
466
+ self._backend.publish_log(self._job_id, entry)
467
+ self._log_queue.clear()
468
+ except Exception as e:
469
+ _logger.error(f'Failed to flush logs: {e}')
470
+
471
+ def _on_finish(self) -> None:
472
+ self._flush_logs()
473
+
474
+
475
+ class NoOpLogger(BaseLogger):
476
+ """Logger that does nothing. Useful for testing or disabled logging."""
477
+
478
+ def _log_impl(
479
+ self,
480
+ event: str,
481
+ data: dict[str, Any],
482
+ file: str | None,
483
+ step: str | None,
484
+ level: LogLevel | None = None,
485
+ ) -> None:
486
+ pass
487
+
488
+
489
+ __all__ = [
490
+ 'BaseLogger',
491
+ 'BackendLogger',
492
+ 'ConsoleLogger',
493
+ 'LogEntry',
494
+ 'LoggerBackend',
495
+ 'NoOpLogger',
496
+ 'ProgressData',
497
+ ]
synapse_sdk/mcp/MCP.md ADDED
@@ -0,0 +1,69 @@
1
+ # Synapse MCP Server
2
+
3
+ MCP server for AI assistants to interact with Synapse infrastructure.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Initialize config
9
+ uvx --from 'synapse-sdk[mcp]' synapse mcp init
10
+ ```
11
+
12
+ ### Setup via AI assistant
13
+
14
+ ```
15
+ 1. add_environment(name="prod", backend_url="...", access_token="...")
16
+ 2. list_agents()
17
+ 3. select_agent(123) # Required for job operations
18
+ ```
19
+
20
+ ## Configuration
21
+
22
+ `~/.synapse/config.yaml`:
23
+
24
+ ## Cursor Setup
25
+
26
+ `~/.cursor/mcp.json`:
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "synapse": {
31
+ "command": "uvx",
32
+ "args": ["--from", "synapse-sdk[mcp]", "synapse", "mcp", "serve"]
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ ## Claude Code Setup
39
+
40
+ ```bash
41
+ claude mcp add synapse -- uvx --from 'synapse-sdk[mcp]' synapse mcp serve
42
+
43
+ # Local development:
44
+ claude mcp add synapse -- uv run --directory <path-to-sdk> synapse mcp serve
45
+ ```
46
+
47
+ ## Tools
48
+
49
+ | Category | Tools |
50
+ |----------|-------|
51
+ | Environment | `switch_environment`, `list_environments`, `get_current_environment`, `add_environment` |
52
+ | Agents | `list_agents`, `select_agent`, `clear_agent` |
53
+ | Plugins | `list_plugin_releases`, `get_plugin_release`, `discover_local_plugin`, `get_action_config`, `validate_plugin_config`, `publish_plugin` |
54
+ | Execution | `run_plugin`, `run_debug_plugin`, `run_local_plugin` |
55
+ | Jobs | `list_jobs`, `get_job`, `get_job_logs`, `stop_job` |
56
+ | Deployments | `list_serve_applications`, `get_serve_application`, `delete_serve_application` |
57
+ | Models | `list_models`, `get_model` |
58
+
59
+ ## Resources
60
+
61
+ - `synapse://config` - Current configuration
62
+ - `synapse://environments` - List of environments
63
+ - `synapse://plugin/{path}/config` - Plugin config.yaml
64
+ - `synapse://plugin/{path}/actions` - Plugin actions
65
+ - `synapse://plugin/{path}/action/{name}/schema` - Action schema
66
+
67
+ ## Prompts
68
+
69
+ `debug_plugin`, `publish_plugin_workflow`, `diagnose_job`, `setup_environment`