anyscale 0.24.87__py3-none-any.whl → 0.24.91__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 (120) hide show
  1. anyscale/__init__.py +56 -0
  2. anyscale/_private/anyscale_client/anyscale_client.py +158 -28
  3. anyscale/_private/anyscale_client/common.py +82 -0
  4. anyscale/_private/anyscale_client/fake_anyscale_client.py +193 -1
  5. anyscale/_private/docgen/README.md +1 -1
  6. anyscale/_private/docgen/__main__.py +74 -19
  7. anyscale/_private/docgen/api.md +0 -20
  8. anyscale/_private/docgen/generator.py +4 -2
  9. anyscale/_private/docgen/models.md +1 -46
  10. anyscale/_private/workload/workload_config.py +1 -1
  11. anyscale/aggregated_instance_usage/__init__.py +1 -1
  12. anyscale/aggregated_instance_usage/commands.py +2 -4
  13. anyscale/aggregated_instance_usage/models.py +8 -8
  14. anyscale/client/README.md +19 -23
  15. anyscale/client/openapi_client/__init__.py +13 -15
  16. anyscale/client/openapi_client/api/default_api.py +825 -962
  17. anyscale/client/openapi_client/models/__init__.py +13 -15
  18. anyscale/client/openapi_client/models/admin_create_user.py +255 -0
  19. anyscale/client/openapi_client/models/admin_created_user.py +281 -0
  20. anyscale/client/openapi_client/models/{sessionevent_list_response.py → admincreateduser_list_response.py} +15 -15
  21. anyscale/client/openapi_client/models/aggregated_usage_query.py +1 -29
  22. anyscale/client/openapi_client/models/{session_event_types.py → cloud_deployment_config.py} +35 -24
  23. anyscale/client/openapi_client/models/{platformfinetuningjob_response.py → clouddeploymentconfig_response.py} +11 -11
  24. anyscale/client/openapi_client/models/{company_size.py → cluster_size.py} +10 -10
  25. anyscale/client/openapi_client/models/cluster_status_details.py +2 -1
  26. anyscale/client/openapi_client/models/create_experimental_workspace.py +29 -1
  27. anyscale/client/openapi_client/models/{resubmit_ft_job_request.py → describe_machine_pool_request.py} +21 -20
  28. anyscale/client/openapi_client/models/describe_machine_pool_response.py +123 -0
  29. anyscale/client/openapi_client/models/{fine_tuning_job_status.py → describemachinepoolresponse_response.py} +34 -16
  30. anyscale/client/openapi_client/models/machine_allocation_state.py +3 -1
  31. anyscale/client/openapi_client/models/machine_state_info.py +326 -0
  32. anyscale/client/openapi_client/models/organization_marketing_questions.py +80 -54
  33. anyscale/client/openapi_client/models/request_state_info.py +210 -0
  34. anyscale/client/openapi_client/models/{platformfinetuningjob_list_response.py → scheduler_info.py} +43 -38
  35. anyscale/client/openapi_client/models/usage_by_cluster.py +28 -1
  36. anyscale/client/openapi_client/models/usage_by_user.py +30 -3
  37. anyscale/client/openapi_client/models/workload_info.py +210 -0
  38. anyscale/cloud/__init__.py +83 -0
  39. anyscale/cloud/_private/cloud_sdk.py +25 -0
  40. anyscale/cloud/commands.py +45 -0
  41. anyscale/cloud/models.py +91 -0
  42. anyscale/cluster_compute.py +1 -1
  43. anyscale/commands/aggregated_instance_usage_commands.py +4 -4
  44. anyscale/commands/cloud_commands.py +38 -2
  45. anyscale/commands/command_examples.py +67 -0
  46. anyscale/commands/job_commands.py +15 -3
  47. anyscale/commands/machine_pool_commands.py +113 -1
  48. anyscale/commands/organization_invitation_commands.py +98 -0
  49. anyscale/commands/project_commands.py +52 -2
  50. anyscale/commands/resource_quota_commands.py +98 -11
  51. anyscale/commands/service_commands.py +1 -1
  52. anyscale/commands/session_commands_hidden.py +5 -1
  53. anyscale/commands/user_commands.py +49 -0
  54. anyscale/commands/util.py +1 -1
  55. anyscale/commands/workspace_commands.py +1 -1
  56. anyscale/connect.py +1 -1
  57. anyscale/connect_utils/project.py +7 -4
  58. anyscale/controllers/cloud_controller.py +6 -6
  59. anyscale/controllers/cloud_functional_verification_controller.py +1 -1
  60. anyscale/controllers/cluster_controller.py +2 -2
  61. anyscale/controllers/compute_config_controller.py +1 -1
  62. anyscale/controllers/experimental_integrations_controller.py +1 -1
  63. anyscale/controllers/job_controller.py +8 -3
  64. anyscale/controllers/list_controller.py +2 -2
  65. anyscale/controllers/machine_pool_controller.py +12 -1
  66. anyscale/controllers/project_controller.py +4 -3
  67. anyscale/controllers/schedule_controller.py +1 -1
  68. anyscale/controllers/service_controller.py +1 -1
  69. anyscale/controllers/workspace_controller.py +1 -1
  70. anyscale/models/job_model.py +1 -1
  71. anyscale/organization_invitation/__init__.py +61 -0
  72. anyscale/organization_invitation/_private/organization_invitation_sdk.py +24 -0
  73. anyscale/organization_invitation/commands.py +84 -0
  74. anyscale/organization_invitation/models.py +45 -0
  75. anyscale/project/__init__.py +35 -0
  76. anyscale/project/_private/project_sdk.py +27 -0
  77. anyscale/project/commands.py +56 -0
  78. anyscale/project/models.py +91 -0
  79. anyscale/{project.py → project_utils.py} +3 -4
  80. anyscale/resource_quota/__init__.py +99 -0
  81. anyscale/resource_quota/_private/resource_quota_sdk.py +111 -0
  82. anyscale/resource_quota/commands.py +150 -0
  83. anyscale/resource_quota/models.py +303 -0
  84. anyscale/scripts.py +6 -0
  85. anyscale/sdk/anyscale_client/__init__.py +0 -5
  86. anyscale/sdk/anyscale_client/api/default_api.py +0 -150
  87. anyscale/sdk/anyscale_client/models/__init__.py +0 -5
  88. anyscale/sdk/anyscale_client/models/cluster_status_details.py +2 -1
  89. anyscale/sdk/anyscale_client/sdk.py +1 -1
  90. anyscale/user/__init__.py +35 -0
  91. anyscale/user/_private/user_sdk.py +32 -0
  92. anyscale/user/commands.py +42 -0
  93. anyscale/user/models.py +201 -0
  94. anyscale/util.py +15 -0
  95. anyscale/utils/cloud_utils.py +1 -1
  96. anyscale/version.py +1 -1
  97. anyscale/workspace_utils.py +1 -1
  98. {anyscale-0.24.87.dist-info → anyscale-0.24.91.dist-info}/METADATA +1 -5
  99. {anyscale-0.24.87.dist-info → anyscale-0.24.91.dist-info}/RECORD +105 -92
  100. anyscale/client/openapi_client/models/create_fine_tuning_hyperparameters.py +0 -156
  101. anyscale/client/openapi_client/models/create_fine_tuning_job_product_request.py +0 -353
  102. anyscale/client/openapi_client/models/finish_ft_job_request.py +0 -204
  103. anyscale/client/openapi_client/models/log_level_types.py +0 -100
  104. anyscale/client/openapi_client/models/paging_context.py +0 -172
  105. anyscale/client/openapi_client/models/platform_fine_tuning_job.py +0 -577
  106. anyscale/client/openapi_client/models/session_event.py +0 -267
  107. anyscale/client/openapi_client/models/session_event_cause.py +0 -150
  108. anyscale/controllers/resource_quota_controller.py +0 -183
  109. anyscale/sdk/anyscale_client/models/log_level_types.py +0 -100
  110. anyscale/sdk/anyscale_client/models/session_event.py +0 -267
  111. anyscale/sdk/anyscale_client/models/session_event_cause.py +0 -150
  112. anyscale/sdk/anyscale_client/models/session_event_types.py +0 -111
  113. anyscale/sdk/anyscale_client/models/sessionevent_list_response.py +0 -147
  114. anyscale/utils/imports/azure.py +0 -14
  115. /anyscale/{cloud.py → cloud_utils.py} +0 -0
  116. {anyscale-0.24.87.dist-info → anyscale-0.24.91.dist-info}/LICENSE +0 -0
  117. {anyscale-0.24.87.dist-info → anyscale-0.24.91.dist-info}/NOTICE +0 -0
  118. {anyscale-0.24.87.dist-info → anyscale-0.24.91.dist-info}/WHEEL +0 -0
  119. {anyscale-0.24.87.dist-info → anyscale-0.24.91.dist-info}/entry_points.txt +0 -0
  120. {anyscale-0.24.87.dist-info → anyscale-0.24.91.dist-info}/top_level.txt +0 -0
@@ -36,17 +36,19 @@ class UsageByUser(object):
36
36
  'anyscale_credits': 'float',
37
37
  'date': 'date',
38
38
  'user_id': 'str',
39
- 'user_email': 'str'
39
+ 'user_email': 'str',
40
+ 'user_name': 'str'
40
41
  }
41
42
 
42
43
  attribute_map = {
43
44
  'anyscale_credits': 'anyscale_credits',
44
45
  'date': 'date',
45
46
  'user_id': 'user_id',
46
- 'user_email': 'user_email'
47
+ 'user_email': 'user_email',
48
+ 'user_name': 'user_name'
47
49
  }
48
50
 
49
- def __init__(self, anyscale_credits=None, date=None, user_id=None, user_email=None, local_vars_configuration=None): # noqa: E501
51
+ def __init__(self, anyscale_credits=None, date=None, user_id=None, user_email=None, user_name=None, local_vars_configuration=None): # noqa: E501
50
52
  """UsageByUser - a model defined in OpenAPI""" # noqa: E501
51
53
  if local_vars_configuration is None:
52
54
  local_vars_configuration = Configuration()
@@ -56,6 +58,7 @@ class UsageByUser(object):
56
58
  self._date = None
57
59
  self._user_id = None
58
60
  self._user_email = None
61
+ self._user_name = None
59
62
  self.discriminator = None
60
63
 
61
64
  self.anyscale_credits = anyscale_credits
@@ -63,6 +66,7 @@ class UsageByUser(object):
63
66
  self.date = date
64
67
  self.user_id = user_id
65
68
  self.user_email = user_email
69
+ self.user_name = user_name
66
70
 
67
71
  @property
68
72
  def anyscale_credits(self):
@@ -154,6 +158,29 @@ class UsageByUser(object):
154
158
 
155
159
  self._user_email = user_email
156
160
 
161
+ @property
162
+ def user_name(self):
163
+ """Gets the user_name of this UsageByUser. # noqa: E501
164
+
165
+
166
+ :return: The user_name of this UsageByUser. # noqa: E501
167
+ :rtype: str
168
+ """
169
+ return self._user_name
170
+
171
+ @user_name.setter
172
+ def user_name(self, user_name):
173
+ """Sets the user_name of this UsageByUser.
174
+
175
+
176
+ :param user_name: The user_name of this UsageByUser. # noqa: E501
177
+ :type: str
178
+ """
179
+ if self.local_vars_configuration.client_side_validation and user_name is None: # noqa: E501
180
+ raise ValueError("Invalid value for `user_name`, must not be `None`") # noqa: E501
181
+
182
+ self._user_name = user_name
183
+
157
184
  def to_dict(self):
158
185
  """Returns the model properties as a dict"""
159
186
  result = {}
@@ -0,0 +1,210 @@
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 WorkloadInfo(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
+ 'workload_name': 'str',
37
+ 'workload_type': 'str',
38
+ 'workload_start_time': 'datetime',
39
+ 'workload_cloud': 'str'
40
+ }
41
+
42
+ attribute_map = {
43
+ 'workload_name': 'workload_name',
44
+ 'workload_type': 'workload_type',
45
+ 'workload_start_time': 'workload_start_time',
46
+ 'workload_cloud': 'workload_cloud'
47
+ }
48
+
49
+ def __init__(self, workload_name=None, workload_type=None, workload_start_time=None, workload_cloud=None, local_vars_configuration=None): # noqa: E501
50
+ """WorkloadInfo - a model defined in OpenAPI""" # noqa: E501
51
+ if local_vars_configuration is None:
52
+ local_vars_configuration = Configuration()
53
+ self.local_vars_configuration = local_vars_configuration
54
+
55
+ self._workload_name = None
56
+ self._workload_type = None
57
+ self._workload_start_time = None
58
+ self._workload_cloud = None
59
+ self.discriminator = None
60
+
61
+ self.workload_name = workload_name
62
+ self.workload_type = workload_type
63
+ self.workload_start_time = workload_start_time
64
+ self.workload_cloud = workload_cloud
65
+
66
+ @property
67
+ def workload_name(self):
68
+ """Gets the workload_name of this WorkloadInfo. # noqa: E501
69
+
70
+ The name of the workload. # noqa: E501
71
+
72
+ :return: The workload_name of this WorkloadInfo. # noqa: E501
73
+ :rtype: str
74
+ """
75
+ return self._workload_name
76
+
77
+ @workload_name.setter
78
+ def workload_name(self, workload_name):
79
+ """Sets the workload_name of this WorkloadInfo.
80
+
81
+ The name of the workload. # noqa: E501
82
+
83
+ :param workload_name: The workload_name of this WorkloadInfo. # noqa: E501
84
+ :type: str
85
+ """
86
+ if self.local_vars_configuration.client_side_validation and workload_name is None: # noqa: E501
87
+ raise ValueError("Invalid value for `workload_name`, must not be `None`") # noqa: E501
88
+
89
+ self._workload_name = workload_name
90
+
91
+ @property
92
+ def workload_type(self):
93
+ """Gets the workload_type of this WorkloadInfo. # noqa: E501
94
+
95
+ The type of the workload. # noqa: E501
96
+
97
+ :return: The workload_type of this WorkloadInfo. # noqa: E501
98
+ :rtype: str
99
+ """
100
+ return self._workload_type
101
+
102
+ @workload_type.setter
103
+ def workload_type(self, workload_type):
104
+ """Sets the workload_type of this WorkloadInfo.
105
+
106
+ The type of the workload. # noqa: E501
107
+
108
+ :param workload_type: The workload_type of this WorkloadInfo. # noqa: E501
109
+ :type: str
110
+ """
111
+ if self.local_vars_configuration.client_side_validation and workload_type is None: # noqa: E501
112
+ raise ValueError("Invalid value for `workload_type`, must not be `None`") # noqa: E501
113
+
114
+ self._workload_type = workload_type
115
+
116
+ @property
117
+ def workload_start_time(self):
118
+ """Gets the workload_start_time of this WorkloadInfo. # noqa: E501
119
+
120
+ The start time of the workload. # noqa: E501
121
+
122
+ :return: The workload_start_time of this WorkloadInfo. # noqa: E501
123
+ :rtype: datetime
124
+ """
125
+ return self._workload_start_time
126
+
127
+ @workload_start_time.setter
128
+ def workload_start_time(self, workload_start_time):
129
+ """Sets the workload_start_time of this WorkloadInfo.
130
+
131
+ The start time of the workload. # noqa: E501
132
+
133
+ :param workload_start_time: The workload_start_time of this WorkloadInfo. # noqa: E501
134
+ :type: datetime
135
+ """
136
+ if self.local_vars_configuration.client_side_validation and workload_start_time is None: # noqa: E501
137
+ raise ValueError("Invalid value for `workload_start_time`, must not be `None`") # noqa: E501
138
+
139
+ self._workload_start_time = workload_start_time
140
+
141
+ @property
142
+ def workload_cloud(self):
143
+ """Gets the workload_cloud of this WorkloadInfo. # noqa: E501
144
+
145
+ The cloud of the workload. # noqa: E501
146
+
147
+ :return: The workload_cloud of this WorkloadInfo. # noqa: E501
148
+ :rtype: str
149
+ """
150
+ return self._workload_cloud
151
+
152
+ @workload_cloud.setter
153
+ def workload_cloud(self, workload_cloud):
154
+ """Sets the workload_cloud of this WorkloadInfo.
155
+
156
+ The cloud of the workload. # noqa: E501
157
+
158
+ :param workload_cloud: The workload_cloud of this WorkloadInfo. # noqa: E501
159
+ :type: str
160
+ """
161
+ if self.local_vars_configuration.client_side_validation and workload_cloud is None: # noqa: E501
162
+ raise ValueError("Invalid value for `workload_cloud`, must not be `None`") # noqa: E501
163
+
164
+ self._workload_cloud = workload_cloud
165
+
166
+ def to_dict(self):
167
+ """Returns the model properties as a dict"""
168
+ result = {}
169
+
170
+ for attr, _ in six.iteritems(self.openapi_types):
171
+ value = getattr(self, attr)
172
+ if isinstance(value, list):
173
+ result[attr] = list(map(
174
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
175
+ value
176
+ ))
177
+ elif hasattr(value, "to_dict"):
178
+ result[attr] = value.to_dict()
179
+ elif isinstance(value, dict):
180
+ result[attr] = dict(map(
181
+ lambda item: (item[0], item[1].to_dict())
182
+ if hasattr(item[1], "to_dict") else item,
183
+ value.items()
184
+ ))
185
+ else:
186
+ result[attr] = value
187
+
188
+ return result
189
+
190
+ def to_str(self):
191
+ """Returns the string representation of the model"""
192
+ return pprint.pformat(self.to_dict())
193
+
194
+ def __repr__(self):
195
+ """For `print` and `pprint`"""
196
+ return self.to_str()
197
+
198
+ def __eq__(self, other):
199
+ """Returns true if both objects are equal"""
200
+ if not isinstance(other, WorkloadInfo):
201
+ return False
202
+
203
+ return self.to_dict() == other.to_dict()
204
+
205
+ def __ne__(self, other):
206
+ """Returns true if both objects are not equal"""
207
+ if not isinstance(other, WorkloadInfo):
208
+ return True
209
+
210
+ return self.to_dict() != other.to_dict()
@@ -0,0 +1,83 @@
1
+ import inspect
2
+ import sys
3
+ from types import ModuleType
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ from anyscale._private.anyscale_client import AnyscaleClientInterface
7
+ from anyscale._private.sdk import sdk_docs
8
+ from anyscale._private.sdk.base_sdk import Timer
9
+ from anyscale.cli_logger import BlockLogger
10
+ from anyscale.cloud._private.cloud_sdk import PrivateCloudSDK
11
+ from anyscale.cloud.commands import (
12
+ _ADD_COLLABORATORS_ARG_DOCSTRINGS,
13
+ _ADD_COLLABORATORS_EXAMPLE,
14
+ add_collaborators,
15
+ )
16
+ from anyscale.cloud.models import CreateCloudCollaborator
17
+ from anyscale.connect import ClientBuilder
18
+
19
+
20
+ class CloudSDK:
21
+ def __init__(
22
+ self,
23
+ *,
24
+ client: Optional[AnyscaleClientInterface] = None,
25
+ logger: Optional[BlockLogger] = None,
26
+ timer: Optional[Timer] = None,
27
+ ):
28
+ self._private_sdk = PrivateCloudSDK(client=client, logger=logger, timer=timer)
29
+
30
+ @sdk_docs(
31
+ doc_py_example=_ADD_COLLABORATORS_EXAMPLE,
32
+ arg_docstrings=_ADD_COLLABORATORS_ARG_DOCSTRINGS,
33
+ )
34
+ def add_collaborators( # noqa: F811
35
+ self, cloud: str, collaborators: List[CreateCloudCollaborator]
36
+ ) -> str:
37
+ """Batch add collaborators to a cloud."""
38
+ return self._private_sdk.add_collaborators(cloud, collaborators)
39
+
40
+
41
+ # Note: indentation here matches that of connect.py::ClientBuilder.
42
+ BUILDER_HELP_FOOTER = """
43
+ See ``anyscale.ClientBuilder`` for full documentation of
44
+ this experimental feature."""
45
+
46
+
47
+ class CloudModule(ModuleType):
48
+ """
49
+ A custom callable module object for `anyscale.cloud`.
50
+
51
+ This hack is needed since `anyscale.cloud` is a function for Anyscale connect but also a module for the SDK.
52
+ """
53
+
54
+ def __init__(self):
55
+ # Expose attributes from the SDK.
56
+ self.CloudSDK = CloudSDK
57
+ self.add_collaborators = add_collaborators
58
+
59
+ # Expose Anyscale connect
60
+ self.new_builder = self._new_builder()
61
+
62
+ # This code is copied from frontend/cli/anyscale/__init__.py.
63
+ def _new_builder(self) -> Any:
64
+ target = ClientBuilder.cloud
65
+
66
+ def new_session_builder(*a: List[Any], **kw: Dict[str, Any]) -> Any:
67
+ builder = ClientBuilder()
68
+ return target(builder, *a, **kw) # type: ignore
69
+
70
+ new_session_builder.__name__ = "cloud"
71
+ new_session_builder.__doc__ = target.__doc__ + BUILDER_HELP_FOOTER # type: ignore
72
+ new_session_builder.__signature__ = inspect.signature(target) # type: ignore
73
+
74
+ return new_session_builder
75
+
76
+ def __call__(self, *args, **kwargs):
77
+ """
78
+ Define the behavior when `anyscale.cloud` is called for Anyscale connect.
79
+ """
80
+ return self.new_builder(*args, **kwargs)
81
+
82
+
83
+ sys.modules[__name__] = CloudModule()
@@ -0,0 +1,25 @@
1
+ from typing import List
2
+
3
+ from anyscale._private.sdk.base_sdk import BaseSDK
4
+ from anyscale.client.openapi_client.models import (
5
+ CreateCloudCollaborator as CreateCloudCollaboratorModel,
6
+ )
7
+ from anyscale.cloud.models import CreateCloudCollaborator
8
+
9
+
10
+ class PrivateCloudSDK(BaseSDK):
11
+ def add_collaborators(
12
+ self, cloud: str, collaborators: List[CreateCloudCollaborator]
13
+ ) -> str:
14
+ cloud_id = self.client.get_cloud_id(cloud_name=cloud, compute_config_id=None)
15
+
16
+ return self.client.add_cloud_collaborators(
17
+ cloud_id=cloud_id,
18
+ collaborators=[
19
+ CreateCloudCollaboratorModel(
20
+ email=collaborator.email,
21
+ permission_level=collaborator.permission_level.lower(),
22
+ )
23
+ for collaborator in collaborators
24
+ ],
25
+ )
@@ -0,0 +1,45 @@
1
+ from typing import List
2
+
3
+ from anyscale._private.sdk import sdk_command
4
+ from anyscale.cloud._private.cloud_sdk import PrivateCloudSDK
5
+ from anyscale.cloud.models import CreateCloudCollaborator
6
+
7
+
8
+ _CLOUD_SDK_SINGLETON_KEY = "cloud_sdk"
9
+
10
+ _ADD_COLLABORATORS_EXAMPLE = """
11
+ import anyscale
12
+ from anyscale.cloud.models import CloudPermissionLevel, CreateCloudCollaborator
13
+
14
+ anyscale.cloud.add_collaborators(
15
+ cloud="cloud_name",
16
+ collaborators=[
17
+ CreateCloudCollaborator(
18
+ email="test1@anyscale.com",
19
+ permission_level=CloudPermissionLevel.WRITE,
20
+ ),
21
+ CreateCloudCollaborator(
22
+ email="test2@anyscale.com",
23
+ permission_level=CloudPermissionLevel.READONLY,
24
+ ),
25
+ ],
26
+ )
27
+ """
28
+
29
+ _ADD_COLLABORATORS_ARG_DOCSTRINGS = {
30
+ "cloud": "The cloud to add users to.",
31
+ "collaborators": "The list of collaborators to add to the cloud.",
32
+ }
33
+
34
+
35
+ @sdk_command(
36
+ _CLOUD_SDK_SINGLETON_KEY,
37
+ PrivateCloudSDK,
38
+ doc_py_example=_ADD_COLLABORATORS_EXAMPLE,
39
+ arg_docstrings=_ADD_COLLABORATORS_ARG_DOCSTRINGS,
40
+ )
41
+ def add_collaborators(
42
+ cloud: str, collaborators: List[CreateCloudCollaborator], *, _sdk: PrivateCloudSDK
43
+ ) -> str:
44
+ """Batch add collaborators to a cloud."""
45
+ return _sdk.add_collaborators(cloud, collaborators)
@@ -0,0 +1,91 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Any, Dict, List
3
+
4
+ from anyscale._private.models import ModelBase, ModelEnum
5
+
6
+
7
+ class CloudPermissionLevel(ModelEnum):
8
+ WRITE = "WRITE"
9
+ READONLY = "READONLY"
10
+
11
+ __docstrings__ = {
12
+ WRITE: "Write permission level for the cloud",
13
+ READONLY: "Readonly permission level for the cloud",
14
+ }
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class CreateCloudCollaborator(ModelBase):
19
+ """User to be added as a collaborator to a cloud.
20
+ """
21
+
22
+ __doc_py_example__ = """\
23
+ import anyscale
24
+ from anyscale.cloud.models import CloudPermissionLevel, CreateCloudCollaborator
25
+
26
+ create_cloud_collaborator = CreateCloudCollaborator(
27
+ # Email of the user to be added as a collaborator
28
+ email="test@anyscale.com",
29
+ # Permission level for the user to the cloud (CloudPermissionLevel.WRITE, CloudPermissionLevel.READONLY)
30
+ permission_level=CloudPermissionLevel.READONLY,
31
+ )
32
+ """
33
+
34
+ def _validate_email(self, email: str):
35
+ if not isinstance(email, str):
36
+ raise TypeError("Email must be a string.")
37
+
38
+ email: str = field(
39
+ metadata={"docstring": "Email of the user to be added as a collaborator."},
40
+ )
41
+
42
+ def _validate_permission_level(
43
+ self, permission_level: CloudPermissionLevel
44
+ ) -> CloudPermissionLevel:
45
+ if isinstance(permission_level, str):
46
+ return CloudPermissionLevel.validate(permission_level)
47
+ elif isinstance(permission_level, CloudPermissionLevel):
48
+ return permission_level
49
+ else:
50
+ raise TypeError(
51
+ f"'permission_level' must be a 'CloudPermissionLevel' (it is {type(permission_level)})."
52
+ )
53
+
54
+ permission_level: CloudPermissionLevel = field( # type: ignore
55
+ default=CloudPermissionLevel.READONLY, # type: ignore
56
+ metadata={
57
+ "docstring": "Permission level the added user should have for the cloud" # type: ignore
58
+ f"(one of: {','.join([str(m.value) for m in CloudPermissionLevel])}", # type: ignore
59
+ },
60
+ )
61
+
62
+
63
+ @dataclass(frozen=True)
64
+ class CreateCloudCollaborators(ModelBase):
65
+ """List of users to be added as collaborators to a cloud.
66
+ """
67
+
68
+ __doc_py_example__ = """\
69
+ import anyscale
70
+ from anyscale.cloud.models import CloudPermissionLevel, CreateCloudCollaborator, CreateCloudCollaborators
71
+
72
+ create_cloud_collaborator = CreateCloudCollaborator(
73
+ # Email of the user to be added as a collaborator
74
+ email="test@anyscale.com",
75
+ # Permission level for the user to the cloud (CloudPermissionLevel.WRITE, CloudPermissionLevel.READONLY)
76
+ permission_level=CloudPermissionLevel.READONLY,
77
+ )
78
+ create_cloud_collaborators = CreateCloudCollaborators(
79
+ collaborators=[create_cloud_collaborator]
80
+ )
81
+ """
82
+
83
+ collaborators: List[Dict[str, Any]] = field(
84
+ metadata={
85
+ "docstring": "List of users to be added as collaborators to a cloud."
86
+ },
87
+ )
88
+
89
+ def _validate_collaborators(self, collaborators: List[Dict[str, Any]]):
90
+ if not isinstance(collaborators, list):
91
+ raise TypeError("Collaborators must be a list.")
@@ -4,7 +4,7 @@ from typing import Optional, Tuple
4
4
  from anyscale.authenticate import get_auth_api_client
5
5
  from anyscale.cli_logger import BlockLogger
6
6
  from anyscale.client.openapi_client.api.default_api import DefaultApi
7
- from anyscale.cloud import get_cloud_id_and_name, get_last_used_cloud
7
+ from anyscale.cloud_utils import get_cloud_id_and_name, get_last_used_cloud
8
8
  from anyscale.sdk.anyscale_client import (
9
9
  ArchiveStatus,
10
10
  ComputeTemplateConfig,
@@ -26,13 +26,13 @@ def aggregated_instance_usage_cli() -> None:
26
26
  "--start-date",
27
27
  required=True,
28
28
  type=str,
29
- help="The start date of the aggregated instance usage report (inclusive). Format: YYYY-MM-DD",
29
+ help="The start date (inclusive) of the aggregated instance usage report. Format: YYYY-MM-DD",
30
30
  )
31
31
  @click.option(
32
32
  "--end-date",
33
33
  required=True,
34
34
  type=str,
35
- help="The end date of the aggregated instance usage report (inclusive). Format: YYYY-MM-DD",
35
+ help="The end date (inclusive) of the aggregated instance usage report. Format: YYYY-MM-DD",
36
36
  )
37
37
  @click.option(
38
38
  "--cloud",
@@ -50,7 +50,7 @@ def aggregated_instance_usage_cli() -> None:
50
50
  "--directory",
51
51
  required=False,
52
52
  type=str,
53
- help="The directory to save the CSV file to. Default is the current directory",
53
+ help="The directory to save the CSV file to. Default is the current directory.",
54
54
  )
55
55
  @click.option(
56
56
  "--hide-progress-bar",
@@ -67,7 +67,7 @@ def download_csv(
67
67
  hide_progress_bar: Optional[bool] = False,
68
68
  ) -> None:
69
69
  """
70
- Download an aggregated instance usage report as a zipped csv to the provided directory.
70
+ Download an aggregated instance usage report as a zipped CSV to the provided directory.
71
71
  """
72
72
  log.info("Downloading aggregated instance usage CSV...")
73
73
  try:
@@ -3,11 +3,13 @@ from typing import List, Optional
3
3
 
4
4
  import click
5
5
 
6
+ import anyscale
6
7
  from anyscale.cli_logger import BlockLogger
7
8
  from anyscale.client.openapi_client.models import ClusterManagementStackVersions
8
9
  from anyscale.client.openapi_client.models.compute_stack import ComputeStack
9
- from anyscale.commands import cloud_commands_util
10
- from anyscale.commands.util import OptionPromptNull
10
+ from anyscale.cloud.models import CreateCloudCollaborator, CreateCloudCollaborators
11
+ from anyscale.commands import cloud_commands_util, command_examples
12
+ from anyscale.commands.util import AnyscaleCommand, OptionPromptNull
11
13
  from anyscale.controllers.cloud_controller import CloudController
12
14
  from anyscale.util import validate_non_negative_arg
13
15
 
@@ -1017,3 +1019,37 @@ def cloud_edit( # noqa: PLR0913
1017
1019
  raise click.ClickException(
1018
1020
  "Please provide at least one of the following arguments: --aws-s3-id, --aws-efs-id, --aws-efs-mount-target-ip, --memorydb-cluster-id, --gcp-filestore-instance-id, --gcp-filestore-location, --gcp-cloud-storage-bucket-name, --memorystore-instance-name, --enable-auto-add-user, --disable-auto-add-user."
1019
1021
  )
1022
+
1023
+
1024
+ @cloud_cli.command(
1025
+ name="add-collaborators",
1026
+ help="Add collaborators to the cloud.",
1027
+ cls=AnyscaleCommand,
1028
+ example=command_examples.CLOUD_ADD_COLLABORATORS_EXAMPLE,
1029
+ )
1030
+ @click.option(
1031
+ "--cloud", "-c", help="Name of the cloud to add collaborators to.", required=True
1032
+ )
1033
+ @click.option(
1034
+ "--users-file",
1035
+ help="Path to a YAML file containing a list of users to add to the cloud.",
1036
+ required=True,
1037
+ )
1038
+ def add_collaborators(cloud: str, users_file: str,) -> None:
1039
+ collaborators = CreateCloudCollaborators.from_yaml(users_file)
1040
+
1041
+ try:
1042
+ anyscale.cloud.add_collaborators(
1043
+ cloud=cloud,
1044
+ collaborators=[
1045
+ CreateCloudCollaborator(**collaborator)
1046
+ for collaborator in collaborators.collaborators
1047
+ ],
1048
+ )
1049
+ except ValueError as e:
1050
+ log.error(f"Error adding collaborators to cloud: {e}")
1051
+ return
1052
+
1053
+ log.info(
1054
+ f"Successfully added {len(collaborators.collaborators)} collaborators to cloud {cloud}."
1055
+ )