synapse-sdk 1.0.0a23__py3-none-any.whl → 2025.12.3__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.
Files changed (228) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/__init__.py +310 -5
  3. synapse_sdk/cli/alias/__init__.py +22 -0
  4. synapse_sdk/cli/alias/create.py +36 -0
  5. synapse_sdk/cli/alias/dataclass.py +31 -0
  6. synapse_sdk/cli/alias/default.py +16 -0
  7. synapse_sdk/cli/alias/delete.py +15 -0
  8. synapse_sdk/cli/alias/list.py +19 -0
  9. synapse_sdk/cli/alias/read.py +15 -0
  10. synapse_sdk/cli/alias/update.py +17 -0
  11. synapse_sdk/cli/alias/utils.py +61 -0
  12. synapse_sdk/cli/code_server.py +687 -0
  13. synapse_sdk/cli/config.py +440 -0
  14. synapse_sdk/cli/devtools.py +90 -0
  15. synapse_sdk/cli/plugin/__init__.py +33 -0
  16. synapse_sdk/cli/{create_plugin.py → plugin/create.py} +2 -2
  17. synapse_sdk/{plugins/cli → cli/plugin}/publish.py +23 -15
  18. synapse_sdk/clients/agent/__init__.py +9 -3
  19. synapse_sdk/clients/agent/container.py +143 -0
  20. synapse_sdk/clients/agent/core.py +19 -0
  21. synapse_sdk/clients/agent/ray.py +298 -9
  22. synapse_sdk/clients/backend/__init__.py +30 -12
  23. synapse_sdk/clients/backend/annotation.py +13 -5
  24. synapse_sdk/clients/backend/core.py +31 -4
  25. synapse_sdk/clients/backend/data_collection.py +186 -0
  26. synapse_sdk/clients/backend/hitl.py +17 -0
  27. synapse_sdk/clients/backend/integration.py +16 -1
  28. synapse_sdk/clients/backend/ml.py +5 -1
  29. synapse_sdk/clients/backend/models.py +78 -0
  30. synapse_sdk/clients/base.py +384 -41
  31. synapse_sdk/clients/ray/serve.py +2 -0
  32. synapse_sdk/clients/validators/collections.py +31 -0
  33. synapse_sdk/devtools/config.py +94 -0
  34. synapse_sdk/devtools/server.py +41 -0
  35. synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
  36. synapse_sdk/devtools/streamlit_app/app.py +128 -0
  37. synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
  38. synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
  39. synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
  40. synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
  41. synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
  42. synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
  43. synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
  44. synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
  45. synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
  46. synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
  47. synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
  48. synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
  49. synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
  50. synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
  51. synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
  52. synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
  53. synapse_sdk/devtools/streamlit_app.py +10 -0
  54. synapse_sdk/loggers.py +120 -9
  55. synapse_sdk/plugins/README.md +1340 -0
  56. synapse_sdk/plugins/__init__.py +0 -13
  57. synapse_sdk/plugins/categories/base.py +117 -11
  58. synapse_sdk/plugins/categories/data_validation/actions/validation.py +72 -0
  59. synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +33 -5
  60. synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
  61. synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
  62. synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
  63. synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
  64. synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
  65. synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
  66. synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
  67. synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
  68. synapse_sdk/plugins/categories/export/templates/config.yaml +21 -0
  69. synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
  70. synapse_sdk/plugins/categories/export/templates/plugin/export.py +160 -0
  71. synapse_sdk/plugins/categories/neural_net/actions/deployment.py +13 -12
  72. synapse_sdk/plugins/categories/neural_net/actions/train.py +1134 -31
  73. synapse_sdk/plugins/categories/neural_net/actions/tune.py +534 -0
  74. synapse_sdk/plugins/categories/neural_net/base/inference.py +1 -1
  75. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +32 -4
  76. synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +26 -10
  77. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
  78. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
  79. synapse_sdk/plugins/categories/{export/actions/export.py → pre_annotation/actions/pre_annotation/action.py} +4 -4
  80. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
  81. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +148 -0
  82. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
  83. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
  84. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
  85. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +100 -0
  86. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +248 -0
  87. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
  88. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
  89. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +265 -0
  90. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
  91. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
  92. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +92 -0
  93. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +243 -0
  94. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
  95. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +19 -0
  96. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/to_task.py +40 -0
  97. synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +2 -0
  98. synapse_sdk/plugins/categories/upload/__init__.py +0 -0
  99. synapse_sdk/plugins/categories/upload/actions/__init__.py +0 -0
  100. synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
  101. synapse_sdk/plugins/categories/upload/actions/upload/action.py +236 -0
  102. synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
  103. synapse_sdk/plugins/categories/upload/actions/upload/enums.py +493 -0
  104. synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
  105. synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
  106. synapse_sdk/plugins/categories/upload/actions/upload/models.py +214 -0
  107. synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
  108. synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
  109. synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
  110. synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
  111. synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
  112. synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
  113. synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
  114. synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +91 -0
  115. synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
  116. synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
  117. synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +201 -0
  118. synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +104 -0
  119. synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
  120. synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
  121. synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
  122. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
  123. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
  124. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
  125. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
  126. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +300 -0
  127. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +287 -0
  128. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
  129. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
  130. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
  131. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
  132. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
  133. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
  134. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
  135. synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
  136. synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
  137. synapse_sdk/plugins/categories/upload/templates/config.yaml +33 -0
  138. synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +310 -0
  139. synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +102 -0
  140. synapse_sdk/plugins/enums.py +3 -1
  141. synapse_sdk/plugins/models.py +148 -11
  142. synapse_sdk/plugins/templates/plugin-config-schema.json +406 -0
  143. synapse_sdk/plugins/templates/schema.json +491 -0
  144. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +1 -0
  145. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +1 -1
  146. synapse_sdk/plugins/utils/__init__.py +46 -0
  147. synapse_sdk/plugins/utils/actions.py +119 -0
  148. synapse_sdk/plugins/utils/config.py +203 -0
  149. synapse_sdk/plugins/{utils.py → utils/legacy.py} +26 -46
  150. synapse_sdk/plugins/utils/ray_gcs.py +66 -0
  151. synapse_sdk/plugins/utils/registry.py +58 -0
  152. synapse_sdk/shared/__init__.py +25 -0
  153. synapse_sdk/shared/enums.py +93 -0
  154. synapse_sdk/types.py +19 -0
  155. synapse_sdk/utils/converters/__init__.py +240 -0
  156. synapse_sdk/utils/converters/coco/__init__.py +0 -0
  157. synapse_sdk/utils/converters/coco/from_dm.py +322 -0
  158. synapse_sdk/utils/converters/coco/to_dm.py +215 -0
  159. synapse_sdk/utils/converters/dm/__init__.py +57 -0
  160. synapse_sdk/utils/converters/dm/base.py +137 -0
  161. synapse_sdk/utils/converters/dm/from_v1.py +273 -0
  162. synapse_sdk/utils/converters/dm/to_v1.py +321 -0
  163. synapse_sdk/utils/converters/dm/tools/__init__.py +214 -0
  164. synapse_sdk/utils/converters/dm/tools/answer.py +95 -0
  165. synapse_sdk/utils/converters/dm/tools/bounding_box.py +132 -0
  166. synapse_sdk/utils/converters/dm/tools/bounding_box_3d.py +121 -0
  167. synapse_sdk/utils/converters/dm/tools/classification.py +75 -0
  168. synapse_sdk/utils/converters/dm/tools/keypoint.py +117 -0
  169. synapse_sdk/utils/converters/dm/tools/named_entity.py +111 -0
  170. synapse_sdk/utils/converters/dm/tools/polygon.py +122 -0
  171. synapse_sdk/utils/converters/dm/tools/polyline.py +124 -0
  172. synapse_sdk/utils/converters/dm/tools/prompt.py +94 -0
  173. synapse_sdk/utils/converters/dm/tools/relation.py +86 -0
  174. synapse_sdk/utils/converters/dm/tools/segmentation.py +141 -0
  175. synapse_sdk/utils/converters/dm/tools/segmentation_3d.py +83 -0
  176. synapse_sdk/utils/converters/dm/types.py +168 -0
  177. synapse_sdk/utils/converters/dm/utils.py +162 -0
  178. synapse_sdk/utils/converters/dm_legacy/__init__.py +56 -0
  179. synapse_sdk/utils/converters/dm_legacy/from_v1.py +627 -0
  180. synapse_sdk/utils/converters/dm_legacy/to_v1.py +367 -0
  181. synapse_sdk/utils/converters/pascal/__init__.py +0 -0
  182. synapse_sdk/utils/converters/pascal/from_dm.py +244 -0
  183. synapse_sdk/utils/converters/pascal/to_dm.py +214 -0
  184. synapse_sdk/utils/converters/yolo/__init__.py +0 -0
  185. synapse_sdk/utils/converters/yolo/from_dm.py +384 -0
  186. synapse_sdk/utils/converters/yolo/to_dm.py +267 -0
  187. synapse_sdk/utils/dataset.py +46 -0
  188. synapse_sdk/utils/encryption.py +158 -0
  189. synapse_sdk/utils/file/__init__.py +58 -0
  190. synapse_sdk/utils/file/archive.py +32 -0
  191. synapse_sdk/utils/file/checksum.py +56 -0
  192. synapse_sdk/utils/file/chunking.py +31 -0
  193. synapse_sdk/utils/file/download.py +385 -0
  194. synapse_sdk/utils/file/encoding.py +40 -0
  195. synapse_sdk/utils/file/io.py +22 -0
  196. synapse_sdk/utils/file/upload.py +165 -0
  197. synapse_sdk/utils/file/video/__init__.py +29 -0
  198. synapse_sdk/utils/file/video/transcode.py +307 -0
  199. synapse_sdk/utils/file.py.backup +301 -0
  200. synapse_sdk/utils/http.py +138 -0
  201. synapse_sdk/utils/network.py +309 -0
  202. synapse_sdk/utils/storage/__init__.py +72 -0
  203. synapse_sdk/utils/storage/providers/__init__.py +183 -0
  204. synapse_sdk/utils/storage/providers/file_system.py +134 -0
  205. synapse_sdk/utils/storage/providers/gcp.py +13 -0
  206. synapse_sdk/utils/storage/providers/http.py +190 -0
  207. synapse_sdk/utils/storage/providers/s3.py +91 -0
  208. synapse_sdk/utils/storage/providers/sftp.py +47 -0
  209. synapse_sdk/utils/storage/registry.py +17 -0
  210. synapse_sdk-2025.12.3.dist-info/METADATA +123 -0
  211. synapse_sdk-2025.12.3.dist-info/RECORD +279 -0
  212. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/WHEEL +1 -1
  213. synapse_sdk/clients/backend/dataset.py +0 -51
  214. synapse_sdk/plugins/categories/import/actions/import.py +0 -10
  215. synapse_sdk/plugins/cli/__init__.py +0 -21
  216. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
  217. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
  218. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
  219. synapse_sdk/utils/file.py +0 -168
  220. synapse_sdk/utils/storage.py +0 -91
  221. synapse_sdk-1.0.0a23.dist-info/METADATA +0 -44
  222. synapse_sdk-1.0.0a23.dist-info/RECORD +0 -114
  223. /synapse_sdk/{plugins/cli → cli/plugin}/run.py +0 -0
  224. /synapse_sdk/{plugins/categories/import → clients/validators}/__init__.py +0 -0
  225. /synapse_sdk/{plugins/categories/import/actions → devtools}/__init__.py +0 -0
  226. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/entry_points.txt +0 -0
  227. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info/licenses}/LICENSE +0 -0
  228. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,289 @@
1
+ """Unified UI components for consistent styling across the application."""
2
+
3
+ from typing import Any, Callable, Optional
4
+
5
+ import streamlit as st
6
+
7
+
8
+ def render_action_button(
9
+ label: str,
10
+ key: str,
11
+ type: str = 'secondary',
12
+ icon: Optional[str] = None,
13
+ on_click: Optional[Callable] = None,
14
+ args: Optional[tuple] = None,
15
+ disabled: bool = False,
16
+ use_container_width: bool = False,
17
+ help: Optional[str] = None,
18
+ ) -> bool:
19
+ """Render a styled action button with consistent appearance."""
20
+ button_types = {
21
+ 'primary': 'primary',
22
+ 'secondary': 'secondary',
23
+ 'danger': 'secondary', # Streamlit doesn't have danger, use CSS
24
+ 'success': 'secondary',
25
+ 'minimal': 'secondary',
26
+ }
27
+
28
+ # Add icon to label if provided
29
+ if icon:
30
+ display_label = f'{icon} {label}'
31
+ else:
32
+ display_label = label
33
+
34
+ # Create button with appropriate styling
35
+ clicked = st.button(
36
+ display_label,
37
+ key=key,
38
+ type=button_types.get(type, 'secondary'),
39
+ on_click=on_click,
40
+ args=args,
41
+ disabled=disabled,
42
+ use_container_width=use_container_width,
43
+ help=help,
44
+ )
45
+
46
+ # Apply custom CSS for special button types
47
+ if type == 'danger':
48
+ st.markdown(
49
+ f"""
50
+ <style>
51
+ button[kind="secondary"][key="{key}"] {{
52
+ background-color: #fff;
53
+ color: #dc3545;
54
+ border-color: #dc3545;
55
+ }}
56
+ button[kind="secondary"][key="{key}"]:hover {{
57
+ background-color: #dc3545;
58
+ color: white;
59
+ }}
60
+ </style>
61
+ """,
62
+ unsafe_allow_html=True,
63
+ )
64
+ elif type == 'success':
65
+ st.markdown(
66
+ f"""
67
+ <style>
68
+ button[kind="secondary"][key="{key}"] {{
69
+ background-color: #fff;
70
+ color: #28a745;
71
+ border-color: #28a745;
72
+ }}
73
+ button[kind="secondary"][key="{key}"]:hover {{
74
+ background-color: #28a745;
75
+ color: white;
76
+ }}
77
+ </style>
78
+ """,
79
+ unsafe_allow_html=True,
80
+ )
81
+ elif type == 'minimal':
82
+ st.markdown(
83
+ f"""
84
+ <style>
85
+ button[kind="secondary"][key="{key}"] {{
86
+ background-color: transparent;
87
+ border: none;
88
+ color: #007bff;
89
+ padding: 4px 8px;
90
+ }}
91
+ button[kind="secondary"][key="{key}"]:hover {{
92
+ background-color: #f8f9fa;
93
+ text-decoration: underline;
94
+ }}
95
+ </style>
96
+ """,
97
+ unsafe_allow_html=True,
98
+ )
99
+
100
+ return clicked
101
+
102
+
103
+ def render_section_header(title: str, subtitle: Optional[str] = None):
104
+ """Render a consistent section header."""
105
+ subtitle_html = (
106
+ f'<p style="margin-top: 0.25rem; color: #6c757d; font-size: 14px;">{subtitle}</p>' if subtitle else ''
107
+ )
108
+ st.markdown(
109
+ f'<div style="margin-bottom: 1.5rem;">'
110
+ f'<h3 style="margin: 0; font-weight: 600; color: #1a1a1a;">{title}</h3>'
111
+ f'{subtitle_html}'
112
+ f'</div>',
113
+ unsafe_allow_html=True,
114
+ )
115
+
116
+
117
+ def render_info_card(title: str, content: str, type: str = 'info', icon: Optional[str] = None):
118
+ """Render an information card with consistent styling."""
119
+ colors = {
120
+ 'info': {'bg': '#d1ecf1', 'border': '#bee5eb', 'text': '#0c5460'},
121
+ 'success': {'bg': '#d4edda', 'border': '#c3e6cb', 'text': '#155724'},
122
+ 'warning': {'bg': '#fff3cd', 'border': '#ffeaa7', 'text': '#856404'},
123
+ 'error': {'bg': '#f8d7da', 'border': '#f5c6cb', 'text': '#721c24'},
124
+ 'neutral': {'bg': '#f8f9fa', 'border': '#dee2e6', 'text': '#495057'},
125
+ }
126
+
127
+ color_scheme = colors.get(type, colors['info'])
128
+ icon_html = f'<span style="margin-right: 8px;">{icon}</span>' if icon else ''
129
+
130
+ st.markdown(
131
+ f'<div style="'
132
+ f' background-color: {color_scheme["bg"]};'
133
+ f' border: 1px solid {color_scheme["border"]};'
134
+ f' border-radius: 6px;'
135
+ f' padding: 12px 16px;'
136
+ f' margin: 12px 0;'
137
+ f'">'
138
+ f'<div style="color: {color_scheme["text"]}; font-weight: 500; margin-bottom: 4px;">'
139
+ f'{icon_html}{title}'
140
+ f'</div>'
141
+ f'<div style="color: {color_scheme["text"]}; font-size: 14px;">{content}</div>'
142
+ f'</div>',
143
+ unsafe_allow_html=True,
144
+ )
145
+
146
+
147
+ def render_metric_card(label: str, value: Any, delta: Optional[str] = None, delta_color: str = 'normal'):
148
+ """Render a metric card with consistent styling."""
149
+ delta_html = ''
150
+ if delta:
151
+ color = {'normal': '#28a745', 'inverse': '#dc3545', 'off': '#6c757d'}.get(delta_color, '#28a745')
152
+ delta_html = f'<div style="color: {color}; font-size: 12px; margin-top: 4px;">{delta}</div>'
153
+
154
+ st.markdown(
155
+ f'<div style="'
156
+ f' background-color: #fff;'
157
+ f' border: 1px solid #e0e0e0;'
158
+ f' border-radius: 8px;'
159
+ f' padding: 16px;'
160
+ f' margin: 8px 0;'
161
+ f'">'
162
+ f'<div style="color: #6c757d; font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">' # noqa: E501
163
+ f'{label}'
164
+ f'</div>'
165
+ f'<div style="color: #212529; font-size: 24px; font-weight: 600; margin-top: 4px;">'
166
+ f'{value}'
167
+ f'</div>'
168
+ f'{delta_html}'
169
+ f'</div>',
170
+ unsafe_allow_html=True,
171
+ )
172
+
173
+
174
+ def render_key_value_pair(key: str, value: Any, monospace_value: bool = False, inline: bool = True):
175
+ """Render a key-value pair with consistent styling."""
176
+ value_style = 'font-family: monospace;' if monospace_value else ''
177
+
178
+ if inline:
179
+ st.markdown(
180
+ f'<div style="margin-bottom: 8px; font-size: 14px;">'
181
+ f'<span style="color: #495057; font-weight: 500;">{key}:</span> '
182
+ f'<span style="color: #212529; {value_style}">{value}</span>'
183
+ f'</div>',
184
+ unsafe_allow_html=True,
185
+ )
186
+ else:
187
+ st.markdown(
188
+ f'<div style="margin-bottom: 12px;">'
189
+ f'<div style="color: #495057; font-weight: 500; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;">' # noqa: E501
190
+ f'{key}'
191
+ f'</div>'
192
+ f'<div style="color: #212529; font-size: 14px; {value_style}">{value}</div>'
193
+ f'</div>',
194
+ unsafe_allow_html=True,
195
+ )
196
+
197
+
198
+ def render_divider(margin: str = '1.5rem'):
199
+ """Render a consistent divider."""
200
+ st.markdown(
201
+ f'<hr style="margin: {margin} 0; border: none; border-top: 1px solid #e0e0e0;">', unsafe_allow_html=True
202
+ )
203
+
204
+
205
+ def render_table_header(columns: list, column_widths: list = None):
206
+ """Render a consistent table header."""
207
+ if not column_widths:
208
+ column_widths = [1] * len(columns)
209
+
210
+ header_cols = st.columns(column_widths)
211
+ for i, header in enumerate(columns):
212
+ with header_cols[i]:
213
+ st.markdown(
214
+ f'<div style="'
215
+ f' color: #495057;'
216
+ f' font-size: 12px;'
217
+ f' font-weight: 600;'
218
+ f' text-transform: uppercase;'
219
+ f' letter-spacing: 0.5px;'
220
+ f' padding: 8px 0;'
221
+ f' border-bottom: 2px solid #dee2e6;'
222
+ f'">{header}</div>',
223
+ unsafe_allow_html=True,
224
+ )
225
+ return header_cols
226
+
227
+
228
+ def render_empty_state(
229
+ message: str,
230
+ icon: Optional[str] = None,
231
+ action_label: Optional[str] = None,
232
+ action_callback: Optional[Callable] = None,
233
+ ):
234
+ """Render an empty state message with optional action."""
235
+ icon_html = f'<div style="font-size: 48px; margin-bottom: 16px;">{icon}</div>' if icon else ''
236
+
237
+ st.markdown(
238
+ f'<div style="'
239
+ f' text-align: center;'
240
+ f' padding: 48px 24px;'
241
+ f' background-color: #f8f9fa;'
242
+ f' border-radius: 8px;'
243
+ f' margin: 24px 0;'
244
+ f'">'
245
+ f'{icon_html}'
246
+ f'<div style="color: #6c757d; font-size: 16px;">{message}</div>'
247
+ f'</div>',
248
+ unsafe_allow_html=True,
249
+ )
250
+
251
+ if action_label and action_callback:
252
+ col1, col2, col3 = st.columns([1, 1, 1])
253
+ with col2:
254
+ if st.button(action_label, key='empty_state_action', type='primary'):
255
+ action_callback()
256
+
257
+
258
+ def render_progress_bar(progress: float, label: Optional[str] = None, show_percentage: bool = True):
259
+ """Render a custom progress bar with consistent styling."""
260
+ percentage = int(progress * 100)
261
+
262
+ label_html = f'<div style="font-size: 12px; color: #6c757d; margin-bottom: 4px;">{label}</div>' if label else ''
263
+ percentage_html = (
264
+ f'<span style="font-size: 12px; color: #495057; margin-left: 8px;">{percentage}%</span>'
265
+ if show_percentage
266
+ else ''
267
+ )
268
+
269
+ st.markdown(
270
+ f'{label_html}'
271
+ f'<div style="display: flex; align-items: center;">'
272
+ f'<div style="'
273
+ f' flex: 1;'
274
+ f' height: 8px;'
275
+ f' background-color: #e9ecef;'
276
+ f' border-radius: 4px;'
277
+ f' overflow: hidden;'
278
+ f'">'
279
+ f'<div style="'
280
+ f' width: {percentage}%;'
281
+ f' height: 100%;'
282
+ f' background: linear-gradient(90deg, #007bff 0%, #0056b3 100%);'
283
+ f' transition: width 0.3s ease;'
284
+ f'"></div>'
285
+ f'</div>'
286
+ f'{percentage_html}'
287
+ f'</div>',
288
+ unsafe_allow_html=True,
289
+ )
@@ -0,0 +1,10 @@
1
+ """Streamlit-based Synapse DevTools Application.
2
+
3
+ This module imports from the modularized structure.
4
+ The main application logic is now in streamlit_app/app.py
5
+ """
6
+
7
+ from synapse_sdk.devtools.streamlit_app import main
8
+
9
+ if __name__ == '__main__':
10
+ main()
synapse_sdk/loggers.py CHANGED
@@ -1,21 +1,48 @@
1
1
  import datetime
2
2
  import time
3
+ from typing import Any, Dict
3
4
 
4
5
  from synapse_sdk.clients.exceptions import ClientError
5
6
 
6
7
 
7
8
  class BaseLogger:
9
+ """Base class for logging progress and events.
10
+
11
+ Args:
12
+ progress_record(dict): Progress record to track the progress of a task.
13
+ progress_categories: dict | None: List of categories for progress tracking.
14
+ metrics_categories: dict | None: List of categories for metrics tracking.
15
+ current_progress_category: str | None: Current progress category.
16
+ time_begin_per_category(dict): Dictionary to track the start time for each category.
17
+ metrics_record(dict): Dictionary to record metrics.
18
+ """
19
+
8
20
  progress_record = {}
21
+ metrics_record = {}
9
22
  progress_categories = None
10
- current_category = None
23
+ metrics_categories = None
24
+ current_progress_category = None
11
25
  time_begin_per_category = {}
12
26
 
13
- def __init__(self, progress_categories=None):
27
+ def __init__(self, progress_categories=None, metrics_categories=None):
28
+ # Setup progress categories
14
29
  self.progress_categories = progress_categories
15
30
  if progress_categories:
16
31
  self.progress_record['categories'] = progress_categories
17
32
 
18
- def set_progress(self, current, total, category=None):
33
+ # Setup metrics categories
34
+ self.metrics_categories = metrics_categories
35
+ if metrics_categories:
36
+ self.metrics_record['categories'] = metrics_categories
37
+
38
+ def set_progress(self, current: int, total: int, category: str | None = None):
39
+ """Set progress for plugin run.
40
+
41
+ Args:
42
+ current(int): current progress value
43
+ total(int): total progress value
44
+ category(str | None): progress category
45
+ """
19
46
  assert 0 <= current <= total and total > 0
20
47
  assert category is not None or 'categories' not in self.progress_record
21
48
 
@@ -32,11 +59,42 @@ class BaseLogger:
32
59
  current_progress = {'percent': percent, 'time_remaining': time_remaining}
33
60
 
34
61
  if category:
35
- self.current_category = category
62
+ self.current_progress_category = category
36
63
  self.progress_record['categories'][category].update(current_progress)
37
64
  else:
38
65
  self.progress_record.update(current_progress)
39
66
 
67
+ def set_progress_failed(self, category: str | None = None):
68
+ """Mark progress as failed with elapsed time but no completion.
69
+
70
+ This method should be called when an operation fails to indicate that
71
+ no progress was made, but still track how long the operation ran before failing.
72
+
73
+ Args:
74
+ category(str | None): progress category
75
+ """
76
+ assert category is not None or 'categories' not in self.progress_record
77
+
78
+ # Calculate elapsed time if start time was recorded
79
+ elapsed_time = None
80
+ if category in self.time_begin_per_category:
81
+ elapsed_time = time.time() - self.time_begin_per_category[category]
82
+ elapsed_time = round(elapsed_time, 2)
83
+
84
+ # Progress is 0% (not completed), no time remaining, but track elapsed time
85
+ failed_progress = {
86
+ 'percent': 0.0,
87
+ 'time_remaining': None,
88
+ 'elapsed_time': elapsed_time,
89
+ 'status': 'failed',
90
+ }
91
+
92
+ if category:
93
+ self.current_progress_category = category
94
+ self.progress_record['categories'][category].update(failed_progress)
95
+ else:
96
+ self.progress_record.update(failed_progress)
97
+
40
98
  def get_current_progress(self):
41
99
  categories = self.progress_record.get('categories')
42
100
 
@@ -45,15 +103,15 @@ class BaseLogger:
45
103
 
46
104
  overall = 0
47
105
  for category, category_record in categories.items():
48
- if category == self.current_category:
106
+ if category == self.current_progress_category:
49
107
  break
50
108
  overall += category_record['proportion']
51
109
 
52
- category_record = categories[self.current_category]
110
+ category_record = categories[self.current_progress_category]
53
111
  category_percent = category_record.get('percent', 0)
54
112
  if not category_progress and 'percent' in category_record:
55
113
  category_progress = {
56
- 'category': self.current_category,
114
+ 'category': self.current_progress_category,
57
115
  'percent': category_percent,
58
116
  'time_remaining': category_record.get('time_remaining'),
59
117
  }
@@ -68,13 +126,41 @@ class BaseLogger:
68
126
 
69
127
  return progress
70
128
 
129
+ def set_metrics(self, value: Dict[Any, Any], category: str):
130
+ """Set metrics for plugin run.
131
+
132
+ * Metrics which are representing the progress of the plugin run should be set in the metrics_record.
133
+
134
+ Args:
135
+ value(Dict[Any, Any]): metrics value
136
+ category(str): metrics category
137
+ """
138
+ assert category is not None and category != '', 'A category argument must be a non-empty string.'
139
+ assert isinstance(value, dict), f'A value argument must be a dictionary, but got {type(value).__name__}.'
140
+
141
+ if 'categories' not in self.metrics_record:
142
+ self.metrics_record['categories'] = {}
143
+
144
+ self.metrics_record['categories'].setdefault(category, {}).update(value)
145
+
146
+ def log(self, action, data, file=None):
147
+ raise NotImplementedError
148
+
71
149
 
72
150
  class ConsoleLogger(BaseLogger):
73
151
  def set_progress(self, current, total, category=None):
74
152
  super().set_progress(current, total, category=category)
75
153
  print(self.get_current_progress())
76
154
 
77
- def log(self, action, data):
155
+ def set_progress_failed(self, category: str | None = None):
156
+ super().set_progress_failed(category=category)
157
+ print(self.get_current_progress())
158
+
159
+ def set_metrics(self, value: Dict[Any, Any], category: str):
160
+ super().set_metrics(value, category)
161
+ print(self.metrics_record)
162
+
163
+ def log(self, action, data, file=None):
78
164
  print(action, data)
79
165
 
80
166
 
@@ -99,7 +185,28 @@ class BackendLogger(BaseLogger):
99
185
  except ClientError:
100
186
  pass
101
187
 
102
- def log(self, event, data):
188
+ def set_progress_failed(self, category: str | None = None):
189
+ super().set_progress_failed(category=category)
190
+ try:
191
+ progress_record = {
192
+ 'record': self.progress_record,
193
+ 'current_progress': self.get_current_progress(),
194
+ }
195
+ self.client.update_job(self.job_id, data={'progress_record': progress_record})
196
+ except ClientError:
197
+ pass
198
+
199
+ def set_metrics(self, value: Dict[Any, Any], category: str):
200
+ super().set_metrics(value, category)
201
+ try:
202
+ metrics_record = {
203
+ 'record': self.metrics_record,
204
+ }
205
+ self.client.update_job(self.job_id, data={'metrics_record': metrics_record})
206
+ except ClientError:
207
+ pass
208
+
209
+ def log(self, event, data, file=None):
103
210
  print(event, data)
104
211
 
105
212
  log = {
@@ -108,7 +215,11 @@ class BackendLogger(BaseLogger):
108
215
  'datetime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'),
109
216
  'job': self.job_id,
110
217
  }
218
+ if file:
219
+ log['file'] = file
220
+
111
221
  self.logs_queue.append(log)
222
+
112
223
  try:
113
224
  self.client.create_logs(self.logs_queue)
114
225
  self.logs_queue.clear()