synapse-sdk 1.0.0a35__py3-none-any.whl → 2025.11.7__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 (307) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/__init__.py +308 -5
  3. synapse_sdk/cli/alias/utils.py +1 -1
  4. synapse_sdk/cli/code_server.py +687 -0
  5. synapse_sdk/cli/config.py +440 -0
  6. synapse_sdk/cli/devtools.py +90 -0
  7. synapse_sdk/cli/plugin/publish.py +23 -15
  8. synapse_sdk/clients/agent/__init__.py +9 -3
  9. synapse_sdk/clients/agent/container.py +133 -0
  10. synapse_sdk/clients/agent/core.py +19 -0
  11. synapse_sdk/clients/agent/ray.py +298 -9
  12. synapse_sdk/clients/backend/__init__.py +28 -12
  13. synapse_sdk/clients/backend/annotation.py +9 -1
  14. synapse_sdk/clients/backend/core.py +31 -4
  15. synapse_sdk/clients/backend/data_collection.py +186 -0
  16. synapse_sdk/clients/backend/hitl.py +1 -1
  17. synapse_sdk/clients/backend/integration.py +4 -3
  18. synapse_sdk/clients/backend/ml.py +1 -1
  19. synapse_sdk/clients/backend/models.py +35 -1
  20. synapse_sdk/clients/base.py +309 -36
  21. synapse_sdk/clients/ray/serve.py +2 -0
  22. synapse_sdk/devtools/__init__.py +0 -0
  23. synapse_sdk/devtools/config.py +94 -0
  24. synapse_sdk/devtools/docs/.gitignore +20 -0
  25. synapse_sdk/devtools/docs/README.md +41 -0
  26. synapse_sdk/devtools/docs/blog/2019-05-28-first-blog-post.md +12 -0
  27. synapse_sdk/devtools/docs/blog/2019-05-29-long-blog-post.md +44 -0
  28. synapse_sdk/devtools/docs/blog/2021-08-01-mdx-blog-post.mdx +24 -0
  29. synapse_sdk/devtools/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  30. synapse_sdk/devtools/docs/blog/2021-08-26-welcome/index.md +29 -0
  31. synapse_sdk/devtools/docs/blog/authors.yml +25 -0
  32. synapse_sdk/devtools/docs/blog/tags.yml +19 -0
  33. synapse_sdk/devtools/docs/docs/api/clients/agent.md +43 -0
  34. synapse_sdk/devtools/docs/docs/api/clients/annotation-mixin.md +378 -0
  35. synapse_sdk/devtools/docs/docs/api/clients/backend.md +420 -0
  36. synapse_sdk/devtools/docs/docs/api/clients/base.md +257 -0
  37. synapse_sdk/devtools/docs/docs/api/clients/core-mixin.md +477 -0
  38. synapse_sdk/devtools/docs/docs/api/clients/data-collection-mixin.md +422 -0
  39. synapse_sdk/devtools/docs/docs/api/clients/hitl-mixin.md +554 -0
  40. synapse_sdk/devtools/docs/docs/api/clients/index.md +391 -0
  41. synapse_sdk/devtools/docs/docs/api/clients/integration-mixin.md +571 -0
  42. synapse_sdk/devtools/docs/docs/api/clients/ml-mixin.md +578 -0
  43. synapse_sdk/devtools/docs/docs/api/clients/ray.md +342 -0
  44. synapse_sdk/devtools/docs/docs/api/index.md +52 -0
  45. synapse_sdk/devtools/docs/docs/api/plugins/categories.md +43 -0
  46. synapse_sdk/devtools/docs/docs/api/plugins/models.md +114 -0
  47. synapse_sdk/devtools/docs/docs/api/plugins/utils.md +328 -0
  48. synapse_sdk/devtools/docs/docs/categories.md +0 -0
  49. synapse_sdk/devtools/docs/docs/cli-usage.md +280 -0
  50. synapse_sdk/devtools/docs/docs/concepts/index.md +38 -0
  51. synapse_sdk/devtools/docs/docs/configuration.md +83 -0
  52. synapse_sdk/devtools/docs/docs/contributing.md +306 -0
  53. synapse_sdk/devtools/docs/docs/examples/index.md +29 -0
  54. synapse_sdk/devtools/docs/docs/faq.md +179 -0
  55. synapse_sdk/devtools/docs/docs/features/converters/index.md +455 -0
  56. synapse_sdk/devtools/docs/docs/features/index.md +24 -0
  57. synapse_sdk/devtools/docs/docs/features/utils/file.md +415 -0
  58. synapse_sdk/devtools/docs/docs/features/utils/network.md +378 -0
  59. synapse_sdk/devtools/docs/docs/features/utils/storage.md +57 -0
  60. synapse_sdk/devtools/docs/docs/features/utils/types.md +51 -0
  61. synapse_sdk/devtools/docs/docs/installation.md +94 -0
  62. synapse_sdk/devtools/docs/docs/introduction.md +47 -0
  63. synapse_sdk/devtools/docs/docs/plugins/categories/neural-net-plugins/train-action-overview.md +814 -0
  64. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/pre-annotation-plugin-overview.md +198 -0
  65. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-action-development.md +1645 -0
  66. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-overview.md +717 -0
  67. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-template-development.md +1380 -0
  68. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-action.md +948 -0
  69. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-overview.md +544 -0
  70. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-template.md +766 -0
  71. synapse_sdk/devtools/docs/docs/plugins/export-plugins.md +1092 -0
  72. synapse_sdk/devtools/docs/docs/plugins/plugins.md +852 -0
  73. synapse_sdk/devtools/docs/docs/quickstart.md +78 -0
  74. synapse_sdk/devtools/docs/docs/troubleshooting.md +519 -0
  75. synapse_sdk/devtools/docs/docs/tutorial-basics/_category_.json +8 -0
  76. synapse_sdk/devtools/docs/docs/tutorial-basics/congratulations.md +23 -0
  77. synapse_sdk/devtools/docs/docs/tutorial-basics/create-a-blog-post.md +34 -0
  78. synapse_sdk/devtools/docs/docs/tutorial-basics/create-a-document.md +57 -0
  79. synapse_sdk/devtools/docs/docs/tutorial-basics/create-a-page.md +43 -0
  80. synapse_sdk/devtools/docs/docs/tutorial-basics/deploy-your-site.md +31 -0
  81. synapse_sdk/devtools/docs/docs/tutorial-basics/markdown-features.mdx +152 -0
  82. synapse_sdk/devtools/docs/docs/tutorial-extras/_category_.json +7 -0
  83. synapse_sdk/devtools/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
  84. synapse_sdk/devtools/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
  85. synapse_sdk/devtools/docs/docs/tutorial-extras/manage-docs-versions.md +55 -0
  86. synapse_sdk/devtools/docs/docs/tutorial-extras/translate-your-site.md +88 -0
  87. synapse_sdk/devtools/docs/docusaurus.config.ts +148 -0
  88. synapse_sdk/devtools/docs/i18n/ko/code.json +325 -0
  89. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/agent.md +43 -0
  90. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/annotation-mixin.md +289 -0
  91. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/backend.md +420 -0
  92. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/base.md +257 -0
  93. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/core-mixin.md +417 -0
  94. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/data-collection-mixin.md +356 -0
  95. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/hitl-mixin.md +192 -0
  96. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/index.md +391 -0
  97. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/integration-mixin.md +479 -0
  98. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/ml-mixin.md +284 -0
  99. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/ray.md +342 -0
  100. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/index.md +52 -0
  101. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/plugins/models.md +114 -0
  102. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/categories.md +0 -0
  103. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/cli-usage.md +280 -0
  104. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/concepts/index.md +38 -0
  105. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/configuration.md +83 -0
  106. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/contributing.md +306 -0
  107. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/examples/index.md +29 -0
  108. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/faq.md +179 -0
  109. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/converters/index.md +30 -0
  110. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/index.md +24 -0
  111. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/file.md +415 -0
  112. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/network.md +378 -0
  113. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/storage.md +60 -0
  114. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/types.md +51 -0
  115. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/installation.md +94 -0
  116. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/introduction.md +47 -0
  117. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/neural-net-plugins/train-action-overview.md +815 -0
  118. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/pre-annotation-plugin-overview.md +198 -0
  119. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-action-development.md +1645 -0
  120. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-overview.md +717 -0
  121. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-template-development.md +1380 -0
  122. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-action.md +948 -0
  123. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-overview.md +544 -0
  124. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-template.md +766 -0
  125. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/export-plugins.md +1092 -0
  126. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/plugins.md +117 -0
  127. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/quickstart.md +78 -0
  128. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/troubleshooting.md +519 -0
  129. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current.json +34 -0
  130. synapse_sdk/devtools/docs/i18n/ko/docusaurus-theme-classic/footer.json +42 -0
  131. synapse_sdk/devtools/docs/i18n/ko/docusaurus-theme-classic/navbar.json +18 -0
  132. synapse_sdk/devtools/docs/package-lock.json +18784 -0
  133. synapse_sdk/devtools/docs/package.json +48 -0
  134. synapse_sdk/devtools/docs/sidebars.ts +122 -0
  135. synapse_sdk/devtools/docs/src/components/HomepageFeatures/index.tsx +71 -0
  136. synapse_sdk/devtools/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  137. synapse_sdk/devtools/docs/src/css/custom.css +30 -0
  138. synapse_sdk/devtools/docs/src/pages/index.module.css +23 -0
  139. synapse_sdk/devtools/docs/src/pages/index.tsx +21 -0
  140. synapse_sdk/devtools/docs/src/pages/markdown-page.md +7 -0
  141. synapse_sdk/devtools/docs/static/.nojekyll +0 -0
  142. synapse_sdk/devtools/docs/static/img/docusaurus-social-card.jpg +0 -0
  143. synapse_sdk/devtools/docs/static/img/docusaurus.png +0 -0
  144. synapse_sdk/devtools/docs/static/img/favicon.ico +0 -0
  145. synapse_sdk/devtools/docs/static/img/logo.png +0 -0
  146. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  147. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_react.svg +170 -0
  148. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  149. synapse_sdk/devtools/docs/tsconfig.json +8 -0
  150. synapse_sdk/devtools/server.py +41 -0
  151. synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
  152. synapse_sdk/devtools/streamlit_app/app.py +128 -0
  153. synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
  154. synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
  155. synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
  156. synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
  157. synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
  158. synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
  159. synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
  160. synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
  161. synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
  162. synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
  163. synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
  164. synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
  165. synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
  166. synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
  167. synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
  168. synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
  169. synapse_sdk/devtools/streamlit_app.py +10 -0
  170. synapse_sdk/loggers.py +65 -7
  171. synapse_sdk/plugins/README.md +1340 -0
  172. synapse_sdk/plugins/categories/base.py +73 -11
  173. synapse_sdk/plugins/categories/data_validation/actions/validation.py +72 -0
  174. synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +33 -5
  175. synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
  176. synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
  177. synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
  178. synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
  179. synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
  180. synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
  181. synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
  182. synapse_sdk/plugins/categories/export/actions/{export.py → export/utils.py} +47 -82
  183. synapse_sdk/plugins/categories/export/templates/config.yaml +19 -1
  184. synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
  185. synapse_sdk/plugins/categories/export/templates/plugin/export.py +153 -129
  186. synapse_sdk/plugins/categories/neural_net/actions/deployment.py +9 -62
  187. synapse_sdk/plugins/categories/neural_net/actions/train.py +1062 -32
  188. synapse_sdk/plugins/categories/neural_net/actions/tune.py +534 -0
  189. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +27 -5
  190. synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +26 -10
  191. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
  192. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
  193. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/action.py +10 -0
  194. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
  195. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +145 -0
  196. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
  197. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
  198. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
  199. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +97 -0
  200. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +250 -0
  201. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
  202. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
  203. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +287 -0
  204. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
  205. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
  206. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +87 -0
  207. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +127 -0
  208. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
  209. synapse_sdk/plugins/categories/pre_annotation/actions/to_task.py +966 -0
  210. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +19 -0
  211. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/to_task.py +40 -0
  212. synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
  213. synapse_sdk/plugins/categories/upload/actions/upload/action.py +232 -0
  214. synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
  215. synapse_sdk/plugins/categories/upload/actions/upload/enums.py +471 -0
  216. synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
  217. synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
  218. synapse_sdk/plugins/categories/upload/actions/upload/models.py +203 -0
  219. synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
  220. synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
  221. synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
  222. synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
  223. synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
  224. synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
  225. synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
  226. synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +84 -0
  227. synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
  228. synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
  229. synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +203 -0
  230. synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +97 -0
  231. synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
  232. synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
  233. synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
  234. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
  235. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
  236. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
  237. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
  238. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +258 -0
  239. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +281 -0
  240. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
  241. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
  242. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
  243. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
  244. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
  245. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
  246. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
  247. synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
  248. synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
  249. synapse_sdk/plugins/categories/upload/templates/config.yaml +29 -2
  250. synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +294 -0
  251. synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +88 -30
  252. synapse_sdk/plugins/models.py +122 -16
  253. synapse_sdk/plugins/templates/plugin-config-schema.json +406 -0
  254. synapse_sdk/plugins/templates/schema.json +491 -0
  255. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +1 -1
  256. synapse_sdk/plugins/utils/__init__.py +46 -0
  257. synapse_sdk/plugins/utils/actions.py +119 -0
  258. synapse_sdk/plugins/utils/config.py +203 -0
  259. synapse_sdk/plugins/{utils.py → utils/legacy.py} +26 -46
  260. synapse_sdk/plugins/utils/ray_gcs.py +66 -0
  261. synapse_sdk/plugins/utils/registry.py +58 -0
  262. synapse_sdk/shared/__init__.py +25 -0
  263. synapse_sdk/shared/enums.py +93 -0
  264. synapse_sdk/utils/converters/__init__.py +240 -0
  265. synapse_sdk/utils/converters/coco/__init__.py +0 -0
  266. synapse_sdk/utils/converters/coco/from_dm.py +322 -0
  267. synapse_sdk/utils/converters/coco/to_dm.py +215 -0
  268. synapse_sdk/utils/converters/dm/__init__.py +56 -0
  269. synapse_sdk/utils/converters/dm/from_v1.py +627 -0
  270. synapse_sdk/utils/converters/dm/to_v1.py +367 -0
  271. synapse_sdk/utils/converters/pascal/__init__.py +0 -0
  272. synapse_sdk/utils/converters/pascal/from_dm.py +244 -0
  273. synapse_sdk/utils/converters/pascal/to_dm.py +214 -0
  274. synapse_sdk/utils/converters/yolo/__init__.py +0 -0
  275. synapse_sdk/utils/converters/yolo/from_dm.py +384 -0
  276. synapse_sdk/utils/converters/yolo/to_dm.py +267 -0
  277. synapse_sdk/utils/dataset.py +46 -0
  278. synapse_sdk/utils/encryption.py +158 -0
  279. synapse_sdk/utils/file/__init__.py +39 -0
  280. synapse_sdk/utils/file/archive.py +32 -0
  281. synapse_sdk/utils/file/checksum.py +56 -0
  282. synapse_sdk/utils/file/chunking.py +31 -0
  283. synapse_sdk/utils/file/download.py +385 -0
  284. synapse_sdk/utils/file/encoding.py +40 -0
  285. synapse_sdk/utils/file/io.py +22 -0
  286. synapse_sdk/utils/file/video/__init__.py +29 -0
  287. synapse_sdk/utils/file/video/transcode.py +307 -0
  288. synapse_sdk/utils/{file.py → file.py.backup} +84 -2
  289. synapse_sdk/utils/http.py +138 -0
  290. synapse_sdk/utils/network.py +293 -0
  291. synapse_sdk/utils/storage/__init__.py +36 -2
  292. synapse_sdk/utils/storage/providers/__init__.py +141 -0
  293. synapse_sdk/utils/storage/providers/file_system.py +134 -0
  294. synapse_sdk/utils/storage/providers/http.py +190 -0
  295. synapse_sdk/utils/storage/providers/s3.py +54 -6
  296. synapse_sdk/utils/storage/providers/sftp.py +31 -0
  297. synapse_sdk/utils/storage/registry.py +6 -0
  298. synapse_sdk-2025.11.7.dist-info/METADATA +122 -0
  299. synapse_sdk-2025.11.7.dist-info/RECORD +386 -0
  300. {synapse_sdk-1.0.0a35.dist-info → synapse_sdk-2025.11.7.dist-info}/WHEEL +1 -1
  301. synapse_sdk/clients/backend/dataset.py +0 -102
  302. synapse_sdk/plugins/categories/upload/actions/upload.py +0 -293
  303. synapse_sdk-1.0.0a35.dist-info/METADATA +0 -47
  304. synapse_sdk-1.0.0a35.dist-info/RECORD +0 -137
  305. {synapse_sdk-1.0.0a35.dist-info → synapse_sdk-2025.11.7.dist-info}/entry_points.txt +0 -0
  306. {synapse_sdk-1.0.0a35.dist-info → synapse_sdk-2025.11.7.dist-info}/licenses/LICENSE +0 -0
  307. {synapse_sdk-1.0.0a35.dist-info → synapse_sdk-2025.11.7.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,277 @@
1
+ import asyncio
2
+ import queue as queue_module
3
+ import re
4
+ import ssl
5
+ from concurrent.futures import ThreadPoolExecutor
6
+ from dataclasses import dataclass
7
+ from typing import Any, Dict, Generator, Optional
1
8
  from urllib.parse import urlparse, urlunparse
2
9
 
10
+ import requests
11
+
12
+ from synapse_sdk.clients.exceptions import ClientError
13
+
14
+
15
+ @dataclass
16
+ class StreamLimits:
17
+ """Configuration for streaming limits."""
18
+
19
+ max_messages: int = 10000
20
+ max_lines: int = 50000
21
+ max_bytes: int = 50 * 1024 * 1024 # 50MB
22
+ max_message_size: int = 10240 # 10KB
23
+ queue_size: int = 1000
24
+ exception_queue_size: int = 10
25
+
26
+
27
+ def validate_resource_id(resource_id: Any, resource_name: str = 'resource') -> str:
28
+ """Validate resource ID to prevent injection attacks."""
29
+ if not resource_id:
30
+ raise ClientError(400, f'{resource_name} ID cannot be empty')
31
+
32
+ # Allow numeric IDs and UUID formats
33
+ id_str = str(resource_id)
34
+ if not re.match(r'^[a-zA-Z0-9\-_]+$', id_str):
35
+ raise ClientError(400, f'Invalid {resource_name} ID format')
36
+
37
+ if len(id_str) > 100:
38
+ raise ClientError(400, f'{resource_name} ID too long')
39
+
40
+ return id_str
41
+
42
+
43
+ def validate_timeout(timeout: Any, max_timeout: int = 300) -> float:
44
+ """Validate timeout value with bounds checking."""
45
+ if not isinstance(timeout, (int, float)) or timeout <= 0:
46
+ raise ClientError(400, 'Timeout must be a positive number')
47
+
48
+ if timeout > max_timeout:
49
+ raise ClientError(400, f'Timeout cannot exceed {max_timeout} seconds')
50
+
51
+ return float(timeout)
52
+
53
+
54
+ def sanitize_error_message(error_msg: str, context: str = '') -> str:
55
+ """Sanitize error messages to prevent information disclosure."""
56
+ sanitized = str(error_msg)[:100]
57
+ # Remove any potential sensitive information
58
+ sanitized = re.sub(r'["\']([^"\']*)["\']', '"[REDACTED]"', sanitized)
59
+
60
+ if context:
61
+ return f'{context}: {sanitized}'
62
+ return sanitized
63
+
64
+
65
+ def http_to_websocket_url(url: str) -> str:
66
+ """Convert HTTP/HTTPS URL to WebSocket URL safely."""
67
+ try:
68
+ parsed = urlparse(url)
69
+ if parsed.scheme == 'http':
70
+ ws_scheme = 'ws'
71
+ elif parsed.scheme == 'https':
72
+ ws_scheme = 'wss'
73
+ else:
74
+ raise ClientError(400, f'Invalid URL scheme: {parsed.scheme}')
75
+
76
+ ws_url = urlunparse((ws_scheme, parsed.netloc, parsed.path, parsed.params, parsed.query, parsed.fragment))
77
+ return ws_url
78
+ except Exception as e:
79
+ raise ClientError(400, f'Invalid URL format: {str(e)[:50]}')
80
+
81
+
82
+ def check_library_available(library_name: str) -> bool:
83
+ """Check if optional library is available."""
84
+ try:
85
+ __import__(library_name)
86
+ return True
87
+ except ImportError:
88
+ return False
89
+
90
+
91
+ class WebSocketStreamManager:
92
+ """Manages secure WebSocket streaming with rate limiting and error handling."""
93
+
94
+ def __init__(self, thread_pool: ThreadPoolExecutor, limits: Optional[StreamLimits] = None):
95
+ self.thread_pool = thread_pool
96
+ self.limits = limits or StreamLimits()
97
+
98
+ def stream_logs(
99
+ self, ws_url: str, headers: Dict[str, str], timeout: float, context: str
100
+ ) -> Generator[str, None, None]:
101
+ """Stream logs from WebSocket with proper error handling and cleanup."""
102
+ if not check_library_available('websockets'):
103
+ raise ClientError(500, 'websockets library not available for WebSocket connections')
104
+
105
+ try:
106
+ import websockets
107
+
108
+ # Use bounded queues to prevent memory exhaustion
109
+ message_queue = queue_module.Queue(maxsize=self.limits.queue_size)
110
+ exception_queue = queue_module.Queue(maxsize=self.limits.exception_queue_size)
111
+
112
+ async def websocket_client():
113
+ try:
114
+ # Add SSL verification and proper timeouts
115
+ connect_kwargs = {
116
+ 'extra_headers': headers,
117
+ 'close_timeout': timeout,
118
+ 'ping_timeout': timeout,
119
+ 'ping_interval': timeout // 2,
120
+ }
121
+
122
+ # For secure connections, add SSL context
123
+ if ws_url.startswith('wss://'):
124
+ ssl_context = ssl.create_default_context()
125
+ ssl_context.check_hostname = True
126
+ ssl_context.verify_mode = ssl.CERT_REQUIRED
127
+ connect_kwargs['ssl'] = ssl_context
128
+
129
+ async with websockets.connect(ws_url, **connect_kwargs) as websocket:
130
+ message_count = 0
131
+
132
+ async for message in websocket:
133
+ message_count += 1
134
+ if message_count > self.limits.max_messages:
135
+ exception_queue.put_nowait(ClientError(429, f'Message limit exceeded for {context}'))
136
+ break
137
+
138
+ # Validate message size
139
+ if len(str(message)) > self.limits.max_message_size:
140
+ continue
141
+
142
+ try:
143
+ message_queue.put_nowait(f'{message}\n')
144
+ except queue_module.Full:
145
+ exception_queue.put_nowait(ClientError(429, f'Message queue full for {context}'))
146
+ break
147
+
148
+ message_queue.put_nowait(None) # Signal end
149
+
150
+ except websockets.exceptions.ConnectionClosed:
151
+ exception_queue.put_nowait(ClientError(503, f'WebSocket connection closed for {context}'))
152
+ except asyncio.TimeoutError:
153
+ exception_queue.put_nowait(ClientError(408, f'WebSocket timed out for {context}'))
154
+ except Exception as e:
155
+ sanitized_error = sanitize_error_message(str(e), context)
156
+ exception_queue.put_nowait(ClientError(500, sanitized_error))
157
+
158
+ # Use thread pool instead of raw threading
159
+ future = self.thread_pool.submit(lambda: asyncio.run(websocket_client()))
160
+
161
+ # Yield messages with proper cleanup
162
+ try:
163
+ while True:
164
+ # Check for exceptions first
165
+ try:
166
+ exception = exception_queue.get_nowait()
167
+ raise exception
168
+ except queue_module.Empty:
169
+ pass
170
+
171
+ # Get message with timeout
172
+ try:
173
+ message = message_queue.get(timeout=1.0)
174
+ if message is None: # End signal
175
+ break
176
+ yield message
177
+ except queue_module.Empty:
178
+ # Check if future is done
179
+ if future.done():
180
+ try:
181
+ future.result() # This will raise any exception
182
+ break # Normal completion
183
+ except Exception:
184
+ break # Error already in queue
185
+ continue
186
+
187
+ finally:
188
+ # Cleanup: cancel future if still running
189
+ if not future.done():
190
+ future.cancel()
191
+
192
+ except ImportError:
193
+ raise ClientError(500, 'websockets library not available for WebSocket connections')
194
+ except Exception as e:
195
+ if isinstance(e, ClientError):
196
+ raise
197
+ sanitized_error = sanitize_error_message(str(e), context)
198
+ raise ClientError(500, sanitized_error)
199
+
200
+
201
+ class HTTPStreamManager:
202
+ """Manages HTTP streaming with rate limiting and proper resource cleanup."""
203
+
204
+ def __init__(self, requests_session: requests.Session, limits: Optional[StreamLimits] = None):
205
+ self.requests_session = requests_session
206
+ self.limits = limits or StreamLimits()
207
+
208
+ def stream_logs(
209
+ self, url: str, headers: Dict[str, str], timeout: tuple, context: str
210
+ ) -> Generator[str, None, None]:
211
+ """Stream logs from HTTP endpoint with proper error handling and cleanup."""
212
+ response = None
213
+ try:
214
+ # Use timeout for streaming to prevent hanging
215
+ response = self.requests_session.get(url, headers=headers, stream=True, timeout=timeout)
216
+ response.raise_for_status()
217
+
218
+ # Set up streaming with timeout and size limits
219
+ line_count = 0
220
+ total_bytes = 0
221
+
222
+ try:
223
+ for line in response.iter_lines(decode_unicode=True, chunk_size=1024):
224
+ if line:
225
+ line_count += 1
226
+ total_bytes += len(line.encode('utf-8'))
227
+
228
+ # Rate limiting checks
229
+ if line_count > self.limits.max_lines:
230
+ raise ClientError(429, f'Line limit exceeded for {context}')
231
+
232
+ if total_bytes > self.limits.max_bytes:
233
+ raise ClientError(429, f'Size limit exceeded for {context}')
234
+
235
+ # Validate line size
236
+ if len(line) > self.limits.max_message_size:
237
+ continue
238
+
239
+ yield f'{line}\n'
240
+
241
+ except requests.exceptions.ChunkedEncodingError:
242
+ raise ClientError(503, f'Log stream interrupted for {context}')
243
+ except requests.exceptions.ReadTimeout:
244
+ raise ClientError(408, f'Log stream timed out for {context}')
245
+
246
+ except requests.exceptions.ConnectTimeout:
247
+ raise ClientError(408, f'Failed to connect to log stream for {context}')
248
+ except requests.exceptions.ReadTimeout:
249
+ raise ClientError(408, f'Log stream read timeout for {context}')
250
+ except requests.exceptions.ConnectionError as e:
251
+ if 'Connection refused' in str(e):
252
+ raise ClientError(503, f'Agent connection refused for {context}')
253
+ else:
254
+ sanitized_error = sanitize_error_message(str(e), context)
255
+ raise ClientError(503, f'Agent connection error: {sanitized_error}')
256
+ except requests.exceptions.HTTPError as e:
257
+ if hasattr(e.response, 'status_code'):
258
+ status_code = e.response.status_code
259
+ else:
260
+ status_code = 500
261
+ raise ClientError(status_code, f'HTTP error streaming logs for {context}')
262
+ except Exception as e:
263
+ if isinstance(e, ClientError):
264
+ raise
265
+ sanitized_error = sanitize_error_message(str(e), context)
266
+ raise ClientError(500, sanitized_error)
267
+ finally:
268
+ # Ensure response is properly closed
269
+ if response is not None:
270
+ try:
271
+ response.close()
272
+ except Exception:
273
+ pass # Ignore cleanup errors
274
+
3
275
 
4
276
  def clean_url(url, remove_query_params=True, remove_fragment=True):
5
277
  parsed = urlparse(url)
@@ -14,3 +286,24 @@ def clean_url(url, remove_query_params=True, remove_fragment=True):
14
286
  query,
15
287
  fragment,
16
288
  ))
289
+
290
+
291
+ def get_available_ports_host(start_port=8900, end_port=8990):
292
+ import nmap
293
+
294
+ nm = nmap.PortScanner()
295
+
296
+ scan_range = f'{start_port}-{end_port}'
297
+ nm.scan(hosts='host.docker.internal', arguments=f'-p {scan_range}')
298
+
299
+ try:
300
+ open_ports = nm['host.docker.internal']['tcp'].keys()
301
+ open_ports = [int(port) for port in open_ports]
302
+ except KeyError:
303
+ open_ports = []
304
+
305
+ for port in range(start_port, end_port + 1):
306
+ if port not in open_ports:
307
+ return port
308
+
309
+ raise IOError(f'No free ports available in range {start_port}-{end_port}')
@@ -20,7 +20,11 @@ def get_storage(connection_param: str | dict):
20
20
  else:
21
21
  storage_scheme = urlparse(connection_param).scheme
22
22
 
23
- assert storage_scheme in STORAGE_PROVIDERS.keys(), _('Storage provider not supported.')
23
+ assert storage_scheme in STORAGE_PROVIDERS.keys(), _(
24
+ f'Storage provider not supported. Got scheme: {storage_scheme}. '
25
+ f'Valid schemes: {", ".join(STORAGE_PROVIDERS.keys())}. '
26
+ f'Full connection_param: {connection_param}'
27
+ )
24
28
  return STORAGE_PROVIDERS[storage_scheme](connection_param)
25
29
 
26
30
 
@@ -29,10 +33,40 @@ def get_pathlib(storage_config: str | dict, path_root: str) -> Path:
29
33
 
30
34
  Args:
31
35
  storage_config (str | dict): The storage config by synapse-backend storage api.
32
- path_root (str): The path root.
36
+ path_root (str): The path root.
33
37
 
34
38
  Returns:
35
39
  pathlib.Path: The pathlib object.
36
40
  """
37
41
  storage_class = get_storage(storage_config)
38
42
  return storage_class.get_pathlib(path_root)
43
+
44
+
45
+ def get_path_file_count(storage_config: str | dict, path_root: str) -> int:
46
+ """Get the file count in the path.
47
+
48
+ Args:
49
+ storage_config (str | dict): The storage config by synapse-backend storage api.
50
+ path (str): The path.
51
+
52
+ Returns:
53
+ int: The file count in the path.
54
+ """
55
+ storage_class = get_storage(storage_config)
56
+ pathlib_obj = storage_class.get_pathlib(path_root)
57
+ return storage_class.get_path_file_count(pathlib_obj)
58
+
59
+
60
+ def get_path_total_size(storage_config: str | dict, path_root: str) -> int:
61
+ """Get total size of the files in the path.
62
+
63
+ Args:
64
+ storage_config (str | dict): The storage config by synapse-backend storage api.
65
+ path (str): The path.
66
+
67
+ Returns:
68
+ int: The total size of the files in the path.
69
+ """
70
+ storage_class = get_storage(storage_config)
71
+ pathlib_obj = storage_class.get_pathlib(path_root)
72
+ return storage_class.get_path_total_size(pathlib_obj)
@@ -2,11 +2,52 @@ from urllib.parse import parse_qs, urlparse
2
2
 
3
3
 
4
4
  class BaseStorage:
5
+ """Base storage provider class for all storage implementations.
6
+
7
+ This is an abstract base class that defines the interface for storage providers.
8
+ All storage providers (S3, GCP, SFTP, HTTP, FileSystem) must inherit from this
9
+ class and implement all the abstract methods.
10
+
11
+ Args:
12
+ connection_params (str | dict): The connection parameters. Can be either:
13
+ - A URL string (e.g., 's3://bucket/path?access_key=key&secret_key=secret')
14
+ - A dict with 'provider' and 'configuration' keys
15
+
16
+ Attributes:
17
+ url: Parsed URL object if connection_params was a string
18
+ base_path: Base path for storage operations (provider-specific)
19
+ options: Storage-specific options
20
+ query_params: Parsed query parameters from URL or configuration dict
21
+ OPTION_CASTS: Dictionary mapping option names to type casting functions
22
+
23
+ Example:
24
+ >>> # URL-based initialization
25
+ >>> storage = SomeStorage('s3://bucket/path?access_key=key&secret_key=secret')
26
+ >>>
27
+ >>> # Dict-based initialization
28
+ >>> config = {
29
+ ... 'provider': 's3',
30
+ ... 'configuration': {
31
+ ... 'bucket_name': 'my-bucket',
32
+ ... 'access_key': 'key',
33
+ ... 'secret_key': 'secret'
34
+ ... }
35
+ ... }
36
+ >>> storage = SomeStorage(config)
37
+ """
38
+
5
39
  url = None
6
40
  options = None
7
41
  OPTION_CASTS = {}
8
42
 
9
43
  def __init__(self, connection_params: str | dict):
44
+ """Initialize the storage provider with connection parameters.
45
+
46
+ Args:
47
+ connection_params (str | dict): Connection parameters for the storage provider.
48
+ If string, should be a valid URL with scheme and query parameters.
49
+ If dict, should contain 'provider' and 'configuration' keys.
50
+ """
10
51
  self.url = None
11
52
 
12
53
  if isinstance(connection_params, dict):
@@ -16,6 +57,15 @@ class BaseStorage:
16
57
  self.query_params = self.url_querystring_to_dict()
17
58
 
18
59
  def url_querystring_to_dict(self):
60
+ """Parse URL query string into a dictionary with proper type casting.
61
+
62
+ Converts query parameters from the URL into a dictionary, applying
63
+ type casting based on OPTION_CASTS mapping. Single-value lists are
64
+ flattened to scalar values.
65
+
66
+ Returns:
67
+ dict: Parsed and type-cast query parameters.
68
+ """
19
69
  query_string = self.url.query
20
70
 
21
71
  query_dict = parse_qs(query_string)
@@ -30,13 +80,104 @@ class BaseStorage:
30
80
  }
31
81
 
32
82
  def upload(self, source, target):
83
+ """Upload a file from source to target location.
84
+
85
+ Args:
86
+ source (str): Path to the source file on local filesystem.
87
+ target (str): Target path in the storage provider.
88
+
89
+ Returns:
90
+ str: URL or identifier of the uploaded file.
91
+
92
+ Raises:
93
+ NotImplementedError: This method must be implemented by subclasses.
94
+ """
33
95
  raise NotImplementedError
34
96
 
35
97
  def exists(self, target):
98
+ """Check if a file exists at the target location.
99
+
100
+ Args:
101
+ target (str): Target path in the storage provider.
102
+
103
+ Returns:
104
+ bool: True if the file exists, False otherwise.
105
+
106
+ Raises:
107
+ NotImplementedError: This method must be implemented by subclasses.
108
+ """
36
109
  raise NotImplementedError
37
110
 
38
111
  def get_url(self, target):
112
+ """Get the URL for accessing a file at the target location.
113
+
114
+ Args:
115
+ target (str): Target path in the storage provider.
116
+
117
+ Returns:
118
+ str: URL that can be used to access the file.
119
+
120
+ Raises:
121
+ NotImplementedError: This method must be implemented by subclasses.
122
+ """
39
123
  raise NotImplementedError
40
124
 
41
125
  def get_pathlib(self, path):
126
+ """Get the path as a pathlib-compatible object.
127
+
128
+ Args:
129
+ path (str): The path to convert, relative to the storage root.
130
+
131
+ Returns:
132
+ pathlib.Path or UPath: A pathlib-compatible object representing the path.
133
+ The exact type depends on the storage provider implementation.
134
+
135
+ Raises:
136
+ NotImplementedError: This method must be implemented by subclasses.
137
+
138
+ Note:
139
+ Different storage providers may return different path object types:
140
+ - FileSystemStorage returns pathlib.Path objects
141
+ - Cloud storage providers typically return UPath objects
142
+ """
143
+ raise NotImplementedError
144
+
145
+ def get_path_file_count(self, pathlib_obj):
146
+ """Get the total number of files in the given path.
147
+
148
+ Args:
149
+ pathlib_obj (Path | UPath): The path object to count files in.
150
+ Should be obtained from get_pathlib().
151
+
152
+ Returns:
153
+ int: The total number of files found recursively in the path.
154
+ Returns 1 for individual files, 0 for non-existent paths.
155
+
156
+ Raises:
157
+ NotImplementedError: This method must be implemented by subclasses.
158
+
159
+ Note:
160
+ This method counts files recursively, including files in subdirectories.
161
+ Directories themselves are not counted, only regular files.
162
+ """
163
+ raise NotImplementedError
164
+
165
+ def get_path_total_size(self, pathlib_obj):
166
+ """Get the total size of all files in the given path.
167
+
168
+ Args:
169
+ pathlib_obj (Path | UPath): The path object to calculate size for.
170
+ Should be obtained from get_pathlib().
171
+
172
+ Returns:
173
+ int: The total size in bytes of all files found recursively in the path.
174
+ Returns the file size for individual files, 0 for non-existent paths.
175
+
176
+ Raises:
177
+ NotImplementedError: This method must be implemented by subclasses.
178
+
179
+ Note:
180
+ This method calculates the total size recursively, including files
181
+ in subdirectories. Only regular files contribute to the total size.
182
+ """
42
183
  raise NotImplementedError
@@ -0,0 +1,134 @@
1
+ import shutil
2
+ from pathlib import Path
3
+
4
+ from synapse_sdk.utils.storage.providers import BaseStorage
5
+
6
+
7
+ class FileSystemStorage(BaseStorage):
8
+ """Storage provider for file system.
9
+
10
+ * This storage do not support url based initialization.
11
+
12
+ Args:
13
+ url (str): The URL of the storage provider.
14
+
15
+ Examples:
16
+ >>> # Dict-based initialization
17
+ >>> config = {
18
+ ... 'provider': 'filesystem',
19
+ ... 'configuration': {
20
+ ... 'location': '/data'
21
+ ... }
22
+ ... }
23
+ >>> storage = FileSystemStorage(config)
24
+ """
25
+
26
+ def __init__(self, connection_params: str | dict):
27
+ super().__init__(connection_params)
28
+ self.base_path = Path(self.query_params['location'])
29
+
30
+ def get_pathlib(self, path):
31
+ """Get the path as a pathlib object.
32
+
33
+ Args:
34
+ path (str): The path to convert.
35
+
36
+ Returns:
37
+ pathlib.Path: The converted path.
38
+ """
39
+ if path == '/' or path == '':
40
+ return self.base_path
41
+
42
+ # Strip leading slash to ensure path is relative to base_path.
43
+ # Path('/data') / '/subdir' would incorrectly resolve to '/subdir' instead of '/data/subdir'
44
+ if isinstance(path, str) and path.startswith('/'):
45
+ path = path.lstrip('/')
46
+
47
+ return self.base_path / path
48
+
49
+ def upload(self, source, target):
50
+ """Upload a file from source to target location.
51
+
52
+ Args:
53
+ source (str): Path to source file
54
+ target (str): Target path relative to base path
55
+
56
+ Returns:
57
+ str: URL of uploaded file
58
+ """
59
+ source_path = Path(source)
60
+ target_path = self.base_path / target
61
+
62
+ # Create parent directories if they don't exist
63
+ target_path.parent.mkdir(parents=True, exist_ok=True)
64
+
65
+ # Copy the file
66
+ shutil.copy2(source_path, target_path)
67
+
68
+ return self.get_url(target)
69
+
70
+ def exists(self, target):
71
+ """Check if target file exists.
72
+
73
+ Args:
74
+ target (str): Target path relative to base path
75
+
76
+ Returns:
77
+ bool: True if file exists, False otherwise
78
+ """
79
+ target_path = self.base_path / target
80
+ return target_path.exists()
81
+
82
+ def get_url(self, target):
83
+ """Get URL for target file.
84
+
85
+ Args:
86
+ target (str): Target path relative to base path
87
+
88
+ Returns:
89
+ str: File URL
90
+ """
91
+ target_path = self.base_path / target
92
+ return f'file://{target_path.absolute()}'
93
+
94
+ def get_path_file_count(self, pathlib_obj):
95
+ """Get the file count in the path.
96
+
97
+ Args:
98
+ pathlib_obj (Path): The path to get file count.
99
+
100
+ Returns:
101
+ int: The file count in the path.
102
+ """
103
+ if not pathlib_obj.exists():
104
+ return 0
105
+
106
+ if pathlib_obj.is_file():
107
+ return 1
108
+
109
+ count = 0
110
+ for item in pathlib_obj.rglob('*'):
111
+ if item.is_file():
112
+ count += 1
113
+ return count
114
+
115
+ def get_path_total_size(self, pathlib_obj):
116
+ """Get the total size of the path.
117
+
118
+ Args:
119
+ pathlib_obj (Path): The path to get total size.
120
+
121
+ Returns:
122
+ int: The total size of the path.
123
+ """
124
+ if not pathlib_obj.exists():
125
+ return 0
126
+
127
+ if pathlib_obj.is_file():
128
+ return pathlib_obj.stat().st_size
129
+
130
+ total_size = 0
131
+ for item in pathlib_obj.rglob('*'):
132
+ if item.is_file():
133
+ total_size += item.stat().st_size
134
+ return total_size