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
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""``mlspace_docker_registry`` — manage the MLSpace Docker registry.
|
|
2
|
+
|
|
3
|
+
Pure declarative data following the EXEMPLAR ``inference.py``. The registry has
|
|
4
|
+
multiple resource types (registry, repositories, images, tags, credentials), so
|
|
5
|
+
action names suffix the noun. All request mechanics live in the core.
|
|
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
|
+
"current_registry": Op(
|
|
16
|
+
"GET", "/public/v2/docker_registry/v2/registries/current",
|
|
17
|
+
help="Get the current Docker registry for the workspace.",
|
|
18
|
+
),
|
|
19
|
+
"list_repos": Op(
|
|
20
|
+
"GET", "/public/v2/docker_registry/v2/repositories/",
|
|
21
|
+
# page/page_size are a working proband lever here. NOTE we deliberately do NOT
|
|
22
|
+
# paginate list_images/list_tags: there pagination splits ONE image across
|
|
23
|
+
# pages with partial tags (08#4), so surfacing it would amplify that bug.
|
|
24
|
+
query_params={"page": "page", "page_size": "page_size"},
|
|
25
|
+
# registry_repos kind (not plain "list") elides the heavy recent_image.
|
|
26
|
+
# extra_attrs (~39% of the response) with a pointer to get_image.
|
|
27
|
+
kind="registry_repos",
|
|
28
|
+
help="List repositories in the registry.",
|
|
29
|
+
),
|
|
30
|
+
"get_repo": Op(
|
|
31
|
+
"GET", "/public/v2/docker_registry/v2/repositories/{repository_id}",
|
|
32
|
+
path_params={"repository_id": "repository_id"},
|
|
33
|
+
required=("repository_id",),
|
|
34
|
+
help="Get one repository.",
|
|
35
|
+
),
|
|
36
|
+
"update_repo": Op(
|
|
37
|
+
"PUT", "/public/v2/docker_registry/v2/repositories/{repository_id}",
|
|
38
|
+
path_params={"repository_id": "repository_id"},
|
|
39
|
+
body_field="body",
|
|
40
|
+
required=("repository_id", "body"),
|
|
41
|
+
write=True,
|
|
42
|
+
help="Update a repository; `body` = UpdateRepository: "
|
|
43
|
+
"{description, labels, labels_to_delete}.",
|
|
44
|
+
),
|
|
45
|
+
"delete_repos": Op(
|
|
46
|
+
"DELETE", "/public/v2/docker_registry/v2/repositories/",
|
|
47
|
+
body_field="body",
|
|
48
|
+
required=("body",),
|
|
49
|
+
write=True,
|
|
50
|
+
confirm=True,
|
|
51
|
+
help="Delete repositories; `body` = DeleteRepositories.",
|
|
52
|
+
),
|
|
53
|
+
"repo_fav": Op(
|
|
54
|
+
"POST", "/public/v2/docker_registry/v2/repositories/{repository_id}/fav",
|
|
55
|
+
path_params={"repository_id": "repository_id"},
|
|
56
|
+
body_field="body",
|
|
57
|
+
required=("repository_id", "body"),
|
|
58
|
+
write=True,
|
|
59
|
+
help="Mark/unmark a repository as favorite; `body` = FavoriteInput.",
|
|
60
|
+
),
|
|
61
|
+
"list_images": Op(
|
|
62
|
+
"GET", "/public/v2/docker_registry/v2/images/",
|
|
63
|
+
query_params={"repository_id": "repository_id"},
|
|
64
|
+
required=("repository_id",),
|
|
65
|
+
kind="list",
|
|
66
|
+
help="List images in a repository (requires repository_id).",
|
|
67
|
+
),
|
|
68
|
+
"get_image": Op(
|
|
69
|
+
"GET", "/public/v2/docker_registry/v2/images/{image_id}",
|
|
70
|
+
path_params={"image_id": "image_id"},
|
|
71
|
+
required=("image_id",),
|
|
72
|
+
help="Get one image.",
|
|
73
|
+
),
|
|
74
|
+
"delete_images": Op(
|
|
75
|
+
"DELETE", "/public/v2/docker_registry/v2/images/",
|
|
76
|
+
body_field="body",
|
|
77
|
+
required=("body",),
|
|
78
|
+
write=True,
|
|
79
|
+
confirm=True,
|
|
80
|
+
help="Delete images; `body` = DeleteImages.",
|
|
81
|
+
),
|
|
82
|
+
"list_tags": Op(
|
|
83
|
+
"GET", "/public/v2/docker_registry/v2/tags/",
|
|
84
|
+
query_params={"image_id": "image_id"},
|
|
85
|
+
required=("image_id",),
|
|
86
|
+
kind="list",
|
|
87
|
+
help="List tags for an image (requires image_id).",
|
|
88
|
+
),
|
|
89
|
+
"create_tag": Op(
|
|
90
|
+
"POST", "/public/v2/docker_registry/v2/tags/",
|
|
91
|
+
body_field="body",
|
|
92
|
+
required=("body",),
|
|
93
|
+
write=True,
|
|
94
|
+
help="Create a tag; `body` = CreateTag.",
|
|
95
|
+
),
|
|
96
|
+
"update_tag": Op(
|
|
97
|
+
"PUT", "/public/v2/docker_registry/v2/tags/{tag_id}",
|
|
98
|
+
path_params={"tag_id": "tag_id"},
|
|
99
|
+
body_field="body",
|
|
100
|
+
required=("tag_id", "body"),
|
|
101
|
+
write=True,
|
|
102
|
+
help="Update a tag; `body` = UpdateTag.",
|
|
103
|
+
),
|
|
104
|
+
"delete_tags": Op(
|
|
105
|
+
"DELETE", "/public/v2/docker_registry/v2/tags/",
|
|
106
|
+
body_field="body",
|
|
107
|
+
required=("body",),
|
|
108
|
+
write=True,
|
|
109
|
+
confirm=True,
|
|
110
|
+
help="Delete tags; `body` = DeleteTags.",
|
|
111
|
+
),
|
|
112
|
+
"generate_password": Op(
|
|
113
|
+
"GET", "/public/v2/docker_registry/v1/users/generate_password",
|
|
114
|
+
write=True,
|
|
115
|
+
# irreversible credential rotation: the previous password immediately stops
|
|
116
|
+
# working, so a single mistaken call would lock out anything using it. Gate it
|
|
117
|
+
# behind confirm=true like the other destructive ops.
|
|
118
|
+
confirm=True,
|
|
119
|
+
help="Rotate and return the registry password for your personal account "
|
|
120
|
+
"(not service accounts). The previous password stops working. "
|
|
121
|
+
"Destructive: requires confirm=true.",
|
|
122
|
+
),
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
PARAMS: list[Param] = [
|
|
126
|
+
Param("repository_id", str, "Repository ID (UUID)."),
|
|
127
|
+
Param("image_id", str, "Image ID (UUID); also the query filter for list_images/list_tags."),
|
|
128
|
+
Param("tag_id", str, "Tag ID (UUID)."),
|
|
129
|
+
Param(
|
|
130
|
+
"page",
|
|
131
|
+
int,
|
|
132
|
+
"Page number (list_repos, 1-based). The response is a bare array with no "
|
|
133
|
+
"total_count: a short/empty page does NOT prove the end of the list.",
|
|
134
|
+
),
|
|
135
|
+
Param("page_size", int, "Page size (list_repos)."),
|
|
136
|
+
Param(
|
|
137
|
+
"body",
|
|
138
|
+
dict,
|
|
139
|
+
"JSON body. update_repo=UpdateRepository; delete_repos=DeleteRepositories "
|
|
140
|
+
"{repository_ids:[..]}; repo_fav=FavoriteInput {favorite}; "
|
|
141
|
+
"delete_images=DeleteImages {image_ids:[..]}; create_tag=CreateTag "
|
|
142
|
+
"{image_id, name, color}; update_tag=UpdateTag {color}; "
|
|
143
|
+
"delete_tags=DeleteTags {tag_ids:[..]}.",
|
|
144
|
+
),
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
DOMAIN = DomainTool(
|
|
148
|
+
domain="docker_registry",
|
|
149
|
+
name="mlspace_docker_registry",
|
|
150
|
+
title="MLSpace Docker Registry",
|
|
151
|
+
summary="Manage the MLSpace Docker registry: repositories, images, tags, "
|
|
152
|
+
"favorites, and registry credentials.",
|
|
153
|
+
actions=ACTIONS,
|
|
154
|
+
params=PARAMS,
|
|
155
|
+
redact_result_keys=("password",),
|
|
156
|
+
)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""``mlspace_inference`` — manage and call MLSpace inference services.
|
|
2
|
+
|
|
3
|
+
EXEMPLAR module: every other domain module follows this exact shape — a single
|
|
4
|
+
``DOMAIN: DomainTool`` made of pure data. No request mechanics live here; the
|
|
5
|
+
core (registry.py + client.py) owns headers, path templating, query encoding,
|
|
6
|
+
bodies, readonly, confirm, formatting. To add a domain, copy this structure and
|
|
7
|
+
fill ``ACTIONS`` / ``PARAMS`` from the domain's slice in ``_planning/slices``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from ..registry import DomainTool, Op, Param
|
|
13
|
+
|
|
14
|
+
# action -> Op. The (method, path) pairs are the single source of truth for the
|
|
15
|
+
# drift test; paths are copied verbatim from the spec, placeholders included.
|
|
16
|
+
ACTIONS: dict[str, Op] = {
|
|
17
|
+
"list": Op(
|
|
18
|
+
"GET", "/public/v2/inference/v2/",
|
|
19
|
+
kind="list",
|
|
20
|
+
help="List inference services in the workspace.",
|
|
21
|
+
),
|
|
22
|
+
"get": Op(
|
|
23
|
+
"GET", "/public/v2/inference/v2/{inference_name}",
|
|
24
|
+
path_params={"inference_name": "inference_name"},
|
|
25
|
+
query_params={"region": "region"},
|
|
26
|
+
required=("inference_name", "region"),
|
|
27
|
+
help="Get one inference service.",
|
|
28
|
+
),
|
|
29
|
+
"predict": Op(
|
|
30
|
+
"POST", "/public/v2/inference/v2/predict/{inference_name}/{model_name}/",
|
|
31
|
+
path_params={"inference_name": "inference_name", "model_name": "model_name"},
|
|
32
|
+
body_field="body",
|
|
33
|
+
required=("inference_name", "model_name"),
|
|
34
|
+
side_effect=True, # runs a deployed (paid) model — not a read
|
|
35
|
+
help="Call a deployed model; put the model's input in `body` (free-form).",
|
|
36
|
+
),
|
|
37
|
+
"create": Op(
|
|
38
|
+
"POST", "/public/v2/inference/v2/",
|
|
39
|
+
body_field="body",
|
|
40
|
+
required=("body",),
|
|
41
|
+
write=True,
|
|
42
|
+
help="Create an inference service; `body` = CreateInferenceServiceRequestV2.",
|
|
43
|
+
),
|
|
44
|
+
"update": Op(
|
|
45
|
+
"PATCH", "/public/v2/inference/v2/{inference_name}",
|
|
46
|
+
path_params={"inference_name": "inference_name"},
|
|
47
|
+
body_field="body",
|
|
48
|
+
required=("inference_name", "body"),
|
|
49
|
+
write=True,
|
|
50
|
+
help="Update an inference service; `body` = PatchInferenceServiceRequestV2.",
|
|
51
|
+
),
|
|
52
|
+
"delete": Op(
|
|
53
|
+
"DELETE", "/public/v2/inference/v2/{inference_name}",
|
|
54
|
+
path_params={"inference_name": "inference_name"},
|
|
55
|
+
query_params={"region": "region"},
|
|
56
|
+
required=("inference_name", "region"),
|
|
57
|
+
write=True,
|
|
58
|
+
confirm=True,
|
|
59
|
+
help="Delete an inference service.",
|
|
60
|
+
),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
PARAMS: list[Param] = [
|
|
64
|
+
Param("inference_name", str, "Name of the inference service."),
|
|
65
|
+
Param("model_name", str, "Model name within the service (predict only)."),
|
|
66
|
+
Param(
|
|
67
|
+
"region",
|
|
68
|
+
str,
|
|
69
|
+
"Inference region, e.g. DGX2-INF-001, CCE-INF, INF-002 "
|
|
70
|
+
"(authoritative list via mlspace_resources configs cluster_type=INF). "
|
|
71
|
+
"Required for get/delete.",
|
|
72
|
+
),
|
|
73
|
+
Param(
|
|
74
|
+
"body",
|
|
75
|
+
dict,
|
|
76
|
+
"JSON body. create=CreateInferenceServiceRequestV2; update="
|
|
77
|
+
"PatchInferenceServiceRequestV2; predict=the model's own input payload.",
|
|
78
|
+
),
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
DOMAIN = DomainTool(
|
|
82
|
+
domain="inference",
|
|
83
|
+
name="mlspace_inference",
|
|
84
|
+
title="MLSpace Inference",
|
|
85
|
+
summary="Manage and call MLSpace inference services (deploy, inspect, predict, delete).",
|
|
86
|
+
actions=ACTIONS,
|
|
87
|
+
params=PARAMS,
|
|
88
|
+
)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""``mlspace_jobs`` — manage MLSpace training jobs.
|
|
2
|
+
|
|
3
|
+
Pure declarative data following the exemplar in ``inference.py``. All request
|
|
4
|
+
mechanics (headers, path templating, query encoding, bodies, readonly, confirm,
|
|
5
|
+
formatting) live in the core; this module only declares ``ACTIONS`` / ``PARAMS``.
|
|
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
|
+
# NOTE: most paths use {job_name}, but /params and /preemptors use {name}.
|
|
15
|
+
ACTIONS: dict[str, Op] = {
|
|
16
|
+
"list": Op(
|
|
17
|
+
"GET", "/public/v2/jobs",
|
|
18
|
+
query_params={
|
|
19
|
+
"region": "region",
|
|
20
|
+
"allocation_name": "allocation_name",
|
|
21
|
+
"status": "status",
|
|
22
|
+
"job_author": "job_author",
|
|
23
|
+
"queue_id": "queue_id",
|
|
24
|
+
"offset": "offset",
|
|
25
|
+
"limit": "limit",
|
|
26
|
+
"start_date": "start_date",
|
|
27
|
+
"end_date": "end_date",
|
|
28
|
+
},
|
|
29
|
+
kind="list",
|
|
30
|
+
help="List one page of training jobs in a target region (filter by status/author/queue/dates). "
|
|
31
|
+
"For broad connected-workspace reads use mlspace_jobs_overview (dynamic regions/pages). "
|
|
32
|
+
"Pass `region` (e.g. SR006): without it the API returns count=0 (empty), not all regions.",
|
|
33
|
+
),
|
|
34
|
+
"get": Op(
|
|
35
|
+
"GET", "/public/v2/jobs/{job_name}",
|
|
36
|
+
path_params={"job_name": "job_name"},
|
|
37
|
+
required=("job_name",),
|
|
38
|
+
# kind='job' triggers _normalise_job: flags the created→'job not found' race
|
|
39
|
+
# and the failed+empty-error trap, and adds a canonical status.
|
|
40
|
+
kind="job",
|
|
41
|
+
help="Get one job's status using resource_ref or its exact target/name.",
|
|
42
|
+
),
|
|
43
|
+
"logs": Op(
|
|
44
|
+
"GET", "/public/v2/jobs/{job_name}/logs",
|
|
45
|
+
path_params={"job_name": "job_name"},
|
|
46
|
+
query_params={"tail": "tail", "verbose": "verbose", "region": "region"},
|
|
47
|
+
# API rejects logs without region (422) though the spec marks it optional —
|
|
48
|
+
# both blind-model runs hit this, so require it.
|
|
49
|
+
required=("job_name", "region"),
|
|
50
|
+
kind="log",
|
|
51
|
+
help="Get a job's logs by name (use `tail` to limit lines).",
|
|
52
|
+
),
|
|
53
|
+
"list_nodes": Op(
|
|
54
|
+
"GET", "/public/v2/jobs/{job_name}/nodes",
|
|
55
|
+
path_params={"job_name": "job_name"},
|
|
56
|
+
required=("job_name",),
|
|
57
|
+
kind="list",
|
|
58
|
+
help="List nodes that were used for a job's execution.",
|
|
59
|
+
),
|
|
60
|
+
"list_pods": Op(
|
|
61
|
+
"GET", "/public/v2/jobs/elastic/{job_name}/pods",
|
|
62
|
+
path_params={"job_name": "job_name"},
|
|
63
|
+
required=("job_name",),
|
|
64
|
+
kind="list",
|
|
65
|
+
help="List Elastic job pods by job name (pytorch_elastic jobs).",
|
|
66
|
+
),
|
|
67
|
+
"get_params": Op(
|
|
68
|
+
"GET", "/public/v2/jobs/{name}/params",
|
|
69
|
+
path_params={"name": "job_name"},
|
|
70
|
+
required=("job_name",),
|
|
71
|
+
help="Get the parameters a job was run with, by job name.",
|
|
72
|
+
),
|
|
73
|
+
"get_preemptors": Op(
|
|
74
|
+
"GET", "/public/v2/jobs/{name}/preemptors",
|
|
75
|
+
path_params={"name": "job_name"},
|
|
76
|
+
required=("job_name",),
|
|
77
|
+
kind="list",
|
|
78
|
+
help="Get the preemptors that gained priority over a job, by job name.",
|
|
79
|
+
),
|
|
80
|
+
"run": Op(
|
|
81
|
+
"POST", "/public/v2/jobs",
|
|
82
|
+
body_field="body",
|
|
83
|
+
required=("body",),
|
|
84
|
+
write=True,
|
|
85
|
+
help="Run a training job; `body` = RunJobRequest: "
|
|
86
|
+
"{script, base_image, instance_type, region, type, n_workers, ...}. "
|
|
87
|
+
"NOTE: `region` must be explicit. For type=binary, `script` is a non-empty "
|
|
88
|
+
"command or executable path and is forwarded unchanged; verify that it is "
|
|
89
|
+
"available in the selected image. Other job types require an NFS script path "
|
|
90
|
+
"under /home/jovyan/... in that same region. NFS volumes are region-scoped, "
|
|
91
|
+
"and the Public API cannot stat the file.",
|
|
92
|
+
),
|
|
93
|
+
"restart": Op(
|
|
94
|
+
"POST", "/public/v2/jobs/restart",
|
|
95
|
+
body_field="body",
|
|
96
|
+
required=("body",),
|
|
97
|
+
write=True,
|
|
98
|
+
help="Restart a job by name (new job, same params); `body` = "
|
|
99
|
+
"RestartJobRequest: {job_name}.",
|
|
100
|
+
),
|
|
101
|
+
"delete": Op(
|
|
102
|
+
"DELETE", "/public/v2/jobs/{job_name}",
|
|
103
|
+
path_params={"job_name": "job_name"},
|
|
104
|
+
query_params={"region": "region"},
|
|
105
|
+
required=("job_name", "region"),
|
|
106
|
+
write=True,
|
|
107
|
+
confirm=True,
|
|
108
|
+
help="Delete a job by name.",
|
|
109
|
+
),
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
PARAMS: list[Param] = [
|
|
113
|
+
Param("job_name", str, "Name of the job (path param for get/logs/nodes/pods/"
|
|
114
|
+
"params/preemptors/delete)."),
|
|
115
|
+
Param(
|
|
116
|
+
"region",
|
|
117
|
+
str,
|
|
118
|
+
"Model-training region, e.g. DGX2-MT, A100-MT, SR003-SR006, SR008 "
|
|
119
|
+
"(configs may omit allocated regions; use jobs overview or workspace allocations "
|
|
120
|
+
"to discover existing-job regions). Required for logs and delete, "
|
|
121
|
+
"and effectively required for `list` (omitting it returns count=0, not all regions).",
|
|
122
|
+
),
|
|
123
|
+
Param("allocation_name", str, "Filter list by allocation name."),
|
|
124
|
+
Param(
|
|
125
|
+
"status",
|
|
126
|
+
list[str],
|
|
127
|
+
"Filter list by job statuses (repeatable). Known values: Completed, "
|
|
128
|
+
"Completing, Deleted, Failed, Pending, Running, Stopped, Succeeded, "
|
|
129
|
+
"Terminated, Terminating, Restarting.",
|
|
130
|
+
),
|
|
131
|
+
Param("job_author", list[str], "Filter list by job authors (repeatable)."),
|
|
132
|
+
Param("queue_id", str, "Filter list by queue UUID."),
|
|
133
|
+
Param("offset", int, "Number of list items to skip.", default=0),
|
|
134
|
+
Param("limit", int, "Max number of list items to return."),
|
|
135
|
+
Param("start_date", str, "List start-date filter (RFC3339, e.g. 2026-03-31T00:00:00Z)."),
|
|
136
|
+
Param("end_date", str, "List end-date filter (RFC3339, e.g. 2026-03-31T23:00:00Z)."),
|
|
137
|
+
Param("tail", int, "logs: number of recent log lines to display.", default=0),
|
|
138
|
+
Param("verbose", bool, "logs: if true, include timestamps on each line.", default=True),
|
|
139
|
+
Param(
|
|
140
|
+
"body",
|
|
141
|
+
dict,
|
|
142
|
+
"JSON body. run=RunJobRequest (requires script, base_image, instance_type); "
|
|
143
|
+
"for type=binary, script may be a command or executable path; "
|
|
144
|
+
"restart=RestartJobRequest ({job_name}).",
|
|
145
|
+
),
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
DOMAIN = DomainTool(
|
|
149
|
+
domain="jobs",
|
|
150
|
+
name="mlspace_jobs",
|
|
151
|
+
title="MLSpace Training Jobs",
|
|
152
|
+
summary="Manage MLSpace training jobs (run, inspect, logs, nodes, restart, delete).",
|
|
153
|
+
actions=ACTIONS,
|
|
154
|
+
params=PARAMS,
|
|
155
|
+
)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""``mlspace_notebooks`` — manage MLSpace Jupyter Server notebooks.
|
|
2
|
+
|
|
3
|
+
Pure declarative ``DomainTool`` data; the core owns all request mechanics.
|
|
4
|
+
Mixes v1 and v2 API paths — paths are copied verbatim from the slice.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Literal
|
|
10
|
+
|
|
11
|
+
from ..registry import DomainTool, Op, Param
|
|
12
|
+
|
|
13
|
+
# action -> Op. The (method, path) pairs are the single source of truth for the
|
|
14
|
+
# drift test; paths are copied verbatim from the spec, placeholders included.
|
|
15
|
+
ACTIONS: dict[str, Op] = {
|
|
16
|
+
"list": Op(
|
|
17
|
+
"GET", "/public/v2/notebooks/v2/notebooks",
|
|
18
|
+
query_params={
|
|
19
|
+
"notebook_type": "notebook_type",
|
|
20
|
+
"order_by": "order_by",
|
|
21
|
+
"desc": "desc",
|
|
22
|
+
"limit": "limit",
|
|
23
|
+
"offset": "offset",
|
|
24
|
+
"search": "search",
|
|
25
|
+
"status": "status",
|
|
26
|
+
"access_mode": "access_mode",
|
|
27
|
+
},
|
|
28
|
+
kind="list",
|
|
29
|
+
help="List one page of Jupyter servers in the selected target. For the connected-set "
|
|
30
|
+
"inventory use mlspace_notebooks_overview. Retain paused notebooks with status; "
|
|
31
|
+
"configured GPUs are not evidence of active use. These are workloads, not compute nodes.",
|
|
32
|
+
),
|
|
33
|
+
"get": Op(
|
|
34
|
+
"GET", "/public/v2/notebooks/v1/notebook/{notebook_uuid}",
|
|
35
|
+
path_params={"notebook_uuid": "notebook_uuid"},
|
|
36
|
+
required=("notebook_uuid",),
|
|
37
|
+
help="Get one Jupyter server by uuid.",
|
|
38
|
+
),
|
|
39
|
+
"autoshutdown_get": Op(
|
|
40
|
+
"GET", "/public/v2/notebooks/v2/autoshutdown-rules/workspace/{workspace_id}",
|
|
41
|
+
path_params={"workspace_id": "workspace_id"},
|
|
42
|
+
required=("workspace_id",),
|
|
43
|
+
help="Get the workspace-level autoshutdown rule.",
|
|
44
|
+
),
|
|
45
|
+
"create": Op(
|
|
46
|
+
"POST", "/public/v2/notebooks/v2/{namespace}/notebook",
|
|
47
|
+
path_params={"namespace": "namespace"},
|
|
48
|
+
body_field="body",
|
|
49
|
+
required=("namespace", "body"),
|
|
50
|
+
write=True,
|
|
51
|
+
help="Create a Jupyter server; `body` = CreateNotebookPayload.",
|
|
52
|
+
),
|
|
53
|
+
"delete": Op(
|
|
54
|
+
"DELETE", "/public/v2/notebooks/v2/notebook/{notebook_uuid}",
|
|
55
|
+
path_params={"notebook_uuid": "notebook_uuid"},
|
|
56
|
+
required=("notebook_uuid",),
|
|
57
|
+
write=True,
|
|
58
|
+
confirm=True,
|
|
59
|
+
help="Delete a Jupyter server asynchronously.",
|
|
60
|
+
),
|
|
61
|
+
"users_list": Op(
|
|
62
|
+
"GET", "/public/v2/notebooks/v2/{namespace}/{notebook_name}/users",
|
|
63
|
+
path_params={"namespace": "namespace", "notebook_name": "notebook_name"},
|
|
64
|
+
required=("notebook_name",),
|
|
65
|
+
kind="list",
|
|
66
|
+
help="List users a notebook is shared with. Keyed by notebook NAME (not uuid).",
|
|
67
|
+
),
|
|
68
|
+
"users_set": Op(
|
|
69
|
+
"PUT", "/public/v2/notebooks/v2/{namespace}/{notebook_name}/users",
|
|
70
|
+
path_params={"namespace": "namespace", "notebook_name": "notebook_name"},
|
|
71
|
+
body_field="body",
|
|
72
|
+
required=("notebook_name", "body"),
|
|
73
|
+
write=True,
|
|
74
|
+
help="Grant notebook access to users; `body` = user access payload.",
|
|
75
|
+
),
|
|
76
|
+
"users_revoke": Op(
|
|
77
|
+
"DELETE", "/public/v2/notebooks/v2/{namespace}/{notebook_name}/users",
|
|
78
|
+
path_params={"namespace": "namespace", "notebook_name": "notebook_name"},
|
|
79
|
+
body_field="body",
|
|
80
|
+
required=("notebook_name", "body"),
|
|
81
|
+
write=True,
|
|
82
|
+
confirm=True,
|
|
83
|
+
help="Revoke notebook access from users; `body` = user access payload.",
|
|
84
|
+
),
|
|
85
|
+
"pause": Op(
|
|
86
|
+
"POST", "/public/v2/notebooks/v2/notebook/{notebook_uuid}/pause",
|
|
87
|
+
path_params={"notebook_uuid": "notebook_uuid"},
|
|
88
|
+
required=("notebook_uuid",),
|
|
89
|
+
write=True,
|
|
90
|
+
help="Pause a Jupyter server asynchronously.",
|
|
91
|
+
),
|
|
92
|
+
"resume": Op(
|
|
93
|
+
"POST", "/public/v2/notebooks/v1/{namespace}/notebook/{notebook_uuid}/resume",
|
|
94
|
+
path_params={"namespace": "namespace", "notebook_uuid": "notebook_uuid"},
|
|
95
|
+
body_field="body",
|
|
96
|
+
required=("namespace", "notebook_uuid", "body"),
|
|
97
|
+
write=True,
|
|
98
|
+
help="Resume a Jupyter server; `body` = ResumeNotebookPayload.",
|
|
99
|
+
),
|
|
100
|
+
"modify": Op(
|
|
101
|
+
"POST", "/public/v2/notebooks/v2/notebook/{notebook_uuid}/modify",
|
|
102
|
+
path_params={"notebook_uuid": "notebook_uuid"},
|
|
103
|
+
body_field="body",
|
|
104
|
+
required=("notebook_uuid", "body"),
|
|
105
|
+
write=True,
|
|
106
|
+
help="Modify a Jupyter server; `body` = ModifyNotebookV2Request.",
|
|
107
|
+
),
|
|
108
|
+
"autoshutdown_set": Op(
|
|
109
|
+
"POST", "/public/v2/notebooks/v2/autoshutdown-rules/workspace/{workspace_id}",
|
|
110
|
+
path_params={"workspace_id": "workspace_id"},
|
|
111
|
+
body_field="body",
|
|
112
|
+
required=("workspace_id", "body"),
|
|
113
|
+
write=True,
|
|
114
|
+
help="Create the workspace-level autoshutdown rule; `body` = AutoShutdownV2Config.",
|
|
115
|
+
),
|
|
116
|
+
"autoshutdown_delete": Op(
|
|
117
|
+
"DELETE", "/public/v2/notebooks/v1/autoshutdown-rules/workspace/{workspace_id}",
|
|
118
|
+
path_params={"workspace_id": "workspace_id"},
|
|
119
|
+
required=("workspace_id",),
|
|
120
|
+
write=True,
|
|
121
|
+
confirm=True,
|
|
122
|
+
help="Delete the workspace-level autoshutdown rule.",
|
|
123
|
+
),
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
PARAMS: list[Param] = [
|
|
127
|
+
Param("notebook_uuid", str, "UUID of the Jupyter server (uid in list results), or use resource_ref."),
|
|
128
|
+
Param("notebook_name", str, "Notebook NAME (users_* actions are keyed by name, "
|
|
129
|
+
"not by uuid)."),
|
|
130
|
+
Param("namespace", str, "Namespace the notebook belongs to (create/resume). "
|
|
131
|
+
"Optional: auto-filled from the selected target if omitted."),
|
|
132
|
+
Param("workspace_id", str, "Workspace UUID for autoshutdown rules (path parameter). "
|
|
133
|
+
"Optional: auto-filled from the selected target if omitted."),
|
|
134
|
+
Param(
|
|
135
|
+
"notebook_type",
|
|
136
|
+
Literal["Spark", "Default"],
|
|
137
|
+
"List filter: notebook type.",
|
|
138
|
+
),
|
|
139
|
+
Param(
|
|
140
|
+
"order_by",
|
|
141
|
+
Literal["name", "creationTimestamp"],
|
|
142
|
+
"List ordering field.",
|
|
143
|
+
),
|
|
144
|
+
Param("desc", bool, "List: sort descending."),
|
|
145
|
+
Param("limit", int, "List: max results to return."),
|
|
146
|
+
Param("offset", int, "List: pagination offset."),
|
|
147
|
+
Param("search", str, "List: search by name."),
|
|
148
|
+
Param("status", str, "List: filter by status (comma-separated)."),
|
|
149
|
+
Param("access_mode", str, "List: filter by access mode (comma-separated)."),
|
|
150
|
+
Param(
|
|
151
|
+
"body",
|
|
152
|
+
dict,
|
|
153
|
+
"JSON body. create=CreateNotebookPayload {name, image, instance_type, ...} — "
|
|
154
|
+
"`image` is an OBJECT {name, tag, type}, where type is datahub|custom; a bare "
|
|
155
|
+
"\"repo:tag\" string is rejected with 422. "
|
|
156
|
+
"resume=ResumeNotebookPayload {region, instance_type} — the body is REQUIRED "
|
|
157
|
+
"even though both fields have defaults, and the API is known to ignore "
|
|
158
|
+
"`instance_type`, resuming on the notebook's previous configuration while "
|
|
159
|
+
"answering 200: check `instanceType` in the response before assuming the "
|
|
160
|
+
"requested size, or a paid GPU may come back up instead of a free one. "
|
|
161
|
+
"modify=ModifyNotebookV2Request; autoshutdown_set=AutoShutdownV2Config "
|
|
162
|
+
"{by_timer | by_load | by_schedule}.",
|
|
163
|
+
),
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
DOMAIN = DomainTool(
|
|
167
|
+
domain="notebooks",
|
|
168
|
+
name="mlspace_notebooks",
|
|
169
|
+
title="MLSpace Notebooks",
|
|
170
|
+
summary="Manage MLSpace Jupyter Server notebooks (create, inspect, pause, resume, "
|
|
171
|
+
"modify, delete, autoshutdown rules).",
|
|
172
|
+
actions=ACTIONS,
|
|
173
|
+
params=PARAMS,
|
|
174
|
+
)
|