mlspace-plugin 0.2.1__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.
- mlspace_mcp/__init__.py +3 -0
- mlspace_mcp/__main__.py +125 -0
- mlspace_mcp/_status.py +18 -0
- mlspace_mcp/auth.py +109 -0
- mlspace_mcp/bodyspec.py +184 -0
- mlspace_mcp/client.py +170 -0
- mlspace_mcp/client_setup.py +235 -0
- mlspace_mcp/config.py +283 -0
- mlspace_mcp/errors.py +241 -0
- mlspace_mcp/formatting.py +502 -0
- mlspace_mcp/init_cli.py +356 -0
- mlspace_mcp/job_hooks.py +242 -0
- mlspace_mcp/jsonc.py +90 -0
- mlspace_mcp/overview.py +421 -0
- mlspace_mcp/registry.py +632 -0
- mlspace_mcp/resource_refs.py +188 -0
- mlspace_mcp/server.py +190 -0
- mlspace_mcp/setup_ui.py +103 -0
- mlspace_mcp/skills/mlspace-call-deployed-model-predict/SKILL.md +36 -0
- mlspace_mcp/skills/mlspace-deploy-inference-service/SKILL.md +42 -0
- mlspace_mcp/skills/mlspace-diagnose-data-transfer/SKILL.md +41 -0
- mlspace_mcp/skills/mlspace-diagnose-failed-training-job/SKILL.md +64 -0
- mlspace_mcp/skills/mlspace-diagnose-job-launch-rejection/SKILL.md +44 -0
- mlspace_mcp/skills/mlspace-diagnose-unhealthy-inference/SKILL.md +40 -0
- mlspace_mcp/skills/mlspace-discover-launchable-resources/SKILL.md +45 -0
- mlspace_mcp/skills/mlspace-launch-training-job/SKILL.md +66 -0
- mlspace_mcp/skills/mlspace-list-running-jobs/SKILL.md +43 -0
- mlspace_mcp/skills/mlspace-resume-notebook-and-set-autoshutdown/SKILL.md +44 -0
- mlspace_mcp/skills/mlspace-stage-s3-to-nfs/SKILL.md +45 -0
- mlspace_mcp/skills/mlspace-transfer-compute-node/SKILL.md +63 -0
- mlspace_mcp/spec/body_schemas.json +7506 -0
- mlspace_mcp/spec/openapi.json +28347 -0
- mlspace_mcp/tls.py +46 -0
- mlspace_mcp/tools/__init__.py +1 -0
- mlspace_mcp/tools/allocations.py +92 -0
- mlspace_mcp/tools/async_inference.py +77 -0
- mlspace_mcp/tools/build_image.py +76 -0
- mlspace_mcp/tools/dalle.py +52 -0
- mlspace_mcp/tools/data_transfer.py +260 -0
- mlspace_mcp/tools/docker_registry.py +156 -0
- mlspace_mcp/tools/inference.py +88 -0
- mlspace_mcp/tools/jobs.py +155 -0
- mlspace_mcp/tools/notebooks.py +174 -0
- mlspace_mcp/tools/queues.py +202 -0
- mlspace_mcp/tools/resources.py +86 -0
- mlspace_mcp/tools/tensorboards.py +112 -0
- mlspace_mcp/tools/workspaces.py +92 -0
- mlspace_mcp/workspace_context.py +107 -0
- mlspace_plugin-0.2.1.dist-info/METADATA +427 -0
- mlspace_plugin-0.2.1.dist-info/RECORD +53 -0
- mlspace_plugin-0.2.1.dist-info/WHEEL +4 -0
- mlspace_plugin-0.2.1.dist-info/entry_points.txt +2 -0
- mlspace_plugin-0.2.1.dist-info/licenses/LICENSE +21 -0
mlspace_mcp/tls.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""One verified TLS policy for setup and runtime; no global SSL monkey patch."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import ssl
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def tls_context(ca_file: str = '') -> ssl.SSLContext:
|
|
11
|
+
"""Use OS trust and optionally an administrator-supplied PEM bundle."""
|
|
12
|
+
extra = ca_file or os.environ.get('SSL_CERT_FILE', '')
|
|
13
|
+
context: ssl.SSLContext
|
|
14
|
+
try:
|
|
15
|
+
import truststore
|
|
16
|
+
context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
17
|
+
except ImportError:
|
|
18
|
+
context = ssl.create_default_context()
|
|
19
|
+
if extra:
|
|
20
|
+
context.load_verify_locations(cafile=extra)
|
|
21
|
+
capath = os.environ.get('SSL_CERT_DIR')
|
|
22
|
+
if capath:
|
|
23
|
+
context.load_verify_locations(capath=capath)
|
|
24
|
+
return context
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def safe_error(exc: Exception) -> str:
|
|
28
|
+
"""Describe the cause without printing exception bodies or submitted values."""
|
|
29
|
+
chain: list[BaseException] = []
|
|
30
|
+
current: BaseException | None = exc
|
|
31
|
+
while current is not None and all(current is not item for item in chain):
|
|
32
|
+
chain.append(current)
|
|
33
|
+
current = current.__cause__ or current.__context__
|
|
34
|
+
if any(isinstance(item, ssl.SSLCertVerificationError) or
|
|
35
|
+
(isinstance(item, (ssl.SSLError, httpx.ConnectError)) and
|
|
36
|
+
'CERTIFICATE_VERIFY_FAILED' in str(item)) for item in chain):
|
|
37
|
+
return ('TLS certificate verification failed. Use the corporate CA from your '
|
|
38
|
+
'administrator: --ca-file /path/to/ca.pem. TLS verification remains enabled.')
|
|
39
|
+
status = getattr(exc, 'status', None)
|
|
40
|
+
if isinstance(status, int):
|
|
41
|
+
return f'API request failed (HTTP {status}); check access keys and workspace permissions.'
|
|
42
|
+
if any(isinstance(item, httpx.TimeoutException) for item in chain):
|
|
43
|
+
return 'API request timed out; check network/VPN and retry.'
|
|
44
|
+
if any(isinstance(item, httpx.ConnectError) for item in chain):
|
|
45
|
+
return 'Could not connect; check network/VPN and the API endpoint.'
|
|
46
|
+
return type(exc).__name__
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Domain umbrella-tool definitions. Each module exposes ``DOMAIN: DomainTool``."""
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""``mlspace_allocations`` — inspect MLSpace compute allocations and their defaults.
|
|
2
|
+
|
|
3
|
+
Pure declarative data: a single ``DOMAIN: DomainTool``. All request mechanics
|
|
4
|
+
(headers, path templating, query encoding, bodies, readonly, confirm,
|
|
5
|
+
formatting) live in the core. See ``inference.py`` for the canonical shape.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from ..registry import DomainTool, Op, Param
|
|
11
|
+
|
|
12
|
+
# action -> Op. The (method, path) pairs are the single source of truth for the
|
|
13
|
+
# drift test; paths are copied verbatim from the spec, placeholders included.
|
|
14
|
+
ACTIONS: dict[str, Op] = {
|
|
15
|
+
"list": Op(
|
|
16
|
+
"GET", "/public/v2/allocations/",
|
|
17
|
+
kind="list",
|
|
18
|
+
help="List allocations in the workspace (personal account only).",
|
|
19
|
+
),
|
|
20
|
+
"list_defaults": Op(
|
|
21
|
+
"GET", "/public/v2/allocations/defaults",
|
|
22
|
+
query_params={"region": "region"},
|
|
23
|
+
required=("region",),
|
|
24
|
+
kind="list",
|
|
25
|
+
help="List default allocations in a region.",
|
|
26
|
+
),
|
|
27
|
+
"get": Op(
|
|
28
|
+
"GET", "/public/v2/allocations/{allocation_id}",
|
|
29
|
+
path_params={"allocation_id": "allocation_id"},
|
|
30
|
+
required=("allocation_id",),
|
|
31
|
+
help="Get one allocation with its resource status.",
|
|
32
|
+
),
|
|
33
|
+
"list_assignments": Op(
|
|
34
|
+
"GET", "/public/v2/allocations/{allocation_id}/assignments",
|
|
35
|
+
path_params={"allocation_id": "allocation_id"},
|
|
36
|
+
required=("allocation_id",),
|
|
37
|
+
kind="list",
|
|
38
|
+
help="List an allocation's assignments by organization structure.",
|
|
39
|
+
),
|
|
40
|
+
"list_instance_types": Op(
|
|
41
|
+
"GET", "/public/v2/allocations/{allocation_id}/instance-types",
|
|
42
|
+
path_params={"allocation_id": "allocation_id"},
|
|
43
|
+
required=("allocation_id",),
|
|
44
|
+
kind="list",
|
|
45
|
+
help="List instance types available in an allocation.",
|
|
46
|
+
),
|
|
47
|
+
"get_nodes": Op(
|
|
48
|
+
"GET", "/public/v2/allocations/{allocation_id}/nodes",
|
|
49
|
+
path_params={"allocation_id": "allocation_id"},
|
|
50
|
+
required=("allocation_id",),
|
|
51
|
+
help="Get the GPU nodes in an allocation.",
|
|
52
|
+
),
|
|
53
|
+
"set_default": Op(
|
|
54
|
+
"POST", "/public/v2/allocations/{allocation_id}/defaults",
|
|
55
|
+
path_params={"allocation_id": "allocation_id"},
|
|
56
|
+
body_field="body",
|
|
57
|
+
required=("allocation_id", "body"),
|
|
58
|
+
write=True,
|
|
59
|
+
help="Set an allocation as default for job/notebook; `body` = DefaultRequest "
|
|
60
|
+
"{org_structure, default_for}.",
|
|
61
|
+
),
|
|
62
|
+
"unset_default": Op(
|
|
63
|
+
"DELETE", "/public/v2/allocations/{allocation_id}/defaults",
|
|
64
|
+
path_params={"allocation_id": "allocation_id"},
|
|
65
|
+
body_field="body",
|
|
66
|
+
required=("allocation_id", "body"),
|
|
67
|
+
write=True,
|
|
68
|
+
help="Unset an allocation as default for job/notebook; `body` = DefaultRequest "
|
|
69
|
+
"{org_structure, default_for}.",
|
|
70
|
+
),
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
PARAMS: list[Param] = [
|
|
74
|
+
Param("allocation_id", str, "Allocation ID (UUID)."),
|
|
75
|
+
Param("region", str, "Region key (required for list_defaults)."),
|
|
76
|
+
Param(
|
|
77
|
+
"body",
|
|
78
|
+
dict,
|
|
79
|
+
"JSON body = DefaultRequest for set_default/unset_default: "
|
|
80
|
+
"{org_structure: {id, type}, default_for: 'job'|'notebook'}.",
|
|
81
|
+
),
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
DOMAIN = DomainTool(
|
|
85
|
+
domain="allocations",
|
|
86
|
+
name="mlspace_allocations",
|
|
87
|
+
title="MLSpace Allocations",
|
|
88
|
+
summary="Inspect MLSpace compute allocations, their nodes, instance types, "
|
|
89
|
+
"assignments, and manage default allocations.",
|
|
90
|
+
actions=ACTIONS,
|
|
91
|
+
params=PARAMS,
|
|
92
|
+
)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""``mlspace_async_inference`` — submit and track asynchronous inference requests.
|
|
2
|
+
|
|
3
|
+
Pure declarative data following the ``inference`` exemplar; all request
|
|
4
|
+
mechanics live in the core (registry.py + client.py).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from ..registry import DomainTool, Op, Param
|
|
10
|
+
|
|
11
|
+
ACTIONS: dict[str, Op] = {
|
|
12
|
+
"list": Op(
|
|
13
|
+
"GET", "/public/v2/async_inferences/v1/",
|
|
14
|
+
query_params={"inference_name": "inference_name"},
|
|
15
|
+
required=("inference_name",),
|
|
16
|
+
kind="list",
|
|
17
|
+
help="List asynchronous requests for an inference service.",
|
|
18
|
+
),
|
|
19
|
+
"predict": Op(
|
|
20
|
+
"POST",
|
|
21
|
+
"/public/v2/async_inferences/v1/{region}/{inference_name}/{predict_path}",
|
|
22
|
+
path_params={
|
|
23
|
+
"region": "region",
|
|
24
|
+
"inference_name": "inference_name",
|
|
25
|
+
"predict_path": "predict_path",
|
|
26
|
+
},
|
|
27
|
+
# predict_path is a deliberate multi-segment tail (e.g. v1/models/svc:predict):
|
|
28
|
+
# it keeps its "/" separators; region and inference_name stay opaque segments.
|
|
29
|
+
path_tail=frozenset({"predict_path"}),
|
|
30
|
+
body_field="body",
|
|
31
|
+
required=("region", "inference_name", "predict_path"),
|
|
32
|
+
side_effect=True, # submits a (paid) inference job — not a read
|
|
33
|
+
help="Send an async prediction request; put the model's input in `body` (free-form).",
|
|
34
|
+
),
|
|
35
|
+
"get_result": Op(
|
|
36
|
+
"GET", "/public/v2/async_inferences/v1/{request_id}/result",
|
|
37
|
+
path_params={"request_id": "request_id"},
|
|
38
|
+
query_params={"inference_name": "inference_name"},
|
|
39
|
+
required=("request_id", "inference_name"),
|
|
40
|
+
help="Get the result of an asynchronous request.",
|
|
41
|
+
),
|
|
42
|
+
"get_status": Op(
|
|
43
|
+
"GET", "/public/v2/async_inferences/v1/{request_id}/status",
|
|
44
|
+
path_params={"request_id": "request_id"},
|
|
45
|
+
query_params={"inference_name": "inference_name"},
|
|
46
|
+
required=("request_id", "inference_name"),
|
|
47
|
+
help="Get the status of an asynchronous request.",
|
|
48
|
+
),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
PARAMS: list[Param] = [
|
|
52
|
+
Param("inference_name", str, "Name of the inference service."),
|
|
53
|
+
Param(
|
|
54
|
+
"region",
|
|
55
|
+
str,
|
|
56
|
+
"Inference region, e.g. DGX2-INF-001, CCE-INF, INF-002 "
|
|
57
|
+
"(authoritative list via mlspace_resources configs cluster_type=INF). "
|
|
58
|
+
"Required for predict.",
|
|
59
|
+
),
|
|
60
|
+
Param(
|
|
61
|
+
"predict_path",
|
|
62
|
+
str,
|
|
63
|
+
"Predict path, usually `v1/models/{inference_name}:predict` (predict only).",
|
|
64
|
+
),
|
|
65
|
+
Param("request_id", str, "ID of the async request (required for get_result/get_status)."),
|
|
66
|
+
Param("body", dict, "Free-form model input payload for predict."),
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
DOMAIN = DomainTool(
|
|
70
|
+
domain="async_inference",
|
|
71
|
+
name="mlspace_async_inference",
|
|
72
|
+
title="MLSpace Async Inference",
|
|
73
|
+
summary="Submit and track asynchronous MLSpace inference requests (predict, status, result).",
|
|
74
|
+
actions=ACTIONS,
|
|
75
|
+
params=PARAMS,
|
|
76
|
+
redact_result_keys=("inference_key",),
|
|
77
|
+
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""``mlspace_build_image`` — manage MLSpace build-image jobs.
|
|
2
|
+
|
|
3
|
+
Pure declarative data; the core (registry.py + client.py) owns all request
|
|
4
|
+
mechanics. Mirrors the exemplar ``inference.py`` in shape.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from ..registry import DomainTool, Op, Param
|
|
10
|
+
|
|
11
|
+
# action -> Op. (method, path) pairs are the single source of truth for the
|
|
12
|
+
# drift test; paths are copied verbatim from the spec.
|
|
13
|
+
ACTIONS: dict[str, Op] = {
|
|
14
|
+
"list": Op(
|
|
15
|
+
"GET", "/public/v2/build-image/jobs",
|
|
16
|
+
kind="list",
|
|
17
|
+
help="List build-image jobs in the workspace.",
|
|
18
|
+
),
|
|
19
|
+
"run": Op(
|
|
20
|
+
"POST", "/public/v2/build-image/jobs",
|
|
21
|
+
body_field="body",
|
|
22
|
+
required=("body",),
|
|
23
|
+
write=True,
|
|
24
|
+
help="Run a build-image job. NOTE: this endpoint requires a "
|
|
25
|
+
"multipart/form-data file upload (a requirements.txt), which this tool "
|
|
26
|
+
"does NOT support — a JSON body will be rejected by the API. Build images "
|
|
27
|
+
"via the MLSpace console/CLI instead.",
|
|
28
|
+
),
|
|
29
|
+
"get": Op(
|
|
30
|
+
"GET", "/public/v2/build-image/jobs/{job_name}",
|
|
31
|
+
path_params={"job_name": "job_name"},
|
|
32
|
+
required=("job_name",),
|
|
33
|
+
help="Get a build-image job's status by name.",
|
|
34
|
+
),
|
|
35
|
+
"get_logs": Op(
|
|
36
|
+
"GET", "/public/v2/build-image/jobs/{job_name}/logs",
|
|
37
|
+
path_params={"job_name": "job_name"},
|
|
38
|
+
required=("job_name",),
|
|
39
|
+
kind="log",
|
|
40
|
+
help="Get a build-image job's logs by name.",
|
|
41
|
+
),
|
|
42
|
+
"get_pods": Op(
|
|
43
|
+
"GET", "/public/v2/build-image/jobs/{job_name}/pods",
|
|
44
|
+
path_params={"job_name": "job_name"},
|
|
45
|
+
required=("job_name",),
|
|
46
|
+
kind="list",
|
|
47
|
+
help="List a build-image job's pods by name.",
|
|
48
|
+
),
|
|
49
|
+
"delete": Op(
|
|
50
|
+
"DELETE", "/public/v2/build-image/jobs/{job_name}",
|
|
51
|
+
path_params={"job_name": "job_name"},
|
|
52
|
+
required=("job_name",),
|
|
53
|
+
write=True,
|
|
54
|
+
confirm=True,
|
|
55
|
+
help="Delete a build-image job by name.",
|
|
56
|
+
),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
PARAMS: list[Param] = [
|
|
60
|
+
Param("job_name", str, "Name of the build-image job."),
|
|
61
|
+
Param(
|
|
62
|
+
"body",
|
|
63
|
+
dict,
|
|
64
|
+
"Unused: the run endpoint needs a multipart file upload, not a JSON body "
|
|
65
|
+
"(not supported by this tool — use the console/CLI to build images).",
|
|
66
|
+
),
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
DOMAIN = DomainTool(
|
|
70
|
+
domain="build_image",
|
|
71
|
+
name="mlspace_build_image",
|
|
72
|
+
title="MLSpace Build Image",
|
|
73
|
+
summary="Manage MLSpace build-image jobs (run, inspect status/logs/pods, delete).",
|
|
74
|
+
actions=ACTIONS,
|
|
75
|
+
params=PARAMS,
|
|
76
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""``mlspace_dalle`` — call the MLSpace DALL-E async inference API.
|
|
2
|
+
|
|
3
|
+
Pure declarative data only; the core (registry.py + client.py) owns all request
|
|
4
|
+
mechanics. Two operations: submit an async predict request, then poll for its
|
|
5
|
+
status/result (which may be a generated image).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from ..registry import DomainTool, Op, Param
|
|
11
|
+
|
|
12
|
+
ACTIONS: dict[str, Op] = {
|
|
13
|
+
"predict": Op(
|
|
14
|
+
"POST", "/public/v2/dalle/v1/predict/{model_name}",
|
|
15
|
+
path_params={"model_name": "model_name"},
|
|
16
|
+
body_field="body",
|
|
17
|
+
required=("model_name",),
|
|
18
|
+
side_effect=True, # submits a (paid) inference job — not a read
|
|
19
|
+
help="Send an async request to DALL-E inference; put the model's input in `body` (free-form). Returns a request_id.",
|
|
20
|
+
),
|
|
21
|
+
"result": Op(
|
|
22
|
+
"GET", "/public/v2/dalle/v1/result/{request_id}",
|
|
23
|
+
path_params={"request_id": "request_id"},
|
|
24
|
+
required=("request_id",),
|
|
25
|
+
kind="binary",
|
|
26
|
+
help="Get status or result (may be a generated image) for a prior predict request_id.",
|
|
27
|
+
),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
PARAMS: list[Param] = [
|
|
31
|
+
Param(
|
|
32
|
+
"model_name",
|
|
33
|
+
str,
|
|
34
|
+
"DALL-E model name to run the prediction on (predict only). NOTE: no MLSpace "
|
|
35
|
+
"endpoint lists DALL-E models — the ai_services catalog returned 403 on the "
|
|
36
|
+
"tested workspace (RBAC role model access checking error) and the /inference "
|
|
37
|
+
"list is empty — so this name is NOT discoverable via the API; take it from the "
|
|
38
|
+
"platform docs/console. A wrong name yields an opaque 404 that is byte-identical "
|
|
39
|
+
"for 'unknown model' and 'service not deployed'.",
|
|
40
|
+
),
|
|
41
|
+
Param("request_id", str, "Request id returned by predict, used to poll for the result."),
|
|
42
|
+
Param("body", dict, "Free-form JSON input payload forwarded to the DALL-E model (predict only)."),
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
DOMAIN = DomainTool(
|
|
46
|
+
domain="dalle",
|
|
47
|
+
name="mlspace_dalle",
|
|
48
|
+
title="MLSpace DALL-E",
|
|
49
|
+
summary="Call the MLSpace DALL-E async inference API: submit a predict request, then poll for the status/result.",
|
|
50
|
+
actions=ACTIONS,
|
|
51
|
+
params=PARAMS,
|
|
52
|
+
)
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""``mlspace_data_transfer`` — manage MLSpace data-transfer connectors, transfers, and history.
|
|
2
|
+
|
|
3
|
+
Pure declarative data (see ``inference.py`` exemplar). The core owns all request
|
|
4
|
+
mechanics. Spans API versions v2/v3/v4/v5; paths are copied verbatim from the
|
|
5
|
+
slice, placeholders included.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from ..registry import DomainTool, Op, Param
|
|
11
|
+
|
|
12
|
+
ACTIONS: dict[str, Op] = {
|
|
13
|
+
# --- connectors (v3 collection + v2 delete) ---------------------------
|
|
14
|
+
"list_connectors": Op(
|
|
15
|
+
"GET", "/public/v2/data_transfer/v3/connectors",
|
|
16
|
+
# page/page_size are the ONLY working shrink lever for this ~27KB list: the
|
|
17
|
+
# API ignores limit/offset here (limit=5&offset=0 still returned all 65). No
|
|
18
|
+
# auto-default page_size — the response has no total_count, so a silent clamp
|
|
19
|
+
# would hide data (breaks principle #1). Pagination stays strictly opt-in.
|
|
20
|
+
query_params={"page": "page", "page_size": "page_size"},
|
|
21
|
+
kind="list",
|
|
22
|
+
help="List all connectors (system, personal, public) in the workspace.",
|
|
23
|
+
),
|
|
24
|
+
"get_connector": Op(
|
|
25
|
+
"GET", "/public/v2/data_transfer/v3/connectors/{connector_type}/{id_}",
|
|
26
|
+
path_params={"connector_type": "connector_type", "id_": "id_"},
|
|
27
|
+
required=("connector_type", "id_"),
|
|
28
|
+
help="Get one connector by type and id.",
|
|
29
|
+
),
|
|
30
|
+
"create_connector": Op(
|
|
31
|
+
"POST", "/public/v2/data_transfer/v3/connectors",
|
|
32
|
+
body_field="body",
|
|
33
|
+
required=("body",),
|
|
34
|
+
write=True,
|
|
35
|
+
help="Create a connector; `body` = ConnectorInputViewModelV3.",
|
|
36
|
+
),
|
|
37
|
+
"update_connector": Op(
|
|
38
|
+
"POST", "/public/v2/data_transfer/v3/connectors/{connector_type}/{id_}",
|
|
39
|
+
path_params={"connector_type": "connector_type", "id_": "id_"},
|
|
40
|
+
body_field="body",
|
|
41
|
+
required=("connector_type", "id_", "body"),
|
|
42
|
+
write=True,
|
|
43
|
+
help="Update a connector; `body` = ConnectorUpdateViewModelV3.",
|
|
44
|
+
),
|
|
45
|
+
"fav_connector": Op(
|
|
46
|
+
"POST", "/public/v2/data_transfer/v3/connectors/{connector_type}/{id_}/fav",
|
|
47
|
+
path_params={"connector_type": "connector_type", "id_": "id_"},
|
|
48
|
+
body_field="body",
|
|
49
|
+
required=("connector_type", "id_", "body"),
|
|
50
|
+
write=True,
|
|
51
|
+
help="Mark/unmark a connector favorite; `body` = FavoriteInputViewModel.",
|
|
52
|
+
),
|
|
53
|
+
"halt_connector": Op(
|
|
54
|
+
"POST", "/public/v2/data_transfer/v3/connectors/{connector_type}/{id_}/halt",
|
|
55
|
+
path_params={"connector_type": "connector_type", "id_": "id_"},
|
|
56
|
+
required=("connector_type", "id_"),
|
|
57
|
+
write=True,
|
|
58
|
+
help="Mark a connector inactive (also halts its transfers).",
|
|
59
|
+
),
|
|
60
|
+
"try_connector": Op(
|
|
61
|
+
"POST", "/public/v2/data_transfer/v3/connectors/{connector_type}/{id_}/try",
|
|
62
|
+
path_params={"connector_type": "connector_type", "id_": "id_"},
|
|
63
|
+
required=("connector_type", "id_"),
|
|
64
|
+
write=True,
|
|
65
|
+
help="Trial-test a connector (connection check; activates the connector on success).",
|
|
66
|
+
),
|
|
67
|
+
"get_connector_logs": Op(
|
|
68
|
+
"GET", "/public/v2/data_transfer/v3/connectors/{connector_type}/{id_}/try/logs",
|
|
69
|
+
path_params={"connector_type": "connector_type", "id_": "id_"},
|
|
70
|
+
required=("connector_type", "id_"),
|
|
71
|
+
kind="log",
|
|
72
|
+
help="Get connector activation (try) logs.",
|
|
73
|
+
),
|
|
74
|
+
"delete_connectors": Op(
|
|
75
|
+
"DELETE", "/public/v2/data_transfer/v2/connectors",
|
|
76
|
+
query_params={"ids": "ids"},
|
|
77
|
+
required=("ids",),
|
|
78
|
+
write=True,
|
|
79
|
+
confirm=True,
|
|
80
|
+
help="Delete connectors by id (ids repeat).",
|
|
81
|
+
),
|
|
82
|
+
"list_sources": Op(
|
|
83
|
+
"GET", "/public/v2/data_transfer/v2/connectors/sources",
|
|
84
|
+
kind="list",
|
|
85
|
+
help="List schemas of all connectors usable as a source.",
|
|
86
|
+
),
|
|
87
|
+
# --- transfers (v4 reads, v5 writes, v2 delete) -----------------------
|
|
88
|
+
"list_transfers": Op(
|
|
89
|
+
"GET", "/public/v2/data_transfer/v4/transfer",
|
|
90
|
+
kind="list",
|
|
91
|
+
help="List all transfers in the workspace.",
|
|
92
|
+
),
|
|
93
|
+
"get_transfer": Op(
|
|
94
|
+
"GET", "/public/v2/data_transfer/v4/transfer/{transfer_id}",
|
|
95
|
+
path_params={"transfer_id": "transfer_id"},
|
|
96
|
+
required=("transfer_id",),
|
|
97
|
+
help="Get one transfer by id.",
|
|
98
|
+
),
|
|
99
|
+
"create_transfer": Op(
|
|
100
|
+
"POST", "/public/v2/data_transfer/v5/transfer",
|
|
101
|
+
body_field="body",
|
|
102
|
+
required=("body",),
|
|
103
|
+
write=True,
|
|
104
|
+
help="Create a transfer; `body` = TransferInputViewModelV5 (runs now unless crontab is set).",
|
|
105
|
+
),
|
|
106
|
+
"update_transfer": Op(
|
|
107
|
+
"POST", "/public/v2/data_transfer/v5/transfer/{id_}",
|
|
108
|
+
path_params={"id_": "id_"},
|
|
109
|
+
body_field="body",
|
|
110
|
+
required=("id_", "body"),
|
|
111
|
+
write=True,
|
|
112
|
+
help="Update a transfer by id; `body` = TransferInputViewModelV5.",
|
|
113
|
+
),
|
|
114
|
+
"fav_transfer": Op(
|
|
115
|
+
"POST", "/public/v2/data_transfer/v4/transfer/{id_}/fav",
|
|
116
|
+
path_params={"id_": "id_"},
|
|
117
|
+
body_field="body",
|
|
118
|
+
required=("id_", "body"),
|
|
119
|
+
write=True,
|
|
120
|
+
help="Mark/unmark a transfer favorite; `body` = FavoriteInputViewModel.",
|
|
121
|
+
),
|
|
122
|
+
"switch_transfer": Op(
|
|
123
|
+
"POST", "/public/v2/data_transfer/v4/transfer/{id_}/switch",
|
|
124
|
+
path_params={"id_": "id_"},
|
|
125
|
+
body_field="body",
|
|
126
|
+
required=("id_", "body"),
|
|
127
|
+
write=True,
|
|
128
|
+
help="Toggle a transfer active/inactive; `body` = StateInputViewModel.",
|
|
129
|
+
),
|
|
130
|
+
"delete_transfers": Op(
|
|
131
|
+
"DELETE", "/public/v2/data_transfer/v2/transfer",
|
|
132
|
+
query_params={"ids": "ids"},
|
|
133
|
+
required=("ids",),
|
|
134
|
+
write=True,
|
|
135
|
+
confirm=True,
|
|
136
|
+
help="Delete transfers by id (ids repeat).",
|
|
137
|
+
),
|
|
138
|
+
# --- history (v3 reads, v2/v3 ops, v2 delete) -------------------------
|
|
139
|
+
"list_history": Op(
|
|
140
|
+
"GET", "/public/v2/data_transfer/v3/history",
|
|
141
|
+
query_params={
|
|
142
|
+
"transfer_id": "transfer_id",
|
|
143
|
+
"source_name": "source_name",
|
|
144
|
+
"page": "page",
|
|
145
|
+
"page_size": "page_size",
|
|
146
|
+
},
|
|
147
|
+
kind="list",
|
|
148
|
+
help="List transfer history (paginated; filter by transfer_id/source_name).",
|
|
149
|
+
),
|
|
150
|
+
"get_history_status": Op(
|
|
151
|
+
"GET", "/public/v2/data_transfer/v3/history/status/stream",
|
|
152
|
+
query_params={"ids": "ids"},
|
|
153
|
+
required=("ids",),
|
|
154
|
+
kind="list",
|
|
155
|
+
help="Get history entries by id (ids repeat).",
|
|
156
|
+
),
|
|
157
|
+
"get_event_logs": Op(
|
|
158
|
+
"GET", "/public/v2/data_transfer/v2/events/list",
|
|
159
|
+
query_params={
|
|
160
|
+
"offset": "offset",
|
|
161
|
+
"limit": "limit",
|
|
162
|
+
"history_id": "history_id",
|
|
163
|
+
"transfer_id": "transfer_id",
|
|
164
|
+
"event_type": "event_type",
|
|
165
|
+
"event_category": "event_category",
|
|
166
|
+
"as_enum_resolved": "as_enum_resolved",
|
|
167
|
+
},
|
|
168
|
+
required=("offset", "limit"),
|
|
169
|
+
kind="list",
|
|
170
|
+
help="Get transfer event logs (needs offset+limit and either history_id or transfer_id).",
|
|
171
|
+
),
|
|
172
|
+
"cancel_history": Op(
|
|
173
|
+
"POST", "/public/v2/data_transfer/v2/history/cancel",
|
|
174
|
+
body_field="body",
|
|
175
|
+
required=("body",),
|
|
176
|
+
write=True,
|
|
177
|
+
confirm=True,
|
|
178
|
+
help="Cancel a transfer; `body` = CancelInput.",
|
|
179
|
+
),
|
|
180
|
+
"rerun_history": Op(
|
|
181
|
+
"POST", "/public/v2/data_transfer/v3/history/rerun",
|
|
182
|
+
body_field="body",
|
|
183
|
+
required=("body",),
|
|
184
|
+
write=True,
|
|
185
|
+
help="Restart a transfer; `body` = RerunInput.",
|
|
186
|
+
),
|
|
187
|
+
"fav_history": Op(
|
|
188
|
+
"POST", "/public/v2/data_transfer/v2/history/{id_}/fav",
|
|
189
|
+
path_params={"id_": "id_"},
|
|
190
|
+
body_field="favorite",
|
|
191
|
+
required=("id_", "favorite"),
|
|
192
|
+
write=True,
|
|
193
|
+
help="Mark/unmark a history entry favorite; `favorite` is a bare boolean body.",
|
|
194
|
+
),
|
|
195
|
+
"delete_history": Op(
|
|
196
|
+
"DELETE", "/public/v2/data_transfer/v2/history",
|
|
197
|
+
query_params={"ids": "ids"},
|
|
198
|
+
required=("ids",),
|
|
199
|
+
write=True,
|
|
200
|
+
confirm=True,
|
|
201
|
+
help="Delete history entries by id (ids repeat).",
|
|
202
|
+
),
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
PARAMS: list[Param] = [
|
|
206
|
+
Param("id_", str, "Entity UUID (connector/transfer/history id, depending on action)."),
|
|
207
|
+
Param("transfer_id", str, "Transfer UUID (get_transfer path; filter for list_history/event logs)."),
|
|
208
|
+
Param(
|
|
209
|
+
"connector_type",
|
|
210
|
+
str,
|
|
211
|
+
"Connector type. Known values: postgresql, mssql, mysql, clickhouse, "
|
|
212
|
+
"oracledb, s3amazon, s3custom, s3google, hdfs, s3mlspace, s3datahub, nfs, "
|
|
213
|
+
"nfsprivate, nfsshared, s3evolution. get_connector accepts the full set; "
|
|
214
|
+
"try/halt/fav/update only the custom subset (the first 8). "
|
|
215
|
+
"Discover live values via list_connectors.",
|
|
216
|
+
),
|
|
217
|
+
Param("ids", list[str], "List of UUIDs (repeated query) for delete/status actions."),
|
|
218
|
+
Param("source_name", str, "Filter history by source_name (list_history)."),
|
|
219
|
+
Param(
|
|
220
|
+
"page",
|
|
221
|
+
int,
|
|
222
|
+
"Page number (list_history, list_connectors, 1-based). NOTE for list_connectors "
|
|
223
|
+
"the response is a bare array with no total_count, so a short/empty page does "
|
|
224
|
+
"NOT prove the end of the list; list_history instead returns {data, total_count}.",
|
|
225
|
+
),
|
|
226
|
+
Param("page_size", int, "Page size (list_history, list_connectors)."),
|
|
227
|
+
Param("offset", int, "Pagination offset (get_event_logs)."),
|
|
228
|
+
Param("limit", int, "Pagination limit (get_event_logs)."),
|
|
229
|
+
Param("history_id", str, "History UUID filter (get_event_logs)."),
|
|
230
|
+
Param("event_type", int, "Event type filter (get_event_logs)."),
|
|
231
|
+
Param("event_category", int, "Event category filter (get_event_logs)."),
|
|
232
|
+
Param("as_enum_resolved", bool, "Resolve enum codes to names (get_event_logs)."),
|
|
233
|
+
Param(
|
|
234
|
+
"favorite",
|
|
235
|
+
bool,
|
|
236
|
+
"Favorite flag for fav_history (sent as a bare boolean body, not an object).",
|
|
237
|
+
),
|
|
238
|
+
Param(
|
|
239
|
+
"body",
|
|
240
|
+
dict,
|
|
241
|
+
"JSON body. create_connector=ConnectorInputViewModelV3; "
|
|
242
|
+
"update_connector=ConnectorUpdateViewModelV3; "
|
|
243
|
+
"fav_connector/fav_transfer=FavoriteInputViewModel {favorite}; "
|
|
244
|
+
"create_transfer/update_transfer=TransferInputViewModelV5; "
|
|
245
|
+
"switch_transfer=StateInputViewModel; cancel_history=CancelInput; "
|
|
246
|
+
"rerun_history=RerunInput {history_id}.",
|
|
247
|
+
),
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
DOMAIN = DomainTool(
|
|
251
|
+
domain="data_transfer",
|
|
252
|
+
name="mlspace_data_transfer",
|
|
253
|
+
title="MLSpace Data Transfer",
|
|
254
|
+
summary=(
|
|
255
|
+
"Manage MLSpace data-transfer connectors, transfers, and migration history "
|
|
256
|
+
"(create/update/try connectors, run/cancel/rerun transfers, inspect logs)."
|
|
257
|
+
),
|
|
258
|
+
actions=ACTIONS,
|
|
259
|
+
params=PARAMS,
|
|
260
|
+
)
|