anyscale 0.26.15__py3-none-any.whl → 0.26.17__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.
- anyscale/_private/anyscale_client/anyscale_client.py +4 -2
- anyscale/_private/anyscale_client/common.py +6 -4
- anyscale/_private/anyscale_client/fake_anyscale_client.py +15 -6
- anyscale/_private/docgen/__main__.py +4 -4
- anyscale/_private/docgen/generator.py +2 -2
- anyscale/_private/sdk/__init__.py +2 -2
- anyscale/_private/workload/workload_sdk.py +6 -4
- anyscale/aggregated_instance_usage/commands.py +6 -2
- anyscale/anyscale-cloud-setup-gcp.yaml +2 -0
- anyscale/client/README.md +11 -6
- anyscale/client/openapi_client/__init__.py +8 -4
- anyscale/client/openapi_client/api/default_api.py +402 -270
- anyscale/client/openapi_client/models/__init__.py +8 -4
- anyscale/client/openapi_client/models/alert_type.py +11 -2
- anyscale/client/openapi_client/models/create_job_queue_requests.py +3 -32
- anyscale/client/openapi_client/models/i_know_response.py +174 -0
- anyscale/client/openapi_client/models/i_know_time_series_event.py +148 -0
- anyscale/client/openapi_client/models/job_report.py +199 -0
- anyscale/client/openapi_client/models/job_with_report.py +254 -0
- anyscale/client/openapi_client/models/jobwithreport_list_response.py +147 -0
- anyscale/client/openapi_client/models/{product_autoscaler_flag.py → list_ray_sessions_response.py} +22 -23
- anyscale/client/openapi_client/models/{productautoscalerflag_response.py → listraysessionsresponse_response.py} +11 -11
- anyscale/client/openapi_client/models/metric.py +133 -3
- anyscale/client/openapi_client/models/ray_session.py +121 -0
- anyscale/cloud/__init__.py +2 -2
- anyscale/cloud/_private/cloud_sdk.py +2 -2
- anyscale/cloud/commands.py +9 -6
- anyscale/cloud_utils.py +5 -4
- anyscale/cluster_compute.py +2 -2
- anyscale/cluster_env.py +2 -0
- anyscale/commands/cloud_commands.py +43 -0
- anyscale/commands/login_commands.py +24 -3
- anyscale/commands/schedule_commands.py +2 -1
- anyscale/compute_config/commands.py +15 -7
- anyscale/controllers/cloud_controller.py +151 -8
- anyscale/controllers/cluster_controller.py +1 -0
- anyscale/controllers/job_controller.py +1 -1
- anyscale/controllers/service_controller.py +1 -0
- anyscale/image/commands.py +6 -6
- anyscale/job/_private/job_sdk.py +22 -24
- anyscale/job/commands.py +12 -12
- anyscale/organization_invitation/commands.py +11 -7
- anyscale/project/__init__.py +2 -2
- anyscale/project/_private/project_sdk.py +2 -2
- anyscale/project/commands.py +3 -3
- anyscale/project_utils.py +1 -1
- anyscale/resource_quota/commands.py +18 -10
- anyscale/schedule/commands.py +12 -8
- anyscale/sdk/anyscale_client/sdk.py +1 -0
- anyscale/service/commands.py +17 -17
- anyscale/service_account/commands.py +12 -10
- anyscale/user/commands.py +5 -3
- anyscale/utils/gcp_utils.py +25 -9
- anyscale/version.py +1 -1
- anyscale/workspace/__init__.py +1 -1
- anyscale/workspace/_private/workspace_sdk.py +5 -3
- anyscale/workspace/commands.py +26 -24
- {anyscale-0.26.15.dist-info → anyscale-0.26.17.dist-info}/METADATA +1 -1
- {anyscale-0.26.15.dist-info → anyscale-0.26.17.dist-info}/RECORD +64 -60
- anyscale/client/openapi_client/models/aviary_model_config_v2.py +0 -358
- anyscale/client/openapi_client/models/finish_ft_job_request_v2.py +0 -183
- {anyscale-0.26.15.dist-info → anyscale-0.26.17.dist-info}/LICENSE +0 -0
- {anyscale-0.26.15.dist-info → anyscale-0.26.17.dist-info}/NOTICE +0 -0
- {anyscale-0.26.15.dist-info → anyscale-0.26.17.dist-info}/WHEEL +0 -0
- {anyscale-0.26.15.dist-info → anyscale-0.26.17.dist-info}/entry_points.txt +0 -0
- {anyscale-0.26.15.dist-info → anyscale-0.26.17.dist-info}/top_level.txt +0 -0
@@ -36,17 +36,27 @@ class Metric(object):
|
|
36
36
|
'name': 'str',
|
37
37
|
'current_value': 'float',
|
38
38
|
'max_over_time': 'float',
|
39
|
-
'
|
39
|
+
'min_over_time': 'float',
|
40
|
+
'median_over_time': 'float',
|
41
|
+
'rate': 'float',
|
42
|
+
'max_rate': 'float',
|
43
|
+
'min_rate': 'float',
|
44
|
+
'median_rate': 'float'
|
40
45
|
}
|
41
46
|
|
42
47
|
attribute_map = {
|
43
48
|
'name': 'name',
|
44
49
|
'current_value': 'current_value',
|
45
50
|
'max_over_time': 'max_over_time',
|
46
|
-
'
|
51
|
+
'min_over_time': 'min_over_time',
|
52
|
+
'median_over_time': 'median_over_time',
|
53
|
+
'rate': 'rate',
|
54
|
+
'max_rate': 'max_rate',
|
55
|
+
'min_rate': 'min_rate',
|
56
|
+
'median_rate': 'median_rate'
|
47
57
|
}
|
48
58
|
|
49
|
-
def __init__(self, name=None, current_value=None, max_over_time=None, rate=None, local_vars_configuration=None): # noqa: E501
|
59
|
+
def __init__(self, name=None, current_value=None, max_over_time=None, min_over_time=None, median_over_time=None, rate=None, max_rate=None, min_rate=None, median_rate=None, local_vars_configuration=None): # noqa: E501
|
50
60
|
"""Metric - a model defined in OpenAPI""" # noqa: E501
|
51
61
|
if local_vars_configuration is None:
|
52
62
|
local_vars_configuration = Configuration()
|
@@ -55,7 +65,12 @@ class Metric(object):
|
|
55
65
|
self._name = None
|
56
66
|
self._current_value = None
|
57
67
|
self._max_over_time = None
|
68
|
+
self._min_over_time = None
|
69
|
+
self._median_over_time = None
|
58
70
|
self._rate = None
|
71
|
+
self._max_rate = None
|
72
|
+
self._min_rate = None
|
73
|
+
self._median_rate = None
|
59
74
|
self.discriminator = None
|
60
75
|
|
61
76
|
self.name = name
|
@@ -63,8 +78,18 @@ class Metric(object):
|
|
63
78
|
self.current_value = current_value
|
64
79
|
if max_over_time is not None:
|
65
80
|
self.max_over_time = max_over_time
|
81
|
+
if min_over_time is not None:
|
82
|
+
self.min_over_time = min_over_time
|
83
|
+
if median_over_time is not None:
|
84
|
+
self.median_over_time = median_over_time
|
66
85
|
if rate is not None:
|
67
86
|
self.rate = rate
|
87
|
+
if max_rate is not None:
|
88
|
+
self.max_rate = max_rate
|
89
|
+
if min_rate is not None:
|
90
|
+
self.min_rate = min_rate
|
91
|
+
if median_rate is not None:
|
92
|
+
self.median_rate = median_rate
|
68
93
|
|
69
94
|
@property
|
70
95
|
def name(self):
|
@@ -131,6 +156,48 @@ class Metric(object):
|
|
131
156
|
|
132
157
|
self._max_over_time = max_over_time
|
133
158
|
|
159
|
+
@property
|
160
|
+
def min_over_time(self):
|
161
|
+
"""Gets the min_over_time of this Metric. # noqa: E501
|
162
|
+
|
163
|
+
|
164
|
+
:return: The min_over_time of this Metric. # noqa: E501
|
165
|
+
:rtype: float
|
166
|
+
"""
|
167
|
+
return self._min_over_time
|
168
|
+
|
169
|
+
@min_over_time.setter
|
170
|
+
def min_over_time(self, min_over_time):
|
171
|
+
"""Sets the min_over_time of this Metric.
|
172
|
+
|
173
|
+
|
174
|
+
:param min_over_time: The min_over_time of this Metric. # noqa: E501
|
175
|
+
:type: float
|
176
|
+
"""
|
177
|
+
|
178
|
+
self._min_over_time = min_over_time
|
179
|
+
|
180
|
+
@property
|
181
|
+
def median_over_time(self):
|
182
|
+
"""Gets the median_over_time of this Metric. # noqa: E501
|
183
|
+
|
184
|
+
|
185
|
+
:return: The median_over_time of this Metric. # noqa: E501
|
186
|
+
:rtype: float
|
187
|
+
"""
|
188
|
+
return self._median_over_time
|
189
|
+
|
190
|
+
@median_over_time.setter
|
191
|
+
def median_over_time(self, median_over_time):
|
192
|
+
"""Sets the median_over_time of this Metric.
|
193
|
+
|
194
|
+
|
195
|
+
:param median_over_time: The median_over_time of this Metric. # noqa: E501
|
196
|
+
:type: float
|
197
|
+
"""
|
198
|
+
|
199
|
+
self._median_over_time = median_over_time
|
200
|
+
|
134
201
|
@property
|
135
202
|
def rate(self):
|
136
203
|
"""Gets the rate of this Metric. # noqa: E501
|
@@ -152,6 +219,69 @@ class Metric(object):
|
|
152
219
|
|
153
220
|
self._rate = rate
|
154
221
|
|
222
|
+
@property
|
223
|
+
def max_rate(self):
|
224
|
+
"""Gets the max_rate of this Metric. # noqa: E501
|
225
|
+
|
226
|
+
|
227
|
+
:return: The max_rate of this Metric. # noqa: E501
|
228
|
+
:rtype: float
|
229
|
+
"""
|
230
|
+
return self._max_rate
|
231
|
+
|
232
|
+
@max_rate.setter
|
233
|
+
def max_rate(self, max_rate):
|
234
|
+
"""Sets the max_rate of this Metric.
|
235
|
+
|
236
|
+
|
237
|
+
:param max_rate: The max_rate of this Metric. # noqa: E501
|
238
|
+
:type: float
|
239
|
+
"""
|
240
|
+
|
241
|
+
self._max_rate = max_rate
|
242
|
+
|
243
|
+
@property
|
244
|
+
def min_rate(self):
|
245
|
+
"""Gets the min_rate of this Metric. # noqa: E501
|
246
|
+
|
247
|
+
|
248
|
+
:return: The min_rate of this Metric. # noqa: E501
|
249
|
+
:rtype: float
|
250
|
+
"""
|
251
|
+
return self._min_rate
|
252
|
+
|
253
|
+
@min_rate.setter
|
254
|
+
def min_rate(self, min_rate):
|
255
|
+
"""Sets the min_rate of this Metric.
|
256
|
+
|
257
|
+
|
258
|
+
:param min_rate: The min_rate of this Metric. # noqa: E501
|
259
|
+
:type: float
|
260
|
+
"""
|
261
|
+
|
262
|
+
self._min_rate = min_rate
|
263
|
+
|
264
|
+
@property
|
265
|
+
def median_rate(self):
|
266
|
+
"""Gets the median_rate of this Metric. # noqa: E501
|
267
|
+
|
268
|
+
|
269
|
+
:return: The median_rate of this Metric. # noqa: E501
|
270
|
+
:rtype: float
|
271
|
+
"""
|
272
|
+
return self._median_rate
|
273
|
+
|
274
|
+
@median_rate.setter
|
275
|
+
def median_rate(self, median_rate):
|
276
|
+
"""Sets the median_rate of this Metric.
|
277
|
+
|
278
|
+
|
279
|
+
:param median_rate: The median_rate of this Metric. # noqa: E501
|
280
|
+
:type: float
|
281
|
+
"""
|
282
|
+
|
283
|
+
self._median_rate = median_rate
|
284
|
+
|
155
285
|
def to_dict(self):
|
156
286
|
"""Returns the model properties as a dict"""
|
157
287
|
result = {}
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
"""
|
4
|
+
Managed Ray API
|
5
|
+
|
6
|
+
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) # noqa: E501
|
7
|
+
|
8
|
+
The version of the OpenAPI document: 0.1.0
|
9
|
+
Generated by: https://openapi-generator.tech
|
10
|
+
"""
|
11
|
+
|
12
|
+
|
13
|
+
import pprint
|
14
|
+
import re # noqa: F401
|
15
|
+
|
16
|
+
import six
|
17
|
+
|
18
|
+
from openapi_client.configuration import Configuration
|
19
|
+
|
20
|
+
|
21
|
+
class RaySession(object):
|
22
|
+
"""NOTE: This class is auto generated by OpenAPI Generator.
|
23
|
+
Ref: https://openapi-generator.tech
|
24
|
+
|
25
|
+
Do not edit the class manually.
|
26
|
+
"""
|
27
|
+
|
28
|
+
"""
|
29
|
+
Attributes:
|
30
|
+
openapi_types (dict): The key is attribute name
|
31
|
+
and the value is attribute type.
|
32
|
+
attribute_map (dict): The key is attribute name
|
33
|
+
and the value is json key in definition.
|
34
|
+
"""
|
35
|
+
openapi_types = {
|
36
|
+
'ray_session_name': 'str'
|
37
|
+
}
|
38
|
+
|
39
|
+
attribute_map = {
|
40
|
+
'ray_session_name': 'ray_session_name'
|
41
|
+
}
|
42
|
+
|
43
|
+
def __init__(self, ray_session_name=None, local_vars_configuration=None): # noqa: E501
|
44
|
+
"""RaySession - a model defined in OpenAPI""" # noqa: E501
|
45
|
+
if local_vars_configuration is None:
|
46
|
+
local_vars_configuration = Configuration()
|
47
|
+
self.local_vars_configuration = local_vars_configuration
|
48
|
+
|
49
|
+
self._ray_session_name = None
|
50
|
+
self.discriminator = None
|
51
|
+
|
52
|
+
self.ray_session_name = ray_session_name
|
53
|
+
|
54
|
+
@property
|
55
|
+
def ray_session_name(self):
|
56
|
+
"""Gets the ray_session_name of this RaySession. # noqa: E501
|
57
|
+
|
58
|
+
|
59
|
+
:return: The ray_session_name of this RaySession. # noqa: E501
|
60
|
+
:rtype: str
|
61
|
+
"""
|
62
|
+
return self._ray_session_name
|
63
|
+
|
64
|
+
@ray_session_name.setter
|
65
|
+
def ray_session_name(self, ray_session_name):
|
66
|
+
"""Sets the ray_session_name of this RaySession.
|
67
|
+
|
68
|
+
|
69
|
+
:param ray_session_name: The ray_session_name of this RaySession. # noqa: E501
|
70
|
+
:type: str
|
71
|
+
"""
|
72
|
+
if self.local_vars_configuration.client_side_validation and ray_session_name is None: # noqa: E501
|
73
|
+
raise ValueError("Invalid value for `ray_session_name`, must not be `None`") # noqa: E501
|
74
|
+
|
75
|
+
self._ray_session_name = ray_session_name
|
76
|
+
|
77
|
+
def to_dict(self):
|
78
|
+
"""Returns the model properties as a dict"""
|
79
|
+
result = {}
|
80
|
+
|
81
|
+
for attr, _ in six.iteritems(self.openapi_types):
|
82
|
+
value = getattr(self, attr)
|
83
|
+
if isinstance(value, list):
|
84
|
+
result[attr] = list(map(
|
85
|
+
lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
|
86
|
+
value
|
87
|
+
))
|
88
|
+
elif hasattr(value, "to_dict"):
|
89
|
+
result[attr] = value.to_dict()
|
90
|
+
elif isinstance(value, dict):
|
91
|
+
result[attr] = dict(map(
|
92
|
+
lambda item: (item[0], item[1].to_dict())
|
93
|
+
if hasattr(item[1], "to_dict") else item,
|
94
|
+
value.items()
|
95
|
+
))
|
96
|
+
else:
|
97
|
+
result[attr] = value
|
98
|
+
|
99
|
+
return result
|
100
|
+
|
101
|
+
def to_str(self):
|
102
|
+
"""Returns the string representation of the model"""
|
103
|
+
return pprint.pformat(self.to_dict())
|
104
|
+
|
105
|
+
def __repr__(self):
|
106
|
+
"""For `print` and `pprint`"""
|
107
|
+
return self.to_str()
|
108
|
+
|
109
|
+
def __eq__(self, other):
|
110
|
+
"""Returns true if both objects are equal"""
|
111
|
+
if not isinstance(other, RaySession):
|
112
|
+
return False
|
113
|
+
|
114
|
+
return self.to_dict() == other.to_dict()
|
115
|
+
|
116
|
+
def __ne__(self, other):
|
117
|
+
"""Returns true if both objects are not equal"""
|
118
|
+
if not isinstance(other, RaySession):
|
119
|
+
return True
|
120
|
+
|
121
|
+
return self.to_dict() != other.to_dict()
|
anyscale/cloud/__init__.py
CHANGED
@@ -38,14 +38,14 @@ class CloudSDK:
|
|
38
38
|
)
|
39
39
|
def add_collaborators(
|
40
40
|
self, cloud: str, collaborators: List[CreateCloudCollaborator],
|
41
|
-
) ->
|
41
|
+
) -> None:
|
42
42
|
"""
|
43
43
|
Batch add collaborators to a cloud.
|
44
44
|
|
45
45
|
:param cloud: The cloud to add users to.
|
46
46
|
:param collaborators: The list of collaborators to add to the cloud.
|
47
47
|
"""
|
48
|
-
|
48
|
+
self._private_sdk.add_collaborators(cloud, collaborators)
|
49
49
|
|
50
50
|
@sdk_docs(
|
51
51
|
doc_py_example=_GET_EXAMPLE, arg_docstrings=_GET_ARG_DOCSTRINGS,
|
@@ -16,10 +16,10 @@ from anyscale.cloud.models import (
|
|
16
16
|
class PrivateCloudSDK(BaseSDK):
|
17
17
|
def add_collaborators(
|
18
18
|
self, cloud: str, collaborators: List[CreateCloudCollaborator]
|
19
|
-
) ->
|
19
|
+
) -> None:
|
20
20
|
cloud_id = self.client.get_cloud_id(cloud_name=cloud, compute_config_id=None)
|
21
21
|
|
22
|
-
|
22
|
+
self.client.add_cloud_collaborators(
|
23
23
|
cloud_id=cloud_id,
|
24
24
|
collaborators=[
|
25
25
|
CreateCloudCollaboratorModel(
|
anyscale/cloud/commands.py
CHANGED
@@ -39,7 +39,10 @@ _ADD_COLLABORATORS_ARG_DOCSTRINGS = {
|
|
39
39
|
arg_docstrings=_ADD_COLLABORATORS_ARG_DOCSTRINGS,
|
40
40
|
)
|
41
41
|
def add_collaborators(
|
42
|
-
cloud: str,
|
42
|
+
cloud: str,
|
43
|
+
collaborators: List[CreateCloudCollaborator],
|
44
|
+
*,
|
45
|
+
_private_sdk: Optional[PrivateCloudSDK] = None,
|
43
46
|
) -> str:
|
44
47
|
"""
|
45
48
|
Batch add collaborators to a cloud.
|
@@ -47,7 +50,7 @@ def add_collaborators(
|
|
47
50
|
:param cloud: The cloud to add users to.
|
48
51
|
:param collaborators: The list of collaborators to add to the cloud.
|
49
52
|
"""
|
50
|
-
return
|
53
|
+
return _private_sdk.add_collaborators(cloud, collaborators) # type: ignore
|
51
54
|
|
52
55
|
|
53
56
|
_GET_EXAMPLE = """
|
@@ -76,7 +79,7 @@ def get(
|
|
76
79
|
id: Optional[str] = None, # noqa: A002
|
77
80
|
name: Optional[str] = None,
|
78
81
|
*,
|
79
|
-
|
82
|
+
_private_sdk: Optional[PrivateCloudSDK] = None,
|
80
83
|
) -> Optional[Cloud]:
|
81
84
|
"""
|
82
85
|
Get the cloud model for the provided cloud ID or name.
|
@@ -87,7 +90,7 @@ def get(
|
|
87
90
|
:param name: The name of the cloud to retrieve.
|
88
91
|
:return: A `Cloud` object if found, otherwise `None`.
|
89
92
|
"""
|
90
|
-
return
|
93
|
+
return _private_sdk.get(id=id, name=name) # type: ignore
|
91
94
|
|
92
95
|
|
93
96
|
_GET_DEFAULT_EXAMPLE = """
|
@@ -104,10 +107,10 @@ default_cloud = anyscale.cloud.get_default()
|
|
104
107
|
doc_py_example=_GET_DEFAULT_EXAMPLE,
|
105
108
|
arg_docstrings={},
|
106
109
|
)
|
107
|
-
def get_default(*,
|
110
|
+
def get_default(*, _private_sdk: Optional[PrivateCloudSDK] = None) -> Optional[Cloud]:
|
108
111
|
"""
|
109
112
|
Get the user's default cloud.
|
110
113
|
|
111
114
|
:return: The default `Cloud` object if it exists, otherwise `None`.
|
112
115
|
"""
|
113
|
-
return
|
116
|
+
return _private_sdk.get_default() # type: ignore
|
anyscale/cloud_utils.py
CHANGED
@@ -100,6 +100,7 @@ def get_cloud_resource_by_cloud_id(
|
|
100
100
|
) -> Union[CreateCloudResource, CreateCloudResourceGCP]:
|
101
101
|
if anyscale_api_client is None:
|
102
102
|
anyscale_api_client = get_auth_api_client().anyscale_api_client
|
103
|
+
assert anyscale_api_client is not None
|
103
104
|
cloud_resource = None
|
104
105
|
if cloud_provider == "AWS":
|
105
106
|
cloud = anyscale_api_client.get_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_router_cloud_id_get(
|
@@ -133,12 +134,12 @@ def get_last_used_cloud(
|
|
133
134
|
if anyscale_api_client is None:
|
134
135
|
anyscale_api_client = get_auth_api_client().anyscale_api_client
|
135
136
|
if project_id:
|
136
|
-
cloud_id = anyscale_api_client.get_project(project_id).result.last_used_cloud_id
|
137
|
+
cloud_id = anyscale_api_client.get_project(project_id).result.last_used_cloud_id # type: ignore
|
137
138
|
else:
|
138
139
|
cloud_id = None
|
139
140
|
if cloud_id:
|
140
141
|
try:
|
141
|
-
cloud = anyscale_api_client.get_cloud(cloud_id).result
|
142
|
+
cloud = anyscale_api_client.get_cloud(cloud_id).result # type: ignore
|
142
143
|
except Exception: # noqa: BLE001
|
143
144
|
raise click.ClickException(
|
144
145
|
f"Failed to fetch Cloud with id: {cloud_id}. Please specify `cloud` in the command."
|
@@ -175,12 +176,12 @@ def get_all_clouds(
|
|
175
176
|
if anyscale_api_client is None:
|
176
177
|
anyscale_api_client = get_auth_api_client().anyscale_api_client
|
177
178
|
|
178
|
-
cloud_list_response = anyscale_api_client.search_clouds({"paging": {"count": 50}})
|
179
|
+
cloud_list_response = anyscale_api_client.search_clouds({"paging": {"count": 50}}) # type: ignore
|
179
180
|
all_clouds = cloud_list_response.results
|
180
181
|
next_paging_token = cloud_list_response.metadata.next_paging_token
|
181
182
|
|
182
183
|
while next_paging_token:
|
183
|
-
cloud_list_response = anyscale_api_client.search_clouds(
|
184
|
+
cloud_list_response = anyscale_api_client.search_clouds( # type: ignore
|
184
185
|
{"paging": {"count": 50, "paging_token": next_paging_token}}
|
185
186
|
)
|
186
187
|
next_paging_token = cloud_list_response.metadata.next_paging_token
|
anyscale/cluster_compute.py
CHANGED
@@ -43,7 +43,7 @@ def get_default_cluster_compute(
|
|
43
43
|
cloud_name = get_last_used_cloud(project_id, anyscale_api_client)
|
44
44
|
|
45
45
|
cloud_id, _ = get_cloud_id_and_name(api_client, cloud_name=cloud_name)
|
46
|
-
config_object = anyscale_api_client.get_default_compute_config(cloud_id).result
|
46
|
+
config_object = anyscale_api_client.get_default_compute_config(cloud_id).result # type: ignore
|
47
47
|
compute_template = register_compute_template(config_object, api_client=api_client)
|
48
48
|
return compute_template
|
49
49
|
|
@@ -156,7 +156,7 @@ def get_selected_cloud_id_or_default(
|
|
156
156
|
api_client=api_client, cloud_id=cloud_id, cloud_name=cloud_name,
|
157
157
|
)
|
158
158
|
elif cluster_compute_id:
|
159
|
-
parent_cloud_id = anyscale_api_client.get_cluster_compute(
|
159
|
+
parent_cloud_id = anyscale_api_client.get_cluster_compute( # type: ignore
|
160
160
|
cluster_compute_id
|
161
161
|
).result.config.cloud_id
|
162
162
|
elif cluster_compute_config:
|
anyscale/cluster_env.py
CHANGED
@@ -114,6 +114,7 @@ def get_cluster_env_from_name(
|
|
114
114
|
|
115
115
|
if anyscale_api_client is None:
|
116
116
|
anyscale_api_client = get_auth_api_client().anyscale_api_client
|
117
|
+
assert anyscale_api_client is not None
|
117
118
|
cluster_envs = anyscale_api_client.search_cluster_environments(
|
118
119
|
{"name": {"equals": cluster_env_name}, "paging": {"count": 1}}
|
119
120
|
).results
|
@@ -164,6 +165,7 @@ def validate_successful_build(
|
|
164
165
|
|
165
166
|
if anyscale_api_client is None:
|
166
167
|
anyscale_api_client = get_auth_api_client().anyscale_api_client
|
168
|
+
assert anyscale_api_client is not None
|
167
169
|
build = anyscale_api_client.get_cluster_environment_build(build_id).result
|
168
170
|
if build.status != "succeeded":
|
169
171
|
cluster_env = anyscale_api_client.get_cluster_environment(
|
@@ -1166,3 +1166,46 @@ def get_default_cloud() -> None:
|
|
1166
1166
|
|
1167
1167
|
except ValueError as e:
|
1168
1168
|
log.error(f"Error retrieving default cloud: {e}")
|
1169
|
+
|
1170
|
+
|
1171
|
+
@cloud_cli.command(
|
1172
|
+
name="jobs-report",
|
1173
|
+
help=(
|
1174
|
+
"Generate a report of the jobs created in the last 7 days in HTML format. "
|
1175
|
+
"Shows unused CPU-hours, unused GPU-hours, and other data."
|
1176
|
+
),
|
1177
|
+
cls=AnyscaleCommand,
|
1178
|
+
hidden=True,
|
1179
|
+
)
|
1180
|
+
@click.option(
|
1181
|
+
"--cloud-id",
|
1182
|
+
help="ID of the cloud to generate a report on.",
|
1183
|
+
type=str,
|
1184
|
+
required=True,
|
1185
|
+
)
|
1186
|
+
@click.option(
|
1187
|
+
"--csv",
|
1188
|
+
help="Outputs the report in CSV format.",
|
1189
|
+
type=bool,
|
1190
|
+
required=False,
|
1191
|
+
default=False,
|
1192
|
+
)
|
1193
|
+
@click.option(
|
1194
|
+
"--out",
|
1195
|
+
help="Output file name for the report.",
|
1196
|
+
type=str,
|
1197
|
+
required=False,
|
1198
|
+
default="jobs_report.html",
|
1199
|
+
)
|
1200
|
+
def generate_jobs_report(cloud_id: str, csv: bool, out: str) -> None:
|
1201
|
+
"""
|
1202
|
+
Generate a report of the jobs created in the last 7 days in HTML format.
|
1203
|
+
Shows unused CPU-hours, unused GPU-hours, and other data.
|
1204
|
+
:param cloud_id: The ID of the cloud to generate a report on.
|
1205
|
+
:param csv: Outputs the report in CSV format.
|
1206
|
+
:param out: Output file name for the report.
|
1207
|
+
"""
|
1208
|
+
try:
|
1209
|
+
CloudController().generate_jobs_report(cloud_id, csv, out)
|
1210
|
+
except ValueError as e:
|
1211
|
+
log.error(f"Error generating jobs report: {e}")
|
@@ -32,7 +32,10 @@ def get_unauthenticated_openapi_client():
|
|
32
32
|
default=7,
|
33
33
|
help="Expire the token after this many days.",
|
34
34
|
)
|
35
|
-
|
35
|
+
@click.option(
|
36
|
+
"--no-browser", is_flag=True, default=False, help="Do not open the browser.",
|
37
|
+
)
|
38
|
+
def anyscale_login(no_expire: bool, expire_in_days: int, no_browser: bool) -> None:
|
36
39
|
"""Log in to Anyscale using a URL
|
37
40
|
This is the only unauthenticated API usage in the CLI."""
|
38
41
|
if expire_in_days < 0 or no_expire:
|
@@ -52,8 +55,26 @@ def anyscale_login(no_expire: bool, expire_in_days: int) -> None:
|
|
52
55
|
|
53
56
|
# Open the URL in the browser. This will work on most platforms.
|
54
57
|
# OK to suppress any uncaught exceptions, because the URL will be printed out anyway.
|
55
|
-
|
56
|
-
|
58
|
+
if not no_browser:
|
59
|
+
with contextlib.suppress(Exception):
|
60
|
+
try:
|
61
|
+
# Only attempt to open browser if it's not a CLI browser (which doesn't support JavaScript)
|
62
|
+
if webbrowser.get().basename not in [
|
63
|
+
"www-browser",
|
64
|
+
"elinks",
|
65
|
+
"links",
|
66
|
+
"lynx",
|
67
|
+
"w3m",
|
68
|
+
]:
|
69
|
+
webbrowser.open_new_tab(r.url)
|
70
|
+
else:
|
71
|
+
log.info(
|
72
|
+
"Could not open a JavaScript-capable web browser. Open the above URL in your browser manually."
|
73
|
+
)
|
74
|
+
except webbrowser.Error:
|
75
|
+
log.info(
|
76
|
+
"Could not open a web browser. Open the above URL in your browser manually."
|
77
|
+
)
|
57
78
|
|
58
79
|
# give user 3 minutes to log in (3 seconds per attempt)
|
59
80
|
for _i in range(60):
|
@@ -11,7 +11,7 @@ from anyscale.cli_logger import BlockLogger
|
|
11
11
|
from anyscale.commands import command_examples
|
12
12
|
from anyscale.commands.util import AnyscaleCommand, LegacyAnyscaleCommand
|
13
13
|
from anyscale.controllers.schedule_controller import ScheduleController
|
14
|
-
from anyscale.schedule.models import ScheduleConfig, ScheduleState
|
14
|
+
from anyscale.schedule.models import JobConfig, ScheduleConfig, ScheduleState
|
15
15
|
|
16
16
|
|
17
17
|
log = BlockLogger() # CLI Logger
|
@@ -85,6 +85,7 @@ def apply(config_file: str, name: Optional[str],) -> None:
|
|
85
85
|
config = ScheduleConfig.from_yaml(config_file)
|
86
86
|
|
87
87
|
if name is not None:
|
88
|
+
assert isinstance(config.job_config, JobConfig)
|
88
89
|
config = config.options(job_config=config.job_config.options(name=name),)
|
89
90
|
|
90
91
|
log.info(f"Applying schedule with config {config}.")
|
@@ -45,13 +45,16 @@ _CREATE_ARG_DOCSTRINGS = {
|
|
45
45
|
arg_docstrings=_CREATE_ARG_DOCSTRINGS,
|
46
46
|
)
|
47
47
|
def create(
|
48
|
-
config: ComputeConfig,
|
48
|
+
config: ComputeConfig,
|
49
|
+
*,
|
50
|
+
name: Optional[str],
|
51
|
+
_private_sdk: Optional[PrivateComputeConfigSDK] = None,
|
49
52
|
) -> str:
|
50
53
|
"""Create a new version of a compute config.
|
51
54
|
|
52
55
|
Returns the full name of the registered compute config, including the version.
|
53
56
|
"""
|
54
|
-
full_name, _ =
|
57
|
+
full_name, _ = _private_sdk.create_compute_config(config, name=name) # type: ignore
|
55
58
|
return full_name
|
56
59
|
|
57
60
|
|
@@ -74,11 +77,11 @@ _GET_ARG_DOCSTRINGS = {
|
|
74
77
|
arg_docstrings=_GET_ARG_DOCSTRINGS,
|
75
78
|
)
|
76
79
|
def get(
|
77
|
-
name: str,
|
80
|
+
name: Optional[str],
|
78
81
|
*,
|
79
82
|
include_archived: bool = False,
|
80
83
|
_id: Optional[str] = None,
|
81
|
-
|
84
|
+
_private_sdk: Optional[PrivateComputeConfigSDK] = None,
|
82
85
|
) -> ComputeConfigVersion:
|
83
86
|
"""Get the compute config with the specified name.
|
84
87
|
|
@@ -88,7 +91,7 @@ def get(
|
|
88
91
|
# NOTE(edoakes): I want to avoid exposing fetching by ID in the public API,
|
89
92
|
# but it's needed for parity with the existing CLI. Therefore I am adding it
|
90
93
|
# as a hidden private API that can be used like: (`name="", _id=id`).
|
91
|
-
return
|
94
|
+
return _private_sdk.get_compute_config( # type: ignore
|
92
95
|
name=name or None, id=_id, include_archived=include_archived
|
93
96
|
)
|
94
97
|
|
@@ -108,7 +111,12 @@ _ARCHIVE_ARG_DOCSTRINGS = {"name": "Name of the compute config."}
|
|
108
111
|
doc_py_example=_ARCHIVE_EXAMPLE,
|
109
112
|
arg_docstrings=_ARCHIVE_ARG_DOCSTRINGS,
|
110
113
|
)
|
111
|
-
def archive(
|
114
|
+
def archive(
|
115
|
+
name: Optional[str],
|
116
|
+
*,
|
117
|
+
_id: Optional[str] = None,
|
118
|
+
_private_sdk: Optional[PrivateComputeConfigSDK] = None,
|
119
|
+
):
|
112
120
|
"""Archive a compute config and all of its versions.
|
113
121
|
|
114
122
|
The name can contain an optional version, e.g., 'name:version'.
|
@@ -119,4 +127,4 @@ def archive(name: str, *, _id: Optional[str] = None, _sdk: PrivateComputeConfigS
|
|
119
127
|
# NOTE(edoakes): I want to avoid exposing fetching by ID in the public API,
|
120
128
|
# but it's needed for parity with the existing CLI. Therefore I am adding it
|
121
129
|
# as a hidden private API that can be used like: (`name="", _id=id`).
|
122
|
-
return
|
130
|
+
return _private_sdk.archive_compute_config(name=name or None, id=_id) # type: ignore
|