aioli-sdk 0.2.1.dev0__tar.gz → 0.2.1.dev1__tar.gz
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.
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/PKG-INFO +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/__version__.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/deployment.py +91 -6
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/model.py +79 -60
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/registry.py +4 -23
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/test/test_cli.py +88 -10
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/common/util.py +43 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli_sdk.egg-info/PKG-INFO +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/__init__.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/api/authentication_api.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/api/deployments_api.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/api/information_api.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/api/packaged_models_api.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/api/registries_api.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/api/roles_api.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/api/templates_api.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/api/users_api.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/api_client.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/configuration.py +2 -2
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/exceptions.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/__init__.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/auto_scaling_template.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/autoscaling.py +3 -13
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/configuration_resources.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/deployment.py +3 -13
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/deployment_model_version.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/deployment_request.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/deployment_state.py +3 -13
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/error_response.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/event_info.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/failure_info.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/login_request.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/login_response.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/model_auth_token.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/observability.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/packaged_model.py +4 -14
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/packaged_model_request.py +3 -13
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/resource_profile.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/resources_template.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/role.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/role_assignment.py +3 -10
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/role_assignments.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/security.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/success_response.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/trained_model_registry.py +8 -15
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/trained_model_registry_request.py +8 -15
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/user.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/user_patch_request.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/models/user_request.py +3 -13
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/rest.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/setup.py +1 -1
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/README.md +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/__init__.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/__init__.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/__main__.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/_util.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/cli.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/errors.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/render.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/role.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/sso.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/test/conftest.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/user.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/cli/version.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/common/__init__.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/common/api/__init__.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/common/api/_util.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/common/api/authentication.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/common/api/certs.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/common/api/errors.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/common/api/request.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/common/check.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/common/constants.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/common/declarative_argparse.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/common/requests.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli/util.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli_sdk.egg-info/SOURCES.txt +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli_sdk.egg-info/dependency_links.txt +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli_sdk.egg-info/entry_points.txt +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli_sdk.egg-info/not-zip-safe +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli_sdk.egg-info/requires.txt +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aioli_sdk.egg-info/top_level.txt +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/api/__init__.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/api_response.py +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/aiolirest/py.typed +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/pyproject.toml +0 -0
- {aioli-sdk-0.2.1.dev0 → aioli_sdk-0.2.1.dev1}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# © Copyright 2024 Hewlett Packard Enterprise Development LP
|
|
2
|
-
__version__ = "0.2.1-
|
|
2
|
+
__version__ = "0.2.1-dev1"
|
|
@@ -9,18 +9,35 @@ from aioli.common import api
|
|
|
9
9
|
from aioli.common.api import authentication
|
|
10
10
|
from aioli.common.api.errors import NotFoundException
|
|
11
11
|
from aioli.common.declarative_argparse import Arg, ArgsDescription, Cmd, Group
|
|
12
|
+
from aioli.common.util import (
|
|
13
|
+
construct_arguments,
|
|
14
|
+
construct_environment,
|
|
15
|
+
launch_dashboard,
|
|
16
|
+
)
|
|
12
17
|
from aiolirest.models.autoscaling import Autoscaling
|
|
13
18
|
from aiolirest.models.deployment import Deployment, DeploymentState
|
|
14
19
|
from aiolirest.models.deployment_request import DeploymentRequest
|
|
20
|
+
from aiolirest.models.event_info import EventInfo
|
|
15
21
|
from aiolirest.models.security import Security
|
|
16
22
|
|
|
17
23
|
|
|
24
|
+
@authentication.required
|
|
25
|
+
def dashboard(args: Namespace) -> None:
|
|
26
|
+
with cli.setup_session(args) as session:
|
|
27
|
+
api_instance = aiolirest.DeploymentsApi(session)
|
|
28
|
+
|
|
29
|
+
deployment: Deployment = lookup_deployment(args.name, api_instance)
|
|
30
|
+
|
|
31
|
+
observability = api_instance.deployments_id_observability_get(deployment.id)
|
|
32
|
+
launch_dashboard(args, observability.dashboard_url)
|
|
33
|
+
|
|
34
|
+
|
|
18
35
|
@authentication.required
|
|
19
36
|
def show_deployment(args: Namespace) -> None:
|
|
20
37
|
with cli.setup_session(args) as session:
|
|
21
38
|
api_instance = aiolirest.DeploymentsApi(session)
|
|
22
39
|
|
|
23
|
-
deployment = lookup_deployment(args.name, api_instance)
|
|
40
|
+
deployment: Deployment = lookup_deployment(args.name, api_instance)
|
|
24
41
|
|
|
25
42
|
# For a more useful display, replace the model ID with its name
|
|
26
43
|
packaged_models_api = aiolirest.PackagedModelsApi(session)
|
|
@@ -59,8 +76,6 @@ def format_json(response: List[Deployment], model_api: aiolirest.PackagedModelsA
|
|
|
59
76
|
# Use model name instead of id
|
|
60
77
|
model = model_api.models_id_get(d.model)
|
|
61
78
|
m_dict["model"] = model.name
|
|
62
|
-
# Remove pop as part of INF-503
|
|
63
|
-
m_dict.pop("service", None)
|
|
64
79
|
m_dict.pop("clusterName", None)
|
|
65
80
|
deps.append(m_dict)
|
|
66
81
|
|
|
@@ -117,6 +132,7 @@ def format_deployments(
|
|
|
117
132
|
def create(args: Namespace) -> None:
|
|
118
133
|
with cli.setup_session(args) as session:
|
|
119
134
|
api_instance = aiolirest.DeploymentsApi(session)
|
|
135
|
+
|
|
120
136
|
sec = Security(authenticationRequired=False)
|
|
121
137
|
if args.authentication_required is not None:
|
|
122
138
|
val = args.authentication_required.lower() == "true"
|
|
@@ -142,6 +158,8 @@ def create(args: Namespace) -> None:
|
|
|
142
158
|
namespace=args.namespace,
|
|
143
159
|
autoScaling=auto,
|
|
144
160
|
canaryTrafficPercent=args.canary_traffic_percent,
|
|
161
|
+
environment=construct_environment(args),
|
|
162
|
+
arguments=construct_arguments(args),
|
|
145
163
|
)
|
|
146
164
|
api_instance.deployments_post(r)
|
|
147
165
|
|
|
@@ -201,6 +219,12 @@ def update(args: Namespace) -> None:
|
|
|
201
219
|
val = args.authentication_required.lower() == "true"
|
|
202
220
|
request.security.authentication_required = val
|
|
203
221
|
|
|
222
|
+
if args.env is not None:
|
|
223
|
+
request.environment = construct_environment(args)
|
|
224
|
+
|
|
225
|
+
if args.arg is not None:
|
|
226
|
+
request.arguments = construct_arguments(args)
|
|
227
|
+
|
|
204
228
|
headers = {"Content-Type": "application/json"}
|
|
205
229
|
assert found.id is not None
|
|
206
230
|
api_instance.deployments_id_put(found.id, request, _headers=headers)
|
|
@@ -215,6 +239,32 @@ def delete_deployment(args: Namespace) -> None:
|
|
|
215
239
|
api_instance.deployments_id_delete(found.id)
|
|
216
240
|
|
|
217
241
|
|
|
242
|
+
@authentication.required
|
|
243
|
+
def get_deployment_events(args: Namespace) -> None:
|
|
244
|
+
def format_events(event: EventInfo) -> List[Any]:
|
|
245
|
+
result = [
|
|
246
|
+
event.reason,
|
|
247
|
+
event.message,
|
|
248
|
+
event.time,
|
|
249
|
+
event.event_type,
|
|
250
|
+
]
|
|
251
|
+
return result
|
|
252
|
+
|
|
253
|
+
with cli.setup_session(args) as session:
|
|
254
|
+
api_instance = aiolirest.DeploymentsApi(session)
|
|
255
|
+
found = lookup_deployment(args.name, api_instance)
|
|
256
|
+
assert found.id is not None
|
|
257
|
+
events = api_instance.deployments_id_events_get(found.id)
|
|
258
|
+
headers = [
|
|
259
|
+
"Reason",
|
|
260
|
+
"Message",
|
|
261
|
+
"Time",
|
|
262
|
+
"Event Type",
|
|
263
|
+
]
|
|
264
|
+
values = [format_events(r) for r in events]
|
|
265
|
+
render.tabulate_or_csv(headers, values, args.csv)
|
|
266
|
+
|
|
267
|
+
|
|
218
268
|
common_deployment_args: ArgsDescription = [
|
|
219
269
|
Arg(
|
|
220
270
|
"--authentication-required",
|
|
@@ -235,10 +285,24 @@ common_deployment_args: ArgsDescription = [
|
|
|
235
285
|
type=int,
|
|
236
286
|
default=100,
|
|
237
287
|
),
|
|
288
|
+
Arg(
|
|
289
|
+
"-a",
|
|
290
|
+
"--arg",
|
|
291
|
+
help="Argument to be added to the service command line. "
|
|
292
|
+
"If specifying an argument that starts with a '-', use the form --arg=<your-argument>",
|
|
293
|
+
action="append",
|
|
294
|
+
),
|
|
295
|
+
Arg(
|
|
296
|
+
"-e",
|
|
297
|
+
"--env",
|
|
298
|
+
help="Specifies an environment variable & value as name=value, "
|
|
299
|
+
"to be passed to the launched container",
|
|
300
|
+
action="append",
|
|
301
|
+
),
|
|
238
302
|
]
|
|
239
303
|
|
|
240
304
|
main_cmd = Cmd(
|
|
241
|
-
"d|eployment",
|
|
305
|
+
"d|eployment|s",
|
|
242
306
|
None,
|
|
243
307
|
"manage trained deployments",
|
|
244
308
|
[
|
|
@@ -262,12 +326,24 @@ main_cmd = Cmd(
|
|
|
262
326
|
Arg(
|
|
263
327
|
"name",
|
|
264
328
|
help="The name of the deployment. Must begin with a letter, but may contain "
|
|
265
|
-
"letters, numbers,
|
|
329
|
+
"letters, numbers, and hyphen",
|
|
266
330
|
),
|
|
267
331
|
Arg("--model", help="Model to be deployed", required="true"),
|
|
268
332
|
]
|
|
269
333
|
+ common_deployment_args,
|
|
270
334
|
),
|
|
335
|
+
# dashboard command.
|
|
336
|
+
Cmd(
|
|
337
|
+
"dashboard",
|
|
338
|
+
dashboard,
|
|
339
|
+
"launch the deployment dashboard",
|
|
340
|
+
[
|
|
341
|
+
Arg(
|
|
342
|
+
"name",
|
|
343
|
+
help="The name of the deployment.",
|
|
344
|
+
),
|
|
345
|
+
],
|
|
346
|
+
),
|
|
271
347
|
# Show command.
|
|
272
348
|
Cmd(
|
|
273
349
|
"show",
|
|
@@ -294,7 +370,7 @@ main_cmd = Cmd(
|
|
|
294
370
|
Arg(
|
|
295
371
|
"--name",
|
|
296
372
|
help="The new name of the deployment. Must begin with a letter, but may "
|
|
297
|
-
"contain letters, numbers,
|
|
373
|
+
"contain letters, numbers, and hyphen",
|
|
298
374
|
),
|
|
299
375
|
Arg("--model", help="Model to be deployed", required="true"),
|
|
300
376
|
]
|
|
@@ -308,6 +384,15 @@ main_cmd = Cmd(
|
|
|
308
384
|
Arg("name", help="The name of the deployment"),
|
|
309
385
|
],
|
|
310
386
|
),
|
|
387
|
+
Cmd(
|
|
388
|
+
"event|s",
|
|
389
|
+
get_deployment_events,
|
|
390
|
+
"get deployment events",
|
|
391
|
+
[
|
|
392
|
+
Arg("name", help="The name of the deployment"),
|
|
393
|
+
Arg("--csv", action="store_true", help="print as CSV"),
|
|
394
|
+
],
|
|
395
|
+
),
|
|
311
396
|
],
|
|
312
397
|
)
|
|
313
398
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# © Copyright 2023-2024 Hewlett Packard Enterprise Development LP
|
|
2
2
|
from argparse import Namespace
|
|
3
|
-
from typing import Any,
|
|
3
|
+
from typing import Any, List
|
|
4
4
|
|
|
5
5
|
from pydantic import StrictInt
|
|
6
6
|
|
|
@@ -12,6 +12,11 @@ from aioli.common import api
|
|
|
12
12
|
from aioli.common.api import authentication
|
|
13
13
|
from aioli.common.api.errors import NotFoundException, VersionRequiredException
|
|
14
14
|
from aioli.common.declarative_argparse import Arg, ArgsDescription, Cmd, Group
|
|
15
|
+
from aioli.common.util import (
|
|
16
|
+
construct_arguments,
|
|
17
|
+
construct_environment,
|
|
18
|
+
launch_dashboard,
|
|
19
|
+
)
|
|
15
20
|
from aiolirest.models.configuration_resources import ConfigurationResources
|
|
16
21
|
from aiolirest.models.deployment_model_version import DeploymentModelVersion
|
|
17
22
|
from aiolirest.models.packaged_model import PackagedModel
|
|
@@ -74,32 +79,6 @@ def format_models(
|
|
|
74
79
|
render.tabulate_or_csv(headers, values, args.csv)
|
|
75
80
|
|
|
76
81
|
|
|
77
|
-
def construct_environment(args: Namespace) -> Dict[str, str]:
|
|
78
|
-
environment: Dict[str, str] = {}
|
|
79
|
-
if args.env is None:
|
|
80
|
-
return environment
|
|
81
|
-
|
|
82
|
-
for entry in args.env:
|
|
83
|
-
# split to name & value
|
|
84
|
-
the_split = entry.split("=", maxsplit=1)
|
|
85
|
-
name: str = the_split[0]
|
|
86
|
-
value: str = ""
|
|
87
|
-
if len(the_split) > 1:
|
|
88
|
-
value = the_split[1]
|
|
89
|
-
environment[name] = value
|
|
90
|
-
return environment
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def construct_arguments(args: Namespace) -> List[str]:
|
|
94
|
-
arguments: List[str] = []
|
|
95
|
-
if args.arg is None:
|
|
96
|
-
return arguments
|
|
97
|
-
|
|
98
|
-
for entry in args.arg:
|
|
99
|
-
arguments.append(entry.strip())
|
|
100
|
-
return arguments
|
|
101
|
-
|
|
102
|
-
|
|
103
82
|
@authentication.required
|
|
104
83
|
def create(args: Namespace) -> None:
|
|
105
84
|
with cli.setup_session(args) as session:
|
|
@@ -125,40 +104,72 @@ def create(args: Namespace) -> None:
|
|
|
125
104
|
api_instance.models_post(r)
|
|
126
105
|
|
|
127
106
|
|
|
128
|
-
def lookup_model(name: str, api: aiolirest.PackagedModelsApi) -> PackagedModel:
|
|
129
|
-
model
|
|
130
|
-
|
|
107
|
+
def lookup_model(name: str, args: Namespace, api: aiolirest.PackagedModelsApi) -> PackagedModel:
|
|
108
|
+
# From the database, get the model record. If the model exists in multiple versions,
|
|
109
|
+
# then sufficient version information must be part of the request.
|
|
110
|
+
model_count = 0
|
|
111
|
+
models: List[PackagedModel] = api.models_get()
|
|
112
|
+
for r in models:
|
|
131
113
|
if r.name == name:
|
|
132
|
-
if model is not None:
|
|
133
|
-
raise VersionRequiredException(
|
|
134
|
-
f"please specify model version as {name} matches more than one model"
|
|
135
|
-
)
|
|
136
114
|
model = r
|
|
115
|
+
model_count += 1
|
|
137
116
|
|
|
138
|
-
if
|
|
139
|
-
|
|
140
|
-
|
|
117
|
+
if model_count == 1:
|
|
118
|
+
return model # Found a single version of the specified model
|
|
119
|
+
if model_count > 1 and not args.version:
|
|
120
|
+
raise_version_required(name)
|
|
141
121
|
|
|
122
|
+
if model_count == 0:
|
|
123
|
+
# The specified model does not exist; extract the version suffix if any
|
|
124
|
+
split_on = ".v"
|
|
125
|
+
split = name.lower().rsplit(split_on, 1)
|
|
142
126
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
127
|
+
if len(split) != 1:
|
|
128
|
+
version = split[1] # without the ".v"
|
|
129
|
+
# Extract name as everything to the left of the last ".v"
|
|
130
|
+
name = name[0 : len(name) - len(version) - len(split_on)]
|
|
131
|
+
|
|
132
|
+
# if there is an explicit version specified, then use that
|
|
133
|
+
if args.version:
|
|
134
|
+
version = args.version
|
|
135
|
+
if version is None:
|
|
136
|
+
raise_version_required(name)
|
|
137
|
+
|
|
138
|
+
return lookup_model_and_version(name, version, models)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def raise_version_required(name: str) -> None:
|
|
142
|
+
raise VersionRequiredException(
|
|
143
|
+
f"please specify model version as {name} matches more than one model"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def lookup_model_and_version(name: str, version: str, models: List[PackagedModel]) -> PackagedModel:
|
|
148
|
+
# The version may optionally be expressed with a prefix of 'v'
|
|
149
|
+
version_no_prefix: str = version.lstrip("vV")
|
|
150
|
+
for r in models:
|
|
151
|
+
if r.name == name and r.version == StrictInt(version_no_prefix):
|
|
148
152
|
return r
|
|
149
153
|
raise NotFoundException(f"model {name} version {version} not found")
|
|
150
154
|
|
|
151
155
|
|
|
152
156
|
@authentication.required
|
|
153
|
-
def
|
|
157
|
+
def dashboard(args: Namespace) -> None:
|
|
154
158
|
with cli.setup_session(args) as session:
|
|
155
159
|
api_instance = aiolirest.PackagedModelsApi(session)
|
|
156
160
|
|
|
157
|
-
|
|
158
|
-
model = lookup_model_and_version(args.name, args.version, api_instance)
|
|
159
|
-
else:
|
|
160
|
-
model = lookup_model(args.name, api_instance)
|
|
161
|
+
model = lookup_model(args.name, args, api_instance)
|
|
161
162
|
|
|
163
|
+
observability = api_instance.models_id_observability_get(model.id)
|
|
164
|
+
launch_dashboard(args, observability.dashboard_url)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@authentication.required
|
|
168
|
+
def show_model(args: Namespace) -> None:
|
|
169
|
+
with cli.setup_session(args) as session:
|
|
170
|
+
api_instance = aiolirest.PackagedModelsApi(session)
|
|
171
|
+
|
|
172
|
+
model = lookup_model(args.name, args, api_instance)
|
|
162
173
|
registries_api = aiolirest.RegistriesApi(session)
|
|
163
174
|
|
|
164
175
|
rname = lookup_registry_name_by_id(model.registry, registries_api)
|
|
@@ -176,10 +187,7 @@ def show_model(args: Namespace) -> None:
|
|
|
176
187
|
def update(args: Namespace) -> None:
|
|
177
188
|
with cli.setup_session(args) as session:
|
|
178
189
|
api_instance = aiolirest.PackagedModelsApi(session)
|
|
179
|
-
|
|
180
|
-
found = lookup_model_and_version(args.modelname, args.version, api_instance)
|
|
181
|
-
else:
|
|
182
|
-
found = lookup_model(args.modelname, api_instance)
|
|
190
|
+
found = lookup_model(args.modelname, args, api_instance)
|
|
183
191
|
request = PackagedModelRequest(
|
|
184
192
|
description=found.description,
|
|
185
193
|
image=found.image,
|
|
@@ -255,7 +263,7 @@ def update(args: Namespace) -> None:
|
|
|
255
263
|
def delete_model(args: Namespace) -> None:
|
|
256
264
|
with cli.setup_session(args) as session:
|
|
257
265
|
api_instance = aiolirest.PackagedModelsApi(session)
|
|
258
|
-
found = lookup_model_and_version(args.name, args.version, api_instance)
|
|
266
|
+
found = lookup_model_and_version(args.name, args.version, api_instance.models_get())
|
|
259
267
|
|
|
260
268
|
assert found.id is not None
|
|
261
269
|
api_instance.models_id_delete(found.id)
|
|
@@ -265,7 +273,7 @@ def delete_model(args: Namespace) -> None:
|
|
|
265
273
|
def auth_token(args: Namespace) -> None:
|
|
266
274
|
with cli.setup_session(args) as session:
|
|
267
275
|
api_instance = aiolirest.PackagedModelsApi(session)
|
|
268
|
-
found = lookup_model(args.name, api_instance)
|
|
276
|
+
found = lookup_model(args.name, args, api_instance)
|
|
269
277
|
assert found.id is not None
|
|
270
278
|
response = api_instance.models_id_token_get(found.id)
|
|
271
279
|
t = response.to_dict()
|
|
@@ -276,10 +284,7 @@ def auth_token(args: Namespace) -> None:
|
|
|
276
284
|
def list_versions(args: Namespace) -> None:
|
|
277
285
|
with cli.setup_session(args) as session:
|
|
278
286
|
api_instance = aiolirest.PackagedModelsApi(session)
|
|
279
|
-
|
|
280
|
-
found = lookup_model_and_version(args.name, args.version, api_instance)
|
|
281
|
-
else:
|
|
282
|
-
found = lookup_model(args.name, api_instance)
|
|
287
|
+
found = lookup_model(args.name, args, api_instance)
|
|
283
288
|
assert found.id is not None
|
|
284
289
|
response = api_instance.models_versions_get(found.id)
|
|
285
290
|
|
|
@@ -336,7 +341,7 @@ common_model_args: ArgsDescription = [
|
|
|
336
341
|
]
|
|
337
342
|
|
|
338
343
|
main_cmd = Cmd(
|
|
339
|
-
"m|odel",
|
|
344
|
+
"m|odel|s",
|
|
340
345
|
None,
|
|
341
346
|
"manage packaged models",
|
|
342
347
|
[
|
|
@@ -360,12 +365,25 @@ main_cmd = Cmd(
|
|
|
360
365
|
Arg(
|
|
361
366
|
"name",
|
|
362
367
|
help="The name of the packaged model. Must begin with a letter, but may "
|
|
363
|
-
"contain letters, numbers,
|
|
368
|
+
"contain letters, numbers, and hyphen",
|
|
364
369
|
),
|
|
365
370
|
Arg("--image", help="Docker container image servicing the packaged model"),
|
|
366
371
|
]
|
|
367
372
|
+ common_model_args,
|
|
368
373
|
),
|
|
374
|
+
# dashboard command.
|
|
375
|
+
Cmd(
|
|
376
|
+
"dashboard",
|
|
377
|
+
dashboard,
|
|
378
|
+
"launch the packaged model dashboard",
|
|
379
|
+
[
|
|
380
|
+
Arg(
|
|
381
|
+
"name",
|
|
382
|
+
help="The name of the packaged model.",
|
|
383
|
+
),
|
|
384
|
+
Arg("--version", help="The packaged model version to show"),
|
|
385
|
+
],
|
|
386
|
+
),
|
|
369
387
|
# Show command.
|
|
370
388
|
Cmd(
|
|
371
389
|
"show",
|
|
@@ -393,7 +411,7 @@ main_cmd = Cmd(
|
|
|
393
411
|
Arg(
|
|
394
412
|
"--name",
|
|
395
413
|
help="The new name of the packaged model. Must begin with a letter, but may "
|
|
396
|
-
"contain letters, numbers,
|
|
414
|
+
"contain letters, numbers, and hyphen",
|
|
397
415
|
),
|
|
398
416
|
Arg("--image", help="Docker container image servicing the packaged model"),
|
|
399
417
|
Arg("--version", help="The packaged model version to update"),
|
|
@@ -415,6 +433,7 @@ main_cmd = Cmd(
|
|
|
415
433
|
"get packaged model auth token",
|
|
416
434
|
[
|
|
417
435
|
Arg("name", help="The name of the packaged model"),
|
|
436
|
+
Arg("--version", help="The version of the packaged model"),
|
|
418
437
|
],
|
|
419
438
|
),
|
|
420
439
|
Cmd(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# © Copyright 2023-2024 Hewlett Packard Enterprise Development LP
|
|
2
2
|
from argparse import Namespace
|
|
3
|
-
from typing import Any, List, Optional
|
|
3
|
+
from typing import Any, List, Optional
|
|
4
4
|
|
|
5
5
|
import aiolirest
|
|
6
6
|
from aioli import cli
|
|
@@ -11,8 +11,6 @@ from aioli.common.declarative_argparse import Arg, Cmd, Group
|
|
|
11
11
|
from aiolirest.models.trained_model_registry import TrainedModelRegistry
|
|
12
12
|
from aiolirest.models.trained_model_registry_request import TrainedModelRegistryRequest
|
|
13
13
|
|
|
14
|
-
from .errors import CliError
|
|
15
|
-
|
|
16
14
|
# Avoid reporting BrokenPipeError when piping `tabulate` output through
|
|
17
15
|
# a filter like `head`.
|
|
18
16
|
FLUSH = False
|
|
@@ -67,24 +65,11 @@ def format_deployment(response: List[TrainedModelRegistry], args: Namespace) ->
|
|
|
67
65
|
render.tabulate_or_csv(headers, values, args.csv)
|
|
68
66
|
|
|
69
67
|
|
|
70
|
-
def _validate_type_enum(reg_type: Any) -> Union[None, str]:
|
|
71
|
-
if reg_type not in ("s3", "path", "http", "openllm", "ngc"):
|
|
72
|
-
return (
|
|
73
|
-
"Registry type must be one of enum values (s3, path, "
|
|
74
|
-
+ f"http, openllm, ngc), provided '{reg_type}'"
|
|
75
|
-
)
|
|
76
|
-
return None
|
|
77
|
-
|
|
78
|
-
|
|
79
68
|
@authentication.required
|
|
80
69
|
def create(args: Namespace) -> None:
|
|
81
70
|
with cli.setup_session(args) as session:
|
|
82
71
|
api_instance = aiolirest.RegistriesApi(session)
|
|
83
72
|
|
|
84
|
-
err = _validate_type_enum(args.type)
|
|
85
|
-
if err:
|
|
86
|
-
raise CliError(err)
|
|
87
|
-
|
|
88
73
|
r = TrainedModelRegistryRequest(
|
|
89
74
|
name=args.name,
|
|
90
75
|
accessKey=args.access_key,
|
|
@@ -153,10 +138,6 @@ def update(args: Namespace) -> None:
|
|
|
153
138
|
request.name = args.name
|
|
154
139
|
|
|
155
140
|
if args.type is not None:
|
|
156
|
-
err = _validate_type_enum(args.type)
|
|
157
|
-
if err:
|
|
158
|
-
raise CliError(err)
|
|
159
|
-
|
|
160
141
|
request.type = args.type
|
|
161
142
|
|
|
162
143
|
if args.access_key is not None:
|
|
@@ -186,7 +167,7 @@ def delete_registry(args: Namespace) -> None:
|
|
|
186
167
|
|
|
187
168
|
|
|
188
169
|
main_cmd = Cmd(
|
|
189
|
-
"r|egistry",
|
|
170
|
+
"registries r|egistry",
|
|
190
171
|
None,
|
|
191
172
|
"manage packaged model registries",
|
|
192
173
|
[
|
|
@@ -212,7 +193,7 @@ main_cmd = Cmd(
|
|
|
212
193
|
Arg(
|
|
213
194
|
"name",
|
|
214
195
|
help="The name of the model registry. Must begin with a letter, but may "
|
|
215
|
-
"contain letters, numbers,
|
|
196
|
+
"contain letters, numbers, and hyphen",
|
|
216
197
|
),
|
|
217
198
|
Arg("--type", help="The type of this model registry", required="true"),
|
|
218
199
|
Arg("--bucket", help="S3 Bucket name"),
|
|
@@ -247,7 +228,7 @@ main_cmd = Cmd(
|
|
|
247
228
|
Arg(
|
|
248
229
|
"--name",
|
|
249
230
|
help="The new name of the model registry. Must begin with a letter, but may "
|
|
250
|
-
"contain letters, numbers,
|
|
231
|
+
"contain letters, numbers, and hyphen",
|
|
251
232
|
),
|
|
252
233
|
Arg("--type", help="The type of this model registry"),
|
|
253
234
|
Arg("--bucket", help="S3 Bucket name"),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# © Copyright
|
|
1
|
+
# © Copyright 2024 Hewlett Packard Enterprise Development LP
|
|
2
2
|
import csv
|
|
3
3
|
import os
|
|
4
4
|
import subprocess
|
|
@@ -95,15 +95,15 @@ class TestCli:
|
|
|
95
95
|
)
|
|
96
96
|
assert proc.returncode == 1
|
|
97
97
|
expected = (
|
|
98
|
-
"
|
|
99
|
-
+ "(s3,
|
|
98
|
+
"Failed to create a registry: registry type must be one of the values "
|
|
99
|
+
+ "(s3, http, openllm, ngc), provided 'bob'"
|
|
100
100
|
)
|
|
101
101
|
assert proc.stderr.decode("utf-8").find(expected) == 0
|
|
102
102
|
|
|
103
103
|
# List the newly created registry entry and test for expected values
|
|
104
104
|
expected = (
|
|
105
105
|
"bento-registry | s3 | minioadmin | demo-bento-registry | "
|
|
106
|
-
"
|
|
106
|
+
"********** | http://10.30.89.14:30008/"
|
|
107
107
|
)
|
|
108
108
|
actual = subprocess.check_output(["aioli", "registry", "list"]).decode("utf-8")
|
|
109
109
|
assert (actual.find(expected)) > 0
|
|
@@ -120,7 +120,7 @@ class TestCli:
|
|
|
120
120
|
assert row["Type"] == "s3", "Expected s3"
|
|
121
121
|
assert row["Access Key"] == "minioadmin", "Expected minioadmin"
|
|
122
122
|
assert row["Bucket"] == "demo-bento-registry", "Expected demo-bento-registry"
|
|
123
|
-
assert row["Secret Key"] == "
|
|
123
|
+
assert row["Secret Key"] == "**********", "Expected **********"
|
|
124
124
|
assert (
|
|
125
125
|
row["Endpoint URL"] == "http://10.30.89.14:30008/"
|
|
126
126
|
), "Expected http://10.30.89.14:30008/"
|
|
@@ -134,7 +134,7 @@ class TestCli:
|
|
|
134
134
|
' "bucket": "demo-bento-registry",\n'
|
|
135
135
|
' "endpointUrl": "http://10.30.89.14:30008/",\n'
|
|
136
136
|
' "name": "bento-registry",\n'
|
|
137
|
-
' "secretKey": "
|
|
137
|
+
' "secretKey": "**********",\n'
|
|
138
138
|
' "type": "s3"\n'
|
|
139
139
|
' }\n' # noqa: Q000
|
|
140
140
|
']\n' # noqa: Q000
|
|
@@ -168,7 +168,7 @@ class TestCli:
|
|
|
168
168
|
).decode("utf-8")
|
|
169
169
|
expected = (
|
|
170
170
|
"bento-registry1 | s3 | minioadmin | demo-bento-registry | "
|
|
171
|
-
"
|
|
171
|
+
"********** | http://10.30.89.14:30008/"
|
|
172
172
|
)
|
|
173
173
|
actual = subprocess.check_output(["aioli", "registry", "list"]).decode("utf-8")
|
|
174
174
|
assert (actual.find(expected)) > 0
|
|
@@ -187,8 +187,8 @@ class TestCli:
|
|
|
187
187
|
)
|
|
188
188
|
assert proc.returncode == 1
|
|
189
189
|
expected = (
|
|
190
|
-
"
|
|
191
|
-
+ "(s3,
|
|
190
|
+
"Failed to modify a registry: registry type must be one of the values "
|
|
191
|
+
+ "(s3, http, openllm, ngc), provided 'bob'"
|
|
192
192
|
)
|
|
193
193
|
assert proc.stderr.decode("utf-8").find(expected) == 0
|
|
194
194
|
|
|
@@ -252,6 +252,49 @@ class TestCli:
|
|
|
252
252
|
), "Expected fictional.registry.example/imagename"
|
|
253
253
|
assert row["Registry"] == "bento-registry1", "Expected bento-registry1"
|
|
254
254
|
|
|
255
|
+
# Exercise various means of specifying the model version, using the show command as a vehicle.
|
|
256
|
+
def test_model_versions(self, setup_login: None) -> None:
|
|
257
|
+
yaml_output = subprocess.check_output(["aioli", "model", "show", "iris-tf-keras"]).decode(
|
|
258
|
+
"utf-8"
|
|
259
|
+
)
|
|
260
|
+
temp_file: str = "/tmp/test-cli-m.yaml"
|
|
261
|
+
|
|
262
|
+
# Demonstrate that we have valid yaml
|
|
263
|
+
with open(temp_file, "w") as file:
|
|
264
|
+
file.write(yaml_output)
|
|
265
|
+
with open(temp_file, newline="") as file:
|
|
266
|
+
from ruamel.yaml import YAML
|
|
267
|
+
|
|
268
|
+
yaml = YAML(typ="safe")
|
|
269
|
+
model = yaml.load(file)
|
|
270
|
+
|
|
271
|
+
assert model["version"] == 1, "Validity check -- version == 1"
|
|
272
|
+
|
|
273
|
+
yaml_output2 = subprocess.check_output(
|
|
274
|
+
["aioli", "model", "show", "iris-tf-keras.v1"]
|
|
275
|
+
).decode("utf-8")
|
|
276
|
+
assert yaml_output == yaml_output2, "Expect the same output using .v1"
|
|
277
|
+
|
|
278
|
+
yaml_output2 = subprocess.check_output(
|
|
279
|
+
["aioli", "model", "show", "iris-tf-keras.V1"]
|
|
280
|
+
).decode("utf-8")
|
|
281
|
+
assert yaml_output == yaml_output2, "Expect the same output using .V1"
|
|
282
|
+
|
|
283
|
+
yaml_output2 = subprocess.check_output(
|
|
284
|
+
["aioli", "model", "show", "iris-tf-keras", "--version", "1"]
|
|
285
|
+
).decode("utf-8")
|
|
286
|
+
assert yaml_output == yaml_output2, "Expect the same output using --version 1"
|
|
287
|
+
|
|
288
|
+
yaml_output2 = subprocess.check_output(
|
|
289
|
+
["aioli", "model", "show", "iris-tf-keras", "--version", "v1"]
|
|
290
|
+
).decode("utf-8")
|
|
291
|
+
assert yaml_output == yaml_output2, "Expect the same output using --version v1"
|
|
292
|
+
|
|
293
|
+
yaml_output2 = subprocess.check_output(
|
|
294
|
+
["aioli", "model", "show", "iris-tf-keras", "--version", "V1"]
|
|
295
|
+
).decode("utf-8")
|
|
296
|
+
assert yaml_output == yaml_output2, "Expect the same output using --version V1"
|
|
297
|
+
|
|
255
298
|
# fmt: off
|
|
256
299
|
def test_model_list_json(self, setup_login: None) -> None:
|
|
257
300
|
expected = (
|
|
@@ -388,10 +431,11 @@ class TestCli:
|
|
|
388
431
|
assert (actual.find(expected)) > 0
|
|
389
432
|
|
|
390
433
|
# Create a fourth with a new name of the model using the second version
|
|
434
|
+
# (specifying the version with the optional v{n} format)
|
|
391
435
|
assert (
|
|
392
436
|
os.system(
|
|
393
437
|
"aioli model update iris-tf-keras --name iris-tf-keras-v4 "
|
|
394
|
-
"--version
|
|
438
|
+
"--version v2 "
|
|
395
439
|
"--registry bento-registry1 "
|
|
396
440
|
"--url s3://demo-bento-registry/iris-tf-keras_updated_v3 "
|
|
397
441
|
"--image fictional.registry.example/updated_imagename "
|
|
@@ -519,6 +563,8 @@ class TestCli:
|
|
|
519
563
|
"--autoscaling-target 1 "
|
|
520
564
|
"--autoscaling-metric concurrency "
|
|
521
565
|
"--canary-traffic-percent 20 "
|
|
566
|
+
"-a='--debug' "
|
|
567
|
+
"-e MODS=SOME "
|
|
522
568
|
"iris-tf-keras-deployment-2"
|
|
523
569
|
)
|
|
524
570
|
== 0
|
|
@@ -548,6 +594,38 @@ class TestCli:
|
|
|
548
594
|
actual = filtered_result.strip()
|
|
549
595
|
assert actual == expected
|
|
550
596
|
|
|
597
|
+
def test_deployment_update_args_environment(self, setup_login: None) -> None:
|
|
598
|
+
# Update the deployments arguments and environment and check that changes
|
|
599
|
+
# were made.
|
|
600
|
+
assert (
|
|
601
|
+
os.system(
|
|
602
|
+
"aioli deployment update --model iris-tf-keras "
|
|
603
|
+
"-a='--updated' "
|
|
604
|
+
"-e MODS=UPDATED "
|
|
605
|
+
"--env OTHER=VALUE "
|
|
606
|
+
"iris-tf-keras-deployment-2"
|
|
607
|
+
)
|
|
608
|
+
== 0
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
result = subprocess.check_output(
|
|
612
|
+
["aioli", "deployment", "show", "iris-tf-keras-deployment-2"]
|
|
613
|
+
).decode("utf-8")
|
|
614
|
+
result_list = result.split("\n")
|
|
615
|
+
args_found = False
|
|
616
|
+
other_found = False
|
|
617
|
+
for line in result_list:
|
|
618
|
+
if "- --updated" in line:
|
|
619
|
+
args_found = True
|
|
620
|
+
if "MODS: UPDATED" in line:
|
|
621
|
+
mods_found = True
|
|
622
|
+
if "OTHER: VALUE" in line:
|
|
623
|
+
other_found = True
|
|
624
|
+
|
|
625
|
+
assert args_found
|
|
626
|
+
assert mods_found
|
|
627
|
+
assert other_found
|
|
628
|
+
|
|
551
629
|
def test_deployment_delete(self, setup_login: None) -> None:
|
|
552
630
|
assert os.system("aioli deployment delete iris-tf-keras-deployment") == 0
|
|
553
631
|
assert os.system("aioli deployment delete iris-tf-keras-deployment-2") == 0
|