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
|
@@ -1,24 +1,42 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from synapse_sdk.clients.agent.async_ray import AsyncRayClientMixin
|
|
4
|
+
from synapse_sdk.clients.agent.container import ContainerClientMixin
|
|
5
|
+
from synapse_sdk.clients.agent.plugin import PluginClientMixin
|
|
2
6
|
from synapse_sdk.clients.agent.ray import RayClientMixin
|
|
3
|
-
from synapse_sdk.clients.
|
|
4
|
-
|
|
7
|
+
from synapse_sdk.clients.base import AsyncBaseClient, BaseClient
|
|
8
|
+
|
|
5
9
|
|
|
10
|
+
class AgentClient(ContainerClientMixin, PluginClientMixin, RayClientMixin, BaseClient):
|
|
11
|
+
"""Sync client for synapse-agent API."""
|
|
6
12
|
|
|
7
|
-
class AgentClient(CoreClientMixin, RayClientMixin, ServiceClientMixin):
|
|
8
13
|
name = 'Agent'
|
|
9
|
-
agent_token = None
|
|
10
|
-
user_token = None
|
|
11
|
-
tenant = None
|
|
12
|
-
long_poll_handler = None
|
|
13
14
|
|
|
14
|
-
def __init__(
|
|
15
|
-
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
base_url: str,
|
|
18
|
+
agent_token: str,
|
|
19
|
+
*,
|
|
20
|
+
user_token: str | None = None,
|
|
21
|
+
tenant: str | None = None,
|
|
22
|
+
timeout: dict[str, int] | None = None,
|
|
23
|
+
):
|
|
24
|
+
"""Initialize the agent client.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
base_url: Agent API base URL.
|
|
28
|
+
agent_token: Agent authentication token.
|
|
29
|
+
user_token: Optional user token for impersonation.
|
|
30
|
+
tenant: Optional tenant identifier.
|
|
31
|
+
timeout: Request timeout dict with 'connect' and 'read' keys.
|
|
32
|
+
"""
|
|
33
|
+
super().__init__(base_url, timeout=timeout or {'connect': 3, 'read': 10})
|
|
16
34
|
self.agent_token = agent_token
|
|
17
35
|
self.user_token = user_token
|
|
18
36
|
self.tenant = tenant
|
|
19
|
-
self.long_poll_handler = long_poll_handler
|
|
20
37
|
|
|
21
|
-
def _get_headers(self):
|
|
38
|
+
def _get_headers(self) -> dict[str, str]:
|
|
39
|
+
"""Return authentication headers."""
|
|
22
40
|
headers = {'Authorization': self.agent_token}
|
|
23
41
|
if self.user_token:
|
|
24
42
|
headers['SYNAPSE-User'] = f'Token {self.user_token}'
|
|
@@ -26,26 +44,63 @@ class AgentClient(CoreClientMixin, RayClientMixin, ServiceClientMixin):
|
|
|
26
44
|
headers['SYNAPSE-Tenant'] = f'Token {self.tenant}'
|
|
27
45
|
return headers
|
|
28
46
|
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return
|
|
47
|
+
def health_check(self) -> dict:
|
|
48
|
+
"""Check agent health."""
|
|
49
|
+
path = 'health/'
|
|
50
|
+
return self._get(path)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AsyncAgentClient(AsyncRayClientMixin, AsyncBaseClient):
|
|
54
|
+
"""Async client for synapse-agent API.
|
|
55
|
+
|
|
56
|
+
Provides async/await interface for all agent operations including
|
|
57
|
+
WebSocket and HTTP streaming for job log tailing.
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
>>> async with AsyncAgentClient(base_url, agent_token) as client:
|
|
61
|
+
... jobs = await client.list_jobs()
|
|
62
|
+
... async for line in client.tail_job_logs('job-123'):
|
|
63
|
+
... print(line)
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
name = 'Agent'
|
|
33
67
|
|
|
34
|
-
def
|
|
35
|
-
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
base_url: str,
|
|
71
|
+
agent_token: str,
|
|
72
|
+
*,
|
|
73
|
+
user_token: str | None = None,
|
|
74
|
+
tenant: str | None = None,
|
|
75
|
+
timeout: float | None = None,
|
|
76
|
+
):
|
|
77
|
+
"""Initialize the async agent client.
|
|
36
78
|
|
|
37
|
-
|
|
38
|
-
|
|
79
|
+
Args:
|
|
80
|
+
base_url: Agent API base URL.
|
|
81
|
+
agent_token: Agent authentication token.
|
|
82
|
+
user_token: Optional user token for impersonation.
|
|
83
|
+
tenant: Optional tenant identifier.
|
|
84
|
+
timeout: Request timeout in seconds.
|
|
85
|
+
"""
|
|
86
|
+
super().__init__(base_url, timeout=timeout)
|
|
87
|
+
self.agent_token = agent_token
|
|
88
|
+
self.user_token = user_token
|
|
89
|
+
self.tenant = tenant
|
|
39
90
|
|
|
40
|
-
|
|
91
|
+
def _get_headers(self) -> dict[str, str]:
|
|
92
|
+
"""Return authentication headers."""
|
|
93
|
+
headers = {'Authorization': self.agent_token}
|
|
94
|
+
if self.user_token:
|
|
95
|
+
headers['SYNAPSE-User'] = f'Token {self.user_token}'
|
|
96
|
+
if self.tenant:
|
|
97
|
+
headers['SYNAPSE-Tenant'] = f'Token {self.tenant}'
|
|
98
|
+
return headers
|
|
41
99
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
raise ClientError(408, f'{self.name} is not responding')
|
|
100
|
+
async def health_check(self) -> dict:
|
|
101
|
+
"""Check agent health."""
|
|
102
|
+
path = 'health/'
|
|
103
|
+
return await self._get(path)
|
|
47
104
|
|
|
48
|
-
if 400 <= response['status'] < 600:
|
|
49
|
-
raise ClientError(response['status'], response.json() if response['status'] == 400 else response['reason'])
|
|
50
105
|
|
|
51
|
-
|
|
106
|
+
__all__ = ['AgentClient', 'AsyncAgentClient']
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import TYPE_CHECKING, AsyncGenerator, Literal
|
|
5
|
+
|
|
6
|
+
from synapse_sdk.exceptions import ClientError
|
|
7
|
+
from synapse_sdk.utils.network import (
|
|
8
|
+
StreamLimits,
|
|
9
|
+
http_to_websocket_url,
|
|
10
|
+
sanitize_error_message,
|
|
11
|
+
validate_resource_id,
|
|
12
|
+
validate_timeout,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from synapse_sdk.clients.protocols import AsyncClientProtocol
|
|
17
|
+
|
|
18
|
+
StreamProtocol = Literal['websocket', 'http', 'auto']
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AsyncRayClientMixin:
|
|
22
|
+
"""Async mixin for Ray cluster management endpoints."""
|
|
23
|
+
|
|
24
|
+
_stream_limits: StreamLimits | None = None
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def stream_limits(self) -> StreamLimits:
|
|
28
|
+
"""Get stream limits configuration."""
|
|
29
|
+
if self._stream_limits is None:
|
|
30
|
+
self._stream_limits = StreamLimits()
|
|
31
|
+
return self._stream_limits
|
|
32
|
+
|
|
33
|
+
@stream_limits.setter
|
|
34
|
+
def stream_limits(self, value: StreamLimits) -> None:
|
|
35
|
+
"""Set stream limits configuration."""
|
|
36
|
+
self._stream_limits = value
|
|
37
|
+
|
|
38
|
+
# -------------------------------------------------------------------------
|
|
39
|
+
# Jobs
|
|
40
|
+
# -------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
async def list_jobs(self: AsyncClientProtocol) -> list[dict]:
|
|
43
|
+
"""List all Ray jobs."""
|
|
44
|
+
return await self._get('ray/jobs/')
|
|
45
|
+
|
|
46
|
+
async def get_job(self: AsyncClientProtocol, job_id: str) -> dict:
|
|
47
|
+
"""Get a Ray job by ID."""
|
|
48
|
+
return await self._get(f'ray/jobs/{job_id}')
|
|
49
|
+
|
|
50
|
+
async def get_job_logs(self: AsyncClientProtocol, job_id: str) -> str:
|
|
51
|
+
"""Get all logs for a job (non-streaming)."""
|
|
52
|
+
return await self._get(f'ray/jobs/{job_id}/logs')
|
|
53
|
+
|
|
54
|
+
async def stop_job(self: AsyncClientProtocol, job_id: str) -> dict:
|
|
55
|
+
"""Stop a running job."""
|
|
56
|
+
return await self._post(f'ray/jobs/{job_id}/stop/')
|
|
57
|
+
|
|
58
|
+
async def websocket_tail_job_logs(
|
|
59
|
+
self: AsyncClientProtocol,
|
|
60
|
+
job_id: str,
|
|
61
|
+
timeout: float = 30.0,
|
|
62
|
+
) -> AsyncGenerator[str, None]:
|
|
63
|
+
"""Stream job logs via WebSocket protocol (async).
|
|
64
|
+
|
|
65
|
+
Establishes an async WebSocket connection for real-time log streaming.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
job_id: The Ray job ID to tail logs for.
|
|
69
|
+
timeout: Connection and read timeout in seconds.
|
|
70
|
+
|
|
71
|
+
Yields:
|
|
72
|
+
Log message strings.
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
ClientError: On connection, protocol, or validation errors.
|
|
76
|
+
|
|
77
|
+
Example:
|
|
78
|
+
>>> async for line in client.websocket_tail_job_logs('raysubmit_abc123'):
|
|
79
|
+
... print(line)
|
|
80
|
+
"""
|
|
81
|
+
validated_id = validate_resource_id(job_id, 'job')
|
|
82
|
+
validated_timeout = validate_timeout(timeout)
|
|
83
|
+
|
|
84
|
+
url = self._get_url(f'ray/jobs/{validated_id}/logs/')
|
|
85
|
+
ws_url = http_to_websocket_url(f'{self.base_url}/{url}')
|
|
86
|
+
headers = self._get_headers()
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
import websockets
|
|
90
|
+
except ImportError:
|
|
91
|
+
raise ClientError(500, 'websockets package required for async WebSocket streaming')
|
|
92
|
+
|
|
93
|
+
limits = self.stream_limits
|
|
94
|
+
message_count = 0
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
async with websockets.connect(
|
|
98
|
+
ws_url,
|
|
99
|
+
additional_headers=headers,
|
|
100
|
+
close_timeout=validated_timeout,
|
|
101
|
+
ping_timeout=validated_timeout,
|
|
102
|
+
) as ws:
|
|
103
|
+
async for data in ws:
|
|
104
|
+
if not data:
|
|
105
|
+
break
|
|
106
|
+
|
|
107
|
+
message_count += 1
|
|
108
|
+
if message_count > limits.max_messages:
|
|
109
|
+
raise ClientError(429, 'Stream message limit exceeded')
|
|
110
|
+
|
|
111
|
+
if len(str(data)) > limits.max_message_size:
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
event = json.loads(data)
|
|
116
|
+
except json.JSONDecodeError:
|
|
117
|
+
event = {'message': data}
|
|
118
|
+
|
|
119
|
+
event_type = event.get('type')
|
|
120
|
+
if event_type == 'error':
|
|
121
|
+
raise ClientError(500, event.get('message', 'Unknown error'))
|
|
122
|
+
elif event_type == 'complete':
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
if msg := event.get('message'):
|
|
126
|
+
yield msg
|
|
127
|
+
|
|
128
|
+
except ClientError:
|
|
129
|
+
raise
|
|
130
|
+
except Exception as e:
|
|
131
|
+
if 'ConnectionClosed' in type(e).__name__:
|
|
132
|
+
return # Normal close
|
|
133
|
+
raise ClientError(503, sanitize_error_message(str(e), 'WebSocket error'))
|
|
134
|
+
|
|
135
|
+
async def stream_tail_job_logs(
|
|
136
|
+
self: AsyncClientProtocol,
|
|
137
|
+
job_id: str,
|
|
138
|
+
timeout: float = 30.0,
|
|
139
|
+
) -> AsyncGenerator[str, None]:
|
|
140
|
+
"""Stream job logs via HTTP chunked transfer (async).
|
|
141
|
+
|
|
142
|
+
Uses HTTP streaming as an alternative when WebSocket is unavailable.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
job_id: The Ray job ID to tail logs for.
|
|
146
|
+
timeout: Connection timeout in seconds.
|
|
147
|
+
|
|
148
|
+
Yields:
|
|
149
|
+
Log lines as strings.
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
ClientError: On connection, protocol, or validation errors.
|
|
153
|
+
|
|
154
|
+
Example:
|
|
155
|
+
>>> async for line in client.stream_tail_job_logs('raysubmit_abc123'):
|
|
156
|
+
... print(line)
|
|
157
|
+
"""
|
|
158
|
+
validated_id = validate_resource_id(job_id, 'job')
|
|
159
|
+
validated_timeout = validate_timeout(timeout)
|
|
160
|
+
|
|
161
|
+
url = self._get_url(f'ray/jobs/{validated_id}/logs/')
|
|
162
|
+
headers = self._get_headers()
|
|
163
|
+
|
|
164
|
+
client = await self._get_client()
|
|
165
|
+
limits = self.stream_limits
|
|
166
|
+
line_count = 0
|
|
167
|
+
total_bytes = 0
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
async with client.stream(
|
|
171
|
+
'GET',
|
|
172
|
+
url,
|
|
173
|
+
headers=headers,
|
|
174
|
+
timeout=validated_timeout,
|
|
175
|
+
) as response:
|
|
176
|
+
if not response.is_success:
|
|
177
|
+
raise ClientError(response.status_code, 'HTTP streaming failed')
|
|
178
|
+
|
|
179
|
+
async for line in response.aiter_lines():
|
|
180
|
+
if line:
|
|
181
|
+
line_count += 1
|
|
182
|
+
total_bytes += len(line.encode('utf-8'))
|
|
183
|
+
|
|
184
|
+
if line_count > limits.max_lines:
|
|
185
|
+
raise ClientError(429, 'Stream line limit exceeded')
|
|
186
|
+
|
|
187
|
+
if total_bytes > limits.max_bytes:
|
|
188
|
+
raise ClientError(429, 'Stream size limit exceeded')
|
|
189
|
+
|
|
190
|
+
if len(line) > limits.max_message_size:
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
yield line
|
|
194
|
+
|
|
195
|
+
except ClientError:
|
|
196
|
+
raise
|
|
197
|
+
except Exception as e:
|
|
198
|
+
raise ClientError(503, sanitize_error_message(str(e), 'HTTP streaming error'))
|
|
199
|
+
|
|
200
|
+
async def tail_job_logs(
|
|
201
|
+
self: AsyncClientProtocol,
|
|
202
|
+
job_id: str,
|
|
203
|
+
timeout: float = 30.0,
|
|
204
|
+
*,
|
|
205
|
+
protocol: StreamProtocol = 'auto',
|
|
206
|
+
) -> AsyncGenerator[str, None]:
|
|
207
|
+
"""Stream job logs with automatic protocol selection (async).
|
|
208
|
+
|
|
209
|
+
Unified method that supports WebSocket, HTTP, and auto-selection.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
job_id: The Ray job ID to tail logs for.
|
|
213
|
+
timeout: Connection timeout in seconds.
|
|
214
|
+
protocol: Protocol to use:
|
|
215
|
+
- 'websocket': Use WebSocket only
|
|
216
|
+
- 'http': Use HTTP streaming only
|
|
217
|
+
- 'auto': Try WebSocket, fall back to HTTP on connection failure
|
|
218
|
+
|
|
219
|
+
Yields:
|
|
220
|
+
Log message strings.
|
|
221
|
+
|
|
222
|
+
Raises:
|
|
223
|
+
ClientError: On connection, protocol, or validation errors.
|
|
224
|
+
|
|
225
|
+
Example:
|
|
226
|
+
>>> async for line in client.tail_job_logs('raysubmit_abc123'):
|
|
227
|
+
... print(line)
|
|
228
|
+
"""
|
|
229
|
+
validate_resource_id(job_id, 'job')
|
|
230
|
+
validate_timeout(timeout)
|
|
231
|
+
|
|
232
|
+
if protocol == 'websocket':
|
|
233
|
+
async for msg in self.websocket_tail_job_logs(job_id, timeout):
|
|
234
|
+
yield msg
|
|
235
|
+
elif protocol == 'http':
|
|
236
|
+
async for msg in self.stream_tail_job_logs(job_id, timeout):
|
|
237
|
+
yield msg
|
|
238
|
+
elif protocol == 'auto':
|
|
239
|
+
try:
|
|
240
|
+
async for msg in self.websocket_tail_job_logs(job_id, timeout):
|
|
241
|
+
yield msg
|
|
242
|
+
except ClientError as e:
|
|
243
|
+
if e.status_code in (500, 503):
|
|
244
|
+
async for msg in self.stream_tail_job_logs(job_id, timeout):
|
|
245
|
+
yield msg
|
|
246
|
+
else:
|
|
247
|
+
raise
|
|
248
|
+
else:
|
|
249
|
+
raise ClientError(400, f'Invalid protocol: {protocol}')
|
|
250
|
+
|
|
251
|
+
# -------------------------------------------------------------------------
|
|
252
|
+
# Nodes
|
|
253
|
+
# -------------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
async def list_nodes(self: AsyncClientProtocol) -> list[dict]:
|
|
256
|
+
"""List all Ray nodes."""
|
|
257
|
+
return await self._get('ray/nodes/')
|
|
258
|
+
|
|
259
|
+
async def get_node(self: AsyncClientProtocol, node_id: str) -> dict:
|
|
260
|
+
"""Get a Ray node by ID."""
|
|
261
|
+
return await self._get(f'ray/nodes/{node_id}')
|
|
262
|
+
|
|
263
|
+
# -------------------------------------------------------------------------
|
|
264
|
+
# Tasks
|
|
265
|
+
# -------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
async def list_tasks(self: AsyncClientProtocol) -> list[dict]:
|
|
268
|
+
"""List all Ray tasks."""
|
|
269
|
+
return await self._get('ray/tasks/')
|
|
270
|
+
|
|
271
|
+
async def get_task(self: AsyncClientProtocol, task_id: str) -> dict:
|
|
272
|
+
"""Get a Ray task by ID."""
|
|
273
|
+
return await self._get(f'ray/tasks/{task_id}')
|
|
274
|
+
|
|
275
|
+
# -------------------------------------------------------------------------
|
|
276
|
+
# Serve Applications
|
|
277
|
+
# -------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
async def list_serve_applications(self: AsyncClientProtocol) -> list[dict]:
|
|
280
|
+
"""List all Ray Serve applications."""
|
|
281
|
+
return await self._get('ray/serve_applications/')
|
|
282
|
+
|
|
283
|
+
async def get_serve_application(self: AsyncClientProtocol, name: str) -> dict:
|
|
284
|
+
"""Get a Ray Serve application by name."""
|
|
285
|
+
return await self._get(f'ray/serve_applications/{name}')
|
|
286
|
+
|
|
287
|
+
async def delete_serve_application(self: AsyncClientProtocol, name: str) -> None:
|
|
288
|
+
"""Delete a Ray Serve application."""
|
|
289
|
+
await self._delete(f'ray/serve_applications/{name}')
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from synapse_sdk.clients.protocols import ClientProtocol
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ContainerClientMixin:
|
|
10
|
+
"""Mixin for container management endpoints."""
|
|
11
|
+
|
|
12
|
+
# Docker containers
|
|
13
|
+
def list_docker_containers(self: ClientProtocol) -> list[dict]:
|
|
14
|
+
"""List all Docker containers on the host."""
|
|
15
|
+
return self._get('containers/docker/')
|
|
16
|
+
|
|
17
|
+
def get_docker_container(self: ClientProtocol, container_id: str) -> dict:
|
|
18
|
+
"""Get a specific Docker container by ID."""
|
|
19
|
+
return self._get(f'containers/docker/{container_id}')
|
|
20
|
+
|
|
21
|
+
def create_docker_container(
|
|
22
|
+
self: ClientProtocol,
|
|
23
|
+
plugin_release: str,
|
|
24
|
+
*,
|
|
25
|
+
params: dict[str, Any] | None = None,
|
|
26
|
+
envs: dict[str, str] | None = None,
|
|
27
|
+
metadata: dict[str, Any] | None = None,
|
|
28
|
+
labels: list[str] | None = None,
|
|
29
|
+
) -> dict:
|
|
30
|
+
"""Build and run a Docker container for a plugin.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
plugin_release: Plugin identifier (e.g., "plugin_code@version").
|
|
34
|
+
params: Parameters forwarded to the plugin.
|
|
35
|
+
envs: Environment variables injected into the container.
|
|
36
|
+
metadata: Additional metadata stored with the container record.
|
|
37
|
+
labels: Container labels for display or filtering.
|
|
38
|
+
"""
|
|
39
|
+
data = {'plugin_release': plugin_release}
|
|
40
|
+
if params is not None:
|
|
41
|
+
data['params'] = params
|
|
42
|
+
if envs is not None:
|
|
43
|
+
data['envs'] = envs
|
|
44
|
+
if metadata is not None:
|
|
45
|
+
data['metadata'] = metadata
|
|
46
|
+
if labels is not None:
|
|
47
|
+
data['labels'] = labels
|
|
48
|
+
|
|
49
|
+
return self._post('containers/docker/', data=data)
|
|
50
|
+
|
|
51
|
+
def delete_docker_container(self: ClientProtocol, container_id: str) -> None:
|
|
52
|
+
"""Stop and remove a Docker container."""
|
|
53
|
+
self._delete(f'containers/docker/{container_id}')
|
|
54
|
+
|
|
55
|
+
# Database container records
|
|
56
|
+
def list_containers(
|
|
57
|
+
self: ClientProtocol,
|
|
58
|
+
params: dict | None = None,
|
|
59
|
+
*,
|
|
60
|
+
list_all: bool = False,
|
|
61
|
+
) -> dict | tuple[Any, int]:
|
|
62
|
+
"""List tracked containers from database."""
|
|
63
|
+
return self._list('containers/', params=params, list_all=list_all)
|
|
64
|
+
|
|
65
|
+
def get_container(self: ClientProtocol, container_id: int) -> dict:
|
|
66
|
+
"""Get a tracked container by database ID."""
|
|
67
|
+
return self._get(f'containers/{container_id}')
|
|
68
|
+
|
|
69
|
+
def update_container(
|
|
70
|
+
self: ClientProtocol,
|
|
71
|
+
container_id: int,
|
|
72
|
+
*,
|
|
73
|
+
status: str | None = None,
|
|
74
|
+
) -> dict:
|
|
75
|
+
"""Update a tracked container's status."""
|
|
76
|
+
data = {}
|
|
77
|
+
if status is not None:
|
|
78
|
+
data['status'] = status
|
|
79
|
+
return self._patch(f'containers/{container_id}', data=data)
|
|
80
|
+
|
|
81
|
+
def delete_container(self: ClientProtocol, container_id: int) -> None:
|
|
82
|
+
"""Delete a tracked container (stops Docker container too)."""
|
|
83
|
+
self._delete(f'containers/{container_id}')
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from synapse_sdk.clients.protocols import ClientProtocol
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PluginClientMixin:
|
|
10
|
+
"""Mixin for plugin release endpoints."""
|
|
11
|
+
|
|
12
|
+
def list_plugin_releases(
|
|
13
|
+
self: ClientProtocol,
|
|
14
|
+
params: dict | None = None,
|
|
15
|
+
*,
|
|
16
|
+
list_all: bool = False,
|
|
17
|
+
) -> dict | tuple[Any, int]:
|
|
18
|
+
"""List all plugin releases."""
|
|
19
|
+
return self._list('plugin_releases/', params=params, list_all=list_all)
|
|
20
|
+
|
|
21
|
+
def get_plugin_release(self: ClientProtocol, lookup: str) -> dict:
|
|
22
|
+
"""Get a plugin release by ID or code@version."""
|
|
23
|
+
return self._get(f'plugin_releases/{lookup}')
|
|
24
|
+
|
|
25
|
+
def create_plugin_release(
|
|
26
|
+
self: ClientProtocol,
|
|
27
|
+
plugin: str,
|
|
28
|
+
version: str,
|
|
29
|
+
) -> dict:
|
|
30
|
+
"""Fetch and cache a plugin release."""
|
|
31
|
+
return self._post('plugin_releases/', data={'plugin': plugin, 'version': version})
|
|
32
|
+
|
|
33
|
+
def delete_plugin_release(self: ClientProtocol, lookup: str) -> None:
|
|
34
|
+
"""Delete a plugin release."""
|
|
35
|
+
self._delete(f'plugin_releases/{lookup}')
|
|
36
|
+
|
|
37
|
+
def run_plugin_release(
|
|
38
|
+
self: ClientProtocol,
|
|
39
|
+
lookup: str,
|
|
40
|
+
action: str,
|
|
41
|
+
params: dict[str, Any] | None = None,
|
|
42
|
+
*,
|
|
43
|
+
requirements: list[str] | None = None,
|
|
44
|
+
job_id: str | None = None,
|
|
45
|
+
) -> Any:
|
|
46
|
+
"""Run a plugin release action.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
lookup: Plugin identifier (ID or "plugin@version").
|
|
50
|
+
action: Action name to execute.
|
|
51
|
+
params: Parameters to pass to the action.
|
|
52
|
+
requirements: Additional pip requirements.
|
|
53
|
+
job_id: Optional job ID for tracking.
|
|
54
|
+
"""
|
|
55
|
+
data: dict[str, Any] = {'action': action}
|
|
56
|
+
if params is not None:
|
|
57
|
+
data['params'] = params
|
|
58
|
+
if requirements is not None:
|
|
59
|
+
data['requirements'] = requirements
|
|
60
|
+
if job_id is not None:
|
|
61
|
+
data['job_id'] = job_id
|
|
62
|
+
|
|
63
|
+
return self._post(f'plugin_releases/{lookup}/run/', data=data)
|
|
64
|
+
|
|
65
|
+
def run_debug_plugin_release(
|
|
66
|
+
self: ClientProtocol,
|
|
67
|
+
action: str,
|
|
68
|
+
params: dict[str, Any] | None = None,
|
|
69
|
+
*,
|
|
70
|
+
plugin_path: str | None = None,
|
|
71
|
+
config: dict[str, Any] | None = None,
|
|
72
|
+
modules: dict[str, str] | None = None,
|
|
73
|
+
requirements: list[str] | None = None,
|
|
74
|
+
job_id: str | None = None,
|
|
75
|
+
) -> Any:
|
|
76
|
+
"""Run a plugin in debug mode (from source path).
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
action: Action name to execute.
|
|
80
|
+
params: Parameters to pass to the action.
|
|
81
|
+
plugin_path: Path to the plugin source directory.
|
|
82
|
+
config: Plugin configuration override.
|
|
83
|
+
modules: Module source code mapping.
|
|
84
|
+
requirements: Additional pip requirements.
|
|
85
|
+
job_id: Optional job ID for tracking.
|
|
86
|
+
"""
|
|
87
|
+
data: dict[str, Any] = {'action': action}
|
|
88
|
+
if params is not None:
|
|
89
|
+
data['params'] = params
|
|
90
|
+
if plugin_path is not None:
|
|
91
|
+
data['plugin_path'] = plugin_path
|
|
92
|
+
if config is not None:
|
|
93
|
+
data['config'] = config
|
|
94
|
+
if modules is not None:
|
|
95
|
+
data['modules'] = modules
|
|
96
|
+
if requirements is not None:
|
|
97
|
+
data['requirements'] = requirements
|
|
98
|
+
if job_id is not None:
|
|
99
|
+
data['job_id'] = job_id
|
|
100
|
+
|
|
101
|
+
return self._post('plugin_releases/run_debug/', data=data)
|