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

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

Potentially problematic release.


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

Files changed (261) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/__init__.py +9 -8
  3. synapse_sdk/cli/agent/__init__.py +25 -0
  4. synapse_sdk/cli/agent/config.py +104 -0
  5. synapse_sdk/cli/agent/select.py +197 -0
  6. synapse_sdk/cli/auth.py +104 -0
  7. synapse_sdk/cli/main.py +1025 -0
  8. synapse_sdk/cli/plugin/__init__.py +58 -0
  9. synapse_sdk/cli/plugin/create.py +566 -0
  10. synapse_sdk/cli/plugin/job.py +196 -0
  11. synapse_sdk/cli/plugin/publish.py +322 -0
  12. synapse_sdk/cli/plugin/run.py +131 -0
  13. synapse_sdk/cli/plugin/test.py +200 -0
  14. synapse_sdk/clients/README.md +239 -0
  15. synapse_sdk/clients/__init__.py +5 -0
  16. synapse_sdk/clients/_template.py +266 -0
  17. synapse_sdk/clients/agent/__init__.py +84 -29
  18. synapse_sdk/clients/agent/async_ray.py +289 -0
  19. synapse_sdk/clients/agent/container.py +83 -0
  20. synapse_sdk/clients/agent/plugin.py +101 -0
  21. synapse_sdk/clients/agent/ray.py +296 -39
  22. synapse_sdk/clients/backend/__init__.py +152 -12
  23. synapse_sdk/clients/backend/annotation.py +164 -22
  24. synapse_sdk/clients/backend/core.py +101 -0
  25. synapse_sdk/clients/backend/data_collection.py +292 -0
  26. synapse_sdk/clients/backend/hitl.py +87 -0
  27. synapse_sdk/clients/backend/integration.py +374 -46
  28. synapse_sdk/clients/backend/ml.py +134 -22
  29. synapse_sdk/clients/backend/models.py +247 -0
  30. synapse_sdk/clients/base.py +538 -59
  31. synapse_sdk/clients/exceptions.py +35 -7
  32. synapse_sdk/clients/pipeline/__init__.py +5 -0
  33. synapse_sdk/clients/pipeline/client.py +636 -0
  34. synapse_sdk/clients/protocols.py +178 -0
  35. synapse_sdk/clients/utils.py +86 -8
  36. synapse_sdk/clients/validation.py +58 -0
  37. synapse_sdk/enums.py +76 -0
  38. synapse_sdk/exceptions.py +168 -0
  39. synapse_sdk/integrations/__init__.py +74 -0
  40. synapse_sdk/integrations/_base.py +119 -0
  41. synapse_sdk/integrations/_context.py +53 -0
  42. synapse_sdk/integrations/ultralytics/__init__.py +78 -0
  43. synapse_sdk/integrations/ultralytics/_callbacks.py +126 -0
  44. synapse_sdk/integrations/ultralytics/_patches.py +124 -0
  45. synapse_sdk/loggers.py +476 -95
  46. synapse_sdk/mcp/MCP.md +69 -0
  47. synapse_sdk/mcp/__init__.py +48 -0
  48. synapse_sdk/mcp/__main__.py +6 -0
  49. synapse_sdk/mcp/config.py +349 -0
  50. synapse_sdk/mcp/prompts/__init__.py +4 -0
  51. synapse_sdk/mcp/resources/__init__.py +4 -0
  52. synapse_sdk/mcp/server.py +1352 -0
  53. synapse_sdk/mcp/tools/__init__.py +6 -0
  54. synapse_sdk/plugins/__init__.py +133 -9
  55. synapse_sdk/plugins/action.py +229 -0
  56. synapse_sdk/plugins/actions/__init__.py +82 -0
  57. synapse_sdk/plugins/actions/dataset/__init__.py +37 -0
  58. synapse_sdk/plugins/actions/dataset/action.py +471 -0
  59. synapse_sdk/plugins/actions/export/__init__.py +55 -0
  60. synapse_sdk/plugins/actions/export/action.py +183 -0
  61. synapse_sdk/plugins/actions/export/context.py +59 -0
  62. synapse_sdk/plugins/actions/inference/__init__.py +84 -0
  63. synapse_sdk/plugins/actions/inference/action.py +285 -0
  64. synapse_sdk/plugins/actions/inference/context.py +81 -0
  65. synapse_sdk/plugins/actions/inference/deployment.py +322 -0
  66. synapse_sdk/plugins/actions/inference/serve.py +252 -0
  67. synapse_sdk/plugins/actions/train/__init__.py +54 -0
  68. synapse_sdk/plugins/actions/train/action.py +326 -0
  69. synapse_sdk/plugins/actions/train/context.py +57 -0
  70. synapse_sdk/plugins/actions/upload/__init__.py +49 -0
  71. synapse_sdk/plugins/actions/upload/action.py +165 -0
  72. synapse_sdk/plugins/actions/upload/context.py +61 -0
  73. synapse_sdk/plugins/config.py +98 -0
  74. synapse_sdk/plugins/context/__init__.py +109 -0
  75. synapse_sdk/plugins/context/env.py +113 -0
  76. synapse_sdk/plugins/datasets/__init__.py +113 -0
  77. synapse_sdk/plugins/datasets/converters/__init__.py +76 -0
  78. synapse_sdk/plugins/datasets/converters/base.py +347 -0
  79. synapse_sdk/plugins/datasets/converters/yolo/__init__.py +9 -0
  80. synapse_sdk/plugins/datasets/converters/yolo/from_dm.py +468 -0
  81. synapse_sdk/plugins/datasets/converters/yolo/to_dm.py +381 -0
  82. synapse_sdk/plugins/datasets/formats/__init__.py +82 -0
  83. synapse_sdk/plugins/datasets/formats/dm.py +351 -0
  84. synapse_sdk/plugins/datasets/formats/yolo.py +240 -0
  85. synapse_sdk/plugins/decorators.py +83 -0
  86. synapse_sdk/plugins/discovery.py +790 -0
  87. synapse_sdk/plugins/docs/ACTION_DEV_GUIDE.md +933 -0
  88. synapse_sdk/plugins/docs/ARCHITECTURE.md +1225 -0
  89. synapse_sdk/plugins/docs/LOGGING_SYSTEM.md +683 -0
  90. synapse_sdk/plugins/docs/OVERVIEW.md +531 -0
  91. synapse_sdk/plugins/docs/PIPELINE_GUIDE.md +145 -0
  92. synapse_sdk/plugins/docs/README.md +513 -0
  93. synapse_sdk/plugins/docs/STEP.md +656 -0
  94. synapse_sdk/plugins/enums.py +70 -10
  95. synapse_sdk/plugins/errors.py +92 -0
  96. synapse_sdk/plugins/executors/__init__.py +43 -0
  97. synapse_sdk/plugins/executors/local.py +99 -0
  98. synapse_sdk/plugins/executors/ray/__init__.py +18 -0
  99. synapse_sdk/plugins/executors/ray/base.py +282 -0
  100. synapse_sdk/plugins/executors/ray/job.py +298 -0
  101. synapse_sdk/plugins/executors/ray/jobs_api.py +511 -0
  102. synapse_sdk/plugins/executors/ray/packaging.py +137 -0
  103. synapse_sdk/plugins/executors/ray/pipeline.py +792 -0
  104. synapse_sdk/plugins/executors/ray/task.py +257 -0
  105. synapse_sdk/plugins/models/__init__.py +26 -0
  106. synapse_sdk/plugins/models/logger.py +173 -0
  107. synapse_sdk/plugins/models/pipeline.py +25 -0
  108. synapse_sdk/plugins/pipelines/__init__.py +81 -0
  109. synapse_sdk/plugins/pipelines/action_pipeline.py +417 -0
  110. synapse_sdk/plugins/pipelines/context.py +107 -0
  111. synapse_sdk/plugins/pipelines/display.py +311 -0
  112. synapse_sdk/plugins/runner.py +114 -0
  113. synapse_sdk/plugins/schemas/__init__.py +19 -0
  114. synapse_sdk/plugins/schemas/results.py +152 -0
  115. synapse_sdk/plugins/steps/__init__.py +63 -0
  116. synapse_sdk/plugins/steps/base.py +128 -0
  117. synapse_sdk/plugins/steps/context.py +90 -0
  118. synapse_sdk/plugins/steps/orchestrator.py +128 -0
  119. synapse_sdk/plugins/steps/registry.py +103 -0
  120. synapse_sdk/plugins/steps/utils/__init__.py +20 -0
  121. synapse_sdk/plugins/steps/utils/logging.py +85 -0
  122. synapse_sdk/plugins/steps/utils/timing.py +71 -0
  123. synapse_sdk/plugins/steps/utils/validation.py +68 -0
  124. synapse_sdk/plugins/templates/__init__.py +50 -0
  125. synapse_sdk/plugins/templates/base/.gitignore.j2 +26 -0
  126. synapse_sdk/plugins/templates/base/.synapseignore.j2 +11 -0
  127. synapse_sdk/plugins/templates/base/README.md.j2 +26 -0
  128. synapse_sdk/plugins/templates/base/plugin/__init__.py.j2 +1 -0
  129. synapse_sdk/plugins/templates/base/pyproject.toml.j2 +14 -0
  130. synapse_sdk/plugins/templates/base/requirements.txt.j2 +1 -0
  131. synapse_sdk/plugins/templates/custom/plugin/main.py.j2 +18 -0
  132. synapse_sdk/plugins/templates/data_validation/plugin/validate.py.j2 +32 -0
  133. synapse_sdk/plugins/templates/export/plugin/export.py.j2 +36 -0
  134. synapse_sdk/plugins/templates/neural_net/plugin/inference.py.j2 +36 -0
  135. synapse_sdk/plugins/templates/neural_net/plugin/train.py.j2 +33 -0
  136. synapse_sdk/plugins/templates/post_annotation/plugin/post_annotate.py.j2 +32 -0
  137. synapse_sdk/plugins/templates/pre_annotation/plugin/pre_annotate.py.j2 +32 -0
  138. synapse_sdk/plugins/templates/smart_tool/plugin/auto_label.py.j2 +44 -0
  139. synapse_sdk/plugins/templates/upload/plugin/upload.py.j2 +35 -0
  140. synapse_sdk/plugins/testing/__init__.py +25 -0
  141. synapse_sdk/plugins/testing/sample_actions.py +98 -0
  142. synapse_sdk/plugins/types.py +206 -0
  143. synapse_sdk/plugins/upload.py +595 -64
  144. synapse_sdk/plugins/utils.py +325 -37
  145. synapse_sdk/shared/__init__.py +25 -0
  146. synapse_sdk/utils/__init__.py +1 -0
  147. synapse_sdk/utils/auth.py +74 -0
  148. synapse_sdk/utils/file/__init__.py +58 -0
  149. synapse_sdk/utils/file/archive.py +449 -0
  150. synapse_sdk/utils/file/checksum.py +167 -0
  151. synapse_sdk/utils/file/download.py +286 -0
  152. synapse_sdk/utils/file/io.py +129 -0
  153. synapse_sdk/utils/file/requirements.py +36 -0
  154. synapse_sdk/utils/network.py +168 -0
  155. synapse_sdk/utils/storage/__init__.py +238 -0
  156. synapse_sdk/utils/storage/config.py +188 -0
  157. synapse_sdk/utils/storage/errors.py +52 -0
  158. synapse_sdk/utils/storage/providers/__init__.py +13 -0
  159. synapse_sdk/utils/storage/providers/base.py +76 -0
  160. synapse_sdk/utils/storage/providers/gcs.py +168 -0
  161. synapse_sdk/utils/storage/providers/http.py +250 -0
  162. synapse_sdk/utils/storage/providers/local.py +126 -0
  163. synapse_sdk/utils/storage/providers/s3.py +177 -0
  164. synapse_sdk/utils/storage/providers/sftp.py +208 -0
  165. synapse_sdk/utils/storage/registry.py +125 -0
  166. synapse_sdk/utils/websocket.py +99 -0
  167. synapse_sdk-2026.1.1b2.dist-info/METADATA +715 -0
  168. synapse_sdk-2026.1.1b2.dist-info/RECORD +172 -0
  169. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/WHEEL +1 -1
  170. synapse_sdk-2026.1.1b2.dist-info/licenses/LICENSE +201 -0
  171. locale/en/LC_MESSAGES/messages.mo +0 -0
  172. locale/en/LC_MESSAGES/messages.po +0 -39
  173. locale/ko/LC_MESSAGES/messages.mo +0 -0
  174. locale/ko/LC_MESSAGES/messages.po +0 -34
  175. synapse_sdk/cli/create_plugin.py +0 -10
  176. synapse_sdk/clients/agent/core.py +0 -7
  177. synapse_sdk/clients/agent/service.py +0 -15
  178. synapse_sdk/clients/backend/dataset.py +0 -51
  179. synapse_sdk/clients/ray/__init__.py +0 -6
  180. synapse_sdk/clients/ray/core.py +0 -22
  181. synapse_sdk/clients/ray/serve.py +0 -20
  182. synapse_sdk/i18n.py +0 -35
  183. synapse_sdk/plugins/categories/__init__.py +0 -0
  184. synapse_sdk/plugins/categories/base.py +0 -235
  185. synapse_sdk/plugins/categories/data_validation/__init__.py +0 -0
  186. synapse_sdk/plugins/categories/data_validation/actions/__init__.py +0 -0
  187. synapse_sdk/plugins/categories/data_validation/actions/validation.py +0 -10
  188. synapse_sdk/plugins/categories/data_validation/templates/config.yaml +0 -3
  189. synapse_sdk/plugins/categories/data_validation/templates/plugin/__init__.py +0 -0
  190. synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +0 -5
  191. synapse_sdk/plugins/categories/decorators.py +0 -13
  192. synapse_sdk/plugins/categories/export/__init__.py +0 -0
  193. synapse_sdk/plugins/categories/export/actions/__init__.py +0 -0
  194. synapse_sdk/plugins/categories/export/actions/export.py +0 -10
  195. synapse_sdk/plugins/categories/import/__init__.py +0 -0
  196. synapse_sdk/plugins/categories/import/actions/__init__.py +0 -0
  197. synapse_sdk/plugins/categories/import/actions/import.py +0 -10
  198. synapse_sdk/plugins/categories/neural_net/__init__.py +0 -0
  199. synapse_sdk/plugins/categories/neural_net/actions/__init__.py +0 -0
  200. synapse_sdk/plugins/categories/neural_net/actions/deployment.py +0 -45
  201. synapse_sdk/plugins/categories/neural_net/actions/inference.py +0 -18
  202. synapse_sdk/plugins/categories/neural_net/actions/test.py +0 -10
  203. synapse_sdk/plugins/categories/neural_net/actions/train.py +0 -143
  204. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +0 -12
  205. synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py +0 -0
  206. synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +0 -4
  207. synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py +0 -2
  208. synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +0 -14
  209. synapse_sdk/plugins/categories/post_annotation/__init__.py +0 -0
  210. synapse_sdk/plugins/categories/post_annotation/actions/__init__.py +0 -0
  211. synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py +0 -10
  212. synapse_sdk/plugins/categories/post_annotation/templates/config.yaml +0 -3
  213. synapse_sdk/plugins/categories/post_annotation/templates/plugin/__init__.py +0 -0
  214. synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.py +0 -3
  215. synapse_sdk/plugins/categories/pre_annotation/__init__.py +0 -0
  216. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +0 -0
  217. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py +0 -10
  218. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +0 -3
  219. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py +0 -0
  220. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py +0 -3
  221. synapse_sdk/plugins/categories/registry.py +0 -16
  222. synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
  223. synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
  224. synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +0 -37
  225. synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +0 -7
  226. synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
  227. synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +0 -11
  228. synapse_sdk/plugins/categories/templates.py +0 -32
  229. synapse_sdk/plugins/cli/__init__.py +0 -21
  230. synapse_sdk/plugins/cli/publish.py +0 -37
  231. synapse_sdk/plugins/cli/run.py +0 -67
  232. synapse_sdk/plugins/exceptions.py +0 -22
  233. synapse_sdk/plugins/models.py +0 -121
  234. synapse_sdk/plugins/templates/cookiecutter.json +0 -11
  235. synapse_sdk/plugins/templates/hooks/post_gen_project.py +0 -3
  236. synapse_sdk/plugins/templates/hooks/pre_prompt.py +0 -21
  237. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
  238. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
  239. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.gitignore +0 -27
  240. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml +0 -7
  241. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md +0 -5
  242. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -6
  243. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
  244. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py +0 -0
  245. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml +0 -13
  246. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +0 -1
  247. synapse_sdk/shared/enums.py +0 -8
  248. synapse_sdk/utils/debug.py +0 -5
  249. synapse_sdk/utils/file.py +0 -87
  250. synapse_sdk/utils/module_loading.py +0 -29
  251. synapse_sdk/utils/pydantic/__init__.py +0 -0
  252. synapse_sdk/utils/pydantic/config.py +0 -4
  253. synapse_sdk/utils/pydantic/errors.py +0 -33
  254. synapse_sdk/utils/pydantic/validators.py +0 -7
  255. synapse_sdk/utils/storage.py +0 -91
  256. synapse_sdk/utils/string.py +0 -11
  257. synapse_sdk-1.0.0a11.dist-info/LICENSE +0 -21
  258. synapse_sdk-1.0.0a11.dist-info/METADATA +0 -43
  259. synapse_sdk-1.0.0a11.dist-info/RECORD +0 -111
  260. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/entry_points.txt +0 -0
  261. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,168 @@
1
+ """Google Cloud Storage provider."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from synapse_sdk.utils.storage.config import GCSStorageConfig
9
+ from synapse_sdk.utils.storage.errors import (
10
+ StorageConnectionError,
11
+ StorageNotFoundError,
12
+ StorageUploadError,
13
+ )
14
+ from synapse_sdk.utils.storage.providers.base import _BaseStorageMixin
15
+
16
+ if TYPE_CHECKING:
17
+ from upath import UPath
18
+
19
+
20
+ class GCSStorage(_BaseStorageMixin):
21
+ """Storage provider for Google Cloud Storage.
22
+
23
+ Requires: universal-pathlib[gcs] (pip install universal-pathlib[gcs])
24
+
25
+ Args:
26
+ config: Configuration dict with GCS credentials.
27
+
28
+ Example:
29
+ >>> storage = GCSStorage({
30
+ ... 'bucket_name': 'my-bucket',
31
+ ... 'credentials': '/path/to/service-account.json',
32
+ ... })
33
+ >>> storage.upload(Path('/tmp/file.txt'), 'data/file.txt')
34
+ 'gs://my-bucket/data/file.txt'
35
+ """
36
+
37
+ def __init__(self, config: dict[str, Any]):
38
+ try:
39
+ from upath import UPath
40
+ except ImportError as e:
41
+ raise ImportError(
42
+ 'GCSStorage requires universal-pathlib[gcs]. Install with: pip install universal-pathlib[gcs]'
43
+ ) from e
44
+
45
+ validated = GCSStorageConfig.model_validate(config)
46
+
47
+ self._bucket_name = validated.bucket_name
48
+ upath_kwargs: dict[str, Any] = {'token': validated.credentials}
49
+ if validated.project:
50
+ upath_kwargs['project'] = validated.project
51
+
52
+ try:
53
+ self._upath = UPath(
54
+ f'gs://{validated.bucket_name}',
55
+ **upath_kwargs,
56
+ )
57
+ except Exception as e:
58
+ raise StorageConnectionError(
59
+ f'Failed to connect to GCS: {e}',
60
+ details={'bucket': validated.bucket_name},
61
+ ) from e
62
+
63
+ def upload(self, source: Path, target: str) -> str:
64
+ """Upload a file to GCS.
65
+
66
+ Args:
67
+ source: Local file path to upload.
68
+ target: Target path in GCS bucket.
69
+
70
+ Returns:
71
+ gs:// URL of uploaded file.
72
+ """
73
+ source_path = Path(source) if isinstance(source, str) else source
74
+
75
+ if not source_path.exists():
76
+ raise StorageNotFoundError(
77
+ f'Source file not found: {source_path}',
78
+ details={'source': str(source_path)},
79
+ )
80
+
81
+ target_path = self._normalize_path(target)
82
+
83
+ try:
84
+ with open(source_path, 'rb') as f:
85
+ (self._upath / target_path).write_bytes(f.read())
86
+ except Exception as e:
87
+ raise StorageUploadError(
88
+ f'Failed to upload to GCS: {e}',
89
+ details={'source': str(source_path), 'target': target_path},
90
+ ) from e
91
+
92
+ return self.get_url(target)
93
+
94
+ def exists(self, target: str) -> bool:
95
+ """Check if file exists in GCS.
96
+
97
+ Args:
98
+ target: Path to check.
99
+
100
+ Returns:
101
+ True if exists, False otherwise.
102
+ """
103
+ target_path = self._normalize_path(target)
104
+ return (self._upath / target_path).exists()
105
+
106
+ def get_url(self, target: str) -> str:
107
+ """Get gs:// URL for target.
108
+
109
+ Args:
110
+ target: Target path.
111
+
112
+ Returns:
113
+ gs:// URL string.
114
+ """
115
+ target_path = self._normalize_path(target)
116
+ if target_path:
117
+ return f'gs://{self._bucket_name}/{target_path}'
118
+ return f'gs://{self._bucket_name}'
119
+
120
+ def get_pathlib(self, path: str) -> UPath:
121
+ """Get UPath object for path.
122
+
123
+ Args:
124
+ path: Path relative to bucket root.
125
+
126
+ Returns:
127
+ UPath object.
128
+ """
129
+ normalized = self._normalize_path(path)
130
+ if not normalized:
131
+ return self._upath
132
+ return self._upath / normalized
133
+
134
+ def get_path_file_count(self, pathlib_obj: UPath) -> int:
135
+ """Count files in GCS path.
136
+
137
+ Args:
138
+ pathlib_obj: UPath object from get_pathlib().
139
+
140
+ Returns:
141
+ Number of files.
142
+ """
143
+ return self._count_files(pathlib_obj)
144
+
145
+ def get_path_total_size(self, pathlib_obj: UPath) -> int:
146
+ """Calculate total size of files in GCS path.
147
+
148
+ Args:
149
+ pathlib_obj: UPath object from get_pathlib().
150
+
151
+ Returns:
152
+ Total size in bytes.
153
+ """
154
+ return self._calculate_total_size(pathlib_obj)
155
+
156
+ def glob(self, pattern: str) -> list[UPath]:
157
+ """Glob pattern matching in GCS.
158
+
159
+ Args:
160
+ pattern: Glob pattern.
161
+
162
+ Returns:
163
+ List of matching UPath objects.
164
+ """
165
+ return list(self._upath.glob(pattern))
166
+
167
+
168
+ __all__ = ['GCSStorage']
@@ -0,0 +1,250 @@
1
+ """HTTP storage provider."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Any
8
+ from urllib.parse import urljoin
9
+
10
+ import requests
11
+
12
+ from synapse_sdk.utils.storage.config import HTTPStorageConfig
13
+ from synapse_sdk.utils.storage.errors import (
14
+ StorageError,
15
+ StorageNotFoundError,
16
+ StorageUploadError,
17
+ )
18
+
19
+
20
+ class HTTPStorage:
21
+ """Storage provider for HTTP file servers.
22
+
23
+ Note: This provider has limited functionality as HTTP servers typically
24
+ don't support directory listing. File counting and size calculation
25
+ are not supported.
26
+
27
+ Args:
28
+ config: Configuration dict with HTTP server details.
29
+
30
+ Example:
31
+ >>> storage = HTTPStorage({
32
+ ... 'base_url': 'https://files.example.com/uploads/',
33
+ ... 'timeout': 60,
34
+ ... })
35
+ >>> storage.exists('data/file.txt')
36
+ True
37
+ """
38
+
39
+ def __init__(self, config: dict[str, Any]):
40
+ validated = HTTPStorageConfig.model_validate(config)
41
+
42
+ self._base_url = validated.base_url
43
+ self._timeout = validated.timeout
44
+ self._headers = validated.headers
45
+
46
+ # Setup session for connection pooling
47
+ self._session = requests.Session()
48
+ if self._headers:
49
+ self._session.headers.update(self._headers)
50
+
51
+ def _get_full_url(self, path: str) -> str:
52
+ """Get full URL for a path."""
53
+ if path.startswith('/'):
54
+ path = path[1:]
55
+ return urljoin(self._base_url, path)
56
+
57
+ def upload(self, source: Path, target: str) -> str:
58
+ """Upload a file to HTTP server.
59
+
60
+ Note: Requires server to support PUT or POST for file uploads.
61
+
62
+ Args:
63
+ source: Local file path to upload.
64
+ target: Target path on server.
65
+
66
+ Returns:
67
+ URL of uploaded file.
68
+ """
69
+ source_path = Path(source) if isinstance(source, str) else source
70
+
71
+ if not source_path.exists():
72
+ raise StorageNotFoundError(
73
+ f'Source file not found: {source_path}',
74
+ details={'source': str(source_path)},
75
+ )
76
+
77
+ url = self._get_full_url(target)
78
+
79
+ try:
80
+ with open(source_path, 'rb') as f:
81
+ files = {'file': (os.path.basename(str(source_path)), f)}
82
+
83
+ # Try PUT first, fallback to POST
84
+ response = self._session.put(url, files=files, timeout=self._timeout)
85
+
86
+ if response.status_code == 405:
87
+ f.seek(0)
88
+ response = self._session.post(url, files=files, timeout=self._timeout)
89
+
90
+ response.raise_for_status()
91
+ except requests.RequestException as e:
92
+ raise StorageUploadError(
93
+ f'Failed to upload to HTTP server: {e}',
94
+ details={'url': url},
95
+ ) from e
96
+
97
+ return url
98
+
99
+ def exists(self, target: str) -> bool:
100
+ """Check if file exists on HTTP server.
101
+
102
+ Uses HEAD request to check existence.
103
+
104
+ Args:
105
+ target: Path to check.
106
+
107
+ Returns:
108
+ True if file exists (HTTP 200), False otherwise.
109
+ """
110
+ url = self._get_full_url(target)
111
+
112
+ try:
113
+ response = self._session.head(url, timeout=self._timeout)
114
+ return response.status_code == 200
115
+ except requests.RequestException:
116
+ return False
117
+
118
+ def get_url(self, target: str) -> str:
119
+ """Get full URL for target.
120
+
121
+ Args:
122
+ target: Target path.
123
+
124
+ Returns:
125
+ Full HTTP URL.
126
+ """
127
+ return self._get_full_url(target)
128
+
129
+ def get_pathlib(self, path: str) -> HTTPPath:
130
+ """Get HTTPPath object for path.
131
+
132
+ Args:
133
+ path: Path on server.
134
+
135
+ Returns:
136
+ HTTPPath object (pathlib-like interface).
137
+ """
138
+ return HTTPPath(self, path)
139
+
140
+ def get_path_file_count(self, pathlib_obj: HTTPPath) -> int:
141
+ """Not supported for HTTP storage.
142
+
143
+ Raises:
144
+ StorageError: Always, as HTTP servers don't support directory listing.
145
+ """
146
+ raise StorageError(
147
+ 'File counting not supported for HTTP storage',
148
+ details={'reason': 'HTTP servers do not provide directory listing'},
149
+ )
150
+
151
+ def get_path_total_size(self, pathlib_obj: HTTPPath) -> int:
152
+ """Not supported for HTTP storage.
153
+
154
+ Raises:
155
+ StorageError: Always, as HTTP servers don't support directory listing.
156
+ """
157
+ raise StorageError(
158
+ 'Size calculation not supported for HTTP storage',
159
+ details={'reason': 'HTTP servers do not provide directory listing'},
160
+ )
161
+
162
+
163
+ class HTTPPath:
164
+ """Pathlib-like interface for HTTP paths.
165
+
166
+ Provides a subset of pathlib.Path functionality for HTTP resources.
167
+ """
168
+
169
+ def __init__(self, storage: HTTPStorage, path: str):
170
+ self._storage = storage
171
+ self._path = path.strip('/')
172
+
173
+ def __str__(self) -> str:
174
+ return self._storage.get_url(self._path)
175
+
176
+ def __repr__(self) -> str:
177
+ return f"HTTPPath('{self}')"
178
+
179
+ def __truediv__(self, other: str) -> HTTPPath:
180
+ """Join paths using / operator."""
181
+ new_path = f'{self._path}/{other}' if self._path else str(other)
182
+ return HTTPPath(self._storage, new_path)
183
+
184
+ def joinuri(self, *parts: str) -> HTTPPath:
185
+ """Join path parts."""
186
+ all_parts = [self._path] + [str(p).strip('/') for p in parts]
187
+ new_path = '/'.join(p for p in all_parts if p)
188
+ return HTTPPath(self._storage, new_path)
189
+
190
+ @property
191
+ def name(self) -> str:
192
+ """Get the final component of the path."""
193
+ return os.path.basename(self._path)
194
+
195
+ @property
196
+ def parent(self) -> HTTPPath:
197
+ """Get the parent directory."""
198
+ parent_path = os.path.dirname(self._path)
199
+ return HTTPPath(self._storage, parent_path)
200
+
201
+ def exists(self) -> bool:
202
+ """Check if this path exists."""
203
+ return self._storage.exists(self._path)
204
+
205
+ def is_file(self) -> bool:
206
+ """Check if this path is a file (assumes exists = is_file for HTTP)."""
207
+ return self.exists()
208
+
209
+ def is_dir(self) -> bool:
210
+ """Check if this path is a directory.
211
+
212
+ Note: HTTP servers don't typically distinguish directories.
213
+ This always returns False.
214
+ """
215
+ return False
216
+
217
+ def read_bytes(self) -> bytes:
218
+ """Read file contents as bytes."""
219
+ url = self._storage.get_url(self._path)
220
+ response = self._storage._session.get(url, timeout=self._storage._timeout)
221
+ response.raise_for_status()
222
+ return response.content
223
+
224
+ def read_text(self, encoding: str = 'utf-8') -> str:
225
+ """Read file contents as text."""
226
+ return self.read_bytes().decode(encoding)
227
+
228
+ def stat(self) -> HTTPStat:
229
+ """Get file statistics.
230
+
231
+ Note: Only st_size is populated via Content-Length header.
232
+ """
233
+ url = self._storage.get_url(self._path)
234
+ response = self._storage._session.head(url, timeout=self._storage._timeout)
235
+ response.raise_for_status()
236
+
237
+ content_length = response.headers.get('Content-Length')
238
+ size = int(content_length) if content_length else 0
239
+
240
+ return HTTPStat(st_size=size)
241
+
242
+
243
+ class HTTPStat:
244
+ """Minimal stat result for HTTP files."""
245
+
246
+ def __init__(self, st_size: int = 0):
247
+ self.st_size = st_size
248
+
249
+
250
+ __all__ = ['HTTPStorage', 'HTTPPath']
@@ -0,0 +1,126 @@
1
+ """Local filesystem storage provider."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from synapse_sdk.utils.storage.config import LocalStorageConfig
10
+ from synapse_sdk.utils.storage.errors import StorageNotFoundError, StorageUploadError
11
+ from synapse_sdk.utils.storage.providers.base import _BaseStorageMixin
12
+
13
+
14
+ class LocalStorage(_BaseStorageMixin):
15
+ """Storage provider for local filesystem.
16
+
17
+ Args:
18
+ config: Configuration dict with 'location' key.
19
+
20
+ Example:
21
+ >>> storage = LocalStorage({'location': '/data'})
22
+ >>> storage.upload(Path('/tmp/file.txt'), 'uploads/file.txt')
23
+ 'file:///data/uploads/file.txt'
24
+ """
25
+
26
+ def __init__(self, config: dict[str, Any]):
27
+ validated = LocalStorageConfig.model_validate(config)
28
+ self.base_path = Path(validated.location)
29
+
30
+ def upload(self, source: Path, target: str) -> str:
31
+ """Upload a file from source to target location.
32
+
33
+ Args:
34
+ source: Path to source file.
35
+ target: Target path relative to base path.
36
+
37
+ Returns:
38
+ file:// URL of uploaded file.
39
+
40
+ Raises:
41
+ StorageNotFoundError: If source file doesn't exist.
42
+ StorageUploadError: If copy operation fails.
43
+ """
44
+ source_path = Path(source) if isinstance(source, str) else source
45
+
46
+ if not source_path.exists():
47
+ raise StorageNotFoundError(
48
+ f'Source file not found: {source_path}',
49
+ details={'source': str(source_path)},
50
+ )
51
+
52
+ target_path = self.base_path / self._normalize_path(target)
53
+
54
+ try:
55
+ target_path.parent.mkdir(parents=True, exist_ok=True)
56
+ shutil.copy2(source_path, target_path)
57
+ except OSError as e:
58
+ raise StorageUploadError(
59
+ f'Failed to upload file: {e}',
60
+ details={'source': str(source_path), 'target': str(target_path)},
61
+ ) from e
62
+
63
+ return self.get_url(target)
64
+
65
+ def exists(self, target: str) -> bool:
66
+ """Check if target file exists.
67
+
68
+ Args:
69
+ target: Target path relative to base path.
70
+
71
+ Returns:
72
+ True if file exists, False otherwise.
73
+ """
74
+ target_path = self.base_path / self._normalize_path(target)
75
+ return target_path.exists()
76
+
77
+ def get_url(self, target: str) -> str:
78
+ """Get file:// URL for target file.
79
+
80
+ Args:
81
+ target: Target path relative to base path.
82
+
83
+ Returns:
84
+ file:// URL string.
85
+ """
86
+ target_path = self.base_path / self._normalize_path(target)
87
+ return f'file://{target_path.absolute()}'
88
+
89
+ def get_pathlib(self, path: str) -> Path:
90
+ """Get pathlib.Path object for the path.
91
+
92
+ Args:
93
+ path: Path relative to storage root.
94
+
95
+ Returns:
96
+ pathlib.Path object.
97
+ """
98
+ normalized = self._normalize_path(path)
99
+ if not normalized:
100
+ return self.base_path
101
+ return self.base_path / normalized
102
+
103
+ def get_path_file_count(self, pathlib_obj: Path) -> int:
104
+ """Get file count in the path.
105
+
106
+ Args:
107
+ pathlib_obj: Path object from get_pathlib().
108
+
109
+ Returns:
110
+ Number of files.
111
+ """
112
+ return self._count_files(pathlib_obj)
113
+
114
+ def get_path_total_size(self, pathlib_obj: Path) -> int:
115
+ """Get total size of files in the path.
116
+
117
+ Args:
118
+ pathlib_obj: Path object from get_pathlib().
119
+
120
+ Returns:
121
+ Total size in bytes.
122
+ """
123
+ return self._calculate_total_size(pathlib_obj)
124
+
125
+
126
+ __all__ = ['LocalStorage']