synapse-sdk 1.0.0a11__py3-none-any.whl → 2026.1.1b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of synapse-sdk might be problematic. Click here for more details.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/__init__.py +9 -8
- synapse_sdk/cli/agent/__init__.py +25 -0
- synapse_sdk/cli/agent/config.py +104 -0
- synapse_sdk/cli/agent/select.py +197 -0
- synapse_sdk/cli/auth.py +104 -0
- synapse_sdk/cli/main.py +1025 -0
- synapse_sdk/cli/plugin/__init__.py +58 -0
- synapse_sdk/cli/plugin/create.py +566 -0
- synapse_sdk/cli/plugin/job.py +196 -0
- synapse_sdk/cli/plugin/publish.py +322 -0
- synapse_sdk/cli/plugin/run.py +131 -0
- synapse_sdk/cli/plugin/test.py +200 -0
- synapse_sdk/clients/README.md +239 -0
- synapse_sdk/clients/__init__.py +5 -0
- synapse_sdk/clients/_template.py +266 -0
- synapse_sdk/clients/agent/__init__.py +84 -29
- synapse_sdk/clients/agent/async_ray.py +289 -0
- synapse_sdk/clients/agent/container.py +83 -0
- synapse_sdk/clients/agent/plugin.py +101 -0
- synapse_sdk/clients/agent/ray.py +296 -39
- synapse_sdk/clients/backend/__init__.py +152 -12
- synapse_sdk/clients/backend/annotation.py +164 -22
- synapse_sdk/clients/backend/core.py +101 -0
- synapse_sdk/clients/backend/data_collection.py +292 -0
- synapse_sdk/clients/backend/hitl.py +87 -0
- synapse_sdk/clients/backend/integration.py +374 -46
- synapse_sdk/clients/backend/ml.py +134 -22
- synapse_sdk/clients/backend/models.py +247 -0
- synapse_sdk/clients/base.py +538 -59
- synapse_sdk/clients/exceptions.py +35 -7
- synapse_sdk/clients/pipeline/__init__.py +5 -0
- synapse_sdk/clients/pipeline/client.py +636 -0
- synapse_sdk/clients/protocols.py +178 -0
- synapse_sdk/clients/utils.py +86 -8
- synapse_sdk/clients/validation.py +58 -0
- synapse_sdk/enums.py +76 -0
- synapse_sdk/exceptions.py +168 -0
- synapse_sdk/integrations/__init__.py +74 -0
- synapse_sdk/integrations/_base.py +119 -0
- synapse_sdk/integrations/_context.py +53 -0
- synapse_sdk/integrations/ultralytics/__init__.py +78 -0
- synapse_sdk/integrations/ultralytics/_callbacks.py +126 -0
- synapse_sdk/integrations/ultralytics/_patches.py +124 -0
- synapse_sdk/loggers.py +476 -95
- synapse_sdk/mcp/MCP.md +69 -0
- synapse_sdk/mcp/__init__.py +48 -0
- synapse_sdk/mcp/__main__.py +6 -0
- synapse_sdk/mcp/config.py +349 -0
- synapse_sdk/mcp/prompts/__init__.py +4 -0
- synapse_sdk/mcp/resources/__init__.py +4 -0
- synapse_sdk/mcp/server.py +1352 -0
- synapse_sdk/mcp/tools/__init__.py +6 -0
- synapse_sdk/plugins/__init__.py +133 -9
- synapse_sdk/plugins/action.py +229 -0
- synapse_sdk/plugins/actions/__init__.py +82 -0
- synapse_sdk/plugins/actions/dataset/__init__.py +37 -0
- synapse_sdk/plugins/actions/dataset/action.py +471 -0
- synapse_sdk/plugins/actions/export/__init__.py +55 -0
- synapse_sdk/plugins/actions/export/action.py +183 -0
- synapse_sdk/plugins/actions/export/context.py +59 -0
- synapse_sdk/plugins/actions/inference/__init__.py +84 -0
- synapse_sdk/plugins/actions/inference/action.py +285 -0
- synapse_sdk/plugins/actions/inference/context.py +81 -0
- synapse_sdk/plugins/actions/inference/deployment.py +322 -0
- synapse_sdk/plugins/actions/inference/serve.py +252 -0
- synapse_sdk/plugins/actions/train/__init__.py +54 -0
- synapse_sdk/plugins/actions/train/action.py +326 -0
- synapse_sdk/plugins/actions/train/context.py +57 -0
- synapse_sdk/plugins/actions/upload/__init__.py +49 -0
- synapse_sdk/plugins/actions/upload/action.py +165 -0
- synapse_sdk/plugins/actions/upload/context.py +61 -0
- synapse_sdk/plugins/config.py +98 -0
- synapse_sdk/plugins/context/__init__.py +109 -0
- synapse_sdk/plugins/context/env.py +113 -0
- synapse_sdk/plugins/datasets/__init__.py +113 -0
- synapse_sdk/plugins/datasets/converters/__init__.py +76 -0
- synapse_sdk/plugins/datasets/converters/base.py +347 -0
- synapse_sdk/plugins/datasets/converters/yolo/__init__.py +9 -0
- synapse_sdk/plugins/datasets/converters/yolo/from_dm.py +468 -0
- synapse_sdk/plugins/datasets/converters/yolo/to_dm.py +381 -0
- synapse_sdk/plugins/datasets/formats/__init__.py +82 -0
- synapse_sdk/plugins/datasets/formats/dm.py +351 -0
- synapse_sdk/plugins/datasets/formats/yolo.py +240 -0
- synapse_sdk/plugins/decorators.py +83 -0
- synapse_sdk/plugins/discovery.py +790 -0
- synapse_sdk/plugins/docs/ACTION_DEV_GUIDE.md +933 -0
- synapse_sdk/plugins/docs/ARCHITECTURE.md +1225 -0
- synapse_sdk/plugins/docs/LOGGING_SYSTEM.md +683 -0
- synapse_sdk/plugins/docs/OVERVIEW.md +531 -0
- synapse_sdk/plugins/docs/PIPELINE_GUIDE.md +145 -0
- synapse_sdk/plugins/docs/README.md +513 -0
- synapse_sdk/plugins/docs/STEP.md +656 -0
- synapse_sdk/plugins/enums.py +70 -10
- synapse_sdk/plugins/errors.py +92 -0
- synapse_sdk/plugins/executors/__init__.py +43 -0
- synapse_sdk/plugins/executors/local.py +99 -0
- synapse_sdk/plugins/executors/ray/__init__.py +18 -0
- synapse_sdk/plugins/executors/ray/base.py +282 -0
- synapse_sdk/plugins/executors/ray/job.py +298 -0
- synapse_sdk/plugins/executors/ray/jobs_api.py +511 -0
- synapse_sdk/plugins/executors/ray/packaging.py +137 -0
- synapse_sdk/plugins/executors/ray/pipeline.py +792 -0
- synapse_sdk/plugins/executors/ray/task.py +257 -0
- synapse_sdk/plugins/models/__init__.py +26 -0
- synapse_sdk/plugins/models/logger.py +173 -0
- synapse_sdk/plugins/models/pipeline.py +25 -0
- synapse_sdk/plugins/pipelines/__init__.py +81 -0
- synapse_sdk/plugins/pipelines/action_pipeline.py +417 -0
- synapse_sdk/plugins/pipelines/context.py +107 -0
- synapse_sdk/plugins/pipelines/display.py +311 -0
- synapse_sdk/plugins/runner.py +114 -0
- synapse_sdk/plugins/schemas/__init__.py +19 -0
- synapse_sdk/plugins/schemas/results.py +152 -0
- synapse_sdk/plugins/steps/__init__.py +63 -0
- synapse_sdk/plugins/steps/base.py +128 -0
- synapse_sdk/plugins/steps/context.py +90 -0
- synapse_sdk/plugins/steps/orchestrator.py +128 -0
- synapse_sdk/plugins/steps/registry.py +103 -0
- synapse_sdk/plugins/steps/utils/__init__.py +20 -0
- synapse_sdk/plugins/steps/utils/logging.py +85 -0
- synapse_sdk/plugins/steps/utils/timing.py +71 -0
- synapse_sdk/plugins/steps/utils/validation.py +68 -0
- synapse_sdk/plugins/templates/__init__.py +50 -0
- synapse_sdk/plugins/templates/base/.gitignore.j2 +26 -0
- synapse_sdk/plugins/templates/base/.synapseignore.j2 +11 -0
- synapse_sdk/plugins/templates/base/README.md.j2 +26 -0
- synapse_sdk/plugins/templates/base/plugin/__init__.py.j2 +1 -0
- synapse_sdk/plugins/templates/base/pyproject.toml.j2 +14 -0
- synapse_sdk/plugins/templates/base/requirements.txt.j2 +1 -0
- synapse_sdk/plugins/templates/custom/plugin/main.py.j2 +18 -0
- synapse_sdk/plugins/templates/data_validation/plugin/validate.py.j2 +32 -0
- synapse_sdk/plugins/templates/export/plugin/export.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/inference.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/train.py.j2 +33 -0
- synapse_sdk/plugins/templates/post_annotation/plugin/post_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/pre_annotation/plugin/pre_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/smart_tool/plugin/auto_label.py.j2 +44 -0
- synapse_sdk/plugins/templates/upload/plugin/upload.py.j2 +35 -0
- synapse_sdk/plugins/testing/__init__.py +25 -0
- synapse_sdk/plugins/testing/sample_actions.py +98 -0
- synapse_sdk/plugins/types.py +206 -0
- synapse_sdk/plugins/upload.py +595 -64
- synapse_sdk/plugins/utils.py +325 -37
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/utils/__init__.py +1 -0
- synapse_sdk/utils/auth.py +74 -0
- synapse_sdk/utils/file/__init__.py +58 -0
- synapse_sdk/utils/file/archive.py +449 -0
- synapse_sdk/utils/file/checksum.py +167 -0
- synapse_sdk/utils/file/download.py +286 -0
- synapse_sdk/utils/file/io.py +129 -0
- synapse_sdk/utils/file/requirements.py +36 -0
- synapse_sdk/utils/network.py +168 -0
- synapse_sdk/utils/storage/__init__.py +238 -0
- synapse_sdk/utils/storage/config.py +188 -0
- synapse_sdk/utils/storage/errors.py +52 -0
- synapse_sdk/utils/storage/providers/__init__.py +13 -0
- synapse_sdk/utils/storage/providers/base.py +76 -0
- synapse_sdk/utils/storage/providers/gcs.py +168 -0
- synapse_sdk/utils/storage/providers/http.py +250 -0
- synapse_sdk/utils/storage/providers/local.py +126 -0
- synapse_sdk/utils/storage/providers/s3.py +177 -0
- synapse_sdk/utils/storage/providers/sftp.py +208 -0
- synapse_sdk/utils/storage/registry.py +125 -0
- synapse_sdk/utils/websocket.py +99 -0
- synapse_sdk-2026.1.1b2.dist-info/METADATA +715 -0
- synapse_sdk-2026.1.1b2.dist-info/RECORD +172 -0
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/WHEEL +1 -1
- synapse_sdk-2026.1.1b2.dist-info/licenses/LICENSE +201 -0
- locale/en/LC_MESSAGES/messages.mo +0 -0
- locale/en/LC_MESSAGES/messages.po +0 -39
- locale/ko/LC_MESSAGES/messages.mo +0 -0
- locale/ko/LC_MESSAGES/messages.po +0 -34
- synapse_sdk/cli/create_plugin.py +0 -10
- synapse_sdk/clients/agent/core.py +0 -7
- synapse_sdk/clients/agent/service.py +0 -15
- synapse_sdk/clients/backend/dataset.py +0 -51
- synapse_sdk/clients/ray/__init__.py +0 -6
- synapse_sdk/clients/ray/core.py +0 -22
- synapse_sdk/clients/ray/serve.py +0 -20
- synapse_sdk/i18n.py +0 -35
- synapse_sdk/plugins/categories/__init__.py +0 -0
- synapse_sdk/plugins/categories/base.py +0 -235
- synapse_sdk/plugins/categories/data_validation/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/validation.py +0 -10
- synapse_sdk/plugins/categories/data_validation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/data_validation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +0 -5
- synapse_sdk/plugins/categories/decorators.py +0 -13
- synapse_sdk/plugins/categories/export/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/export.py +0 -10
- synapse_sdk/plugins/categories/import/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/import.py +0 -10
- synapse_sdk/plugins/categories/neural_net/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/deployment.py +0 -45
- synapse_sdk/plugins/categories/neural_net/actions/inference.py +0 -18
- synapse_sdk/plugins/categories/neural_net/actions/test.py +0 -10
- synapse_sdk/plugins/categories/neural_net/actions/train.py +0 -143
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +0 -12
- synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +0 -4
- synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py +0 -2
- synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +0 -14
- synapse_sdk/plugins/categories/post_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py +0 -10
- synapse_sdk/plugins/categories/post_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.py +0 -3
- synapse_sdk/plugins/categories/pre_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py +0 -10
- synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py +0 -3
- synapse_sdk/plugins/categories/registry.py +0 -16
- synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +0 -37
- synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +0 -7
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +0 -11
- synapse_sdk/plugins/categories/templates.py +0 -32
- synapse_sdk/plugins/cli/__init__.py +0 -21
- synapse_sdk/plugins/cli/publish.py +0 -37
- synapse_sdk/plugins/cli/run.py +0 -67
- synapse_sdk/plugins/exceptions.py +0 -22
- synapse_sdk/plugins/models.py +0 -121
- synapse_sdk/plugins/templates/cookiecutter.json +0 -11
- synapse_sdk/plugins/templates/hooks/post_gen_project.py +0 -3
- synapse_sdk/plugins/templates/hooks/pre_prompt.py +0 -21
- 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/.gitignore +0 -27
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml +0 -7
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md +0 -5
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -6
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py +0 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml +0 -13
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +0 -1
- synapse_sdk/shared/enums.py +0 -8
- synapse_sdk/utils/debug.py +0 -5
- synapse_sdk/utils/file.py +0 -87
- synapse_sdk/utils/module_loading.py +0 -29
- synapse_sdk/utils/pydantic/__init__.py +0 -0
- synapse_sdk/utils/pydantic/config.py +0 -4
- synapse_sdk/utils/pydantic/errors.py +0 -33
- synapse_sdk/utils/pydantic/validators.py +0 -7
- synapse_sdk/utils/storage.py +0 -91
- synapse_sdk/utils/string.py +0 -11
- synapse_sdk-1.0.0a11.dist-info/LICENSE +0 -21
- synapse_sdk-1.0.0a11.dist-info/METADATA +0 -43
- synapse_sdk-1.0.0a11.dist-info/RECORD +0 -111
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Plugin CLI commands."""
|
|
2
|
+
|
|
3
|
+
from synapse_sdk.cli.plugin.create import (
|
|
4
|
+
CreateResult,
|
|
5
|
+
PluginSpec,
|
|
6
|
+
create_plugin,
|
|
7
|
+
create_plugin_interactive,
|
|
8
|
+
)
|
|
9
|
+
from synapse_sdk.cli.plugin.job import (
|
|
10
|
+
display_job,
|
|
11
|
+
get_job,
|
|
12
|
+
get_job_logs,
|
|
13
|
+
tail_job_logs,
|
|
14
|
+
)
|
|
15
|
+
from synapse_sdk.cli.plugin.publish import (
|
|
16
|
+
PublishResult,
|
|
17
|
+
create_plugin_archive,
|
|
18
|
+
display_files_preview,
|
|
19
|
+
find_config_file,
|
|
20
|
+
load_synapseignore,
|
|
21
|
+
publish_plugin,
|
|
22
|
+
)
|
|
23
|
+
from synapse_sdk.cli.plugin.run import (
|
|
24
|
+
RunResult,
|
|
25
|
+
resolve_plugin_code,
|
|
26
|
+
run_plugin,
|
|
27
|
+
)
|
|
28
|
+
from synapse_sdk.cli.plugin.test import (
|
|
29
|
+
TestResult,
|
|
30
|
+
test_plugin,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
# Create
|
|
35
|
+
'CreateResult',
|
|
36
|
+
'PluginSpec',
|
|
37
|
+
'create_plugin',
|
|
38
|
+
'create_plugin_interactive',
|
|
39
|
+
# Job
|
|
40
|
+
'display_job',
|
|
41
|
+
'get_job',
|
|
42
|
+
'get_job_logs',
|
|
43
|
+
'tail_job_logs',
|
|
44
|
+
# Publish
|
|
45
|
+
'PublishResult',
|
|
46
|
+
'create_plugin_archive',
|
|
47
|
+
'display_files_preview',
|
|
48
|
+
'find_config_file',
|
|
49
|
+
'load_synapseignore',
|
|
50
|
+
'publish_plugin',
|
|
51
|
+
# Run
|
|
52
|
+
'RunResult',
|
|
53
|
+
'resolve_plugin_code',
|
|
54
|
+
'run_plugin',
|
|
55
|
+
# Test
|
|
56
|
+
'TestResult',
|
|
57
|
+
'test_plugin',
|
|
58
|
+
]
|
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
"""Plugin creation command."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import questionary
|
|
12
|
+
import yaml
|
|
13
|
+
from jinja2 import Environment, FileSystemLoader
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
from rich.table import Table
|
|
17
|
+
from rich.tree import Tree
|
|
18
|
+
|
|
19
|
+
from synapse_sdk.plugins.enums import (
|
|
20
|
+
AnnotationCategory,
|
|
21
|
+
AnnotationType,
|
|
22
|
+
DataType,
|
|
23
|
+
PluginCategory,
|
|
24
|
+
SmartToolType,
|
|
25
|
+
)
|
|
26
|
+
from synapse_sdk.plugins.templates import TEMPLATES_DIR
|
|
27
|
+
|
|
28
|
+
# Category descriptions for interactive prompts
|
|
29
|
+
CATEGORY_DESCRIPTIONS = {
|
|
30
|
+
PluginCategory.NEURAL_NET: 'Train and deploy ML models',
|
|
31
|
+
PluginCategory.SMART_TOOL: 'Interactive annotation helpers',
|
|
32
|
+
PluginCategory.EXPORT: 'Data format conversion',
|
|
33
|
+
PluginCategory.UPLOAD: 'External data import',
|
|
34
|
+
PluginCategory.DATA_VALIDATION: 'Pre-annotation data checks',
|
|
35
|
+
PluginCategory.PRE_ANNOTATION: 'Auto-generate initial annotations',
|
|
36
|
+
PluginCategory.POST_ANNOTATION: 'Process annotations after labeling',
|
|
37
|
+
PluginCategory.CUSTOM: 'Custom plugin type',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Tasks by data type
|
|
41
|
+
TASKS_BY_DATA_TYPE = {
|
|
42
|
+
DataType.IMAGE: [
|
|
43
|
+
'object_detection',
|
|
44
|
+
'classification',
|
|
45
|
+
'segmentation',
|
|
46
|
+
'keypoint',
|
|
47
|
+
],
|
|
48
|
+
DataType.TEXT: [
|
|
49
|
+
'classification',
|
|
50
|
+
'ner',
|
|
51
|
+
'qa',
|
|
52
|
+
],
|
|
53
|
+
DataType.VIDEO: [
|
|
54
|
+
'object_tracking',
|
|
55
|
+
'action_recognition',
|
|
56
|
+
'segmentation',
|
|
57
|
+
],
|
|
58
|
+
DataType.AUDIO: [
|
|
59
|
+
'classification',
|
|
60
|
+
'transcription',
|
|
61
|
+
],
|
|
62
|
+
DataType.PCD: [
|
|
63
|
+
'object_detection',
|
|
64
|
+
'segmentation',
|
|
65
|
+
],
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class PluginSpec:
|
|
71
|
+
"""Specification for a plugin to create."""
|
|
72
|
+
|
|
73
|
+
name: str
|
|
74
|
+
code: str
|
|
75
|
+
version: str
|
|
76
|
+
category: PluginCategory
|
|
77
|
+
description: str
|
|
78
|
+
|
|
79
|
+
# Category-specific fields
|
|
80
|
+
data_type: DataType | None = None
|
|
81
|
+
tasks: list[str] = field(default_factory=list)
|
|
82
|
+
annotation_category: AnnotationCategory | None = None
|
|
83
|
+
annotation_type: AnnotationType | None = None
|
|
84
|
+
smart_tool_type: SmartToolType | None = None
|
|
85
|
+
supported_data_types: list[DataType] = field(default_factory=list)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def directory_name(self) -> str:
|
|
89
|
+
"""Get the directory name for the plugin."""
|
|
90
|
+
return f'synapse-{self.code}-plugin'
|
|
91
|
+
|
|
92
|
+
def to_template_context(self) -> dict[str, Any]:
|
|
93
|
+
"""Convert to template context dictionary."""
|
|
94
|
+
return {
|
|
95
|
+
'name': self.name,
|
|
96
|
+
'code': self.code,
|
|
97
|
+
'version': self.version,
|
|
98
|
+
'category': self.category.value,
|
|
99
|
+
'description': self.description,
|
|
100
|
+
'data_type': self.data_type.value if self.data_type else None,
|
|
101
|
+
'tasks': [f'{self.data_type.value}.{t}' for t in self.tasks] if self.data_type and self.tasks else [],
|
|
102
|
+
'annotation_category': self.annotation_category.value if self.annotation_category else None,
|
|
103
|
+
'annotation_type': self.annotation_type.value if self.annotation_type else None,
|
|
104
|
+
'smart_tool_type': self.smart_tool_type.value if self.smart_tool_type else None,
|
|
105
|
+
'supported_data_types': [dt.value for dt in self.supported_data_types],
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def slugify(text: str) -> str:
|
|
110
|
+
"""Convert text to slug format.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
text: Text to slugify.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Slugified text (lowercase, hyphens instead of spaces).
|
|
117
|
+
"""
|
|
118
|
+
# Convert to lowercase and replace spaces/underscores with hyphens
|
|
119
|
+
slug = text.lower().strip()
|
|
120
|
+
slug = re.sub(r'[\s_]+', '-', slug)
|
|
121
|
+
# Remove non-alphanumeric characters except hyphens
|
|
122
|
+
slug = re.sub(r'[^a-z0-9-]', '', slug)
|
|
123
|
+
# Remove consecutive hyphens
|
|
124
|
+
slug = re.sub(r'-+', '-', slug)
|
|
125
|
+
# Remove leading/trailing hyphens
|
|
126
|
+
slug = slug.strip('-')
|
|
127
|
+
return slug
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def validate_code(code: str) -> bool | str:
|
|
131
|
+
"""Validate plugin code format.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
code: Plugin code to validate.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
True if valid, error message otherwise.
|
|
138
|
+
"""
|
|
139
|
+
if not code:
|
|
140
|
+
return 'Code cannot be empty'
|
|
141
|
+
if not re.match(r'^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$', code):
|
|
142
|
+
return 'Code must be lowercase letters, numbers, and hyphens (no leading/trailing hyphens)'
|
|
143
|
+
return True
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def prompt_category() -> PluginCategory:
|
|
147
|
+
"""Prompt user to select a plugin category.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Selected plugin category.
|
|
151
|
+
"""
|
|
152
|
+
choices = [
|
|
153
|
+
questionary.Choice(
|
|
154
|
+
title=f'{cat.value.replace("_", " ").title():20} {CATEGORY_DESCRIPTIONS[cat]}',
|
|
155
|
+
value=cat,
|
|
156
|
+
)
|
|
157
|
+
for cat in PluginCategory
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
return questionary.select(
|
|
161
|
+
'Select plugin category:',
|
|
162
|
+
choices=choices,
|
|
163
|
+
).ask()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def prompt_basic_info(category: PluginCategory) -> tuple[str, str, str, str]:
|
|
167
|
+
"""Prompt user for basic plugin information.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
category: Selected plugin category.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Tuple of (name, code, version, description).
|
|
174
|
+
"""
|
|
175
|
+
name = questionary.text(
|
|
176
|
+
'Plugin name:',
|
|
177
|
+
validate=lambda x: len(x) > 0 or 'Name cannot be empty',
|
|
178
|
+
).ask()
|
|
179
|
+
|
|
180
|
+
default_code = slugify(name)
|
|
181
|
+
code = questionary.text(
|
|
182
|
+
'Plugin code:',
|
|
183
|
+
default=default_code,
|
|
184
|
+
validate=validate_code,
|
|
185
|
+
).ask()
|
|
186
|
+
|
|
187
|
+
version = questionary.text(
|
|
188
|
+
'Version:',
|
|
189
|
+
default='0.1.0',
|
|
190
|
+
).ask()
|
|
191
|
+
|
|
192
|
+
default_description = f'{name} plugin'
|
|
193
|
+
description = questionary.text(
|
|
194
|
+
'Description:',
|
|
195
|
+
default=default_description,
|
|
196
|
+
).ask()
|
|
197
|
+
|
|
198
|
+
return name, code, version, description
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def prompt_neural_net_options() -> tuple[DataType | None, list[str]]:
|
|
202
|
+
"""Prompt user for neural net specific options.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Tuple of (data_type, tasks).
|
|
206
|
+
"""
|
|
207
|
+
data_type_choices = [questionary.Choice(title=dt.value, value=dt) for dt in DataType]
|
|
208
|
+
data_type = questionary.select(
|
|
209
|
+
'Data type:',
|
|
210
|
+
choices=data_type_choices,
|
|
211
|
+
).ask()
|
|
212
|
+
|
|
213
|
+
available_tasks = TASKS_BY_DATA_TYPE.get(data_type, [])
|
|
214
|
+
if available_tasks:
|
|
215
|
+
tasks = questionary.checkbox(
|
|
216
|
+
'Tasks (select with space, confirm with enter):',
|
|
217
|
+
choices=available_tasks,
|
|
218
|
+
).ask()
|
|
219
|
+
else:
|
|
220
|
+
tasks = []
|
|
221
|
+
|
|
222
|
+
return data_type, tasks
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def prompt_smart_tool_options() -> tuple[AnnotationCategory, AnnotationType, SmartToolType]:
|
|
226
|
+
"""Prompt user for smart tool specific options.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Tuple of (annotation_category, annotation_type, smart_tool_type).
|
|
230
|
+
"""
|
|
231
|
+
annotation_category = questionary.select(
|
|
232
|
+
'Annotation category:',
|
|
233
|
+
choices=[ac.value for ac in AnnotationCategory],
|
|
234
|
+
).ask()
|
|
235
|
+
|
|
236
|
+
annotation_type = questionary.select(
|
|
237
|
+
'Annotation type:',
|
|
238
|
+
choices=[at.value for at in AnnotationType],
|
|
239
|
+
).ask()
|
|
240
|
+
|
|
241
|
+
smart_tool_type = questionary.select(
|
|
242
|
+
'Smart tool type:',
|
|
243
|
+
choices=[
|
|
244
|
+
questionary.Choice(title='Interactive - User triggers predictions', value=SmartToolType.INTERACTIVE),
|
|
245
|
+
questionary.Choice(title='Automatic - Runs on all data', value=SmartToolType.AUTOMATIC),
|
|
246
|
+
questionary.Choice(title='Semi-automatic - Suggests, user confirms', value=SmartToolType.SEMI_AUTOMATIC),
|
|
247
|
+
],
|
|
248
|
+
).ask()
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
AnnotationCategory(annotation_category),
|
|
252
|
+
AnnotationType(annotation_type),
|
|
253
|
+
smart_tool_type,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def prompt_upload_options() -> list[DataType]:
|
|
258
|
+
"""Prompt user for upload specific options.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
List of supported data types.
|
|
262
|
+
"""
|
|
263
|
+
choices = [questionary.Choice(title=dt.value, value=dt) for dt in DataType]
|
|
264
|
+
return questionary.checkbox(
|
|
265
|
+
'Supported data types (select with space):',
|
|
266
|
+
choices=choices,
|
|
267
|
+
).ask()
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def collect_plugin_spec(
|
|
271
|
+
*,
|
|
272
|
+
name: str | None = None,
|
|
273
|
+
code: str | None = None,
|
|
274
|
+
category: str | None = None,
|
|
275
|
+
interactive: bool = True,
|
|
276
|
+
) -> PluginSpec | None:
|
|
277
|
+
"""Collect plugin specification from user.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
name: Plugin name (skip prompt if provided).
|
|
281
|
+
code: Plugin code (skip prompt if provided).
|
|
282
|
+
category: Plugin category (skip prompt if provided).
|
|
283
|
+
interactive: Whether to prompt for input.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
PluginSpec if successful, None if user cancelled.
|
|
287
|
+
"""
|
|
288
|
+
# Get category
|
|
289
|
+
if category:
|
|
290
|
+
selected_category = PluginCategory(category)
|
|
291
|
+
elif interactive:
|
|
292
|
+
selected_category = prompt_category()
|
|
293
|
+
if not selected_category:
|
|
294
|
+
return None
|
|
295
|
+
else:
|
|
296
|
+
selected_category = PluginCategory.CUSTOM
|
|
297
|
+
|
|
298
|
+
# Get basic info
|
|
299
|
+
if name and code:
|
|
300
|
+
plugin_name = name
|
|
301
|
+
plugin_code = code
|
|
302
|
+
version = '0.1.0'
|
|
303
|
+
description = f'{name} plugin'
|
|
304
|
+
elif interactive:
|
|
305
|
+
result = prompt_basic_info(selected_category)
|
|
306
|
+
if not all(result):
|
|
307
|
+
return None
|
|
308
|
+
plugin_name, plugin_code, version, description = result
|
|
309
|
+
else:
|
|
310
|
+
raise ValueError('name and code are required in non-interactive mode')
|
|
311
|
+
|
|
312
|
+
# Create base spec
|
|
313
|
+
spec = PluginSpec(
|
|
314
|
+
name=plugin_name,
|
|
315
|
+
code=plugin_code,
|
|
316
|
+
version=version,
|
|
317
|
+
category=selected_category,
|
|
318
|
+
description=description,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# Category-specific prompts
|
|
322
|
+
if interactive:
|
|
323
|
+
if selected_category == PluginCategory.NEURAL_NET:
|
|
324
|
+
data_type, tasks = prompt_neural_net_options()
|
|
325
|
+
if data_type:
|
|
326
|
+
spec.data_type = data_type
|
|
327
|
+
spec.tasks = tasks or []
|
|
328
|
+
|
|
329
|
+
elif selected_category == PluginCategory.SMART_TOOL:
|
|
330
|
+
result = prompt_smart_tool_options()
|
|
331
|
+
if all(result):
|
|
332
|
+
spec.annotation_category, spec.annotation_type, spec.smart_tool_type = result
|
|
333
|
+
|
|
334
|
+
elif selected_category == PluginCategory.UPLOAD:
|
|
335
|
+
supported = prompt_upload_options()
|
|
336
|
+
if supported:
|
|
337
|
+
spec.supported_data_types = supported
|
|
338
|
+
|
|
339
|
+
return spec
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def display_preview(spec: PluginSpec, console: Console) -> None:
|
|
343
|
+
"""Display a preview of the plugin to be created.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
spec: Plugin specification.
|
|
347
|
+
console: Rich console for output.
|
|
348
|
+
"""
|
|
349
|
+
# Create tree view of files
|
|
350
|
+
tree = Tree(f'[bold]{spec.directory_name}/[/bold]')
|
|
351
|
+
tree.add('[dim]config.yaml[/dim] Plugin configuration')
|
|
352
|
+
tree.add('[dim]README.md[/dim] Documentation')
|
|
353
|
+
tree.add('[dim]requirements.txt[/dim] Dependencies')
|
|
354
|
+
tree.add('[dim]pyproject.toml[/dim] Project metadata')
|
|
355
|
+
tree.add('[dim].gitignore[/dim] Git ignore rules')
|
|
356
|
+
tree.add('[dim].synapseignore[/dim] Publish ignore rules')
|
|
357
|
+
|
|
358
|
+
plugin_branch = tree.add('[dim]plugin/[/dim] Action implementations')
|
|
359
|
+
|
|
360
|
+
# Add category-specific files
|
|
361
|
+
category = spec.category.value
|
|
362
|
+
if category == 'neural_net':
|
|
363
|
+
plugin_branch.add('[dim]train.py[/dim]')
|
|
364
|
+
plugin_branch.add('[dim]inference.py[/dim]')
|
|
365
|
+
elif category == 'smart_tool':
|
|
366
|
+
plugin_branch.add('[dim]auto_label.py[/dim]')
|
|
367
|
+
elif category == 'export':
|
|
368
|
+
plugin_branch.add('[dim]export.py[/dim]')
|
|
369
|
+
elif category == 'upload':
|
|
370
|
+
plugin_branch.add('[dim]upload.py[/dim]')
|
|
371
|
+
elif category == 'data_validation':
|
|
372
|
+
plugin_branch.add('[dim]validate.py[/dim]')
|
|
373
|
+
elif category == 'pre_annotation':
|
|
374
|
+
plugin_branch.add('[dim]pre_annotate.py[/dim]')
|
|
375
|
+
elif category == 'post_annotation':
|
|
376
|
+
plugin_branch.add('[dim]post_annotate.py[/dim]')
|
|
377
|
+
else:
|
|
378
|
+
plugin_branch.add('[dim]main.py[/dim]')
|
|
379
|
+
|
|
380
|
+
# Create info table
|
|
381
|
+
table = Table(show_header=False, box=None, padding=(0, 2))
|
|
382
|
+
table.add_column('Key', style='dim')
|
|
383
|
+
table.add_column('Value')
|
|
384
|
+
table.add_row('Name', spec.name)
|
|
385
|
+
table.add_row('Code', spec.code)
|
|
386
|
+
table.add_row('Version', spec.version)
|
|
387
|
+
table.add_row('Category', spec.category.value.replace('_', ' ').title())
|
|
388
|
+
|
|
389
|
+
if spec.data_type:
|
|
390
|
+
table.add_row('Data Type', spec.data_type.value)
|
|
391
|
+
if spec.tasks:
|
|
392
|
+
table.add_row('Tasks', ', '.join(spec.tasks))
|
|
393
|
+
if spec.annotation_type:
|
|
394
|
+
table.add_row('Annotation Type', spec.annotation_type.value)
|
|
395
|
+
if spec.smart_tool_type:
|
|
396
|
+
table.add_row('Tool Type', spec.smart_tool_type.value)
|
|
397
|
+
if spec.supported_data_types:
|
|
398
|
+
table.add_row('Supported Types', ', '.join(dt.value for dt in spec.supported_data_types))
|
|
399
|
+
|
|
400
|
+
console.print()
|
|
401
|
+
console.print(Panel(table, title='Plugin Info', border_style='blue'))
|
|
402
|
+
console.print()
|
|
403
|
+
console.print(Panel(tree, title='Files to Create', border_style='green'))
|
|
404
|
+
console.print()
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def render_template(template_path: Path, context: dict[str, Any]) -> str:
|
|
408
|
+
"""Render a Jinja2 template.
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
template_path: Path to template file.
|
|
412
|
+
context: Template context.
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
Rendered template content.
|
|
416
|
+
"""
|
|
417
|
+
env = Environment(
|
|
418
|
+
loader=FileSystemLoader(template_path.parent),
|
|
419
|
+
keep_trailing_newline=True,
|
|
420
|
+
)
|
|
421
|
+
template = env.get_template(template_path.name)
|
|
422
|
+
return template.render(**context)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def create_plugin(spec: PluginSpec, output_dir: Path, console: Console) -> Path:
|
|
426
|
+
"""Create a plugin from specification.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
spec: Plugin specification.
|
|
430
|
+
output_dir: Directory to create plugin in.
|
|
431
|
+
console: Rich console for output.
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
Path to created plugin directory.
|
|
435
|
+
"""
|
|
436
|
+
plugin_dir = output_dir / spec.directory_name
|
|
437
|
+
context = spec.to_template_context()
|
|
438
|
+
category = spec.category.value
|
|
439
|
+
|
|
440
|
+
# Create plugin directory
|
|
441
|
+
plugin_dir.mkdir(parents=True, exist_ok=True)
|
|
442
|
+
(plugin_dir / 'plugin').mkdir(exist_ok=True)
|
|
443
|
+
|
|
444
|
+
# Render base templates
|
|
445
|
+
base_dir = TEMPLATES_DIR / 'base'
|
|
446
|
+
for template_file in base_dir.glob('*.j2'):
|
|
447
|
+
output_name = template_file.stem # Remove .j2
|
|
448
|
+
content = render_template(template_file, context)
|
|
449
|
+
(plugin_dir / output_name).write_text(content)
|
|
450
|
+
|
|
451
|
+
# Render base plugin/__init__.py
|
|
452
|
+
init_template = base_dir / 'plugin' / '__init__.py.j2'
|
|
453
|
+
if init_template.exists():
|
|
454
|
+
content = render_template(init_template, context)
|
|
455
|
+
(plugin_dir / 'plugin' / '__init__.py').write_text(content)
|
|
456
|
+
|
|
457
|
+
# Render category-specific templates
|
|
458
|
+
category_dir = TEMPLATES_DIR / category
|
|
459
|
+
if category_dir.exists():
|
|
460
|
+
# Merge category config with base config
|
|
461
|
+
category_config_template = category_dir / 'config.yaml.j2'
|
|
462
|
+
if category_config_template.exists():
|
|
463
|
+
base_config_path = plugin_dir / 'config.yaml'
|
|
464
|
+
base_config = yaml.safe_load(base_config_path.read_text())
|
|
465
|
+
category_config_content = render_template(category_config_template, context)
|
|
466
|
+
category_config = yaml.safe_load(category_config_content)
|
|
467
|
+
base_config.update(category_config)
|
|
468
|
+
base_config_path.write_text(yaml.dump(base_config, sort_keys=False, default_flow_style=False))
|
|
469
|
+
|
|
470
|
+
# Copy category-specific plugin files
|
|
471
|
+
category_plugin_dir = category_dir / 'plugin'
|
|
472
|
+
if category_plugin_dir.exists():
|
|
473
|
+
for template_file in category_plugin_dir.glob('*.j2'):
|
|
474
|
+
output_name = template_file.stem
|
|
475
|
+
content = render_template(template_file, context)
|
|
476
|
+
(plugin_dir / 'plugin' / output_name).write_text(content)
|
|
477
|
+
|
|
478
|
+
console.print(f'[green]Created plugin at[/green] {plugin_dir}')
|
|
479
|
+
return plugin_dir
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
@dataclass
|
|
483
|
+
class CreateResult:
|
|
484
|
+
"""Result of plugin creation."""
|
|
485
|
+
|
|
486
|
+
plugin_dir: Path
|
|
487
|
+
spec: PluginSpec
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def create_plugin_interactive(
|
|
491
|
+
*,
|
|
492
|
+
output_dir: Path | None = None,
|
|
493
|
+
name: str | None = None,
|
|
494
|
+
code: str | None = None,
|
|
495
|
+
category: str | None = None,
|
|
496
|
+
console: Console,
|
|
497
|
+
yes: bool = False,
|
|
498
|
+
) -> CreateResult | None:
|
|
499
|
+
"""Create a plugin interactively.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
output_dir: Directory to create plugin in. Defaults to current directory.
|
|
503
|
+
name: Plugin name (skip prompt if provided).
|
|
504
|
+
code: Plugin code (skip prompt if provided).
|
|
505
|
+
category: Plugin category (skip prompt if provided).
|
|
506
|
+
console: Rich console for output.
|
|
507
|
+
yes: Skip confirmation prompt.
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
CreateResult if successful, None if cancelled.
|
|
511
|
+
"""
|
|
512
|
+
output_dir = output_dir or Path.cwd()
|
|
513
|
+
|
|
514
|
+
# Collect spec
|
|
515
|
+
spec = collect_plugin_spec(
|
|
516
|
+
name=name,
|
|
517
|
+
code=code,
|
|
518
|
+
category=category,
|
|
519
|
+
interactive=True,
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
if not spec:
|
|
523
|
+
return None
|
|
524
|
+
|
|
525
|
+
# Check if directory already exists
|
|
526
|
+
plugin_dir = output_dir / spec.directory_name
|
|
527
|
+
if plugin_dir.exists():
|
|
528
|
+
console.print(f'[red]Directory already exists:[/red] {plugin_dir}')
|
|
529
|
+
if not yes:
|
|
530
|
+
overwrite = questionary.confirm(
|
|
531
|
+
'Overwrite existing directory?',
|
|
532
|
+
default=False,
|
|
533
|
+
).ask()
|
|
534
|
+
if not overwrite:
|
|
535
|
+
return None
|
|
536
|
+
shutil.rmtree(plugin_dir)
|
|
537
|
+
|
|
538
|
+
# Show preview
|
|
539
|
+
display_preview(spec, console)
|
|
540
|
+
|
|
541
|
+
# Confirm
|
|
542
|
+
if not yes:
|
|
543
|
+
confirmed = questionary.confirm(
|
|
544
|
+
'Create plugin?',
|
|
545
|
+
default=True,
|
|
546
|
+
).ask()
|
|
547
|
+
if not confirmed:
|
|
548
|
+
console.print('[yellow]Cancelled[/yellow]')
|
|
549
|
+
return None
|
|
550
|
+
|
|
551
|
+
# Create plugin
|
|
552
|
+
plugin_dir = create_plugin(spec, output_dir, console)
|
|
553
|
+
|
|
554
|
+
return CreateResult(plugin_dir=plugin_dir, spec=spec)
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
__all__ = [
|
|
558
|
+
'PluginSpec',
|
|
559
|
+
'CreateResult',
|
|
560
|
+
'create_plugin',
|
|
561
|
+
'create_plugin_interactive',
|
|
562
|
+
'collect_plugin_spec',
|
|
563
|
+
'display_preview',
|
|
564
|
+
'slugify',
|
|
565
|
+
'validate_code',
|
|
566
|
+
]
|