aioli-sdk 0.2.4.dev10__tar.gz → 0.2.4.dev12__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.
Files changed (88) hide show
  1. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/PKG-INFO +1 -1
  2. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/__version__.py +1 -1
  3. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/cli.py +70 -60
  4. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/deployment.py +42 -5
  5. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/model.py +41 -60
  6. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/test/test_cli.py +14 -29
  7. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/version.py +5 -1
  8. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli_sdk.egg-info/PKG-INFO +1 -1
  9. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/__init__.py +1 -1
  10. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/authentication_api.py +1 -1
  11. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/deployments_api.py +1 -1
  12. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/information_api.py +1 -1
  13. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/packaged_models_api.py +18 -1
  14. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/registries_api.py +1 -1
  15. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/roles_api.py +1 -1
  16. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/templates_api.py +1 -1
  17. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/users_api.py +1 -1
  18. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api_client.py +1 -1
  19. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/configuration.py +2 -2
  20. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/exceptions.py +1 -1
  21. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/__init__.py +1 -1
  22. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/auto_scaling_template.py +1 -1
  23. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/autoscaling.py +1 -1
  24. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/configuration_resources.py +1 -1
  25. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/deployment.py +1 -1
  26. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/deployment_model_version.py +1 -1
  27. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/deployment_request.py +1 -1
  28. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/deployment_state.py +1 -1
  29. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/error_response.py +1 -1
  30. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/event_info.py +1 -1
  31. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/failure_info.py +1 -1
  32. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/login_request.py +1 -1
  33. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/login_response.py +1 -1
  34. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/model_auth_token.py +1 -1
  35. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/model_response.py +1 -1
  36. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/observability.py +1 -1
  37. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/packaged_model.py +1 -1
  38. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/packaged_model_request.py +1 -1
  39. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/resource_profile.py +1 -1
  40. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/resources_template.py +1 -1
  41. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/role.py +1 -1
  42. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/role_assignment.py +1 -1
  43. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/role_assignments.py +1 -1
  44. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/security.py +1 -1
  45. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/success_response.py +1 -1
  46. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/trained_model_registry.py +1 -1
  47. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/trained_model_registry_request.py +1 -1
  48. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/user.py +1 -1
  49. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/user_patch_request.py +1 -1
  50. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/user_request.py +1 -1
  51. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/rest.py +1 -1
  52. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/setup.py +1 -1
  53. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/README.md +0 -0
  54. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/__init__.py +0 -0
  55. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/__init__.py +0 -0
  56. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/__main__.py +0 -0
  57. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/_util.py +0 -0
  58. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/errors.py +0 -0
  59. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/registry.py +0 -0
  60. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/render.py +0 -0
  61. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/role.py +0 -0
  62. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/sso.py +0 -0
  63. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/test/conftest.py +0 -0
  64. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/user.py +0 -0
  65. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/__init__.py +0 -0
  66. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/api/__init__.py +0 -0
  67. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/api/_util.py +0 -0
  68. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/api/authentication.py +0 -0
  69. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/api/certs.py +0 -0
  70. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/api/errors.py +0 -0
  71. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/api/request.py +0 -0
  72. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/check.py +0 -0
  73. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/constants.py +0 -0
  74. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/declarative_argparse.py +0 -0
  75. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/requests.py +0 -0
  76. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/util.py +0 -0
  77. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/util.py +0 -0
  78. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli_sdk.egg-info/SOURCES.txt +0 -0
  79. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli_sdk.egg-info/dependency_links.txt +0 -0
  80. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli_sdk.egg-info/entry_points.txt +0 -0
  81. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli_sdk.egg-info/not-zip-safe +0 -0
  82. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli_sdk.egg-info/requires.txt +0 -0
  83. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli_sdk.egg-info/top_level.txt +0 -0
  84. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/__init__.py +0 -0
  85. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api_response.py +0 -0
  86. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/py.typed +0 -0
  87. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/pyproject.toml +0 -0
  88. {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aioli-sdk
3
- Version: 0.2.4.dev10
3
+ Version: 0.2.4.dev12
4
4
  Summary: Aioli (AI OnLine Inference), a platform for deploying AI models at scale.
5
5
  Home-page: https://github.com/determined-ai/aioli
6
6
  Author: HPE AI Solutions
@@ -1,2 +1,2 @@
1
1
  # © Copyright 2024 Hewlett Packard Enterprise Development LP
2
- __version__ = "0.2.4-dev10"
2
+ __version__ = "0.2.4-dev12"
@@ -5,7 +5,12 @@ import os
5
5
  import socket
6
6
  import ssl
7
7
  import sys
8
- from argparse import ArgumentDefaultsHelpFormatter, ArgumentError, ArgumentParser
8
+ from argparse import (
9
+ ArgumentDefaultsHelpFormatter,
10
+ ArgumentError,
11
+ ArgumentParser,
12
+ Namespace,
13
+ )
9
14
  from typing import List, Optional, Sequence, Union, cast
10
15
 
11
16
  import argcomplete
@@ -101,65 +106,7 @@ def main(
101
106
  parser.exit(2, "{}: no subcommand specified\n".format(parser.prog))
102
107
 
103
108
  try:
104
- # Configure the CLI's Cert singleton.
105
- certs.cli_cert = certs.default_load(parsed_args.controller)
106
-
107
- try:
108
- check_version(parsed_args)
109
- except SSLError:
110
- # An SSLError usually means that we queried a controller over HTTPS and got an
111
- # untrusted cert, so allow the user to store and trust the current cert. (It
112
- # could also mean that we tried to talk HTTPS on the HTTP port, but distinguishing
113
- # that based on the exception is annoying, and we'll figure that out in the next
114
- # step anyway.)
115
- addr = api.parse_master_address(parsed_args.controller)
116
- check_not_none(addr.hostname)
117
- check_not_none(addr.port)
118
- try:
119
- ctx = SSL.Context(SSL.TLSv1_2_METHOD)
120
- conn = SSL.Connection(ctx, socket.socket())
121
- conn.set_tlsext_host_name(cast(str, addr.hostname).encode())
122
- conn.connect(cast(Sequence[Union[str, int]], (addr.hostname, addr.port)))
123
- conn.do_handshake()
124
- peer_cert_chain = conn.get_peer_cert_chain()
125
- if peer_cert_chain is None or len(peer_cert_chain) == 0:
126
- # Peer presented no cert. It seems unlikely that this is possible after
127
- # do_handshake() succeeded, but checking for None makes mypy happy.
128
- raise crypto.Error()
129
- cert_pem_data = [
130
- crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode()
131
- for cert in peer_cert_chain
132
- ]
133
- except crypto.Error:
134
- die(
135
- "Tried to connect over HTTPS but couldn't get a certificate from the "
136
- "controller; consider using HTTP"
137
- )
138
-
139
- # Compute the fingerprint of the certificate; this is the same as the output of
140
- # `openssl x509 -fingerprint -sha256 -inform pem -noout -in <cert>`.
141
- cert_hash = hashlib.sha256(ssl.PEM_cert_to_DER_cert(cert_pem_data[0])).hexdigest()
142
- cert_fingerprint = ":".join(chunks(cert_hash, 2))
143
-
144
- if not render.yes_or_no(
145
- "The controller sent an untrusted certificate chain with this SHA256 "
146
- "fingerprint:\n"
147
- "{}\nDo you want to trust this certificate from now on?".format(
148
- cert_fingerprint
149
- )
150
- ):
151
- die("Unable to verify controller certificate")
152
-
153
- joined_certs = "".join(cert_pem_data)
154
- certs.CertStore(certs.default_store()).set_cert(
155
- parsed_args.controller, joined_certs
156
- )
157
- # Reconfigure the CLI's Cert singleton, but preserve the certificate name.
158
- old_cert_name = certs.cli_cert.name
159
- certs.cli_cert = certs.Cert(cert_pem=joined_certs, name=old_cert_name)
160
-
161
- check_version(parsed_args)
162
-
109
+ configure_certificate_for_controller(parsed_args)
163
110
  parsed_args.func(parsed_args)
164
111
  except KeyboardInterrupt as e:
165
112
  raise e
@@ -201,6 +148,69 @@ def main(
201
148
  die("Interrupting...", exit_code=3)
202
149
 
203
150
 
151
+ def configure_certificate_for_controller(parsed_args: Namespace) -> None:
152
+ # Configure the CLI's Cert singleton.
153
+ certs.cli_cert = certs.default_load(parsed_args.controller)
154
+
155
+ try:
156
+ # check_version doesn't require credentials, so we can use it to verify the certificate.
157
+ check_version(parsed_args)
158
+ except SSLError:
159
+ # An SSLError usually means that we queried a controller over HTTPS and got an
160
+ # untrusted cert, so allow the user to store and trust the current cert. (It
161
+ # could also mean that we tried to talk HTTPS on the HTTP port, but distinguishing
162
+ # that based on the exception is annoying, and we'll figure that out in the next
163
+ # step anyway.)
164
+ addr = api.parse_master_address(parsed_args.controller)
165
+ check_not_none(addr.hostname)
166
+ check_not_none(addr.port)
167
+ try:
168
+ ctx = SSL.Context(SSL.TLSv1_2_METHOD)
169
+ conn = SSL.Connection(ctx, socket.socket())
170
+ conn.set_tlsext_host_name(cast(str, addr.hostname).encode())
171
+ conn.connect(cast(Sequence[Union[str, int]], (addr.hostname, addr.port)))
172
+ conn.do_handshake()
173
+ peer_cert_chain = conn.get_peer_cert_chain()
174
+ if peer_cert_chain is None or len(peer_cert_chain) == 0:
175
+ # Peer presented no cert. It seems unlikely that this is possible after
176
+ # do_handshake() succeeded, but checking for None makes mypy happy.
177
+ raise crypto.Error()
178
+ cert_pem_data = [
179
+ crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode()
180
+ for cert in peer_cert_chain
181
+ ]
182
+ except crypto.Error:
183
+ die(
184
+ "Tried to connect over HTTPS but couldn't get a certificate from the "
185
+ "controller; consider using HTTP"
186
+ )
187
+
188
+ # Compute the fingerprint of the certificate; this is the same as the output of
189
+ # `openssl x509 -fingerprint -sha256 -inform pem -noout -in <cert>`.
190
+ cert_hash = hashlib.sha256(ssl.PEM_cert_to_DER_cert(cert_pem_data[0])).hexdigest()
191
+ cert_fingerprint = ":".join(chunks(cert_hash, 2))
192
+
193
+ if not render.yes_or_no(
194
+ "The controller sent an untrusted certificate chain with this SHA256 "
195
+ "fingerprint:\n"
196
+ "{}\nDo you want to trust this certificate from now on?".format(cert_fingerprint)
197
+ ):
198
+ die("Unable to verify controller certificate")
199
+
200
+ joined_certs = "".join(cert_pem_data)
201
+ certs.CertStore(certs.default_store()).set_cert(parsed_args.controller, joined_certs)
202
+ # Reconfigure the CLI's Cert singleton, but preserve the certificate name.
203
+ old_cert_name = certs.cli_cert.name
204
+ certs.cli_cert = certs.Cert(cert_pem=joined_certs, name=old_cert_name)
205
+
206
+ try:
207
+ # let's try that again now that we have reconfigured with the certificate
208
+ # information supplied by the controller.
209
+ check_version(parsed_args)
210
+ except SSLError:
211
+ die("Failed to verify the controller certificate")
212
+
213
+
204
214
  def extract_message(e: ApiException) -> Optional[str]:
205
215
  decoder = json.JSONDecoder()
206
216
  try:
@@ -46,6 +46,12 @@ def show_deployment(args: Namespace) -> None:
46
46
  d = deployment.to_dict()
47
47
  # Remove clusterName for now - INF-243
48
48
  d.pop("clusterName")
49
+
50
+ # Get the model version for the deployment
51
+ deployment_model_versions = packaged_models_api.models_versions_get(deployment.model)
52
+ for r in deployment_model_versions:
53
+ if r.deployment_id == d["id"]:
54
+ d["version"] = r.mdl_version
49
55
  if args.json:
50
56
  render.print_json(d)
51
57
  else:
@@ -57,7 +63,6 @@ def list_deployments(args: Namespace) -> None:
57
63
  with cli.setup_session(args) as session:
58
64
  api_instance = aiolirest.DeploymentsApi(session)
59
65
  response = api_instance.deployments_get()
60
-
61
66
  model_api = aiolirest.PackagedModelsApi(session)
62
67
 
63
68
  if args.json:
@@ -71,14 +76,17 @@ def format_json(response: List[Deployment], model_api: aiolirest.PackagedModelsA
71
76
  for d in response:
72
77
  # Don't use the d.to_json() method as it adds backslash escapes for double quote
73
78
  m_dict = d.to_dict()
74
- m_dict.pop("id")
75
79
  m_dict.pop("modifiedAt")
76
80
  # Use model name instead of id
77
81
  model = model_api.models_id_get(d.model)
82
+ deployment_model_versions = model_api.models_versions_get(model.name)
83
+ for r in deployment_model_versions:
84
+ if r.deployment_id == m_dict["id"]:
85
+ m_dict["version"] = r.mdl_version
86
+ m_dict.pop("id")
78
87
  m_dict["model"] = model.name
79
88
  m_dict.pop("clusterName", None)
80
89
  deps.append(m_dict)
81
-
82
90
  render.print_json(deps)
83
91
 
84
92
 
@@ -90,8 +98,14 @@ def format_deployments(
90
98
  def format_deployment(e: Deployment, models_api: aiolirest.PackagedModelsApi) -> List[Any]:
91
99
  model = models_api.models_id_get(e.model)
92
100
  state = e.state
101
+ total_failures = 0
102
+
93
103
  if state is None:
94
104
  state = DeploymentState()
105
+ else:
106
+ failure_info = state.failure_info
107
+ if failure_info:
108
+ total_failures = len(failure_info)
95
109
 
96
110
  secondary_state = e.secondary_state
97
111
  if secondary_state is None:
@@ -103,9 +117,17 @@ def format_deployments(
103
117
  if auto_scaling is None:
104
118
  auto_scaling = Autoscaling()
105
119
 
120
+ if total_failures > 1:
121
+ e.status = f"{e.status}\n({total_failures} errors)"
122
+ elif total_failures == 1:
123
+ e.status = f"{e.status}\n({total_failures} error)"
124
+
125
+ deployment_model_versions = models_api.models_versions_get(model.name)
126
+ version = next(r.mdl_version for r in deployment_model_versions if r.deployment_id == e.id)
106
127
  result = [
107
128
  e.name,
108
129
  model.name,
130
+ version,
109
131
  e.namespace,
110
132
  e.status,
111
133
  e.security.authentication_required,
@@ -118,12 +140,14 @@ def format_deployments(
118
140
  headers = [
119
141
  "Name",
120
142
  "Model",
143
+ "Version",
121
144
  "Namespace",
122
145
  "Status",
123
146
  "Auth Required",
124
147
  "State",
125
148
  "Traffic %",
126
149
  ]
150
+
127
151
  values = [format_deployment(r, packaged_models_api) for r in response]
128
152
  render.tabulate_or_csv(headers, values, args.csv)
129
153
 
@@ -337,7 +361,14 @@ main_cmd = Cmd(
337
361
  help="The name of the deployment. Must begin with a letter, but may contain "
338
362
  "letters, numbers, and hyphen",
339
363
  ),
340
- Arg("--model", help="Model to be deployed", required="true"),
364
+ Arg(
365
+ "--model",
366
+ help=(
367
+ "The package model id, name or versioned-name (evaluated in that "
368
+ "order) to be deployed"
369
+ ),
370
+ required="true",
371
+ ),
341
372
  ]
342
373
  + common_deployment_args,
343
374
  ),
@@ -381,7 +412,13 @@ main_cmd = Cmd(
381
412
  help="The new name of the deployment. Must begin with a letter, but may "
382
413
  "contain letters, numbers, and hyphen",
383
414
  ),
384
- Arg("--model", help="Model to be deployed"),
415
+ Arg(
416
+ "--model",
417
+ help=(
418
+ "The package model id, name or versioned-name (evaluated in that "
419
+ "order) to be deployed"
420
+ ),
421
+ ),
385
422
  Arg("--pause", action="store_true", help="Pause the deployment"),
386
423
  Arg("--resume", action="store_true", help="Resume the deployment"),
387
424
  ]
@@ -1,10 +1,7 @@
1
1
  # © Copyright 2023-2024 Hewlett Packard Enterprise Development LP
2
- import re
3
2
  from argparse import Namespace
4
3
  from typing import Any, List
5
4
 
6
- from pydantic import StrictInt
7
-
8
5
  import aiolirest
9
6
  from aioli import cli
10
7
  from aioli.cli import render
@@ -105,59 +102,29 @@ def create(args: Namespace) -> None:
105
102
  api_instance.models_post(r)
106
103
 
107
104
 
108
- def lookup_model(name: str, args: Namespace, api: aiolirest.PackagedModelsApi) -> PackagedModel:
105
+ def lookup_model(name: str, api: aiolirest.PackagedModelsApi) -> PackagedModel:
109
106
  # From the database, get the model record. If the model exists in multiple versions,
110
107
  # then sufficient version information must be part of the request.
111
- model_count = 0
112
- models: List[PackagedModel] = api.models_get()
113
- for r in models:
114
- if r.name == name:
115
- model = r
116
- model_count += 1
117
-
118
- if model_count == 1:
119
- return model # Found a single version of the specified model
120
- if model_count > 1 and not args.version:
121
- raise_version_required(name, model_count)
122
-
123
- if model_count == 0:
124
- # The specified model does not exist; extract the version suffix if any
125
- regexp = re.compile(r"^(.+)\.[Vv](\d+)$")
126
- m = regexp.match(name)
127
- if m is None:
128
- raise NotFoundException(
129
- f"model {name} not found. Model versions may optionally be specified "
130
- "using the suffix '.v#', for example, '.v1', '.v100'"
131
- )
132
- name = m.group(1)
133
- version = m.group(2)
134
-
135
- # if there is an explicit version specified, then use that
136
- if args.version:
137
- version = args.version
138
-
139
- return lookup_model_and_version(name, version, models)
108
+ models: List[PackagedModel] = api.models_get(name)
109
+ if len(models):
110
+ return models[0]
111
+
112
+ raise NotFoundException(
113
+ f"model {name} not found. Model versions may optionally be specified "
114
+ "using the suffix '.v#', for example, '.v1', '.v100'"
115
+ )
140
116
 
141
117
 
142
118
  def raise_version_required(name: str, count: int) -> None:
143
119
  raise VersionRequiredException(f"specify a version as model {name} exists in {count} versions")
144
120
 
145
121
 
146
- def lookup_model_and_version(name: str, version: str, models: List[PackagedModel]) -> PackagedModel:
147
- # The version may optionally be expressed with a prefix of 'v'
148
- version_no_prefix: str = version.lstrip("vV")
149
- for r in models:
150
- if r.name == name and r.version == StrictInt(version_no_prefix):
151
- return r
152
- raise NotFoundException(f"model {name} version {version} not found")
153
-
154
-
155
122
  @authentication.required
156
123
  def dashboard(args: Namespace) -> None:
157
124
  with cli.setup_session(args) as session:
158
125
  api_instance = aiolirest.PackagedModelsApi(session)
159
126
 
160
- model = lookup_model(args.name, args, api_instance)
127
+ model = lookup_model(args.name, api_instance)
161
128
 
162
129
  observability = api_instance.models_id_observability_get(model.id)
163
130
  launch_dashboard(args, observability.dashboard_url)
@@ -168,7 +135,7 @@ def show_model(args: Namespace) -> None:
168
135
  with cli.setup_session(args) as session:
169
136
  api_instance = aiolirest.PackagedModelsApi(session)
170
137
 
171
- model = lookup_model(args.name, args, api_instance)
138
+ model = lookup_model(args.name, api_instance)
172
139
  registries_api = aiolirest.RegistriesApi(session)
173
140
 
174
141
  rname = lookup_registry_name_by_id(model.registry, registries_api)
@@ -186,7 +153,7 @@ def show_model(args: Namespace) -> None:
186
153
  def update(args: Namespace) -> None:
187
154
  with cli.setup_session(args) as session:
188
155
  api_instance = aiolirest.PackagedModelsApi(session)
189
- found = lookup_model(args.modelname, args, api_instance)
156
+ found = lookup_model(args.modelname, api_instance)
190
157
  request = PackagedModelRequest(
191
158
  description=found.description,
192
159
  image=found.image,
@@ -262,7 +229,7 @@ def update(args: Namespace) -> None:
262
229
  def delete_model(args: Namespace) -> None:
263
230
  with cli.setup_session(args) as session:
264
231
  api_instance = aiolirest.PackagedModelsApi(session)
265
- found = lookup_model_and_version(args.name, args.version, api_instance.models_get())
232
+ found = lookup_model(args.name, api_instance)
266
233
 
267
234
  assert found.id is not None
268
235
  api_instance.models_id_delete(found.id)
@@ -272,7 +239,7 @@ def delete_model(args: Namespace) -> None:
272
239
  def auth_token(args: Namespace) -> None:
273
240
  with cli.setup_session(args) as session:
274
241
  api_instance = aiolirest.PackagedModelsApi(session)
275
- found = lookup_model(args.name, args, api_instance)
242
+ found = lookup_model(args.name, api_instance)
276
243
  assert found.id is not None
277
244
  response = api_instance.models_id_token_get(found.id)
278
245
  t = response.to_dict()
@@ -283,7 +250,7 @@ def auth_token(args: Namespace) -> None:
283
250
  def list_versions(args: Namespace) -> None:
284
251
  with cli.setup_session(args) as session:
285
252
  api_instance = aiolirest.PackagedModelsApi(session)
286
- found = lookup_model(args.name, args, api_instance)
253
+ found = lookup_model(args.name, api_instance)
287
254
  assert found.id is not None
288
255
  response = api_instance.models_versions_get(found.id)
289
256
 
@@ -339,6 +306,14 @@ common_model_args: ArgsDescription = [
339
306
  Arg("--requests-gpu", help="GPU request"),
340
307
  ]
341
308
 
309
+ VERSIONED_MODEL_HELP_MSG = (
310
+ "The packaged model id, name or versioned-name (evaluated in that order). "
311
+ "A versioned-name is the package model name with suffix of the version "
312
+ "with the format 'name.V###' where '###' is the version number. For example, "
313
+ "a model named 'my-model' with a version of '23' would be represented by "
314
+ "versioned-name of: my-model.V23"
315
+ )
316
+
342
317
  main_cmd = Cmd(
343
318
  "m|odel|s",
344
319
  None,
@@ -378,9 +353,8 @@ main_cmd = Cmd(
378
353
  [
379
354
  Arg(
380
355
  "name",
381
- help="The name of the packaged model.",
356
+ help=VERSIONED_MODEL_HELP_MSG,
382
357
  ),
383
- Arg("--version", help="The packaged model version to show"),
384
358
  ],
385
359
  ),
386
360
  # Show command.
@@ -391,13 +365,12 @@ main_cmd = Cmd(
391
365
  [
392
366
  Arg(
393
367
  "name",
394
- help="The name of the packaged model.",
368
+ help=VERSIONED_MODEL_HELP_MSG,
395
369
  ),
396
370
  Group(
397
371
  Arg("--yaml", action="store_true", help="print as YAML", default=True),
398
372
  Arg("--json", action="store_true", help="print as JSON"),
399
373
  ),
400
- Arg("--version", help="The packaged model version to show"),
401
374
  ],
402
375
  ),
403
376
  # Update command
@@ -406,14 +379,16 @@ main_cmd = Cmd(
406
379
  update,
407
380
  "modify a packaged model",
408
381
  [
409
- Arg("modelname", help="The name of the packaged model"),
382
+ Arg(
383
+ "modelname",
384
+ help=VERSIONED_MODEL_HELP_MSG,
385
+ ),
410
386
  Arg(
411
387
  "--name",
412
388
  help="The new name of the packaged model. Must begin with a letter, but may "
413
389
  "contain letters, numbers, and hyphen",
414
390
  ),
415
391
  Arg("--image", help="Docker container image servicing the packaged model"),
416
- Arg("--version", help="The packaged model version to update"),
417
392
  ]
418
393
  + common_model_args,
419
394
  ),
@@ -422,8 +397,10 @@ main_cmd = Cmd(
422
397
  delete_model,
423
398
  "delete a packaged model",
424
399
  [
425
- Arg("name", help="The name of the packaged model"),
426
- Arg("version", help="The packaged model version to delete"),
400
+ Arg(
401
+ "name",
402
+ help=VERSIONED_MODEL_HELP_MSG,
403
+ ),
427
404
  ],
428
405
  ),
429
406
  Cmd(
@@ -431,8 +408,10 @@ main_cmd = Cmd(
431
408
  auth_token,
432
409
  "get packaged model auth token",
433
410
  [
434
- Arg("name", help="The name of the packaged model"),
435
- Arg("--version", help="The version of the packaged model"),
411
+ Arg(
412
+ "name",
413
+ help=VERSIONED_MODEL_HELP_MSG,
414
+ ),
436
415
  ],
437
416
  ),
438
417
  Cmd(
@@ -441,8 +420,10 @@ main_cmd = Cmd(
441
420
  "list of deployment versions for a packaged model",
442
421
  [
443
422
  Arg("--csv", action="store_true", help="print as CSV"),
444
- Arg("name", help="The name of the packaged model"),
445
- Arg("--version", help="The version of the packaged model"),
423
+ Arg(
424
+ "name",
425
+ help=VERSIONED_MODEL_HELP_MSG,
426
+ ),
446
427
  ],
447
428
  ),
448
429
  ],
@@ -327,21 +327,6 @@ class TestCli:
327
327
  ).decode("utf-8")
328
328
  assert yaml_output == yaml_output2, "Expect the same output using .V1"
329
329
 
330
- yaml_output2 = subprocess.check_output(
331
- ["aioli", "model", "show", "iris-tf-keras", "--version", "1"]
332
- ).decode("utf-8")
333
- assert yaml_output == yaml_output2, "Expect the same output using --version 1"
334
-
335
- yaml_output2 = subprocess.check_output(
336
- ["aioli", "model", "show", "iris-tf-keras", "--version", "v1"]
337
- ).decode("utf-8")
338
- assert yaml_output == yaml_output2, "Expect the same output using --version v1"
339
-
340
- yaml_output2 = subprocess.check_output(
341
- ["aioli", "model", "show", "iris-tf-keras", "--version", "V1"]
342
- ).decode("utf-8")
343
- assert yaml_output == yaml_output2, "Expect the same output using --version V1"
344
-
345
330
  # fmt: off
346
331
  def test_model_list_json(self, setup_login: None) -> None:
347
332
  expected = (
@@ -402,7 +387,7 @@ class TestCli:
402
387
  actual = subprocess.check_output(["aioli", "model", "list"]).decode("utf-8")
403
388
 
404
389
  assert (actual.find(expected)) > 0
405
- assert os.system("aioli model delete openllm 1") == 0
390
+ assert os.system("aioli model delete openllm.v1") == 0
406
391
 
407
392
  def test_model_update_bad_model_name(self, setup_login: None) -> None:
408
393
  # Attempt to update model specifying the version incorrectly. Here we specify
@@ -414,7 +399,7 @@ class TestCli:
414
399
  )
415
400
  except subprocess.CalledProcessError as e:
416
401
  assert e.returncode == 1
417
- expected = "Failed to modify a packaged model: model iris-tf-keras.1 not found"
402
+ expected = "Failed to modify a packaged model: model 'iris-tf-keras.1' does not exist."
418
403
  actual: str = e.output.decode("utf-8")
419
404
  assert actual.find(expected) == 0
420
405
 
@@ -472,8 +457,7 @@ class TestCli:
472
457
  # Create a third version of the model using the second version
473
458
  assert (
474
459
  os.system(
475
- "aioli model update iris-tf-keras --name iris-tf-keras "
476
- "--version 2 "
460
+ "aioli model update iris-tf-keras.v2 --name iris-tf-keras "
477
461
  "--registry bento-registry1 "
478
462
  "--url s3://demo-bento-registry/iris-tf-keras_updated_v3 "
479
463
  "--image fictional.registry.example/updated_imagename "
@@ -495,8 +479,7 @@ class TestCli:
495
479
  # (specifying the version with the optional v{n} format)
496
480
  assert (
497
481
  os.system(
498
- "aioli model update iris-tf-keras --name iris-tf-keras-v4 "
499
- "--version v2 "
482
+ "aioli model update iris-tf-keras.v2 --name iris-tf-keras-v4 "
500
483
  "--registry bento-registry1 "
501
484
  "--url s3://demo-bento-registry/iris-tf-keras_updated_v3 "
502
485
  "--image fictional.registry.example/updated_imagename "
@@ -527,7 +510,7 @@ class TestCli:
527
510
  )
528
511
  actual = subprocess.check_output(["aioli", "deployment", "list"]).decode("utf-8")
529
512
  expected = (
530
- "iris-tf-keras-deployment | iris-tf-keras | aioli "
513
+ "iris-tf-keras-deployment | iris-tf-keras | 3 | aioli "
531
514
  "| Deploying | False | Deploying | 0"
532
515
  )
533
516
 
@@ -565,8 +548,9 @@ class TestCli:
565
548
  == 0
566
549
  )
567
550
  actual = subprocess.check_output(["aioli", "deployment", "list"]).decode("utf-8")
551
+
568
552
  expected = (
569
- "iris-tf-keras-deployment | iris-tf-keras | aioli "
553
+ "iris-tf-keras-deployment | iris-tf-keras | 3 | aioli "
570
554
  "| Deploying | True | Deploying | 0"
571
555
  )
572
556
  assert actual.find(expected) > 0
@@ -621,7 +605,8 @@ class TestCli:
621
605
  ' "status": "Deploying",\n'
622
606
  ' "trafficPercentage": 0\n'
623
607
  ' },\n' # noqa: Q000
624
- ' "status": "Deploying"\n'
608
+ ' "status": "Deploying",\n'
609
+ ' "version": "3"\n'
625
610
  ' }\n' # noqa: Q000
626
611
  ']\n' # noqa: Q000
627
612
  )
@@ -710,11 +695,11 @@ class TestCli:
710
695
  assert os.system("aioli deployment delete iris-tf-keras-deployment-2") == 0
711
696
 
712
697
  def test_model_delete(self, setup_login: None) -> None:
713
- assert os.system("aioli model delete iris-tf-keras1 1") == 0
714
- assert os.system("aioli model delete iris-tf-keras 1") == 0
715
- assert os.system("aioli model delete iris-tf-keras 2") == 0
716
- assert os.system("aioli model delete iris-tf-keras 3") == 0
717
- assert os.system("aioli model delete iris-tf-keras-v4 1") == 0
698
+ assert os.system("aioli model delete iris-tf-keras1") == 0
699
+ assert os.system("aioli model delete iris-tf-keras.v1") == 0
700
+ assert os.system("aioli model delete iris-tf-keras.v2") == 0
701
+ assert os.system("aioli model delete iris-tf-keras.v3") == 0
702
+ assert os.system("aioli model delete iris-tf-keras-v4.v1") == 0
718
703
 
719
704
  def test_registry_delete(self, setup_login: None) -> None:
720
705
  # Delete the registry entry
@@ -77,7 +77,11 @@ def check_version(parsed_args: argparse.Namespace) -> None:
77
77
  print(
78
78
  termcolor.colored(
79
79
  "Controller version {} is less than CLI version {}. "
80
- "Consider upgrading the controller.".format(controller_version, client_version),
80
+ "This CLI may utilize features not supported by an older "
81
+ "controller. Install a compatible CLI with: "
82
+ " pip install aioli-sdk=={}".format(
83
+ controller_version, client_version, controller_version
84
+ ),
81
85
  "yellow",
82
86
  ),
83
87
  file=sys.stderr,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aioli-sdk
3
- Version: 0.2.4.dev10
3
+ Version: 0.2.4.dev12
4
4
  Summary: Aioli (AI OnLine Inference), a platform for deploying AI models at scale.
5
5
  Home-page: https://github.com/determined-ai/aioli
6
6
  Author: HPE AI Solutions
@@ -7,7 +7,7 @@
7
7
 
8
8
  HPE MLIS is *Aioli* -- The AI On-line Inference Platform that enables easy deployment, tracking, and serving of your packaged models regardless of your preferred AI framework.
9
9
 
10
- The version of the OpenAPI document: 0.2.4-dev10
10
+ The version of the OpenAPI document: 0.2.4-dev12
11
11
  Contact: community@determined-ai
12
12
  Generated by OpenAPI Generator (https://openapi-generator.tech)
13
13
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  HPE MLIS is *Aioli* -- The AI On-line Inference Platform that enables easy deployment, tracking, and serving of your packaged models regardless of your preferred AI framework.
7
7
 
8
- The version of the OpenAPI document: 0.2.4-dev10
8
+ The version of the OpenAPI document: 0.2.4-dev12
9
9
  Contact: community@determined-ai
10
10
  Generated by OpenAPI Generator (https://openapi-generator.tech)
11
11
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  HPE MLIS is *Aioli* -- The AI On-line Inference Platform that enables easy deployment, tracking, and serving of your packaged models regardless of your preferred AI framework.
7
7
 
8
- The version of the OpenAPI document: 0.2.4-dev10
8
+ The version of the OpenAPI document: 0.2.4-dev12
9
9
  Contact: community@determined-ai
10
10
  Generated by OpenAPI Generator (https://openapi-generator.tech)
11
11
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  HPE MLIS is *Aioli* -- The AI On-line Inference Platform that enables easy deployment, tracking, and serving of your packaged models regardless of your preferred AI framework.
7
7
 
8
- The version of the OpenAPI document: 0.2.4-dev10
8
+ The version of the OpenAPI document: 0.2.4-dev12
9
9
  Contact: community@determined-ai
10
10
  Generated by OpenAPI Generator (https://openapi-generator.tech)
11
11