synapse-sdk 1.0.0a13__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.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/__init__.py +310 -5
- synapse_sdk/cli/alias/__init__.py +22 -0
- synapse_sdk/cli/alias/create.py +36 -0
- synapse_sdk/cli/alias/dataclass.py +31 -0
- synapse_sdk/cli/alias/default.py +16 -0
- synapse_sdk/cli/alias/delete.py +15 -0
- synapse_sdk/cli/alias/list.py +19 -0
- synapse_sdk/cli/alias/read.py +15 -0
- synapse_sdk/cli/alias/update.py +17 -0
- synapse_sdk/cli/alias/utils.py +61 -0
- synapse_sdk/cli/code_server.py +687 -0
- synapse_sdk/cli/config.py +440 -0
- synapse_sdk/cli/devtools.py +90 -0
- synapse_sdk/cli/plugin/__init__.py +33 -0
- synapse_sdk/cli/{create_plugin.py → plugin/create.py} +2 -2
- synapse_sdk/cli/plugin/publish.py +45 -0
- synapse_sdk/{plugins/cli → cli/plugin}/run.py +12 -5
- synapse_sdk/clients/agent/__init__.py +9 -3
- synapse_sdk/clients/agent/container.py +133 -0
- synapse_sdk/clients/agent/core.py +19 -0
- synapse_sdk/clients/agent/ray.py +298 -9
- synapse_sdk/clients/backend/__init__.py +41 -12
- synapse_sdk/clients/backend/annotation.py +13 -5
- synapse_sdk/clients/backend/core.py +59 -0
- synapse_sdk/clients/backend/data_collection.py +186 -0
- synapse_sdk/clients/backend/hitl.py +17 -0
- synapse_sdk/clients/backend/integration.py +19 -4
- synapse_sdk/clients/backend/ml.py +10 -7
- synapse_sdk/clients/backend/models.py +78 -0
- synapse_sdk/clients/base.py +381 -34
- synapse_sdk/clients/ray/serve.py +2 -0
- synapse_sdk/clients/validators/collections.py +31 -0
- synapse_sdk/devtools/config.py +94 -0
- synapse_sdk/devtools/docs/.gitignore +20 -0
- synapse_sdk/devtools/docs/README.md +41 -0
- synapse_sdk/devtools/docs/blog/2019-05-28-first-blog-post.md +12 -0
- synapse_sdk/devtools/docs/blog/2019-05-29-long-blog-post.md +44 -0
- synapse_sdk/devtools/docs/blog/2021-08-01-mdx-blog-post.mdx +24 -0
- synapse_sdk/devtools/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- synapse_sdk/devtools/docs/blog/2021-08-26-welcome/index.md +29 -0
- synapse_sdk/devtools/docs/blog/authors.yml +25 -0
- synapse_sdk/devtools/docs/blog/tags.yml +19 -0
- synapse_sdk/devtools/docs/docs/api/clients/agent.md +43 -0
- synapse_sdk/devtools/docs/docs/api/clients/annotation-mixin.md +378 -0
- synapse_sdk/devtools/docs/docs/api/clients/backend.md +420 -0
- synapse_sdk/devtools/docs/docs/api/clients/base.md +257 -0
- synapse_sdk/devtools/docs/docs/api/clients/core-mixin.md +477 -0
- synapse_sdk/devtools/docs/docs/api/clients/data-collection-mixin.md +422 -0
- synapse_sdk/devtools/docs/docs/api/clients/hitl-mixin.md +554 -0
- synapse_sdk/devtools/docs/docs/api/clients/index.md +391 -0
- synapse_sdk/devtools/docs/docs/api/clients/integration-mixin.md +571 -0
- synapse_sdk/devtools/docs/docs/api/clients/ml-mixin.md +578 -0
- synapse_sdk/devtools/docs/docs/api/clients/ray.md +342 -0
- synapse_sdk/devtools/docs/docs/api/index.md +52 -0
- synapse_sdk/devtools/docs/docs/api/plugins/categories.md +43 -0
- synapse_sdk/devtools/docs/docs/api/plugins/models.md +114 -0
- synapse_sdk/devtools/docs/docs/api/plugins/utils.md +328 -0
- synapse_sdk/devtools/docs/docs/categories.md +0 -0
- synapse_sdk/devtools/docs/docs/cli-usage.md +280 -0
- synapse_sdk/devtools/docs/docs/concepts/index.md +38 -0
- synapse_sdk/devtools/docs/docs/configuration.md +83 -0
- synapse_sdk/devtools/docs/docs/contributing.md +306 -0
- synapse_sdk/devtools/docs/docs/examples/index.md +29 -0
- synapse_sdk/devtools/docs/docs/faq.md +179 -0
- synapse_sdk/devtools/docs/docs/features/converters/index.md +455 -0
- synapse_sdk/devtools/docs/docs/features/index.md +24 -0
- synapse_sdk/devtools/docs/docs/features/utils/file.md +415 -0
- synapse_sdk/devtools/docs/docs/features/utils/network.md +378 -0
- synapse_sdk/devtools/docs/docs/features/utils/storage.md +57 -0
- synapse_sdk/devtools/docs/docs/features/utils/types.md +51 -0
- synapse_sdk/devtools/docs/docs/installation.md +94 -0
- synapse_sdk/devtools/docs/docs/introduction.md +47 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/neural-net-plugins/train-action-overview.md +814 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/pre-annotation-plugin-overview.md +198 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-action-development.md +1645 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-overview.md +717 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-template-development.md +1380 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-action.md +948 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-overview.md +544 -0
- synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-template.md +766 -0
- synapse_sdk/devtools/docs/docs/plugins/export-plugins.md +1092 -0
- synapse_sdk/devtools/docs/docs/plugins/plugins.md +852 -0
- synapse_sdk/devtools/docs/docs/quickstart.md +78 -0
- synapse_sdk/devtools/docs/docs/troubleshooting.md +519 -0
- synapse_sdk/devtools/docs/docs/tutorial-basics/_category_.json +8 -0
- synapse_sdk/devtools/docs/docs/tutorial-basics/congratulations.md +23 -0
- synapse_sdk/devtools/docs/docs/tutorial-basics/create-a-blog-post.md +34 -0
- synapse_sdk/devtools/docs/docs/tutorial-basics/create-a-document.md +57 -0
- synapse_sdk/devtools/docs/docs/tutorial-basics/create-a-page.md +43 -0
- synapse_sdk/devtools/docs/docs/tutorial-basics/deploy-your-site.md +31 -0
- synapse_sdk/devtools/docs/docs/tutorial-basics/markdown-features.mdx +152 -0
- synapse_sdk/devtools/docs/docs/tutorial-extras/_category_.json +7 -0
- synapse_sdk/devtools/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
- synapse_sdk/devtools/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
- synapse_sdk/devtools/docs/docs/tutorial-extras/manage-docs-versions.md +55 -0
- synapse_sdk/devtools/docs/docs/tutorial-extras/translate-your-site.md +88 -0
- synapse_sdk/devtools/docs/docusaurus.config.ts +148 -0
- synapse_sdk/devtools/docs/i18n/ko/code.json +325 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/agent.md +43 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/annotation-mixin.md +289 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/backend.md +420 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/base.md +257 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/core-mixin.md +417 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/data-collection-mixin.md +356 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/hitl-mixin.md +192 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/index.md +391 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/integration-mixin.md +479 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/ml-mixin.md +284 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/ray.md +342 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/index.md +52 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/plugins/models.md +114 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/categories.md +0 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/cli-usage.md +280 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/concepts/index.md +38 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/configuration.md +83 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/contributing.md +306 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/examples/index.md +29 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/faq.md +179 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/converters/index.md +30 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/index.md +24 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/file.md +415 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/network.md +378 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/storage.md +60 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/types.md +51 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/installation.md +94 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/introduction.md +47 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/neural-net-plugins/train-action-overview.md +815 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/pre-annotation-plugin-overview.md +198 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-action-development.md +1645 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-overview.md +717 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-template-development.md +1380 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-action.md +948 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-overview.md +544 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-template.md +766 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/export-plugins.md +1092 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/plugins.md +117 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/quickstart.md +78 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/troubleshooting.md +519 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current.json +34 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-theme-classic/footer.json +42 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-theme-classic/navbar.json +18 -0
- synapse_sdk/devtools/docs/package-lock.json +18784 -0
- synapse_sdk/devtools/docs/package.json +48 -0
- synapse_sdk/devtools/docs/sidebars.ts +122 -0
- synapse_sdk/devtools/docs/src/components/HomepageFeatures/index.tsx +71 -0
- synapse_sdk/devtools/docs/src/components/HomepageFeatures/styles.module.css +11 -0
- synapse_sdk/devtools/docs/src/css/custom.css +30 -0
- synapse_sdk/devtools/docs/src/pages/index.module.css +23 -0
- synapse_sdk/devtools/docs/src/pages/index.tsx +21 -0
- synapse_sdk/devtools/docs/src/pages/markdown-page.md +7 -0
- synapse_sdk/devtools/docs/static/.nojekyll +0 -0
- synapse_sdk/devtools/docs/static/img/docusaurus-social-card.jpg +0 -0
- synapse_sdk/devtools/docs/static/img/docusaurus.png +0 -0
- synapse_sdk/devtools/docs/static/img/favicon.ico +0 -0
- synapse_sdk/devtools/docs/static/img/logo.png +0 -0
- synapse_sdk/devtools/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
- synapse_sdk/devtools/docs/static/img/undraw_docusaurus_react.svg +170 -0
- synapse_sdk/devtools/docs/static/img/undraw_docusaurus_tree.svg +40 -0
- synapse_sdk/devtools/docs/tsconfig.json +8 -0
- synapse_sdk/devtools/server.py +41 -0
- synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
- synapse_sdk/devtools/streamlit_app/app.py +128 -0
- synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
- synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
- synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
- synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
- synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
- synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
- synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
- synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
- synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
- synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
- synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
- synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
- synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
- synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
- synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
- synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
- synapse_sdk/devtools/streamlit_app.py +10 -0
- synapse_sdk/loggers.py +74 -9
- synapse_sdk/plugins/README.md +1340 -0
- synapse_sdk/plugins/__init__.py +0 -13
- synapse_sdk/plugins/categories/base.py +145 -30
- synapse_sdk/plugins/categories/data_validation/actions/validation.py +72 -0
- synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +33 -5
- synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
- synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
- synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
- synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
- synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
- synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
- synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
- synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
- synapse_sdk/plugins/categories/export/templates/config.yaml +21 -0
- synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
- synapse_sdk/plugins/categories/export/templates/plugin/export.py +160 -0
- synapse_sdk/plugins/categories/neural_net/actions/deployment.py +29 -14
- synapse_sdk/plugins/categories/neural_net/actions/inference.py +13 -1
- synapse_sdk/plugins/categories/neural_net/actions/train.py +1084 -38
- synapse_sdk/plugins/categories/neural_net/actions/tune.py +534 -0
- synapse_sdk/plugins/categories/neural_net/base/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/base/inference.py +37 -0
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +30 -5
- synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +26 -10
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
- synapse_sdk/plugins/categories/{export/actions/export.py → pre_annotation/actions/pre_annotation/action.py} +4 -4
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +145 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +97 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +250 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +287 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +87 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +127 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task.py +966 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +19 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/to_task.py +40 -0
- synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +5 -2
- synapse_sdk/plugins/categories/upload/__init__.py +0 -0
- synapse_sdk/plugins/categories/upload/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
- synapse_sdk/plugins/categories/upload/actions/upload/action.py +232 -0
- synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
- synapse_sdk/plugins/categories/upload/actions/upload/enums.py +471 -0
- synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
- synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
- synapse_sdk/plugins/categories/upload/actions/upload/models.py +203 -0
- synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
- synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
- synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +84 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +203 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +97 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +258 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +281 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
- synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
- synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
- synapse_sdk/plugins/categories/upload/templates/config.yaml +33 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +294 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +102 -0
- synapse_sdk/plugins/enums.py +3 -1
- synapse_sdk/plugins/models.py +140 -16
- synapse_sdk/plugins/templates/plugin-config-schema.json +406 -0
- synapse_sdk/plugins/templates/schema.json +491 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +1 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +1 -1
- synapse_sdk/plugins/utils/__init__.py +46 -0
- synapse_sdk/plugins/utils/actions.py +119 -0
- synapse_sdk/plugins/utils/config.py +203 -0
- synapse_sdk/plugins/utils/legacy.py +95 -0
- synapse_sdk/plugins/utils/ray_gcs.py +66 -0
- synapse_sdk/plugins/utils/registry.py +58 -0
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/shared/enums.py +93 -0
- synapse_sdk/types.py +19 -0
- synapse_sdk/utils/converters/__init__.py +240 -0
- synapse_sdk/utils/converters/coco/__init__.py +0 -0
- synapse_sdk/utils/converters/coco/from_dm.py +322 -0
- synapse_sdk/utils/converters/coco/to_dm.py +215 -0
- synapse_sdk/utils/converters/dm/__init__.py +56 -0
- synapse_sdk/utils/converters/dm/from_v1.py +627 -0
- synapse_sdk/utils/converters/dm/to_v1.py +367 -0
- synapse_sdk/utils/converters/pascal/__init__.py +0 -0
- synapse_sdk/utils/converters/pascal/from_dm.py +244 -0
- synapse_sdk/utils/converters/pascal/to_dm.py +214 -0
- synapse_sdk/utils/converters/yolo/__init__.py +0 -0
- synapse_sdk/utils/converters/yolo/from_dm.py +384 -0
- synapse_sdk/utils/converters/yolo/to_dm.py +267 -0
- synapse_sdk/utils/dataset.py +46 -0
- synapse_sdk/utils/encryption.py +158 -0
- synapse_sdk/utils/file/__init__.py +39 -0
- synapse_sdk/utils/file/archive.py +32 -0
- synapse_sdk/utils/file/checksum.py +56 -0
- synapse_sdk/utils/file/chunking.py +31 -0
- synapse_sdk/utils/file/download.py +385 -0
- synapse_sdk/utils/file/encoding.py +40 -0
- synapse_sdk/utils/file/io.py +22 -0
- synapse_sdk/utils/file/video/__init__.py +29 -0
- synapse_sdk/utils/file/video/transcode.py +307 -0
- synapse_sdk/utils/file.py.backup +301 -0
- synapse_sdk/utils/http.py +138 -0
- synapse_sdk/utils/network.py +309 -0
- synapse_sdk/utils/storage/__init__.py +72 -0
- synapse_sdk/utils/storage/providers/__init__.py +183 -0
- synapse_sdk/utils/storage/providers/file_system.py +134 -0
- synapse_sdk/utils/storage/providers/gcp.py +13 -0
- synapse_sdk/utils/storage/providers/http.py +190 -0
- synapse_sdk/utils/storage/providers/s3.py +91 -0
- synapse_sdk/utils/storage/providers/sftp.py +47 -0
- synapse_sdk/utils/storage/registry.py +17 -0
- synapse_sdk-2025.11.7.dist-info/METADATA +122 -0
- synapse_sdk-2025.11.7.dist-info/RECORD +386 -0
- {synapse_sdk-1.0.0a13.dist-info → synapse_sdk-2025.11.7.dist-info}/WHEEL +1 -1
- synapse_sdk/clients/backend/dataset.py +0 -51
- synapse_sdk/plugins/categories/import/actions/import.py +0 -10
- synapse_sdk/plugins/cli/__init__.py +0 -21
- synapse_sdk/plugins/cli/publish.py +0 -37
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
- synapse_sdk/plugins/utils.py +0 -50
- synapse_sdk/utils/file.py +0 -87
- synapse_sdk/utils/storage.py +0 -91
- synapse_sdk-1.0.0a13.dist-info/METADATA +0 -43
- synapse_sdk-1.0.0a13.dist-info/RECORD +0 -111
- /synapse_sdk/{plugins/categories/import → clients/validators}/__init__.py +0 -0
- /synapse_sdk/{plugins/categories/import/actions → devtools}/__init__.py +0 -0
- {synapse_sdk-1.0.0a13.dist-info → synapse_sdk-2025.11.7.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0a13.dist-info → synapse_sdk-2025.11.7.dist-info/licenses}/LICENSE +0 -0
- {synapse_sdk-1.0.0a13.dist-info → synapse_sdk-2025.11.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
import uuid
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
from synapse_sdk.utils.network import get_available_ports_host
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SingleFileHttpServer(SimpleHTTPRequestHandler):
|
|
16
|
+
"""
|
|
17
|
+
Custom HTTP request handler that serves a single specified file
|
|
18
|
+
regardless of the request path.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, *args, file_path=None, content_type=None, random_path=None, **kwargs):
|
|
22
|
+
self.file_path = file_path
|
|
23
|
+
self.content_type = content_type
|
|
24
|
+
self.random_path = random_path
|
|
25
|
+
super().__init__(*args, **kwargs)
|
|
26
|
+
|
|
27
|
+
def do_GET(self):
|
|
28
|
+
"""Handle GET requests by serving the single file."""
|
|
29
|
+
try:
|
|
30
|
+
# Check if the path matches our random path
|
|
31
|
+
if self.random_path and self.path == f'/{self.random_path}':
|
|
32
|
+
self.send_response(200)
|
|
33
|
+
if self.content_type:
|
|
34
|
+
self.send_header('Content-type', self.content_type)
|
|
35
|
+
self.send_header('Content-Length', str(os.path.getsize(self.file_path)))
|
|
36
|
+
self.end_headers()
|
|
37
|
+
|
|
38
|
+
with open(self.file_path, 'rb') as file:
|
|
39
|
+
self.wfile.write(file.read())
|
|
40
|
+
elif self.path == '/':
|
|
41
|
+
# Redirect root to the random path
|
|
42
|
+
self.send_response(302)
|
|
43
|
+
self.send_header('Location', f'/{self.random_path}')
|
|
44
|
+
self.end_headers()
|
|
45
|
+
else:
|
|
46
|
+
self.send_error(404, 'File not found')
|
|
47
|
+
|
|
48
|
+
except Exception as e:
|
|
49
|
+
self.send_error(500, str(e))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@contextmanager
|
|
53
|
+
def temp_file_server(image=None, file_path=None, format='JPEG', host='0.0.0.0', port=None, content_type=None):
|
|
54
|
+
"""
|
|
55
|
+
Context manager that serves a file temporarily via HTTP.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
image: A PIL Image object to serve (optional)
|
|
59
|
+
file_path: Path to an existing file to serve (optional - used if image not provided)
|
|
60
|
+
format: Image format when saving a PIL Image (default: "JPEG")
|
|
61
|
+
host: Host to serve on (default: "0.0.0.0")
|
|
62
|
+
port: Port to serve on (default: auto-selected free port)
|
|
63
|
+
content_type: Content type header (default: auto-detected based on format)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
URL where the file is being served
|
|
67
|
+
|
|
68
|
+
Usage:
|
|
69
|
+
with temp_file_serve(image=my_pillow_img) as url:
|
|
70
|
+
# use url to access the image
|
|
71
|
+
print(f"Image available at: {url}")
|
|
72
|
+
"""
|
|
73
|
+
if image is None and file_path is None:
|
|
74
|
+
raise ValueError('Either image or file_path must be provided')
|
|
75
|
+
|
|
76
|
+
# Use a free port if none specified
|
|
77
|
+
if port is None:
|
|
78
|
+
port = get_available_ports_host(start_port=8991, end_port=8999)
|
|
79
|
+
|
|
80
|
+
temp_dir = None
|
|
81
|
+
temp_file_path = None
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
random_filename = f'{uuid.uuid4().hex}'
|
|
85
|
+
|
|
86
|
+
if image is not None:
|
|
87
|
+
temp_dir = tempfile.mkdtemp()
|
|
88
|
+
ext_map = {'JPEG': '.jpg', 'PNG': '.png', 'GIF': '.gif', 'WEBP': '.webp'}
|
|
89
|
+
content_type_map = {'JPEG': 'image/jpeg', 'PNG': 'image/png', 'GIF': 'image/gif', 'WEBP': 'image/webp'}
|
|
90
|
+
|
|
91
|
+
ext = ext_map.get(format, '.jpg')
|
|
92
|
+
if content_type is None:
|
|
93
|
+
content_type = content_type_map.get(format, 'image/jpeg')
|
|
94
|
+
|
|
95
|
+
temp_file_path = os.path.join(temp_dir, f'temp_image{ext}')
|
|
96
|
+
image.save(temp_file_path, format=format)
|
|
97
|
+
file_path = temp_file_path
|
|
98
|
+
random_filename += ext
|
|
99
|
+
else:
|
|
100
|
+
_, ext = os.path.splitext(file_path)
|
|
101
|
+
random_filename += ext
|
|
102
|
+
|
|
103
|
+
def handler(*args, **kwargs):
|
|
104
|
+
return SingleFileHttpServer(
|
|
105
|
+
*args, file_path=file_path, content_type=content_type, random_path=random_filename, **kwargs
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
server = HTTPServer((host, port), handler)
|
|
109
|
+
|
|
110
|
+
server_thread = threading.Thread(target=server.serve_forever)
|
|
111
|
+
server_thread.daemon = True
|
|
112
|
+
server_thread.start()
|
|
113
|
+
|
|
114
|
+
url = f'http://localhost:{port}/{random_filename}'
|
|
115
|
+
|
|
116
|
+
while True:
|
|
117
|
+
try:
|
|
118
|
+
response = requests.get(url)
|
|
119
|
+
if response.status_code == 200:
|
|
120
|
+
break
|
|
121
|
+
except requests.exceptions.ConnectionError:
|
|
122
|
+
pass
|
|
123
|
+
time.sleep(0.01)
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
yield url
|
|
127
|
+
finally:
|
|
128
|
+
server.shutdown()
|
|
129
|
+
server.server_close()
|
|
130
|
+
|
|
131
|
+
finally:
|
|
132
|
+
if temp_dir:
|
|
133
|
+
try:
|
|
134
|
+
if temp_file_path and os.path.exists(temp_file_path):
|
|
135
|
+
os.unlink(temp_file_path)
|
|
136
|
+
os.rmdir(temp_dir)
|
|
137
|
+
except Exception as e:
|
|
138
|
+
logging.warning(f'Error cleaning up temporary files: {e}')
|
|
@@ -0,0 +1,309 @@
|
|
|
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
|
|
8
|
+
from urllib.parse import urlparse, urlunparse
|
|
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
|
+
|
|
275
|
+
|
|
276
|
+
def clean_url(url, remove_query_params=True, remove_fragment=True):
|
|
277
|
+
parsed = urlparse(url)
|
|
278
|
+
query = '' if remove_query_params else parsed.query
|
|
279
|
+
fragment = '' if remove_fragment else parsed.fragment
|
|
280
|
+
|
|
281
|
+
return urlunparse((
|
|
282
|
+
parsed.scheme,
|
|
283
|
+
parsed.netloc,
|
|
284
|
+
parsed.path,
|
|
285
|
+
parsed.params,
|
|
286
|
+
query,
|
|
287
|
+
fragment,
|
|
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}')
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from urllib.parse import urlparse
|
|
3
|
+
|
|
4
|
+
from synapse_sdk.i18n import gettext as _
|
|
5
|
+
from synapse_sdk.utils.storage.registry import STORAGE_PROVIDERS
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_storage(connection_param: str | dict):
|
|
9
|
+
"""Get storage class with connection param.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
connection_param (str | dict): The connection param for the Storage provider.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
BaseStorage: The storage class object with connection param.
|
|
16
|
+
"""
|
|
17
|
+
storage_scheme = None
|
|
18
|
+
if isinstance(connection_param, dict):
|
|
19
|
+
storage_scheme = connection_param['provider']
|
|
20
|
+
else:
|
|
21
|
+
storage_scheme = urlparse(connection_param).scheme
|
|
22
|
+
|
|
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
|
+
)
|
|
28
|
+
return STORAGE_PROVIDERS[storage_scheme](connection_param)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_pathlib(storage_config: str | dict, path_root: str) -> Path:
|
|
32
|
+
"""Get pathlib object with synapse-backend storage config.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
storage_config (str | dict): The storage config by synapse-backend storage api.
|
|
36
|
+
path_root (ㅔstr): The path root.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
pathlib.Path: The pathlib object.
|
|
40
|
+
"""
|
|
41
|
+
storage_class = get_storage(storage_config)
|
|
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)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from urllib.parse import parse_qs, urlparse
|
|
2
|
+
|
|
3
|
+
|
|
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
|
+
|
|
39
|
+
url = None
|
|
40
|
+
options = None
|
|
41
|
+
OPTION_CASTS = {}
|
|
42
|
+
|
|
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
|
+
"""
|
|
51
|
+
self.url = None
|
|
52
|
+
|
|
53
|
+
if isinstance(connection_params, dict):
|
|
54
|
+
self.query_params = connection_params['configuration']
|
|
55
|
+
else:
|
|
56
|
+
self.url = urlparse(connection_params)
|
|
57
|
+
self.query_params = self.url_querystring_to_dict()
|
|
58
|
+
|
|
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
|
+
"""
|
|
69
|
+
query_string = self.url.query
|
|
70
|
+
|
|
71
|
+
query_dict = parse_qs(query_string)
|
|
72
|
+
|
|
73
|
+
for key, value in query_dict.items():
|
|
74
|
+
if len(value) == 1:
|
|
75
|
+
query_dict[key] = value[0]
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
key: self.OPTION_CASTS[key](value) if key in self.OPTION_CASTS else value
|
|
79
|
+
for key, value in query_dict.items()
|
|
80
|
+
}
|
|
81
|
+
|
|
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
|
+
"""
|
|
95
|
+
raise NotImplementedError
|
|
96
|
+
|
|
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
|
+
"""
|
|
109
|
+
raise NotImplementedError
|
|
110
|
+
|
|
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
|
+
"""
|
|
123
|
+
raise NotImplementedError
|
|
124
|
+
|
|
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
|
+
"""
|
|
183
|
+
raise NotImplementedError
|