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

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

Potentially problematic release.


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

Files changed (261) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/__init__.py +9 -8
  3. synapse_sdk/cli/agent/__init__.py +25 -0
  4. synapse_sdk/cli/agent/config.py +104 -0
  5. synapse_sdk/cli/agent/select.py +197 -0
  6. synapse_sdk/cli/auth.py +104 -0
  7. synapse_sdk/cli/main.py +1025 -0
  8. synapse_sdk/cli/plugin/__init__.py +58 -0
  9. synapse_sdk/cli/plugin/create.py +566 -0
  10. synapse_sdk/cli/plugin/job.py +196 -0
  11. synapse_sdk/cli/plugin/publish.py +322 -0
  12. synapse_sdk/cli/plugin/run.py +131 -0
  13. synapse_sdk/cli/plugin/test.py +200 -0
  14. synapse_sdk/clients/README.md +239 -0
  15. synapse_sdk/clients/__init__.py +5 -0
  16. synapse_sdk/clients/_template.py +266 -0
  17. synapse_sdk/clients/agent/__init__.py +84 -29
  18. synapse_sdk/clients/agent/async_ray.py +289 -0
  19. synapse_sdk/clients/agent/container.py +83 -0
  20. synapse_sdk/clients/agent/plugin.py +101 -0
  21. synapse_sdk/clients/agent/ray.py +296 -39
  22. synapse_sdk/clients/backend/__init__.py +152 -12
  23. synapse_sdk/clients/backend/annotation.py +164 -22
  24. synapse_sdk/clients/backend/core.py +101 -0
  25. synapse_sdk/clients/backend/data_collection.py +292 -0
  26. synapse_sdk/clients/backend/hitl.py +87 -0
  27. synapse_sdk/clients/backend/integration.py +374 -46
  28. synapse_sdk/clients/backend/ml.py +134 -22
  29. synapse_sdk/clients/backend/models.py +247 -0
  30. synapse_sdk/clients/base.py +538 -59
  31. synapse_sdk/clients/exceptions.py +35 -7
  32. synapse_sdk/clients/pipeline/__init__.py +5 -0
  33. synapse_sdk/clients/pipeline/client.py +636 -0
  34. synapse_sdk/clients/protocols.py +178 -0
  35. synapse_sdk/clients/utils.py +86 -8
  36. synapse_sdk/clients/validation.py +58 -0
  37. synapse_sdk/enums.py +76 -0
  38. synapse_sdk/exceptions.py +168 -0
  39. synapse_sdk/integrations/__init__.py +74 -0
  40. synapse_sdk/integrations/_base.py +119 -0
  41. synapse_sdk/integrations/_context.py +53 -0
  42. synapse_sdk/integrations/ultralytics/__init__.py +78 -0
  43. synapse_sdk/integrations/ultralytics/_callbacks.py +126 -0
  44. synapse_sdk/integrations/ultralytics/_patches.py +124 -0
  45. synapse_sdk/loggers.py +476 -95
  46. synapse_sdk/mcp/MCP.md +69 -0
  47. synapse_sdk/mcp/__init__.py +48 -0
  48. synapse_sdk/mcp/__main__.py +6 -0
  49. synapse_sdk/mcp/config.py +349 -0
  50. synapse_sdk/mcp/prompts/__init__.py +4 -0
  51. synapse_sdk/mcp/resources/__init__.py +4 -0
  52. synapse_sdk/mcp/server.py +1352 -0
  53. synapse_sdk/mcp/tools/__init__.py +6 -0
  54. synapse_sdk/plugins/__init__.py +133 -9
  55. synapse_sdk/plugins/action.py +229 -0
  56. synapse_sdk/plugins/actions/__init__.py +82 -0
  57. synapse_sdk/plugins/actions/dataset/__init__.py +37 -0
  58. synapse_sdk/plugins/actions/dataset/action.py +471 -0
  59. synapse_sdk/plugins/actions/export/__init__.py +55 -0
  60. synapse_sdk/plugins/actions/export/action.py +183 -0
  61. synapse_sdk/plugins/actions/export/context.py +59 -0
  62. synapse_sdk/plugins/actions/inference/__init__.py +84 -0
  63. synapse_sdk/plugins/actions/inference/action.py +285 -0
  64. synapse_sdk/plugins/actions/inference/context.py +81 -0
  65. synapse_sdk/plugins/actions/inference/deployment.py +322 -0
  66. synapse_sdk/plugins/actions/inference/serve.py +252 -0
  67. synapse_sdk/plugins/actions/train/__init__.py +54 -0
  68. synapse_sdk/plugins/actions/train/action.py +326 -0
  69. synapse_sdk/plugins/actions/train/context.py +57 -0
  70. synapse_sdk/plugins/actions/upload/__init__.py +49 -0
  71. synapse_sdk/plugins/actions/upload/action.py +165 -0
  72. synapse_sdk/plugins/actions/upload/context.py +61 -0
  73. synapse_sdk/plugins/config.py +98 -0
  74. synapse_sdk/plugins/context/__init__.py +109 -0
  75. synapse_sdk/plugins/context/env.py +113 -0
  76. synapse_sdk/plugins/datasets/__init__.py +113 -0
  77. synapse_sdk/plugins/datasets/converters/__init__.py +76 -0
  78. synapse_sdk/plugins/datasets/converters/base.py +347 -0
  79. synapse_sdk/plugins/datasets/converters/yolo/__init__.py +9 -0
  80. synapse_sdk/plugins/datasets/converters/yolo/from_dm.py +468 -0
  81. synapse_sdk/plugins/datasets/converters/yolo/to_dm.py +381 -0
  82. synapse_sdk/plugins/datasets/formats/__init__.py +82 -0
  83. synapse_sdk/plugins/datasets/formats/dm.py +351 -0
  84. synapse_sdk/plugins/datasets/formats/yolo.py +240 -0
  85. synapse_sdk/plugins/decorators.py +83 -0
  86. synapse_sdk/plugins/discovery.py +790 -0
  87. synapse_sdk/plugins/docs/ACTION_DEV_GUIDE.md +933 -0
  88. synapse_sdk/plugins/docs/ARCHITECTURE.md +1225 -0
  89. synapse_sdk/plugins/docs/LOGGING_SYSTEM.md +683 -0
  90. synapse_sdk/plugins/docs/OVERVIEW.md +531 -0
  91. synapse_sdk/plugins/docs/PIPELINE_GUIDE.md +145 -0
  92. synapse_sdk/plugins/docs/README.md +513 -0
  93. synapse_sdk/plugins/docs/STEP.md +656 -0
  94. synapse_sdk/plugins/enums.py +70 -10
  95. synapse_sdk/plugins/errors.py +92 -0
  96. synapse_sdk/plugins/executors/__init__.py +43 -0
  97. synapse_sdk/plugins/executors/local.py +99 -0
  98. synapse_sdk/plugins/executors/ray/__init__.py +18 -0
  99. synapse_sdk/plugins/executors/ray/base.py +282 -0
  100. synapse_sdk/plugins/executors/ray/job.py +298 -0
  101. synapse_sdk/plugins/executors/ray/jobs_api.py +511 -0
  102. synapse_sdk/plugins/executors/ray/packaging.py +137 -0
  103. synapse_sdk/plugins/executors/ray/pipeline.py +792 -0
  104. synapse_sdk/plugins/executors/ray/task.py +257 -0
  105. synapse_sdk/plugins/models/__init__.py +26 -0
  106. synapse_sdk/plugins/models/logger.py +173 -0
  107. synapse_sdk/plugins/models/pipeline.py +25 -0
  108. synapse_sdk/plugins/pipelines/__init__.py +81 -0
  109. synapse_sdk/plugins/pipelines/action_pipeline.py +417 -0
  110. synapse_sdk/plugins/pipelines/context.py +107 -0
  111. synapse_sdk/plugins/pipelines/display.py +311 -0
  112. synapse_sdk/plugins/runner.py +114 -0
  113. synapse_sdk/plugins/schemas/__init__.py +19 -0
  114. synapse_sdk/plugins/schemas/results.py +152 -0
  115. synapse_sdk/plugins/steps/__init__.py +63 -0
  116. synapse_sdk/plugins/steps/base.py +128 -0
  117. synapse_sdk/plugins/steps/context.py +90 -0
  118. synapse_sdk/plugins/steps/orchestrator.py +128 -0
  119. synapse_sdk/plugins/steps/registry.py +103 -0
  120. synapse_sdk/plugins/steps/utils/__init__.py +20 -0
  121. synapse_sdk/plugins/steps/utils/logging.py +85 -0
  122. synapse_sdk/plugins/steps/utils/timing.py +71 -0
  123. synapse_sdk/plugins/steps/utils/validation.py +68 -0
  124. synapse_sdk/plugins/templates/__init__.py +50 -0
  125. synapse_sdk/plugins/templates/base/.gitignore.j2 +26 -0
  126. synapse_sdk/plugins/templates/base/.synapseignore.j2 +11 -0
  127. synapse_sdk/plugins/templates/base/README.md.j2 +26 -0
  128. synapse_sdk/plugins/templates/base/plugin/__init__.py.j2 +1 -0
  129. synapse_sdk/plugins/templates/base/pyproject.toml.j2 +14 -0
  130. synapse_sdk/plugins/templates/base/requirements.txt.j2 +1 -0
  131. synapse_sdk/plugins/templates/custom/plugin/main.py.j2 +18 -0
  132. synapse_sdk/plugins/templates/data_validation/plugin/validate.py.j2 +32 -0
  133. synapse_sdk/plugins/templates/export/plugin/export.py.j2 +36 -0
  134. synapse_sdk/plugins/templates/neural_net/plugin/inference.py.j2 +36 -0
  135. synapse_sdk/plugins/templates/neural_net/plugin/train.py.j2 +33 -0
  136. synapse_sdk/plugins/templates/post_annotation/plugin/post_annotate.py.j2 +32 -0
  137. synapse_sdk/plugins/templates/pre_annotation/plugin/pre_annotate.py.j2 +32 -0
  138. synapse_sdk/plugins/templates/smart_tool/plugin/auto_label.py.j2 +44 -0
  139. synapse_sdk/plugins/templates/upload/plugin/upload.py.j2 +35 -0
  140. synapse_sdk/plugins/testing/__init__.py +25 -0
  141. synapse_sdk/plugins/testing/sample_actions.py +98 -0
  142. synapse_sdk/plugins/types.py +206 -0
  143. synapse_sdk/plugins/upload.py +595 -64
  144. synapse_sdk/plugins/utils.py +325 -37
  145. synapse_sdk/shared/__init__.py +25 -0
  146. synapse_sdk/utils/__init__.py +1 -0
  147. synapse_sdk/utils/auth.py +74 -0
  148. synapse_sdk/utils/file/__init__.py +58 -0
  149. synapse_sdk/utils/file/archive.py +449 -0
  150. synapse_sdk/utils/file/checksum.py +167 -0
  151. synapse_sdk/utils/file/download.py +286 -0
  152. synapse_sdk/utils/file/io.py +129 -0
  153. synapse_sdk/utils/file/requirements.py +36 -0
  154. synapse_sdk/utils/network.py +168 -0
  155. synapse_sdk/utils/storage/__init__.py +238 -0
  156. synapse_sdk/utils/storage/config.py +188 -0
  157. synapse_sdk/utils/storage/errors.py +52 -0
  158. synapse_sdk/utils/storage/providers/__init__.py +13 -0
  159. synapse_sdk/utils/storage/providers/base.py +76 -0
  160. synapse_sdk/utils/storage/providers/gcs.py +168 -0
  161. synapse_sdk/utils/storage/providers/http.py +250 -0
  162. synapse_sdk/utils/storage/providers/local.py +126 -0
  163. synapse_sdk/utils/storage/providers/s3.py +177 -0
  164. synapse_sdk/utils/storage/providers/sftp.py +208 -0
  165. synapse_sdk/utils/storage/registry.py +125 -0
  166. synapse_sdk/utils/websocket.py +99 -0
  167. synapse_sdk-2026.1.1b2.dist-info/METADATA +715 -0
  168. synapse_sdk-2026.1.1b2.dist-info/RECORD +172 -0
  169. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/WHEEL +1 -1
  170. synapse_sdk-2026.1.1b2.dist-info/licenses/LICENSE +201 -0
  171. locale/en/LC_MESSAGES/messages.mo +0 -0
  172. locale/en/LC_MESSAGES/messages.po +0 -39
  173. locale/ko/LC_MESSAGES/messages.mo +0 -0
  174. locale/ko/LC_MESSAGES/messages.po +0 -34
  175. synapse_sdk/cli/create_plugin.py +0 -10
  176. synapse_sdk/clients/agent/core.py +0 -7
  177. synapse_sdk/clients/agent/service.py +0 -15
  178. synapse_sdk/clients/backend/dataset.py +0 -51
  179. synapse_sdk/clients/ray/__init__.py +0 -6
  180. synapse_sdk/clients/ray/core.py +0 -22
  181. synapse_sdk/clients/ray/serve.py +0 -20
  182. synapse_sdk/i18n.py +0 -35
  183. synapse_sdk/plugins/categories/__init__.py +0 -0
  184. synapse_sdk/plugins/categories/base.py +0 -235
  185. synapse_sdk/plugins/categories/data_validation/__init__.py +0 -0
  186. synapse_sdk/plugins/categories/data_validation/actions/__init__.py +0 -0
  187. synapse_sdk/plugins/categories/data_validation/actions/validation.py +0 -10
  188. synapse_sdk/plugins/categories/data_validation/templates/config.yaml +0 -3
  189. synapse_sdk/plugins/categories/data_validation/templates/plugin/__init__.py +0 -0
  190. synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +0 -5
  191. synapse_sdk/plugins/categories/decorators.py +0 -13
  192. synapse_sdk/plugins/categories/export/__init__.py +0 -0
  193. synapse_sdk/plugins/categories/export/actions/__init__.py +0 -0
  194. synapse_sdk/plugins/categories/export/actions/export.py +0 -10
  195. synapse_sdk/plugins/categories/import/__init__.py +0 -0
  196. synapse_sdk/plugins/categories/import/actions/__init__.py +0 -0
  197. synapse_sdk/plugins/categories/import/actions/import.py +0 -10
  198. synapse_sdk/plugins/categories/neural_net/__init__.py +0 -0
  199. synapse_sdk/plugins/categories/neural_net/actions/__init__.py +0 -0
  200. synapse_sdk/plugins/categories/neural_net/actions/deployment.py +0 -45
  201. synapse_sdk/plugins/categories/neural_net/actions/inference.py +0 -18
  202. synapse_sdk/plugins/categories/neural_net/actions/test.py +0 -10
  203. synapse_sdk/plugins/categories/neural_net/actions/train.py +0 -143
  204. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +0 -12
  205. synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py +0 -0
  206. synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +0 -4
  207. synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py +0 -2
  208. synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +0 -14
  209. synapse_sdk/plugins/categories/post_annotation/__init__.py +0 -0
  210. synapse_sdk/plugins/categories/post_annotation/actions/__init__.py +0 -0
  211. synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py +0 -10
  212. synapse_sdk/plugins/categories/post_annotation/templates/config.yaml +0 -3
  213. synapse_sdk/plugins/categories/post_annotation/templates/plugin/__init__.py +0 -0
  214. synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.py +0 -3
  215. synapse_sdk/plugins/categories/pre_annotation/__init__.py +0 -0
  216. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +0 -0
  217. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py +0 -10
  218. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +0 -3
  219. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py +0 -0
  220. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py +0 -3
  221. synapse_sdk/plugins/categories/registry.py +0 -16
  222. synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
  223. synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
  224. synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +0 -37
  225. synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +0 -7
  226. synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
  227. synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +0 -11
  228. synapse_sdk/plugins/categories/templates.py +0 -32
  229. synapse_sdk/plugins/cli/__init__.py +0 -21
  230. synapse_sdk/plugins/cli/publish.py +0 -37
  231. synapse_sdk/plugins/cli/run.py +0 -67
  232. synapse_sdk/plugins/exceptions.py +0 -22
  233. synapse_sdk/plugins/models.py +0 -121
  234. synapse_sdk/plugins/templates/cookiecutter.json +0 -11
  235. synapse_sdk/plugins/templates/hooks/post_gen_project.py +0 -3
  236. synapse_sdk/plugins/templates/hooks/pre_prompt.py +0 -21
  237. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
  238. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
  239. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.gitignore +0 -27
  240. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml +0 -7
  241. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md +0 -5
  242. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -6
  243. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
  244. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py +0 -0
  245. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml +0 -13
  246. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +0 -1
  247. synapse_sdk/shared/enums.py +0 -8
  248. synapse_sdk/utils/debug.py +0 -5
  249. synapse_sdk/utils/file.py +0 -87
  250. synapse_sdk/utils/module_loading.py +0 -29
  251. synapse_sdk/utils/pydantic/__init__.py +0 -0
  252. synapse_sdk/utils/pydantic/config.py +0 -4
  253. synapse_sdk/utils/pydantic/errors.py +0 -33
  254. synapse_sdk/utils/pydantic/validators.py +0 -7
  255. synapse_sdk/utils/storage.py +0 -91
  256. synapse_sdk/utils/string.py +0 -11
  257. synapse_sdk-1.0.0a11.dist-info/LICENSE +0 -21
  258. synapse_sdk-1.0.0a11.dist-info/METADATA +0 -43
  259. synapse_sdk-1.0.0a11.dist-info/RECORD +0 -111
  260. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/entry_points.txt +0 -0
  261. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/top_level.txt +0 -0
@@ -1,29 +1,171 @@
1
- from synapse_sdk.clients.base import BaseClient
2
- from synapse_sdk.clients.utils import get_default_url_conversion
1
+ """Annotation client mixin for project and task operations."""
3
2
 
3
+ from __future__ import annotations
4
4
 
5
- class AnnotationClientMixin(BaseClient):
6
- def get_project(self, pk):
7
- path = f'projects/{pk}/'
8
- return self._get(path)
5
+ from typing import TYPE_CHECKING, Any
9
6
 
10
- def get_task_tag(self, pk):
11
- path = f'task_tags/{pk}/'
12
- return self._get(path)
7
+ from synapse_sdk.clients.backend.models import SetTagsRequest, TaskCreateRequest
13
8
 
14
- def list_task_tags(self, data):
15
- path = 'task_tags/'
16
- return self._list(path, data=data)
9
+ if TYPE_CHECKING:
10
+ from synapse_sdk.clients.protocols import ClientProtocol
17
11
 
18
- def list_tasks(self, data, url_conversion=None, list_all=False):
19
- path = 'tasks/'
20
- url_conversion = get_default_url_conversion(url_conversion, files_fields=['files'])
21
- return self._list(path, data=data, url_conversion=url_conversion, list_all=list_all)
22
12
 
23
- def create_tasks(self, data):
24
- path = 'tasks/'
25
- return self._post(path, data=data)
13
+ class AnnotationClientMixin:
14
+ """Mixin for annotation-related API endpoints.
26
15
 
27
- def set_tags_tasks(self, data, params=None):
28
- path = 'tasks/set_tags/'
29
- return self._post(path, data=data, params=params)
16
+ Provides methods for managing projects, tasks, and task tags.
17
+ """
18
+
19
+ def get_project(self: ClientProtocol, project_id: int) -> dict[str, Any]:
20
+ """Get project details by ID.
21
+
22
+ Args:
23
+ project_id: Project ID.
24
+
25
+ Returns:
26
+ Project data including configuration and statistics.
27
+ """
28
+ return self._get(f'projects/{project_id}/')
29
+
30
+ def get_task(
31
+ self: ClientProtocol,
32
+ task_id: int,
33
+ *,
34
+ params: dict[str, Any] | None = None,
35
+ ) -> dict[str, Any]:
36
+ """Get task details by ID.
37
+
38
+ Args:
39
+ task_id: Task ID.
40
+ params: Optional query parameters (e.g., expand, fields).
41
+
42
+ Returns:
43
+ Task data including annotations and metadata.
44
+ """
45
+ return self._get(f'tasks/{task_id}/', params=params)
46
+
47
+ def annotate_task_data(
48
+ self: ClientProtocol,
49
+ task_id: int,
50
+ data: dict[str, Any],
51
+ ) -> dict[str, Any]:
52
+ """Submit annotation data for a task.
53
+
54
+ Args:
55
+ task_id: Task ID to annotate.
56
+ data: Annotation data payload.
57
+
58
+ Returns:
59
+ Updated task data.
60
+ """
61
+ return self._put(f'tasks/{task_id}/annotate_task_data/', data=data)
62
+
63
+ def get_task_tag(self: ClientProtocol, tag_id: int) -> dict[str, Any]:
64
+ """Get task tag details by ID.
65
+
66
+ Args:
67
+ tag_id: Tag ID.
68
+
69
+ Returns:
70
+ Tag data including name and color.
71
+ """
72
+ return self._get(f'task_tags/{tag_id}/')
73
+
74
+ def list_task_tags(
75
+ self: ClientProtocol,
76
+ params: dict[str, Any] | None = None,
77
+ ) -> dict[str, Any]:
78
+ """List available task tags.
79
+
80
+ Args:
81
+ params: Optional query parameters for filtering.
82
+
83
+ Returns:
84
+ Paginated list of task tags.
85
+ """
86
+ return self._get('task_tags/', params=params)
87
+
88
+ def list_tasks(
89
+ self: ClientProtocol,
90
+ params: dict[str, Any] | None = None,
91
+ *,
92
+ url_conversion: dict[str, Any] | None = None,
93
+ list_all: bool = False,
94
+ ) -> dict[str, Any] | tuple[Any, int]:
95
+ """List tasks with optional pagination.
96
+
97
+ Args:
98
+ params: Query parameters for filtering (project, status, etc.).
99
+ url_conversion: URL-to-path conversion config for file fields.
100
+ list_all: If True, returns (generator, count) for all results.
101
+
102
+ Returns:
103
+ Paginated task list, or (generator, count) if list_all=True.
104
+
105
+ Example:
106
+ >>> # Get first page
107
+ >>> tasks = client.list_tasks({'project': 123})
108
+ >>> # Get all tasks as generator
109
+ >>> tasks_gen, count = client.list_tasks({'project': 123}, list_all=True)
110
+ """
111
+ if url_conversion is None:
112
+ url_conversion = {'files_fields': ['files']}
113
+
114
+ return self._list(
115
+ 'sdk/tasks/',
116
+ params=params,
117
+ url_conversion=url_conversion,
118
+ list_all=list_all,
119
+ )
120
+
121
+ def create_tasks(
122
+ self: ClientProtocol,
123
+ data: dict[str, Any] | list[dict[str, Any]],
124
+ ) -> dict[str, Any]:
125
+ """Create one or more annotation tasks.
126
+
127
+ Args:
128
+ data: Task data or list of task data.
129
+
130
+ Returns:
131
+ Created task(s) response.
132
+
133
+ Example:
134
+ >>> client.create_tasks({
135
+ ... 'project': 123,
136
+ ... 'data': [{'image': 'path/to/image.jpg'}]
137
+ ... })
138
+ """
139
+ return self._post('tasks/', request_model=TaskCreateRequest, data=data)
140
+
141
+ def set_tags_tasks(
142
+ self: ClientProtocol,
143
+ data: dict[str, Any],
144
+ *,
145
+ params: dict[str, Any] | None = None,
146
+ ) -> dict[str, Any]:
147
+ """Set tags on multiple tasks.
148
+
149
+ Args:
150
+ data: Tag assignment data with 'ids', 'tags', and 'action'.
151
+ params: Optional query parameters.
152
+
153
+ Returns:
154
+ Operation result.
155
+
156
+ Example:
157
+ >>> client.set_tags_tasks({
158
+ ... 'ids': [1, 2, 3],
159
+ ... 'tags': [10, 20],
160
+ ... 'action': 'add' # or 'remove'
161
+ ... })
162
+ """
163
+ return self._post(
164
+ 'tasks/set_tags/',
165
+ request_model=SetTagsRequest,
166
+ data=data,
167
+ params=params,
168
+ )
169
+
170
+
171
+ __all__ = ['AnnotationClientMixin']
@@ -0,0 +1,101 @@
1
+ """Core backend client mixin with chunked upload support."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import os
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ from synapse_sdk.clients.backend.models import (
11
+ ChunkedUploadFinalizeResponse,
12
+ ChunkedUploadResponse,
13
+ )
14
+ from synapse_sdk.utils.file.io import DEFAULT_CHUNK_SIZE, read_file_in_chunks
15
+
16
+ if TYPE_CHECKING:
17
+ from collections.abc import Callable
18
+
19
+ from synapse_sdk.clients.protocols import ClientProtocol
20
+
21
+
22
+ class CoreClientMixin:
23
+ """Mixin providing chunked upload functionality.
24
+
25
+ Supports resumable uploads with MD5 integrity verification.
26
+ Files are uploaded in 50MB chunks by default.
27
+ """
28
+
29
+ def create_chunked_upload(
30
+ self: ClientProtocol,
31
+ file_path: str | Path,
32
+ *,
33
+ chunk_size: int = DEFAULT_CHUNK_SIZE,
34
+ on_progress: Callable[[int, int], None] | None = None,
35
+ ) -> dict[str, Any]:
36
+ """Upload a file in chunks with MD5 integrity verification.
37
+
38
+ Files are uploaded in configurable chunks (default 50MB) with
39
+ Content-Range headers for resumable uploads. MD5 hash is calculated
40
+ incrementally during upload and verified on finalization.
41
+
42
+ Args:
43
+ file_path: Path to the file to upload.
44
+ chunk_size: Size of each chunk in bytes (default 50MB).
45
+ on_progress: Optional callback(bytes_uploaded, total_bytes) for progress.
46
+
47
+ Returns:
48
+ Finalized upload response with file ID and checksum.
49
+
50
+ Raises:
51
+ FileNotFoundError: If file doesn't exist.
52
+ ClientError: If upload fails.
53
+
54
+ Example:
55
+ >>> def progress(uploaded, total):
56
+ ... print(f'{uploaded}/{total} bytes')
57
+ >>> result = client.create_chunked_upload('/path/to/file.zip', on_progress=progress)
58
+ >>> result['id']
59
+ 123
60
+ """
61
+ path = Path(file_path)
62
+ if not path.exists():
63
+ raise FileNotFoundError(f'File not found: {path}')
64
+
65
+ total_size = os.path.getsize(path)
66
+ hash_md5 = hashlib.md5()
67
+
68
+ # Initial upload URL
69
+ url = 'chunked_upload/'
70
+ offset = 0
71
+
72
+ # Upload each chunk
73
+ for chunk in read_file_in_chunks(path, chunk_size):
74
+ hash_md5.update(chunk)
75
+
76
+ response = self._put(
77
+ url,
78
+ data={'filename': path.name},
79
+ files={'file': ('chunk', chunk)},
80
+ headers={'Content-Range': f'bytes {offset}-{offset + len(chunk) - 1}/{total_size}'},
81
+ )
82
+
83
+ # Validate response
84
+ chunk_response = ChunkedUploadResponse.model_validate(response)
85
+ offset = chunk_response.offset
86
+ url = chunk_response.url
87
+
88
+ # Progress callback
89
+ if on_progress:
90
+ on_progress(offset, total_size)
91
+
92
+ # Finalize with MD5 checksum
93
+ result = self._post(url, data={'md5': hash_md5.hexdigest()})
94
+
95
+ # Validate final response
96
+ ChunkedUploadFinalizeResponse.model_validate(result)
97
+
98
+ return result
99
+
100
+
101
+ __all__ = ['CoreClientMixin']
@@ -0,0 +1,292 @@
1
+ """Data collection client mixin for dataset management."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from concurrent.futures import ThreadPoolExecutor, as_completed
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ from synapse_sdk.clients.backend.models import DataFileResponse, DataUnitCreateRequest
10
+
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Callable
13
+
14
+ from synapse_sdk.clients.protocols import ClientProtocol
15
+
16
+
17
+ # Auto-use chunked upload for files larger than 50MB
18
+ CHUNKED_UPLOAD_THRESHOLD = 1024 * 1024 * 50
19
+
20
+
21
+ def _batch_list(items: list, batch_size: int) -> list[list]:
22
+ """Split a list into batches of specified size."""
23
+ return [items[i : i + batch_size] for i in range(0, len(items), batch_size)]
24
+
25
+
26
+ class DataCollectionClientMixin:
27
+ """Mixin for data collection API endpoints.
28
+
29
+ Provides methods for managing data collections, files, and units.
30
+ """
31
+
32
+ def list_data_collections(self: ClientProtocol) -> dict[str, Any]:
33
+ """List all data collections.
34
+
35
+ Returns:
36
+ Paginated list of data collections.
37
+ """
38
+ return self._get('data_collections/')
39
+
40
+ def get_data_collection(
41
+ self: ClientProtocol,
42
+ collection_id: int,
43
+ ) -> dict[str, Any]:
44
+ """Get data collection details by ID.
45
+
46
+ Automatically expands file specifications.
47
+
48
+ Args:
49
+ collection_id: Data collection ID.
50
+
51
+ Returns:
52
+ Collection data including file specifications.
53
+ """
54
+ return self._get(
55
+ f'data_collections/{collection_id}/',
56
+ params={'expand': 'file_specifications'},
57
+ )
58
+
59
+ def create_data_file(
60
+ self: ClientProtocol,
61
+ file_path: str | Path,
62
+ *,
63
+ use_chunked_upload: bool | None = None,
64
+ ) -> dict[str, Any]:
65
+ """Upload a data file.
66
+
67
+ Automatically uses chunked upload for files >50MB unless
68
+ explicitly specified.
69
+
70
+ Args:
71
+ file_path: Path to the file to upload.
72
+ use_chunked_upload: Force chunked (True) or direct (False) upload.
73
+ None = auto-detect based on file size.
74
+
75
+ Returns:
76
+ File data with ID, checksum, and size.
77
+
78
+ Raises:
79
+ FileNotFoundError: If file doesn't exist.
80
+ """
81
+ path = Path(file_path)
82
+ if not path.exists():
83
+ raise FileNotFoundError(f'File not found: {path}')
84
+
85
+ # Auto-detect upload method based on file size
86
+ if use_chunked_upload is None:
87
+ use_chunked_upload = path.stat().st_size > CHUNKED_UPLOAD_THRESHOLD
88
+
89
+ if use_chunked_upload:
90
+ upload_result = self.create_chunked_upload(path)
91
+ response = self._post(
92
+ 'data_files/',
93
+ data={'chunked_upload': upload_result['id']},
94
+ )
95
+ else:
96
+ response = self._post('data_files/', files={'file': path})
97
+
98
+ DataFileResponse.model_validate(response)
99
+ return response
100
+
101
+ def get_data_unit(
102
+ self: ClientProtocol,
103
+ unit_id: int,
104
+ *,
105
+ params: dict[str, Any] | None = None,
106
+ ) -> dict[str, Any]:
107
+ """Get data unit details by ID.
108
+
109
+ Args:
110
+ unit_id: Data unit ID.
111
+ params: Optional query parameters.
112
+
113
+ Returns:
114
+ Data unit with files and metadata.
115
+ """
116
+ return self._get(f'data_units/{unit_id}/', params=params)
117
+
118
+ def create_data_units(
119
+ self: ClientProtocol,
120
+ data: dict[str, Any] | list[dict[str, Any]],
121
+ ) -> dict[str, Any]:
122
+ """Create data unit bindings.
123
+
124
+ Links uploaded files to a data collection.
125
+
126
+ Args:
127
+ data: Data unit(s) to create.
128
+
129
+ Returns:
130
+ Created data unit(s).
131
+ """
132
+ return self._post(
133
+ 'data_units/',
134
+ request_model=DataUnitCreateRequest,
135
+ data=data,
136
+ )
137
+
138
+ def list_data_units(
139
+ self: ClientProtocol,
140
+ params: dict[str, Any] | None = None,
141
+ *,
142
+ url_conversion: dict[str, Any] | None = None,
143
+ list_all: bool = False,
144
+ page_size: int = 100,
145
+ timeout: int = 60,
146
+ ) -> dict[str, Any] | tuple[Any, int]:
147
+ """List data units with optional pagination.
148
+
149
+ Args:
150
+ params: Query parameters for filtering.
151
+ url_conversion: URL-to-path conversion config.
152
+ list_all: If True, returns (generator, count).
153
+ page_size: Number of items per page. Default 100 (larger pages
154
+ reduce API calls; timeout increased to handle heavy payloads).
155
+ timeout: Read timeout in seconds. Default 60 (longer timeout
156
+ for large page sizes with heavy file metadata).
157
+
158
+ Returns:
159
+ Paginated list or (generator, count).
160
+ """
161
+ if url_conversion is None:
162
+ url_conversion = {'files_fields': ['files']}
163
+
164
+ if params is None:
165
+ params = {}
166
+ params.setdefault('page_size', page_size)
167
+
168
+ return self._list(
169
+ 'data_units/',
170
+ params=params,
171
+ url_conversion=url_conversion,
172
+ list_all=list_all,
173
+ timeout=(5, timeout), # (connect_timeout, read_timeout)
174
+ )
175
+
176
+ def verify_data_files_checksums(
177
+ self: ClientProtocol,
178
+ checksums: list[str],
179
+ ) -> dict[str, Any]:
180
+ """Verify if data files with given checksums exist.
181
+
182
+ Args:
183
+ checksums: List of MD5 checksums to verify.
184
+
185
+ Returns:
186
+ Verification result with existing checksums.
187
+ """
188
+ return self._post('data_files/verify_checksums/', data={'checksums': checksums})
189
+
190
+ def upload_data_file(
191
+ self: ClientProtocol,
192
+ data: dict[str, Any],
193
+ collection_id: int,
194
+ *,
195
+ use_chunked_upload: bool | None = None,
196
+ ) -> dict[str, Any]:
197
+ """Upload individual files for a data unit and return binding data.
198
+
199
+ Args:
200
+ data: Data unit definition with 'files' dict mapping names to paths.
201
+ collection_id: Target data collection ID.
202
+ use_chunked_upload: Force chunked (True) or direct (False) upload.
203
+
204
+ Returns:
205
+ Data ready for create_data_units() with checksums.
206
+
207
+ Example:
208
+ >>> result = client.upload_data_file(
209
+ ... {'files': {'image': '/path/to/img.jpg'}, 'meta': {'label': 1}},
210
+ ... collection_id=123
211
+ ... )
212
+ >>> # result['files']['image']['checksum'] is populated
213
+ """
214
+ files_data = {}
215
+
216
+ for name, file_path in data.get('files', {}).items():
217
+ if isinstance(file_path, str):
218
+ path = Path(file_path)
219
+ else:
220
+ path = file_path
221
+
222
+ upload_result = self.create_data_file(path, use_chunked_upload=use_chunked_upload)
223
+
224
+ files_data[name] = {
225
+ 'checksum': upload_result['checksum'],
226
+ 'path': str(path),
227
+ }
228
+
229
+ return {
230
+ 'files': files_data,
231
+ 'data_collection': collection_id,
232
+ 'meta': data.get('meta', {}),
233
+ }
234
+
235
+ def upload_data_collection(
236
+ self: ClientProtocol,
237
+ collection_id: int,
238
+ data: list[dict[str, Any]],
239
+ *,
240
+ project_id: int | None = None,
241
+ batch_size: int = 1000,
242
+ max_workers: int = 10,
243
+ on_progress: Callable[[int, int], None] | None = None,
244
+ ) -> None:
245
+ """Bulk upload data to a collection.
246
+
247
+ Uploads files in parallel using a thread pool, then creates
248
+ data units in batches. Optionally creates annotation tasks.
249
+
250
+ Args:
251
+ collection_id: Target data collection ID.
252
+ data: List of data unit definitions.
253
+ project_id: Optional project ID to create tasks for.
254
+ batch_size: Number of data units per batch (default 1000).
255
+ max_workers: Number of parallel upload threads (default 10).
256
+ on_progress: Optional callback(completed, total) for progress.
257
+
258
+ Example:
259
+ >>> data = [
260
+ ... {'files': {'image': '/path/1.jpg'}, 'meta': {'label': 'cat'}},
261
+ ... {'files': {'image': '/path/2.jpg'}, 'meta': {'label': 'dog'}},
262
+ ... ]
263
+ >>> client.upload_data_collection(123, data, project_id=456)
264
+ """
265
+ total = len(data)
266
+ completed = 0
267
+ upload_results: list[dict[str, Any]] = []
268
+
269
+ # Upload files in parallel
270
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
271
+ futures = {executor.submit(self.upload_data_file, item, collection_id): i for i, item in enumerate(data)}
272
+
273
+ for future in as_completed(futures):
274
+ result = future.result()
275
+ upload_results.append(result)
276
+ completed += 1
277
+
278
+ if on_progress:
279
+ on_progress(completed, total)
280
+
281
+ # Create data units in batches
282
+ for batch in _batch_list(upload_results, batch_size):
283
+ created_units = self.create_data_units(batch)
284
+
285
+ # Optionally create tasks
286
+ if project_id is not None:
287
+ task_data = [{'data_unit': unit['id']} for unit in created_units.get('results', [created_units])]
288
+ if task_data:
289
+ self._post('tasks/', data={'project': project_id, 'data': task_data})
290
+
291
+
292
+ __all__ = ['DataCollectionClientMixin']
@@ -0,0 +1,87 @@
1
+ """HITL (Human-in-the-Loop) client mixin for assignment operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from synapse_sdk.clients.backend.models import SetTagsRequest
8
+
9
+ if TYPE_CHECKING:
10
+ from synapse_sdk.clients.protocols import ClientProtocol
11
+
12
+
13
+ class HITLClientMixin:
14
+ """Mixin for HITL-related API endpoints.
15
+
16
+ Provides methods for managing annotation assignments.
17
+ """
18
+
19
+ def get_assignment(self: ClientProtocol, assignment_id: int) -> dict[str, Any]:
20
+ """Get assignment details by ID.
21
+
22
+ Args:
23
+ assignment_id: Assignment ID.
24
+
25
+ Returns:
26
+ Assignment data including task and annotator info.
27
+ """
28
+ return self._get(f'assignments/{assignment_id}/')
29
+
30
+ def list_assignments(
31
+ self: ClientProtocol,
32
+ params: dict[str, Any] | None = None,
33
+ *,
34
+ url_conversion: dict[str, Any] | None = None,
35
+ list_all: bool = False,
36
+ ) -> dict[str, Any] | tuple[Any, int]:
37
+ """List assignments with optional pagination.
38
+
39
+ Args:
40
+ params: Query parameters for filtering.
41
+ url_conversion: URL-to-path conversion config.
42
+ list_all: If True, returns (generator, count).
43
+
44
+ Returns:
45
+ Paginated list or (generator, count).
46
+ """
47
+ if url_conversion is None:
48
+ url_conversion = {'files_fields': ['files']}
49
+
50
+ return self._list(
51
+ 'sdk/assignments/',
52
+ params=params,
53
+ url_conversion=url_conversion,
54
+ list_all=list_all,
55
+ )
56
+
57
+ def set_tags_assignments(
58
+ self: ClientProtocol,
59
+ data: dict[str, Any],
60
+ *,
61
+ params: dict[str, Any] | None = None,
62
+ ) -> dict[str, Any]:
63
+ """Set tags on multiple assignments.
64
+
65
+ Args:
66
+ data: Tag assignment data with 'ids', 'tags', and 'action'.
67
+ params: Optional query parameters.
68
+
69
+ Returns:
70
+ Operation result.
71
+
72
+ Example:
73
+ >>> client.set_tags_assignments({
74
+ ... 'ids': [1, 2, 3],
75
+ ... 'tags': [10, 20],
76
+ ... 'action': 'add' # or 'remove'
77
+ ... })
78
+ """
79
+ return self._post(
80
+ 'assignments/set_tags/',
81
+ request_model=SetTagsRequest,
82
+ data=data,
83
+ params=params,
84
+ )
85
+
86
+
87
+ __all__ = ['HITLClientMixin']