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,1340 @@
|
|
|
1
|
+
# Synapse SDK Plugin System - Developer Reference
|
|
2
|
+
|
|
3
|
+
This document provides comprehensive guidance for developers working on the Synapse SDK plugin system architecture, internal APIs, and core infrastructure.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Synapse SDK plugin system is a modular framework that enables distributed execution of ML operations across different categories and execution methods. The system is built around the concept of **actions** - discrete operations that can be packaged, distributed, and executed in various environments.
|
|
8
|
+
|
|
9
|
+
### Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
synapse_sdk/plugins/
|
|
13
|
+
├── categories/ # Plugin category implementations
|
|
14
|
+
│ ├── base.py # Action base class
|
|
15
|
+
│ ├── decorators.py # Registration decorators
|
|
16
|
+
│ ├── registry.py # Action registry
|
|
17
|
+
│ ├── neural_net/ # Neural network actions
|
|
18
|
+
│ ├── export/ # Data export actions
|
|
19
|
+
│ ├── upload/ # File upload actions
|
|
20
|
+
│ ├── smart_tool/ # AI-powered tools
|
|
21
|
+
│ ├── pre_annotation/ # Pre-processing actions
|
|
22
|
+
│ ├── post_annotation/ # Post-processing actions
|
|
23
|
+
│ └── data_validation/ # Validation actions
|
|
24
|
+
├── templates/ # Cookiecutter templates
|
|
25
|
+
├── utils/ # Utility functions
|
|
26
|
+
├── models.py # Core plugin models
|
|
27
|
+
├── enums.py # Plugin enums
|
|
28
|
+
└── exceptions.py # Plugin exceptions
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Key Features
|
|
32
|
+
|
|
33
|
+
- **🔌 Modular Architecture**: Self-contained plugins with isolated dependencies
|
|
34
|
+
- **⚡ Multiple Execution Methods**: Jobs, Tasks, and REST API endpoints
|
|
35
|
+
- **📦 Distributed Execution**: Ray-based scalable computing
|
|
36
|
+
- **🛠️ Template System**: Cookiecutter-based scaffolding
|
|
37
|
+
- **📊 Progress Tracking**: Built-in logging, metrics, and progress monitoring
|
|
38
|
+
- **🔄 Dynamic Loading**: Runtime plugin discovery and registration
|
|
39
|
+
|
|
40
|
+
## Core Components
|
|
41
|
+
|
|
42
|
+
### Action Base Class
|
|
43
|
+
|
|
44
|
+
The `Action` class (`synapse_sdk/plugins/categories/base.py`) provides the unified interface for all plugin actions:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
class Action:
|
|
48
|
+
"""Base class for all plugin actions.
|
|
49
|
+
|
|
50
|
+
Class Variables:
|
|
51
|
+
name (str): Action identifier
|
|
52
|
+
category (PluginCategory): Plugin category
|
|
53
|
+
method (RunMethod): Execution method
|
|
54
|
+
run_class (Run): Run management class
|
|
55
|
+
params_model (BaseModel): Parameter validation model
|
|
56
|
+
progress_categories (Dict): Progress tracking categories
|
|
57
|
+
metrics_categories (Dict): Metrics collection categories
|
|
58
|
+
|
|
59
|
+
Instance Variables:
|
|
60
|
+
params (Dict): Validated action parameters
|
|
61
|
+
plugin_config (Dict): Plugin configuration
|
|
62
|
+
plugin_release (PluginRelease): Plugin metadata
|
|
63
|
+
client: Backend API client
|
|
64
|
+
run (Run): Execution instance
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
# Class configuration
|
|
68
|
+
name = None
|
|
69
|
+
category = None
|
|
70
|
+
method = None
|
|
71
|
+
run_class = Run
|
|
72
|
+
params_model = None
|
|
73
|
+
progress_categories = None
|
|
74
|
+
metrics_categories = None
|
|
75
|
+
|
|
76
|
+
def start(self):
|
|
77
|
+
"""Main action logic - implement in subclasses."""
|
|
78
|
+
raise NotImplementedError
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Plugin Categories
|
|
82
|
+
|
|
83
|
+
The system supports seven main categories defined in `enums.py`:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
class PluginCategory(Enum):
|
|
87
|
+
NEURAL_NET = 'neural_net' # ML training and inference
|
|
88
|
+
EXPORT = 'export' # Data export operations
|
|
89
|
+
UPLOAD = 'upload' # File upload functionality
|
|
90
|
+
SMART_TOOL = 'smart_tool' # AI-powered automation
|
|
91
|
+
POST_ANNOTATION = 'post_annotation' # Post-processing
|
|
92
|
+
PRE_ANNOTATION = 'pre_annotation' # Pre-processing
|
|
93
|
+
DATA_VALIDATION = 'data_validation' # Quality checks
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Execution Methods
|
|
97
|
+
|
|
98
|
+
Three execution methods are supported:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
class RunMethod(Enum):
|
|
102
|
+
JOB = 'job' # Long-running distributed tasks
|
|
103
|
+
TASK = 'task' # Simple operations
|
|
104
|
+
RESTAPI = 'restapi' # HTTP endpoints
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Run Management
|
|
108
|
+
|
|
109
|
+
The `Run` class (`models.py`) manages action execution:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
class Run(BaseModel):
|
|
113
|
+
"""Manages plugin execution lifecycle.
|
|
114
|
+
|
|
115
|
+
Key Methods:
|
|
116
|
+
log_message(message, context): Log execution messages
|
|
117
|
+
set_progress(current, total, category): Update progress
|
|
118
|
+
set_metrics(metrics, category): Record metrics
|
|
119
|
+
log(log_type, data): Structured logging
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def log_message(self, message: str, context: str = 'INFO'):
|
|
123
|
+
"""Log execution messages with context."""
|
|
124
|
+
|
|
125
|
+
def set_progress(self, current: int, total: int, category: str = None):
|
|
126
|
+
"""Update progress tracking."""
|
|
127
|
+
|
|
128
|
+
def set_metrics(self, metrics: dict, category: str):
|
|
129
|
+
"""Record execution metrics."""
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Creating Plugin Categories
|
|
133
|
+
|
|
134
|
+
### 1. Define Category Structure
|
|
135
|
+
|
|
136
|
+
Create a new category directory:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
synapse_sdk/plugins/categories/my_category/
|
|
140
|
+
├── __init__.py
|
|
141
|
+
├── actions/
|
|
142
|
+
│ ├── __init__.py
|
|
143
|
+
│ └── my_action.py
|
|
144
|
+
└── templates/
|
|
145
|
+
└── plugin/
|
|
146
|
+
├── __init__.py
|
|
147
|
+
└── my_action.py
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 2. Implement Base Action
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
# synapse_sdk/plugins/categories/my_category/actions/my_action.py
|
|
154
|
+
from synapse_sdk.plugins.categories.base import Action
|
|
155
|
+
from synapse_sdk.plugins.categories.decorators import register_action
|
|
156
|
+
from synapse_sdk.plugins.enums import PluginCategory, RunMethod
|
|
157
|
+
from pydantic import BaseModel
|
|
158
|
+
|
|
159
|
+
class MyActionParams(BaseModel):
|
|
160
|
+
"""Parameter model for validation."""
|
|
161
|
+
input_path: str
|
|
162
|
+
output_path: str
|
|
163
|
+
config: dict = {}
|
|
164
|
+
|
|
165
|
+
@register_action
|
|
166
|
+
class MyAction(Action):
|
|
167
|
+
"""Base implementation for my_category actions."""
|
|
168
|
+
|
|
169
|
+
name = 'my_action'
|
|
170
|
+
category = PluginCategory.MY_CATEGORY
|
|
171
|
+
method = RunMethod.JOB
|
|
172
|
+
params_model = MyActionParams
|
|
173
|
+
|
|
174
|
+
progress_categories = {
|
|
175
|
+
'preprocessing': {'proportion': 20},
|
|
176
|
+
'processing': {'proportion': 60},
|
|
177
|
+
'postprocessing': {'proportion': 20}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
metrics_categories = {
|
|
181
|
+
'performance': {
|
|
182
|
+
'throughput': 0,
|
|
183
|
+
'latency': 0,
|
|
184
|
+
'accuracy': 0
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
def start(self):
|
|
189
|
+
"""Main execution logic."""
|
|
190
|
+
self.run.log_message("Starting my action...")
|
|
191
|
+
|
|
192
|
+
# Access validated parameters
|
|
193
|
+
input_path = self.params['input_path']
|
|
194
|
+
output_path = self.params['output_path']
|
|
195
|
+
|
|
196
|
+
# Update progress
|
|
197
|
+
self.run.set_progress(0, 100, 'preprocessing')
|
|
198
|
+
|
|
199
|
+
# Your implementation here
|
|
200
|
+
result = self.process_data(input_path, output_path)
|
|
201
|
+
|
|
202
|
+
# Record metrics
|
|
203
|
+
self.run.set_metrics({
|
|
204
|
+
'throughput': result['throughput'],
|
|
205
|
+
'items_processed': result['count']
|
|
206
|
+
}, 'performance')
|
|
207
|
+
|
|
208
|
+
self.run.log_message("Action completed successfully")
|
|
209
|
+
return result
|
|
210
|
+
|
|
211
|
+
def process_data(self, input_path, output_path):
|
|
212
|
+
"""Implement category-specific logic."""
|
|
213
|
+
raise NotImplementedError("Subclasses must implement process_data")
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 3. Create Template
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
# synapse_sdk/plugins/categories/my_category/templates/plugin/my_action.py
|
|
220
|
+
from synapse_sdk.plugins.categories.my_category import MyAction as BaseMyAction
|
|
221
|
+
|
|
222
|
+
class MyAction(BaseMyAction):
|
|
223
|
+
"""Custom implementation of my_action."""
|
|
224
|
+
|
|
225
|
+
def process_data(self, input_path, output_path):
|
|
226
|
+
"""Custom data processing logic."""
|
|
227
|
+
# Plugin developer implements this
|
|
228
|
+
return {"status": "success", "items_processed": 100}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 4. Register Category
|
|
232
|
+
|
|
233
|
+
Update `enums.py`:
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
class PluginCategory(Enum):
|
|
237
|
+
# ... existing categories
|
|
238
|
+
MY_CATEGORY = 'my_category'
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Action Implementation Examples
|
|
242
|
+
|
|
243
|
+
### Upload Action Architecture
|
|
244
|
+
|
|
245
|
+
The upload action demonstrates modular action architecture:
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
# Structure after SYN-5306 refactoring
|
|
249
|
+
synapse_sdk/plugins/categories/upload/actions/upload/
|
|
250
|
+
├── __init__.py # Public API exports
|
|
251
|
+
├── action.py # Main UploadAction class
|
|
252
|
+
├── run.py # UploadRun execution management
|
|
253
|
+
├── models.py # UploadParams validation
|
|
254
|
+
├── enums.py # LogCode and LOG_MESSAGES
|
|
255
|
+
├── exceptions.py # Custom exceptions
|
|
256
|
+
└── utils.py # Utility classes
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Key Implementation Details:**
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
# upload/action.py
|
|
263
|
+
@register_action
|
|
264
|
+
class UploadAction(Action):
|
|
265
|
+
name = 'upload'
|
|
266
|
+
category = PluginCategory.UPLOAD
|
|
267
|
+
method = RunMethod.JOB
|
|
268
|
+
run_class = UploadRun
|
|
269
|
+
|
|
270
|
+
def start(self):
|
|
271
|
+
# Comprehensive upload workflow
|
|
272
|
+
storage_id = self.params.get('storage')
|
|
273
|
+
path = self.params.get('path')
|
|
274
|
+
|
|
275
|
+
# Setup and validation
|
|
276
|
+
storage = self.client.get_storage(storage_id)
|
|
277
|
+
pathlib_cwd = get_pathlib(storage, path)
|
|
278
|
+
|
|
279
|
+
# Excel metadata processing
|
|
280
|
+
excel_metadata = self._read_excel_metadata(pathlib_cwd)
|
|
281
|
+
|
|
282
|
+
# File organization and upload
|
|
283
|
+
file_specification = self._analyze_collection()
|
|
284
|
+
organized_files = self._organize_files(pathlib_cwd, file_specification, excel_metadata)
|
|
285
|
+
|
|
286
|
+
# Upload files
|
|
287
|
+
uploaded_files = self._upload_files(organized_files)
|
|
288
|
+
|
|
289
|
+
# Data unit generation
|
|
290
|
+
generated_data_units = self._generate_data_units(uploaded_files, batch_size)
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
'uploaded_files_count': len(uploaded_files),
|
|
294
|
+
'generated_data_units_count': len(generated_data_units)
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Plugin Action Structure Guidelines
|
|
299
|
+
|
|
300
|
+
For complex actions that require multiple components, follow the modular structure pattern established by the refactored upload action. This approach improves maintainability, testability, and code organization.
|
|
301
|
+
|
|
302
|
+
## Complex Action Refactoring Patterns
|
|
303
|
+
|
|
304
|
+
### Overview
|
|
305
|
+
|
|
306
|
+
As plugin actions evolve and grow in complexity, they often become monolithic files with 900+ lines containing multiple responsibilities. The SYN-5398 UploadAction refactoring demonstrates how to break down complex actions using **Strategy** and **Facade** design patterns, transforming a 1,600+ line monolithic implementation into a maintainable, testable architecture.
|
|
307
|
+
|
|
308
|
+
### When to Apply Complex Refactoring
|
|
309
|
+
|
|
310
|
+
Consider refactoring when your action exhibits:
|
|
311
|
+
|
|
312
|
+
- **Size**: 900+ lines in a single method or file
|
|
313
|
+
- **Multiple Responsibilities**: Handling validation, file processing, uploads, metadata extraction in one method
|
|
314
|
+
- **Conditional Complexity**: Multiple if/else branches for different processing strategies
|
|
315
|
+
- **Testing Difficulty**: Hard to unit test individual components
|
|
316
|
+
- **Maintenance Issues**: Changes require touching multiple unrelated sections
|
|
317
|
+
|
|
318
|
+
### Strategy Pattern for Pluggable Behaviors
|
|
319
|
+
|
|
320
|
+
The Strategy pattern allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. This is ideal for actions that need different implementations of the same behavior.
|
|
321
|
+
|
|
322
|
+
#### Core Strategy Types
|
|
323
|
+
|
|
324
|
+
Based on the UploadAction refactoring, identify these key strategy categories:
|
|
325
|
+
|
|
326
|
+
```python
|
|
327
|
+
# 1. ValidationStrategy - Parameter and environment validation
|
|
328
|
+
class ValidationStrategy(ABC):
|
|
329
|
+
@abstractmethod
|
|
330
|
+
def validate(self, context: ActionContext) -> ValidationResult:
|
|
331
|
+
"""Validate parameters and environment."""
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
class UploadValidationStrategy(ValidationStrategy):
|
|
335
|
+
def validate(self, context: ActionContext) -> ValidationResult:
|
|
336
|
+
# Validate storage, collection, file paths
|
|
337
|
+
return ValidationResult(is_valid=True, errors=[])
|
|
338
|
+
|
|
339
|
+
# 2. FileDiscoveryStrategy - Different file discovery methods
|
|
340
|
+
class FileDiscoveryStrategy(ABC):
|
|
341
|
+
@abstractmethod
|
|
342
|
+
def discover_files(self, context: ActionContext) -> List[Path]:
|
|
343
|
+
"""Discover files to process."""
|
|
344
|
+
pass
|
|
345
|
+
|
|
346
|
+
class RecursiveFileDiscoveryStrategy(FileDiscoveryStrategy):
|
|
347
|
+
def discover_files(self, context: ActionContext) -> List[Path]:
|
|
348
|
+
# Recursive directory traversal
|
|
349
|
+
return list(context.base_path.rglob("*"))
|
|
350
|
+
|
|
351
|
+
# 3. MetadataStrategy - Various metadata extraction approaches
|
|
352
|
+
class MetadataStrategy(ABC):
|
|
353
|
+
@abstractmethod
|
|
354
|
+
def extract_metadata(self, files: List[Path], context: ActionContext) -> Dict:
|
|
355
|
+
"""Extract metadata from files."""
|
|
356
|
+
pass
|
|
357
|
+
|
|
358
|
+
class ExcelMetadataStrategy(MetadataStrategy):
|
|
359
|
+
def extract_metadata(self, files: List[Path], context: ActionContext) -> Dict:
|
|
360
|
+
# Excel-specific metadata extraction
|
|
361
|
+
return {"excel_sheets": [], "total_rows": 0}
|
|
362
|
+
|
|
363
|
+
# 4. UploadStrategy - Different upload implementations
|
|
364
|
+
class UploadStrategy(ABC):
|
|
365
|
+
@abstractmethod
|
|
366
|
+
def upload_files(self, files: List[Path], context: ActionContext) -> List[UploadResult]:
|
|
367
|
+
"""Upload files using specific strategy."""
|
|
368
|
+
pass
|
|
369
|
+
|
|
370
|
+
class StandardUploadStrategy(UploadStrategy):
|
|
371
|
+
def upload_files(self, files: List[Path], context: ActionContext) -> List[UploadResult]:
|
|
372
|
+
# Standard upload implementation
|
|
373
|
+
return [self._upload_single(f) for f in files]
|
|
374
|
+
|
|
375
|
+
# 5. DataUnitStrategy - Different data unit generation methods
|
|
376
|
+
class DataUnitStrategy(ABC):
|
|
377
|
+
@abstractmethod
|
|
378
|
+
def generate_data_units(self, uploaded_files: List[UploadResult], context: ActionContext) -> List[DataUnit]:
|
|
379
|
+
"""Generate data units from uploaded files."""
|
|
380
|
+
pass
|
|
381
|
+
|
|
382
|
+
class StandardDataUnitStrategy(DataUnitStrategy):
|
|
383
|
+
def generate_data_units(self, uploaded_files: List[UploadResult], context: ActionContext) -> List[DataUnit]:
|
|
384
|
+
# Standard data unit creation
|
|
385
|
+
return [DataUnit(file=file) for file in uploaded_files]
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
#### Strategy Factory Pattern
|
|
389
|
+
|
|
390
|
+
Use a factory to create appropriate strategies based on configuration:
|
|
391
|
+
|
|
392
|
+
```python
|
|
393
|
+
class StrategyFactory:
|
|
394
|
+
"""Factory for creating action strategies based on context."""
|
|
395
|
+
|
|
396
|
+
@staticmethod
|
|
397
|
+
def create_validation_strategy(context: ActionContext) -> ValidationStrategy:
|
|
398
|
+
if context.params.get('strict_validation', False):
|
|
399
|
+
return StrictValidationStrategy()
|
|
400
|
+
return StandardValidationStrategy()
|
|
401
|
+
|
|
402
|
+
@staticmethod
|
|
403
|
+
def create_upload_strategy(context: ActionContext) -> UploadStrategy:
|
|
404
|
+
return StandardUploadStrategy()
|
|
405
|
+
|
|
406
|
+
@staticmethod
|
|
407
|
+
def create_file_discovery_strategy(context: ActionContext) -> FileDiscoveryStrategy:
|
|
408
|
+
if context.params.get('is_recursive', False):
|
|
409
|
+
return RecursiveFileDiscoveryStrategy()
|
|
410
|
+
return FlatFileDiscoveryStrategy()
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Facade Pattern with Orchestrator
|
|
414
|
+
|
|
415
|
+
The Facade pattern provides a simplified interface to a complex subsystem. The **Orchestrator** coordinates all strategies through a step-based workflow.
|
|
416
|
+
|
|
417
|
+
#### Orchestrator Implementation
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
class UploadOrchestrator:
|
|
421
|
+
"""Facade that orchestrates the complete upload workflow."""
|
|
422
|
+
|
|
423
|
+
def __init__(self, context: ActionContext):
|
|
424
|
+
self.context = context
|
|
425
|
+
self.factory = StrategyFactory()
|
|
426
|
+
self.steps_completed = []
|
|
427
|
+
|
|
428
|
+
# Initialize strategies
|
|
429
|
+
self.validation_strategy = self.factory.create_validation_strategy(context)
|
|
430
|
+
self.file_discovery_strategy = self.factory.create_file_discovery_strategy(context)
|
|
431
|
+
self.metadata_strategy = self.factory.create_metadata_strategy(context)
|
|
432
|
+
self.upload_strategy = self.factory.create_upload_strategy(context)
|
|
433
|
+
self.data_unit_strategy = self.factory.create_data_unit_strategy(context)
|
|
434
|
+
|
|
435
|
+
def execute_workflow(self) -> UploadResult:
|
|
436
|
+
"""Execute the complete upload workflow with rollback support."""
|
|
437
|
+
try:
|
|
438
|
+
# Step 1: Setup and validation
|
|
439
|
+
self._execute_step("setup_validation", self._setup_and_validate)
|
|
440
|
+
|
|
441
|
+
# Step 2: File discovery
|
|
442
|
+
files = self._execute_step("file_discovery", self._discover_files)
|
|
443
|
+
|
|
444
|
+
# Step 3: Excel metadata extraction
|
|
445
|
+
metadata = self._execute_step("metadata_extraction",
|
|
446
|
+
lambda: self._extract_metadata(files))
|
|
447
|
+
|
|
448
|
+
# Step 4: File organization
|
|
449
|
+
organized_files = self._execute_step("file_organization",
|
|
450
|
+
lambda: self._organize_files(files, metadata))
|
|
451
|
+
|
|
452
|
+
# Step 5: File upload
|
|
453
|
+
uploaded_files = self._execute_step("file_upload",
|
|
454
|
+
lambda: self._upload_files(organized_files))
|
|
455
|
+
|
|
456
|
+
# Step 6: Data unit generation
|
|
457
|
+
data_units = self._execute_step("data_unit_generation",
|
|
458
|
+
lambda: self._generate_data_units(uploaded_files))
|
|
459
|
+
|
|
460
|
+
# Step 7: Cleanup
|
|
461
|
+
self._execute_step("cleanup", self._cleanup_temp_files)
|
|
462
|
+
|
|
463
|
+
# Step 8: Result aggregation
|
|
464
|
+
return self._execute_step("result_aggregation",
|
|
465
|
+
lambda: self._aggregate_results(uploaded_files, data_units))
|
|
466
|
+
|
|
467
|
+
except Exception as e:
|
|
468
|
+
self._rollback_completed_steps()
|
|
469
|
+
raise UploadOrchestrationError(f"Workflow failed at step {len(self.steps_completed)}: {e}")
|
|
470
|
+
|
|
471
|
+
def _execute_step(self, step_name: str, step_func: callable):
|
|
472
|
+
"""Execute a workflow step with error handling and progress tracking."""
|
|
473
|
+
self.context.logger.log_message_with_code(LogCode.STEP_STARTED, step_name)
|
|
474
|
+
|
|
475
|
+
try:
|
|
476
|
+
result = step_func()
|
|
477
|
+
self.steps_completed.append(step_name)
|
|
478
|
+
self.context.logger.log_message_with_code(LogCode.STEP_COMPLETED, step_name)
|
|
479
|
+
return result
|
|
480
|
+
except Exception as e:
|
|
481
|
+
self.context.logger.log_message_with_code(LogCode.STEP_FAILED, step_name, str(e))
|
|
482
|
+
raise
|
|
483
|
+
|
|
484
|
+
def _setup_and_validate(self):
|
|
485
|
+
"""Step 1: Setup and validation using strategy."""
|
|
486
|
+
validation_result = self.validation_strategy.validate(self.context)
|
|
487
|
+
if not validation_result.is_valid:
|
|
488
|
+
raise ValidationError(f"Validation failed: {validation_result.errors}")
|
|
489
|
+
|
|
490
|
+
def _discover_files(self) -> List[Path]:
|
|
491
|
+
"""Step 2: File discovery using strategy."""
|
|
492
|
+
return self.file_discovery_strategy.discover_files(self.context)
|
|
493
|
+
|
|
494
|
+
def _extract_metadata(self, files: List[Path]) -> Dict:
|
|
495
|
+
"""Step 3: Metadata extraction using strategy."""
|
|
496
|
+
return self.metadata_strategy.extract_metadata(files, self.context)
|
|
497
|
+
|
|
498
|
+
def _upload_files(self, files: List[Path]) -> List[UploadResult]:
|
|
499
|
+
"""Step 5: File upload using strategy."""
|
|
500
|
+
return self.upload_strategy.upload_files(files, self.context)
|
|
501
|
+
|
|
502
|
+
def _generate_data_units(self, uploaded_files: List[UploadResult]) -> List[DataUnit]:
|
|
503
|
+
"""Step 6: Data unit generation using strategy."""
|
|
504
|
+
return self.data_unit_strategy.generate_data_units(uploaded_files, self.context)
|
|
505
|
+
|
|
506
|
+
def _rollback_completed_steps(self):
|
|
507
|
+
"""Rollback completed steps in reverse order."""
|
|
508
|
+
for step in reversed(self.steps_completed):
|
|
509
|
+
try:
|
|
510
|
+
rollback_method = getattr(self, f"_rollback_{step}", None)
|
|
511
|
+
if rollback_method:
|
|
512
|
+
rollback_method()
|
|
513
|
+
except Exception as e:
|
|
514
|
+
self.context.logger.log_message_with_code(LogCode.ROLLBACK_FAILED, step, str(e))
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
#### Transformed Action Implementation
|
|
518
|
+
|
|
519
|
+
The main action becomes dramatically simplified:
|
|
520
|
+
|
|
521
|
+
```python
|
|
522
|
+
# Before: 900+ line monolithic start() method
|
|
523
|
+
class UploadAction(Action):
|
|
524
|
+
def start(self):
|
|
525
|
+
# 900+ lines of mixed responsibilities...
|
|
526
|
+
|
|
527
|
+
# After: Clean, orchestrated implementation (196 lines total)
|
|
528
|
+
class UploadAction(Action):
|
|
529
|
+
"""Upload action using Strategy and Facade patterns."""
|
|
530
|
+
|
|
531
|
+
def __init__(self, *args, **kwargs):
|
|
532
|
+
super().__init__(*args, **kwargs)
|
|
533
|
+
self.context = ActionContext(
|
|
534
|
+
params=self.params,
|
|
535
|
+
client=self.client,
|
|
536
|
+
logger=self.run,
|
|
537
|
+
workspace=self.get_workspace_path()
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
def start(self) -> UploadResult:
|
|
541
|
+
"""Main action execution using orchestrator facade."""
|
|
542
|
+
self.run.log_message_with_code(LogCode.UPLOAD_STARTED)
|
|
543
|
+
|
|
544
|
+
try:
|
|
545
|
+
# Create and execute orchestrator
|
|
546
|
+
orchestrator = UploadOrchestrator(self.context)
|
|
547
|
+
result = orchestrator.execute_workflow()
|
|
548
|
+
|
|
549
|
+
self.run.log_message_with_code(LogCode.UPLOAD_COMPLETED,
|
|
550
|
+
result.uploaded_files_count, result.data_units_count)
|
|
551
|
+
return result
|
|
552
|
+
|
|
553
|
+
except Exception as e:
|
|
554
|
+
self.run.log_message_with_code(LogCode.UPLOAD_FAILED, str(e))
|
|
555
|
+
raise ActionError(f"Upload action failed: {e}")
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### Context Management
|
|
559
|
+
|
|
560
|
+
Use a shared context object to pass state between strategies and orchestrator:
|
|
561
|
+
|
|
562
|
+
```python
|
|
563
|
+
@dataclass
|
|
564
|
+
class ActionContext:
|
|
565
|
+
"""Shared context for action execution."""
|
|
566
|
+
params: Dict[str, Any]
|
|
567
|
+
client: Any
|
|
568
|
+
logger: Any
|
|
569
|
+
workspace: Path
|
|
570
|
+
temp_files: List[Path] = field(default_factory=list)
|
|
571
|
+
metrics: Dict[str, Any] = field(default_factory=dict)
|
|
572
|
+
|
|
573
|
+
def add_temp_file(self, file_path: Path):
|
|
574
|
+
"""Track temporary files for cleanup."""
|
|
575
|
+
self.temp_files.append(file_path)
|
|
576
|
+
|
|
577
|
+
def update_metrics(self, category: str, metrics: Dict[str, Any]):
|
|
578
|
+
"""Update execution metrics."""
|
|
579
|
+
if category not in self.metrics:
|
|
580
|
+
self.metrics[category] = {}
|
|
581
|
+
self.metrics[category].update(metrics)
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Migration Guide for Complex Actions
|
|
585
|
+
|
|
586
|
+
#### Step 1: Identify Strategy Boundaries
|
|
587
|
+
|
|
588
|
+
Analyze your monolithic action to identify:
|
|
589
|
+
|
|
590
|
+
1. **Different algorithms** for the same operation (validation methods, file processing approaches)
|
|
591
|
+
2. **Configurable behaviors** that change based on parameters
|
|
592
|
+
3. **Testable units** that can be isolated
|
|
593
|
+
|
|
594
|
+
#### Step 2: Extract Strategies
|
|
595
|
+
|
|
596
|
+
```python
|
|
597
|
+
# Before: Mixed responsibilities in one method
|
|
598
|
+
def start(self):
|
|
599
|
+
# validation logic (100 lines)
|
|
600
|
+
if self.params.get('strict_mode'):
|
|
601
|
+
# strict validation
|
|
602
|
+
else:
|
|
603
|
+
# standard validation
|
|
604
|
+
|
|
605
|
+
# file discovery logic (150 lines)
|
|
606
|
+
if self.params.get('recursive'):
|
|
607
|
+
# recursive discovery
|
|
608
|
+
else:
|
|
609
|
+
# flat discovery
|
|
610
|
+
|
|
611
|
+
# upload logic (200 lines)
|
|
612
|
+
# Direct upload implementation
|
|
613
|
+
uploaded_files = self._upload_files(organized_files)
|
|
614
|
+
|
|
615
|
+
# After: Separated into strategies
|
|
616
|
+
class StrictValidationStrategy(ValidationStrategy): ...
|
|
617
|
+
class StandardValidationStrategy(ValidationStrategy): ...
|
|
618
|
+
class RecursiveFileDiscoveryStrategy(FileDiscoveryStrategy): ...
|
|
619
|
+
class StandardUploadStrategy(UploadStrategy): ...
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
#### Step 3: Create Orchestrator
|
|
623
|
+
|
|
624
|
+
Design your workflow steps:
|
|
625
|
+
|
|
626
|
+
1. Define clear step boundaries
|
|
627
|
+
2. Implement rollback for each step
|
|
628
|
+
3. Add progress tracking and logging
|
|
629
|
+
4. Handle errors gracefully
|
|
630
|
+
|
|
631
|
+
#### Step 4: Update Action Class
|
|
632
|
+
|
|
633
|
+
Transform the main action to use the orchestrator:
|
|
634
|
+
|
|
635
|
+
1. Create context object
|
|
636
|
+
2. Initialize orchestrator
|
|
637
|
+
3. Execute workflow
|
|
638
|
+
4. Handle results and errors
|
|
639
|
+
|
|
640
|
+
### Benefits and Metrics
|
|
641
|
+
|
|
642
|
+
The SYN-5398 refactoring achieved:
|
|
643
|
+
|
|
644
|
+
#### **Code Reduction**
|
|
645
|
+
|
|
646
|
+
- Main action: 900+ lines → 196 lines (-78% reduction)
|
|
647
|
+
- Total codebase: 1,600+ lines → 1,400+ lines (better organized)
|
|
648
|
+
- Cyclomatic complexity: High → Low (single responsibility per class)
|
|
649
|
+
|
|
650
|
+
#### **Improved Testability**
|
|
651
|
+
|
|
652
|
+
- **89 passing tests** with individual strategy testing
|
|
653
|
+
- **Isolated unit tests** for each strategy component
|
|
654
|
+
- **Integration tests** for orchestrator workflow
|
|
655
|
+
- **Rollback testing** for error scenarios
|
|
656
|
+
|
|
657
|
+
#### **Better Maintainability**
|
|
658
|
+
|
|
659
|
+
- **Single responsibility** per strategy class
|
|
660
|
+
- **Clear separation** of concerns
|
|
661
|
+
- **Reusable strategies** across different actions
|
|
662
|
+
- **Easy to extend** with new strategy implementations
|
|
663
|
+
|
|
664
|
+
#### **Enhanced Flexibility**
|
|
665
|
+
|
|
666
|
+
- **Runtime strategy selection** based on parameters
|
|
667
|
+
- **Pluggable algorithms** without changing core logic
|
|
668
|
+
- **Configuration-driven** behavior changes
|
|
669
|
+
- **A/B testing support** through strategy switching
|
|
670
|
+
|
|
671
|
+
### Example: Converting a Complex Action
|
|
672
|
+
|
|
673
|
+
```python
|
|
674
|
+
# Before: Monolithic action (simplified example)
|
|
675
|
+
class ComplexAction(Action):
|
|
676
|
+
def start(self):
|
|
677
|
+
# 50 lines of validation
|
|
678
|
+
if self.params.get('validation_type') == 'strict':
|
|
679
|
+
# strict validation logic
|
|
680
|
+
else:
|
|
681
|
+
# standard validation logic
|
|
682
|
+
|
|
683
|
+
# 100 lines of data processing
|
|
684
|
+
if self.params.get('processing_method') == 'batch':
|
|
685
|
+
# batch processing logic
|
|
686
|
+
else:
|
|
687
|
+
# stream processing logic
|
|
688
|
+
|
|
689
|
+
# 80 lines of output generation
|
|
690
|
+
if self.params.get('output_format') == 'json':
|
|
691
|
+
# JSON output logic
|
|
692
|
+
else:
|
|
693
|
+
# CSV output logic
|
|
694
|
+
|
|
695
|
+
# After: Strategy-based action
|
|
696
|
+
class ComplexAction(Action):
|
|
697
|
+
def start(self):
|
|
698
|
+
context = ActionContext(params=self.params, logger=self.run)
|
|
699
|
+
orchestrator = ComplexActionOrchestrator(context)
|
|
700
|
+
return orchestrator.execute_workflow()
|
|
701
|
+
|
|
702
|
+
# Individual strategies (testable, reusable)
|
|
703
|
+
class StrictValidationStrategy(ValidationStrategy): ...
|
|
704
|
+
class BatchProcessingStrategy(ProcessingStrategy): ...
|
|
705
|
+
class JSONOutputStrategy(OutputStrategy): ...
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
This pattern transformation makes complex actions maintainable, testable, and extensible while preserving all original functionality.
|
|
709
|
+
|
|
710
|
+
### Recommended File Structure
|
|
711
|
+
|
|
712
|
+
```
|
|
713
|
+
synapse_sdk/plugins/categories/{category}/actions/{action}/
|
|
714
|
+
├── __init__.py # Public API exports
|
|
715
|
+
├── action.py # Main action implementation
|
|
716
|
+
├── run.py # Execution and logging management
|
|
717
|
+
├── models.py # Pydantic parameter models
|
|
718
|
+
├── enums.py # Enums and message constants
|
|
719
|
+
├── exceptions.py # Custom exception classes
|
|
720
|
+
├── utils.py # Helper utilities and configurations
|
|
721
|
+
└── README.md # Action-specific documentation
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
### Module Responsibilities
|
|
725
|
+
|
|
726
|
+
#### 1. `__init__.py` - Public API
|
|
727
|
+
|
|
728
|
+
Defines the public interface and maintains backward compatibility:
|
|
729
|
+
|
|
730
|
+
```python
|
|
731
|
+
# Export all public classes for backward compatibility
|
|
732
|
+
from .action import UploadAction
|
|
733
|
+
from .enums import LogCode, LOG_MESSAGES, UploadStatus
|
|
734
|
+
from .exceptions import ExcelParsingError, ExcelSecurityError
|
|
735
|
+
from .models import UploadParams
|
|
736
|
+
from .run import UploadRun
|
|
737
|
+
from .utils import ExcelSecurityConfig, PathAwareJSONEncoder
|
|
738
|
+
|
|
739
|
+
__all__ = [
|
|
740
|
+
'UploadAction',
|
|
741
|
+
'UploadRun',
|
|
742
|
+
'UploadParams',
|
|
743
|
+
'UploadStatus',
|
|
744
|
+
'LogCode',
|
|
745
|
+
'LOG_MESSAGES',
|
|
746
|
+
'ExcelSecurityError',
|
|
747
|
+
'ExcelParsingError',
|
|
748
|
+
'PathAwareJSONEncoder',
|
|
749
|
+
'ExcelSecurityConfig',
|
|
750
|
+
]
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
#### 2. `action.py` - Main Implementation
|
|
754
|
+
|
|
755
|
+
Contains the core action logic, inheriting from the base `Action` class:
|
|
756
|
+
|
|
757
|
+
```python
|
|
758
|
+
from synapse_sdk.plugins.categories.base import Action
|
|
759
|
+
from synapse_sdk.plugins.enums import PluginCategory, RunMethod
|
|
760
|
+
|
|
761
|
+
from .enums import LogCode
|
|
762
|
+
from .models import UploadParams
|
|
763
|
+
from .run import UploadRun
|
|
764
|
+
|
|
765
|
+
class UploadAction(Action):
|
|
766
|
+
"""Main upload action implementation."""
|
|
767
|
+
|
|
768
|
+
name = 'upload'
|
|
769
|
+
category = PluginCategory.UPLOAD
|
|
770
|
+
method = RunMethod.JOB
|
|
771
|
+
run_class = UploadRun
|
|
772
|
+
params_model = UploadParams
|
|
773
|
+
|
|
774
|
+
def start(self):
|
|
775
|
+
"""Main action logic."""
|
|
776
|
+
# Validate parameters
|
|
777
|
+
self.validate_params()
|
|
778
|
+
|
|
779
|
+
# Log start
|
|
780
|
+
self.run.log_message_with_code(LogCode.UPLOAD_STARTED)
|
|
781
|
+
|
|
782
|
+
# Execute main logic
|
|
783
|
+
result = self._process_upload()
|
|
784
|
+
|
|
785
|
+
# Log completion
|
|
786
|
+
self.run.log_message_with_code(LogCode.UPLOAD_COMPLETED)
|
|
787
|
+
|
|
788
|
+
return result
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
#### 3. `models.py` - Parameter Validation
|
|
792
|
+
|
|
793
|
+
Defines Pydantic models for type-safe parameter validation:
|
|
794
|
+
|
|
795
|
+
```python
|
|
796
|
+
from typing import Annotated
|
|
797
|
+
from pydantic import AfterValidator, BaseModel, field_validator
|
|
798
|
+
from synapse_sdk.utils.pydantic.validators import non_blank
|
|
799
|
+
|
|
800
|
+
class UploadParams(BaseModel):
|
|
801
|
+
"""Upload action parameters with validation."""
|
|
802
|
+
|
|
803
|
+
name: Annotated[str, AfterValidator(non_blank)]
|
|
804
|
+
description: str | None = None
|
|
805
|
+
path: str
|
|
806
|
+
storage: int
|
|
807
|
+
collection: int
|
|
808
|
+
project: int | None = None
|
|
809
|
+
is_recursive: bool = False
|
|
810
|
+
max_file_size_mb: int = 50
|
|
811
|
+
|
|
812
|
+
@field_validator('storage', mode='before')
|
|
813
|
+
@classmethod
|
|
814
|
+
def check_storage_exists(cls, value: str, info) -> str:
|
|
815
|
+
"""Validate storage exists via API."""
|
|
816
|
+
action = info.context['action']
|
|
817
|
+
client = action.client
|
|
818
|
+
try:
|
|
819
|
+
client.get_storage(value)
|
|
820
|
+
except ClientError:
|
|
821
|
+
raise PydanticCustomError('client_error', 'Storage not found')
|
|
822
|
+
return value
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
#### 4. `enums.py` - Constants and Enums
|
|
826
|
+
|
|
827
|
+
Centralizes all enum definitions and constant values:
|
|
828
|
+
|
|
829
|
+
```python
|
|
830
|
+
from enum import Enum
|
|
831
|
+
from synapse_sdk.plugins.enums import Context
|
|
832
|
+
|
|
833
|
+
class UploadStatus(str, Enum):
|
|
834
|
+
"""Upload processing status."""
|
|
835
|
+
PENDING = 'pending'
|
|
836
|
+
PROCESSING = 'processing'
|
|
837
|
+
COMPLETED = 'completed'
|
|
838
|
+
FAILED = 'failed'
|
|
839
|
+
|
|
840
|
+
class LogCode(str, Enum):
|
|
841
|
+
"""Type-safe logging codes."""
|
|
842
|
+
UPLOAD_STARTED = 'UPLOAD_STARTED'
|
|
843
|
+
VALIDATION_FAILED = 'VALIDATION_FAILED'
|
|
844
|
+
NO_FILES_FOUND = 'NO_FILES_FOUND'
|
|
845
|
+
UPLOAD_COMPLETED = 'UPLOAD_COMPLETED'
|
|
846
|
+
# ... additional codes
|
|
847
|
+
|
|
848
|
+
LOG_MESSAGES = {
|
|
849
|
+
LogCode.UPLOAD_STARTED: {
|
|
850
|
+
'message': 'Upload process started.',
|
|
851
|
+
'level': Context.INFO,
|
|
852
|
+
},
|
|
853
|
+
LogCode.VALIDATION_FAILED: {
|
|
854
|
+
'message': 'Validation failed: {}',
|
|
855
|
+
'level': Context.DANGER,
|
|
856
|
+
},
|
|
857
|
+
# ... message configurations
|
|
858
|
+
}
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
#### 5. `run.py` - Execution Management
|
|
862
|
+
|
|
863
|
+
Handles execution flow, progress tracking, and specialized logging:
|
|
864
|
+
|
|
865
|
+
```python
|
|
866
|
+
from typing import Optional
|
|
867
|
+
from synapse_sdk.plugins.models import Run
|
|
868
|
+
from synapse_sdk.plugins.enums import Context
|
|
869
|
+
|
|
870
|
+
from .enums import LogCode, LOG_MESSAGES
|
|
871
|
+
|
|
872
|
+
class UploadRun(Run):
|
|
873
|
+
"""Specialized run management for upload actions."""
|
|
874
|
+
|
|
875
|
+
def log_message_with_code(self, code: LogCode, *args, level: Optional[Context] = None):
|
|
876
|
+
"""Type-safe logging with predefined messages."""
|
|
877
|
+
if code not in LOG_MESSAGES:
|
|
878
|
+
self.log_message(f'Unknown log code: {code}')
|
|
879
|
+
return
|
|
880
|
+
|
|
881
|
+
log_config = LOG_MESSAGES[code]
|
|
882
|
+
message = log_config['message'].format(*args) if args else log_config['message']
|
|
883
|
+
log_level = level or log_config['level']
|
|
884
|
+
|
|
885
|
+
self.log_message(message, context=log_level.value)
|
|
886
|
+
|
|
887
|
+
def log_upload_event(self, code: LogCode, *args, level: Optional[Context] = None):
|
|
888
|
+
"""Log upload-specific events with metrics."""
|
|
889
|
+
self.log_message_with_code(code, *args, level)
|
|
890
|
+
# Additional upload-specific logging logic
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
#### 6. `exceptions.py` - Custom Exceptions
|
|
894
|
+
|
|
895
|
+
Defines action-specific exception classes:
|
|
896
|
+
|
|
897
|
+
```python
|
|
898
|
+
class ExcelSecurityError(Exception):
|
|
899
|
+
"""Raised when Excel file security validation fails."""
|
|
900
|
+
pass
|
|
901
|
+
|
|
902
|
+
class ExcelParsingError(Exception):
|
|
903
|
+
"""Raised when Excel file parsing encounters errors."""
|
|
904
|
+
pass
|
|
905
|
+
|
|
906
|
+
class UploadValidationError(Exception):
|
|
907
|
+
"""Raised when upload parameter validation fails."""
|
|
908
|
+
pass
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
#### 7. `utils.py` - Helper Utilities
|
|
912
|
+
|
|
913
|
+
Contains utility classes and helper functions:
|
|
914
|
+
|
|
915
|
+
```python
|
|
916
|
+
import json
|
|
917
|
+
import os
|
|
918
|
+
from pathlib import Path
|
|
919
|
+
|
|
920
|
+
class PathAwareJSONEncoder(json.JSONEncoder):
|
|
921
|
+
"""JSON encoder that handles Path objects."""
|
|
922
|
+
|
|
923
|
+
def default(self, obj):
|
|
924
|
+
if hasattr(obj, '__fspath__') or hasattr(obj, 'as_posix'):
|
|
925
|
+
return str(obj)
|
|
926
|
+
elif hasattr(obj, 'isoformat'):
|
|
927
|
+
return obj.isoformat()
|
|
928
|
+
return super().default(obj)
|
|
929
|
+
|
|
930
|
+
class ExcelSecurityConfig:
|
|
931
|
+
"""Configuration for Excel file security limits."""
|
|
932
|
+
|
|
933
|
+
def __init__(self):
|
|
934
|
+
self.MAX_FILE_SIZE_MB = int(os.getenv('EXCEL_MAX_FILE_SIZE_MB', '10'))
|
|
935
|
+
self.MAX_ROWS = int(os.getenv('EXCEL_MAX_ROWS', '10000'))
|
|
936
|
+
self.MAX_COLUMNS = int(os.getenv('EXCEL_MAX_COLUMNS', '50'))
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
### Migration Guide
|
|
940
|
+
|
|
941
|
+
#### From Monolithic to Modular Structure
|
|
942
|
+
|
|
943
|
+
1. **Identify Components**: Break down the monolithic action into logical components
|
|
944
|
+
2. **Extract Models**: Move parameter validation to `models.py`
|
|
945
|
+
3. **Separate Enums**: Move constants and enums to `enums.py`
|
|
946
|
+
4. **Create Utilities**: Extract helper functions to `utils.py`
|
|
947
|
+
5. **Update Imports**: Ensure backward compatibility through `__init__.py`
|
|
948
|
+
|
|
949
|
+
#### Example Migration Steps
|
|
950
|
+
|
|
951
|
+
```python
|
|
952
|
+
# Before: Single upload.py file (1362 lines)
|
|
953
|
+
class UploadAction(Action):
|
|
954
|
+
# All code in one file...
|
|
955
|
+
|
|
956
|
+
# After: Modular structure
|
|
957
|
+
# action.py - Main logic (546 lines)
|
|
958
|
+
# models.py - Parameter validation (98 lines)
|
|
959
|
+
# enums.py - Constants and logging codes (156 lines)
|
|
960
|
+
# run.py - Execution management (134 lines)
|
|
961
|
+
# utils.py - Helper utilities (89 lines)
|
|
962
|
+
# exceptions.py - Custom exceptions (6 lines)
|
|
963
|
+
# __init__.py - Public API (20 lines)
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
### Benefits of Modular Structure
|
|
967
|
+
|
|
968
|
+
- **Maintainability**: Each file has a single responsibility
|
|
969
|
+
- **Testability**: Individual components can be tested in isolation
|
|
970
|
+
- **Reusability**: Utilities and models can be shared across actions
|
|
971
|
+
- **Type Safety**: Enum-based logging and strong parameter validation
|
|
972
|
+
- **Backward Compatibility**: Public API remains unchanged
|
|
973
|
+
|
|
974
|
+
**Logging System with Enums:**
|
|
975
|
+
|
|
976
|
+
```python
|
|
977
|
+
# upload/enums.py
|
|
978
|
+
class LogCode(str, Enum):
|
|
979
|
+
VALIDATION_FAILED = 'VALIDATION_FAILED'
|
|
980
|
+
NO_FILES_FOUND = 'NO_FILES_FOUND'
|
|
981
|
+
# ... 36 total log codes
|
|
982
|
+
|
|
983
|
+
LOG_MESSAGES = {
|
|
984
|
+
LogCode.VALIDATION_FAILED: {
|
|
985
|
+
'message': 'Validation failed.',
|
|
986
|
+
'level': Context.DANGER,
|
|
987
|
+
},
|
|
988
|
+
# ... message configurations
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
# upload/run.py
|
|
992
|
+
class UploadRun(Run):
|
|
993
|
+
def log_message_with_code(self, code: LogCode, *args, level: Optional[Context] = None):
|
|
994
|
+
"""Type-safe logging with predefined messages."""
|
|
995
|
+
if code not in LOG_MESSAGES:
|
|
996
|
+
self.log_message(f'Unknown log code: {code}')
|
|
997
|
+
return
|
|
998
|
+
|
|
999
|
+
log_config = LOG_MESSAGES[code]
|
|
1000
|
+
message = log_config['message'].format(*args) if args else log_config['message']
|
|
1001
|
+
log_level = level or log_config['level'] or Context.INFO
|
|
1002
|
+
|
|
1003
|
+
if log_level == Context.INFO.value:
|
|
1004
|
+
self.log_message(message, context=log_level.value)
|
|
1005
|
+
else:
|
|
1006
|
+
self.log_upload_event(code, *args, level)
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
## Development Workflow
|
|
1010
|
+
|
|
1011
|
+
### 1. Local Development Setup
|
|
1012
|
+
|
|
1013
|
+
```bash
|
|
1014
|
+
# Set up development environment
|
|
1015
|
+
cd synapse_sdk/plugins/categories/my_category
|
|
1016
|
+
python -m pip install -e .
|
|
1017
|
+
|
|
1018
|
+
# Create test plugin
|
|
1019
|
+
synapse plugin create --category my_category --debug
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
### 2. Action Testing
|
|
1023
|
+
|
|
1024
|
+
```python
|
|
1025
|
+
# Test action implementation
|
|
1026
|
+
from synapse_sdk.plugins.utils import get_action_class
|
|
1027
|
+
|
|
1028
|
+
# Get action class
|
|
1029
|
+
ActionClass = get_action_class("my_category", "my_action")
|
|
1030
|
+
|
|
1031
|
+
# Create test instance
|
|
1032
|
+
action = ActionClass(
|
|
1033
|
+
params={"input_path": "/test/data", "output_path": "/test/output"},
|
|
1034
|
+
plugin_config={"debug": True},
|
|
1035
|
+
envs={"TEST_MODE": "true"}
|
|
1036
|
+
)
|
|
1037
|
+
|
|
1038
|
+
# Run action
|
|
1039
|
+
result = action.run_action()
|
|
1040
|
+
assert result["status"] == "success"
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
### 3. Integration Testing
|
|
1044
|
+
|
|
1045
|
+
```python
|
|
1046
|
+
# Test with Ray backend
|
|
1047
|
+
import ray
|
|
1048
|
+
from synapse_sdk.clients.ray import RayClient
|
|
1049
|
+
|
|
1050
|
+
# Initialize Ray
|
|
1051
|
+
ray.init()
|
|
1052
|
+
client = RayClient()
|
|
1053
|
+
|
|
1054
|
+
# Test distributed execution
|
|
1055
|
+
job_result = client.submit_job(
|
|
1056
|
+
entrypoint="python action.py",
|
|
1057
|
+
runtime_env=action.get_runtime_env()
|
|
1058
|
+
)
|
|
1059
|
+
```
|
|
1060
|
+
|
|
1061
|
+
## Advanced Features
|
|
1062
|
+
|
|
1063
|
+
### Custom Progress Categories
|
|
1064
|
+
|
|
1065
|
+
```python
|
|
1066
|
+
class MyAction(Action):
|
|
1067
|
+
progress_categories = {
|
|
1068
|
+
'data_loading': {
|
|
1069
|
+
'proportion': 10,
|
|
1070
|
+
'description': 'Loading input data'
|
|
1071
|
+
},
|
|
1072
|
+
'feature_extraction': {
|
|
1073
|
+
'proportion': 30,
|
|
1074
|
+
'description': 'Extracting features'
|
|
1075
|
+
},
|
|
1076
|
+
'model_training': {
|
|
1077
|
+
'proportion': 50,
|
|
1078
|
+
'description': 'Training model'
|
|
1079
|
+
},
|
|
1080
|
+
'evaluation': {
|
|
1081
|
+
'proportion': 10,
|
|
1082
|
+
'description': 'Evaluating results'
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
def start(self):
|
|
1087
|
+
# Update specific progress categories
|
|
1088
|
+
self.run.set_progress(50, 100, 'data_loading')
|
|
1089
|
+
self.run.set_progress(25, 100, 'feature_extraction')
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
### Runtime Environment Customization
|
|
1093
|
+
|
|
1094
|
+
```python
|
|
1095
|
+
def get_runtime_env(self):
|
|
1096
|
+
"""Customize execution environment."""
|
|
1097
|
+
env = super().get_runtime_env()
|
|
1098
|
+
|
|
1099
|
+
# Add custom packages
|
|
1100
|
+
env['pip']['packages'].extend([
|
|
1101
|
+
'custom-ml-library==2.0.0',
|
|
1102
|
+
'specialized-tool>=1.5.0'
|
|
1103
|
+
])
|
|
1104
|
+
|
|
1105
|
+
# Set environment variables
|
|
1106
|
+
env['env_vars'].update({
|
|
1107
|
+
'CUDA_VISIBLE_DEVICES': '0,1',
|
|
1108
|
+
'OMP_NUM_THREADS': '8',
|
|
1109
|
+
'CUSTOM_CONFIG_PATH': '/app/config'
|
|
1110
|
+
})
|
|
1111
|
+
|
|
1112
|
+
# Add working directory files
|
|
1113
|
+
env['working_dir_files'] = {
|
|
1114
|
+
'config.yaml': 'path/to/local/config.yaml',
|
|
1115
|
+
'model_weights.pth': 'path/to/weights.pth'
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
return env
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
### Parameter Validation Patterns
|
|
1122
|
+
|
|
1123
|
+
```python
|
|
1124
|
+
from pydantic import BaseModel, validator, Field
|
|
1125
|
+
from typing import Literal, Optional, List
|
|
1126
|
+
|
|
1127
|
+
class AdvancedParams(BaseModel):
|
|
1128
|
+
"""Advanced parameter validation."""
|
|
1129
|
+
|
|
1130
|
+
# Enum-like validation
|
|
1131
|
+
model_type: Literal["cnn", "transformer", "resnet"]
|
|
1132
|
+
|
|
1133
|
+
# Range validation
|
|
1134
|
+
learning_rate: float = Field(gt=0, le=1, default=0.001)
|
|
1135
|
+
batch_size: int = Field(ge=1, le=1024, default=32)
|
|
1136
|
+
|
|
1137
|
+
# File path validation
|
|
1138
|
+
data_path: str
|
|
1139
|
+
output_path: Optional[str] = None
|
|
1140
|
+
|
|
1141
|
+
# Complex validation
|
|
1142
|
+
layers: List[int] = Field(min_items=1, max_items=10)
|
|
1143
|
+
|
|
1144
|
+
@validator('data_path')
|
|
1145
|
+
def validate_data_path(cls, v):
|
|
1146
|
+
if not os.path.exists(v):
|
|
1147
|
+
raise ValueError(f'Data path does not exist: {v}')
|
|
1148
|
+
return v
|
|
1149
|
+
|
|
1150
|
+
@validator('output_path')
|
|
1151
|
+
def validate_output_path(cls, v, values):
|
|
1152
|
+
if v is None:
|
|
1153
|
+
# Auto-generate from data_path
|
|
1154
|
+
data_path = values.get('data_path', '')
|
|
1155
|
+
return f"{data_path}_output"
|
|
1156
|
+
return v
|
|
1157
|
+
|
|
1158
|
+
@validator('layers')
|
|
1159
|
+
def validate_layers(cls, v):
|
|
1160
|
+
if len(v) < 2:
|
|
1161
|
+
raise ValueError('Must specify at least 2 layers')
|
|
1162
|
+
if v[0] <= 0 or v[-1] <= 0:
|
|
1163
|
+
raise ValueError('Input and output layers must be positive')
|
|
1164
|
+
return v
|
|
1165
|
+
```
|
|
1166
|
+
|
|
1167
|
+
## Best Practices
|
|
1168
|
+
|
|
1169
|
+
### 1. Action Design
|
|
1170
|
+
|
|
1171
|
+
- **Single Responsibility**: Each action should have one clear purpose
|
|
1172
|
+
- **Parameterization**: Make actions configurable through well-defined parameters
|
|
1173
|
+
- **Error Handling**: Implement comprehensive error handling and validation
|
|
1174
|
+
- **Progress Reporting**: Provide meaningful progress updates for long operations
|
|
1175
|
+
|
|
1176
|
+
### 2. Code Organization
|
|
1177
|
+
|
|
1178
|
+
```python
|
|
1179
|
+
# Good: Modular structure
|
|
1180
|
+
class UploadAction(Action):
|
|
1181
|
+
def start(self):
|
|
1182
|
+
self._validate_inputs()
|
|
1183
|
+
files = self._discover_files()
|
|
1184
|
+
processed_files = self._process_files(files)
|
|
1185
|
+
return self._generate_output(processed_files)
|
|
1186
|
+
|
|
1187
|
+
def _validate_inputs(self):
|
|
1188
|
+
"""Separate validation logic."""
|
|
1189
|
+
pass
|
|
1190
|
+
|
|
1191
|
+
def _discover_files(self):
|
|
1192
|
+
"""Separate file discovery logic."""
|
|
1193
|
+
pass
|
|
1194
|
+
|
|
1195
|
+
# Good: Use of enums for constants
|
|
1196
|
+
class LogCode(str, Enum):
|
|
1197
|
+
VALIDATION_FAILED = 'VALIDATION_FAILED'
|
|
1198
|
+
FILE_NOT_FOUND = 'FILE_NOT_FOUND'
|
|
1199
|
+
|
|
1200
|
+
# Good: Type hints and documentation
|
|
1201
|
+
def process_batch(self, items: List[Dict[str, Any]], batch_size: int = 100) -> List[Dict[str, Any]]:
|
|
1202
|
+
"""Process items in batches for memory efficiency.
|
|
1203
|
+
|
|
1204
|
+
Args:
|
|
1205
|
+
items: List of items to process
|
|
1206
|
+
batch_size: Number of items per batch
|
|
1207
|
+
|
|
1208
|
+
Returns:
|
|
1209
|
+
List of processed items
|
|
1210
|
+
"""
|
|
1211
|
+
```
|
|
1212
|
+
|
|
1213
|
+
### 3. Performance Optimization
|
|
1214
|
+
|
|
1215
|
+
```python
|
|
1216
|
+
# Use simple sequential processing for file uploads
|
|
1217
|
+
def _upload_files(self, files: List[Path]) -> List[UploadResult]:
|
|
1218
|
+
results = []
|
|
1219
|
+
for file_path in files:
|
|
1220
|
+
result = self._upload_file(file_path)
|
|
1221
|
+
results.append(result)
|
|
1222
|
+
return results
|
|
1223
|
+
|
|
1224
|
+
# Use generators for memory efficiency
|
|
1225
|
+
def _process_large_dataset(self, data_source):
|
|
1226
|
+
"""Process data in chunks to avoid memory issues."""
|
|
1227
|
+
for chunk in self._chunk_data(data_source, chunk_size=1000):
|
|
1228
|
+
processed_chunk = self._process_chunk(chunk)
|
|
1229
|
+
yield processed_chunk
|
|
1230
|
+
|
|
1231
|
+
# Update progress
|
|
1232
|
+
self.run.set_progress(self.processed_count, self.total_count, 'processing')
|
|
1233
|
+
```
|
|
1234
|
+
|
|
1235
|
+
### 4. Error Handling
|
|
1236
|
+
|
|
1237
|
+
```python
|
|
1238
|
+
from synapse_sdk.plugins.exceptions import ActionError
|
|
1239
|
+
|
|
1240
|
+
class MyAction(Action):
|
|
1241
|
+
def start(self):
|
|
1242
|
+
try:
|
|
1243
|
+
return self._execute_main_logic()
|
|
1244
|
+
except ValidationError as e:
|
|
1245
|
+
self.run.log_message(f"Validation error: {e}", "ERROR")
|
|
1246
|
+
raise ActionError(f"Parameter validation failed: {e}")
|
|
1247
|
+
except FileNotFoundError as e:
|
|
1248
|
+
self.run.log_message(f"File not found: {e}", "ERROR")
|
|
1249
|
+
raise ActionError(f"Required file missing: {e}")
|
|
1250
|
+
except Exception as e:
|
|
1251
|
+
self.run.log_message(f"Unexpected error: {e}", "ERROR")
|
|
1252
|
+
raise ActionError(f"Action execution failed: {e}")
|
|
1253
|
+
```
|
|
1254
|
+
|
|
1255
|
+
### 5. Security Considerations
|
|
1256
|
+
|
|
1257
|
+
```python
|
|
1258
|
+
# Good: Validate file paths
|
|
1259
|
+
def _validate_file_path(self, file_path: str) -> Path:
|
|
1260
|
+
"""Validate and sanitize file paths."""
|
|
1261
|
+
path = Path(file_path).resolve()
|
|
1262
|
+
|
|
1263
|
+
# Prevent directory traversal
|
|
1264
|
+
if not str(path).startswith(str(self.workspace_root)):
|
|
1265
|
+
raise ActionError(f"File path outside workspace: {path}")
|
|
1266
|
+
|
|
1267
|
+
return path
|
|
1268
|
+
|
|
1269
|
+
# Good: Sanitize user inputs
|
|
1270
|
+
def _sanitize_filename(self, filename: str) -> str:
|
|
1271
|
+
"""Remove unsafe characters from filename."""
|
|
1272
|
+
import re
|
|
1273
|
+
# Remove path separators and control characters
|
|
1274
|
+
safe_name = re.sub(r'[<>:"/\\|?*\x00-\x1f]', '_', filename)
|
|
1275
|
+
return safe_name[:255] # Limit length
|
|
1276
|
+
|
|
1277
|
+
# Good: Validate data sizes
|
|
1278
|
+
def _validate_data_size(self, data: bytes) -> None:
|
|
1279
|
+
"""Check data size limits."""
|
|
1280
|
+
max_size = 100 * 1024 * 1024 # 100MB
|
|
1281
|
+
if len(data) > max_size:
|
|
1282
|
+
raise ActionError(f"Data too large: {len(data)} bytes (max: {max_size})")
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
## API Reference
|
|
1286
|
+
|
|
1287
|
+
### Core Classes
|
|
1288
|
+
|
|
1289
|
+
#### Action
|
|
1290
|
+
|
|
1291
|
+
Base class for all plugin actions.
|
|
1292
|
+
|
|
1293
|
+
**Methods:**
|
|
1294
|
+
|
|
1295
|
+
- `start()`: Main execution method (abstract)
|
|
1296
|
+
- `run_action()`: Execute action with error handling
|
|
1297
|
+
- `get_runtime_env()`: Get execution environment configuration
|
|
1298
|
+
- `validate_params()`: Validate action parameters
|
|
1299
|
+
|
|
1300
|
+
#### Run
|
|
1301
|
+
|
|
1302
|
+
Manages action execution lifecycle.
|
|
1303
|
+
|
|
1304
|
+
**Methods:**
|
|
1305
|
+
|
|
1306
|
+
- `log_message(message, context)`: Log execution messages
|
|
1307
|
+
- `set_progress(current, total, category)`: Update progress
|
|
1308
|
+
- `set_metrics(metrics, category)`: Record metrics
|
|
1309
|
+
- `log(log_type, data)`: Structured logging
|
|
1310
|
+
|
|
1311
|
+
#### PluginRelease
|
|
1312
|
+
|
|
1313
|
+
Manages plugin metadata and configuration.
|
|
1314
|
+
|
|
1315
|
+
**Attributes:**
|
|
1316
|
+
|
|
1317
|
+
- `code`: Plugin identifier
|
|
1318
|
+
- `name`: Human-readable name
|
|
1319
|
+
- `version`: Semantic version
|
|
1320
|
+
- `category`: Plugin category
|
|
1321
|
+
- `config`: Plugin configuration
|
|
1322
|
+
|
|
1323
|
+
### Utility Functions
|
|
1324
|
+
|
|
1325
|
+
```python
|
|
1326
|
+
# synapse_sdk/plugins/utils/
|
|
1327
|
+
from synapse_sdk.plugins.utils import (
|
|
1328
|
+
get_action_class, # Get action class by category/name
|
|
1329
|
+
load_plugin_config, # Load plugin configuration
|
|
1330
|
+
validate_plugin, # Validate plugin structure
|
|
1331
|
+
register_plugin, # Register plugin in system
|
|
1332
|
+
)
|
|
1333
|
+
|
|
1334
|
+
# Usage examples
|
|
1335
|
+
ActionClass = get_action_class("upload", "upload")
|
|
1336
|
+
config = load_plugin_config("/path/to/plugin")
|
|
1337
|
+
is_valid = validate_plugin("/path/to/plugin")
|
|
1338
|
+
```
|
|
1339
|
+
|
|
1340
|
+
This README provides the foundation for developing and extending the Synapse SDK plugin system. For specific implementation examples, refer to the existing plugin categories and their respective documentation.
|