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.
Files changed (66) hide show
  1. anyscale/_private/anyscale_client/anyscale_client.py +4 -2
  2. anyscale/_private/anyscale_client/common.py +6 -4
  3. anyscale/_private/anyscale_client/fake_anyscale_client.py +15 -6
  4. anyscale/_private/docgen/__main__.py +4 -4
  5. anyscale/_private/docgen/generator.py +2 -2
  6. anyscale/_private/sdk/__init__.py +2 -2
  7. anyscale/_private/workload/workload_sdk.py +6 -4
  8. anyscale/aggregated_instance_usage/commands.py +6 -2
  9. anyscale/anyscale-cloud-setup-gcp.yaml +2 -0
  10. anyscale/client/README.md +11 -6
  11. anyscale/client/openapi_client/__init__.py +8 -4
  12. anyscale/client/openapi_client/api/default_api.py +402 -270
  13. anyscale/client/openapi_client/models/__init__.py +8 -4
  14. anyscale/client/openapi_client/models/alert_type.py +11 -2
  15. anyscale/client/openapi_client/models/create_job_queue_requests.py +3 -32
  16. anyscale/client/openapi_client/models/i_know_response.py +174 -0
  17. anyscale/client/openapi_client/models/i_know_time_series_event.py +148 -0
  18. anyscale/client/openapi_client/models/job_report.py +199 -0
  19. anyscale/client/openapi_client/models/job_with_report.py +254 -0
  20. anyscale/client/openapi_client/models/jobwithreport_list_response.py +147 -0
  21. anyscale/client/openapi_client/models/{product_autoscaler_flag.py → list_ray_sessions_response.py} +22 -23
  22. anyscale/client/openapi_client/models/{productautoscalerflag_response.py → listraysessionsresponse_response.py} +11 -11
  23. anyscale/client/openapi_client/models/metric.py +133 -3
  24. anyscale/client/openapi_client/models/ray_session.py +121 -0
  25. anyscale/cloud/__init__.py +2 -2
  26. anyscale/cloud/_private/cloud_sdk.py +2 -2
  27. anyscale/cloud/commands.py +9 -6
  28. anyscale/cloud_utils.py +5 -4
  29. anyscale/cluster_compute.py +2 -2
  30. anyscale/cluster_env.py +2 -0
  31. anyscale/commands/cloud_commands.py +43 -0
  32. anyscale/commands/login_commands.py +24 -3
  33. anyscale/commands/schedule_commands.py +2 -1
  34. anyscale/compute_config/commands.py +15 -7
  35. anyscale/controllers/cloud_controller.py +151 -8
  36. anyscale/controllers/cluster_controller.py +1 -0
  37. anyscale/controllers/job_controller.py +1 -1
  38. anyscale/controllers/service_controller.py +1 -0
  39. anyscale/image/commands.py +6 -6
  40. anyscale/job/_private/job_sdk.py +22 -24
  41. anyscale/job/commands.py +12 -12
  42. anyscale/organization_invitation/commands.py +11 -7
  43. anyscale/project/__init__.py +2 -2
  44. anyscale/project/_private/project_sdk.py +2 -2
  45. anyscale/project/commands.py +3 -3
  46. anyscale/project_utils.py +1 -1
  47. anyscale/resource_quota/commands.py +18 -10
  48. anyscale/schedule/commands.py +12 -8
  49. anyscale/sdk/anyscale_client/sdk.py +1 -0
  50. anyscale/service/commands.py +17 -17
  51. anyscale/service_account/commands.py +12 -10
  52. anyscale/user/commands.py +5 -3
  53. anyscale/utils/gcp_utils.py +25 -9
  54. anyscale/version.py +1 -1
  55. anyscale/workspace/__init__.py +1 -1
  56. anyscale/workspace/_private/workspace_sdk.py +5 -3
  57. anyscale/workspace/commands.py +26 -24
  58. {anyscale-0.26.15.dist-info → anyscale-0.26.17.dist-info}/METADATA +1 -1
  59. {anyscale-0.26.15.dist-info → anyscale-0.26.17.dist-info}/RECORD +64 -60
  60. anyscale/client/openapi_client/models/aviary_model_config_v2.py +0 -358
  61. anyscale/client/openapi_client/models/finish_ft_job_request_v2.py +0 -183
  62. {anyscale-0.26.15.dist-info → anyscale-0.26.17.dist-info}/LICENSE +0 -0
  63. {anyscale-0.26.15.dist-info → anyscale-0.26.17.dist-info}/NOTICE +0 -0
  64. {anyscale-0.26.15.dist-info → anyscale-0.26.17.dist-info}/WHEEL +0 -0
  65. {anyscale-0.26.15.dist-info → anyscale-0.26.17.dist-info}/entry_points.txt +0 -0
  66. {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
- 'rate': 'float'
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
- 'rate': 'rate'
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()
@@ -38,14 +38,14 @@ class CloudSDK:
38
38
  )
39
39
  def add_collaborators(
40
40
  self, cloud: str, collaborators: List[CreateCloudCollaborator],
41
- ) -> str:
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
- return self._private_sdk.add_collaborators(cloud, collaborators)
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
- ) -> str:
19
+ ) -> None:
20
20
  cloud_id = self.client.get_cloud_id(cloud_name=cloud, compute_config_id=None)
21
21
 
22
- return self.client.add_cloud_collaborators(
22
+ self.client.add_cloud_collaborators(
23
23
  cloud_id=cloud_id,
24
24
  collaborators=[
25
25
  CreateCloudCollaboratorModel(
@@ -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, collaborators: List[CreateCloudCollaborator], *, _sdk: PrivateCloudSDK,
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 _sdk.add_collaborators(cloud, collaborators)
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
- _sdk: PrivateCloudSDK,
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 _sdk.get(id=id, name=name)
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(*, _sdk: PrivateCloudSDK,) -> Optional[Cloud]:
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 _sdk.get_default()
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
@@ -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
- def anyscale_login(no_expire: bool, expire_in_days: int) -> None:
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
- with contextlib.suppress(Exception):
56
- webbrowser.open(r.url)
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, *, name: Optional[str], _sdk: PrivateComputeConfigSDK,
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, _ = _sdk.create_compute_config(config, name=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
- _sdk: PrivateComputeConfigSDK,
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 _sdk.get_compute_config(
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(name: str, *, _id: Optional[str] = None, _sdk: PrivateComputeConfigSDK):
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 _sdk.archive_compute_config(name=name or None, id=_id)
130
+ return _private_sdk.archive_compute_config(name=name or None, id=_id) # type: ignore