anyscale 0.24.88__py3-none-any.whl → 0.25.0__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 (115) hide show
  1. anyscale/__init__.py +46 -0
  2. anyscale/_private/anyscale_client/anyscale_client.py +148 -28
  3. anyscale/_private/anyscale_client/common.py +74 -1
  4. anyscale/_private/anyscale_client/fake_anyscale_client.py +165 -1
  5. anyscale/_private/docgen/README.md +1 -1
  6. anyscale/_private/docgen/__main__.py +62 -19
  7. anyscale/_private/docgen/api.md +0 -20
  8. anyscale/_private/docgen/generator.py +3 -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 +15 -22
  15. anyscale/client/openapi_client/__init__.py +10 -14
  16. anyscale/client/openapi_client/api/default_api.py +634 -957
  17. anyscale/client/openapi_client/models/__init__.py +10 -14
  18. anyscale/client/openapi_client/models/{session_event_types.py → cloud_deployment_config.py} +35 -24
  19. anyscale/client/openapi_client/models/{platformfinetuningjob_response.py → clouddeploymentconfig_response.py} +11 -11
  20. anyscale/client/openapi_client/models/{company_size.py → cluster_size.py} +10 -10
  21. anyscale/client/openapi_client/models/cluster_status_details.py +2 -1
  22. anyscale/client/openapi_client/models/create_experimental_workspace.py +29 -1
  23. anyscale/client/openapi_client/models/{resubmit_ft_job_request.py → describe_machine_pool_request.py} +21 -20
  24. anyscale/client/openapi_client/models/describe_machine_pool_response.py +123 -0
  25. anyscale/client/openapi_client/models/{fine_tuning_job_status.py → describemachinepoolresponse_response.py} +34 -16
  26. anyscale/client/openapi_client/models/machine_allocation_state.py +3 -1
  27. anyscale/client/openapi_client/models/machine_state_info.py +326 -0
  28. anyscale/client/openapi_client/models/organization_marketing_questions.py +80 -54
  29. anyscale/client/openapi_client/models/request_state_info.py +210 -0
  30. anyscale/client/openapi_client/models/{sessionevent_list_response.py → scheduler_info.py} +43 -38
  31. anyscale/client/openapi_client/models/usage_by_cluster.py +28 -1
  32. anyscale/client/openapi_client/models/usage_by_user.py +30 -3
  33. anyscale/client/openapi_client/models/workload_info.py +210 -0
  34. anyscale/cloud/__init__.py +83 -0
  35. anyscale/cloud/_private/cloud_sdk.py +25 -0
  36. anyscale/cloud/commands.py +45 -0
  37. anyscale/cloud/models.py +91 -0
  38. anyscale/cluster_compute.py +1 -1
  39. anyscale/commands/aggregated_instance_usage_commands.py +4 -4
  40. anyscale/commands/cloud_commands.py +38 -2
  41. anyscale/commands/command_examples.py +61 -0
  42. anyscale/commands/job_commands.py +15 -3
  43. anyscale/commands/machine_pool_commands.py +113 -1
  44. anyscale/commands/organization_invitation_commands.py +98 -0
  45. anyscale/commands/project_commands.py +52 -2
  46. anyscale/commands/resource_quota_commands.py +98 -11
  47. anyscale/commands/service_commands.py +1 -1
  48. anyscale/commands/session_commands_hidden.py +5 -1
  49. anyscale/commands/user_commands.py +1 -1
  50. anyscale/commands/util.py +2 -2
  51. anyscale/commands/workspace_commands.py +1 -1
  52. anyscale/connect.py +1 -1
  53. anyscale/connect_utils/project.py +7 -4
  54. anyscale/controllers/cloud_controller.py +6 -6
  55. anyscale/controllers/cloud_functional_verification_controller.py +1 -1
  56. anyscale/controllers/cluster_controller.py +2 -2
  57. anyscale/controllers/compute_config_controller.py +1 -1
  58. anyscale/controllers/experimental_integrations_controller.py +1 -1
  59. anyscale/controllers/job_controller.py +8 -3
  60. anyscale/controllers/list_controller.py +2 -2
  61. anyscale/controllers/machine_pool_controller.py +12 -1
  62. anyscale/controllers/project_controller.py +4 -3
  63. anyscale/controllers/schedule_controller.py +1 -1
  64. anyscale/controllers/service_controller.py +1 -1
  65. anyscale/controllers/workspace_controller.py +1 -1
  66. anyscale/models/job_model.py +1 -1
  67. anyscale/organization_invitation/__init__.py +61 -0
  68. anyscale/organization_invitation/_private/organization_invitation_sdk.py +24 -0
  69. anyscale/organization_invitation/commands.py +84 -0
  70. anyscale/organization_invitation/models.py +45 -0
  71. anyscale/project/__init__.py +35 -0
  72. anyscale/project/_private/project_sdk.py +27 -0
  73. anyscale/project/commands.py +56 -0
  74. anyscale/project/models.py +91 -0
  75. anyscale/{project.py → project_utils.py} +3 -4
  76. anyscale/resource_quota/__init__.py +99 -0
  77. anyscale/resource_quota/_private/resource_quota_sdk.py +111 -0
  78. anyscale/resource_quota/commands.py +150 -0
  79. anyscale/resource_quota/models.py +303 -0
  80. anyscale/scripts.py +4 -0
  81. anyscale/sdk/anyscale_client/__init__.py +0 -5
  82. anyscale/sdk/anyscale_client/api/default_api.py +0 -150
  83. anyscale/sdk/anyscale_client/models/__init__.py +0 -5
  84. anyscale/sdk/anyscale_client/models/cluster_status_details.py +2 -1
  85. anyscale/sdk/anyscale_client/sdk.py +1 -1
  86. anyscale/user/__init__.py +1 -1
  87. anyscale/user/commands.py +1 -1
  88. anyscale/user/models.py +25 -15
  89. anyscale/util.py +15 -0
  90. anyscale/utils/cloud_utils.py +1 -1
  91. anyscale/version.py +1 -1
  92. anyscale/workspace_utils.py +1 -1
  93. {anyscale-0.24.88.dist-info → anyscale-0.25.0.dist-info}/METADATA +1 -5
  94. {anyscale-0.24.88.dist-info → anyscale-0.25.0.dist-info}/RECORD +100 -94
  95. anyscale/client/openapi_client/models/create_fine_tuning_hyperparameters.py +0 -156
  96. anyscale/client/openapi_client/models/create_fine_tuning_job_product_request.py +0 -353
  97. anyscale/client/openapi_client/models/finish_ft_job_request.py +0 -204
  98. anyscale/client/openapi_client/models/log_level_types.py +0 -100
  99. anyscale/client/openapi_client/models/platform_fine_tuning_job.py +0 -577
  100. anyscale/client/openapi_client/models/platformfinetuningjob_list_response.py +0 -147
  101. anyscale/client/openapi_client/models/session_event.py +0 -267
  102. anyscale/client/openapi_client/models/session_event_cause.py +0 -150
  103. anyscale/controllers/resource_quota_controller.py +0 -183
  104. anyscale/sdk/anyscale_client/models/log_level_types.py +0 -100
  105. anyscale/sdk/anyscale_client/models/session_event.py +0 -267
  106. anyscale/sdk/anyscale_client/models/session_event_cause.py +0 -150
  107. anyscale/sdk/anyscale_client/models/session_event_types.py +0 -111
  108. anyscale/sdk/anyscale_client/models/sessionevent_list_response.py +0 -147
  109. anyscale/utils/imports/azure.py +0 -14
  110. /anyscale/{cloud.py → cloud_utils.py} +0 -0
  111. {anyscale-0.24.88.dist-info → anyscale-0.25.0.dist-info}/LICENSE +0 -0
  112. {anyscale-0.24.88.dist-info → anyscale-0.25.0.dist-info}/NOTICE +0 -0
  113. {anyscale-0.24.88.dist-info → anyscale-0.25.0.dist-info}/WHEEL +0 -0
  114. {anyscale-0.24.88.dist-info → anyscale-0.25.0.dist-info}/entry_points.txt +0 -0
  115. {anyscale-0.24.88.dist-info → anyscale-0.25.0.dist-info}/top_level.txt +0 -0
anyscale/__init__.py CHANGED
@@ -20,11 +20,15 @@ path.append(os.path.join(anyscale_dir, "sdk"))
20
20
  import anyscale
21
21
  from anyscale import (
22
22
  aggregated_instance_usage,
23
+ cloud,
23
24
  compute_config,
24
25
  image,
25
26
  integrations,
26
27
  job,
27
28
  llm,
29
+ organization_invitation,
30
+ project,
31
+ resource_quota,
28
32
  schedule,
29
33
  service,
30
34
  user,
@@ -34,6 +38,7 @@ from anyscale._private.sdk.base_sdk import Timer
34
38
  from anyscale.aggregated_instance_usage import AggregatedInstanceUsageSDK
35
39
  from anyscale.authenticate import AuthenticationBlock
36
40
  from anyscale.cli_logger import BlockLogger
41
+ from anyscale.cloud import CloudSDK
37
42
  from anyscale.cluster import get_job_submission_client_cluster_info
38
43
  from anyscale.cluster_compute import get_cluster_compute_from_name
39
44
  from anyscale.compute_config import ComputeConfigSDK
@@ -41,6 +46,9 @@ from anyscale.connect import ClientBuilder
41
46
  from anyscale.image import ImageSDK
42
47
  from anyscale.job import JobSDK
43
48
  from anyscale.llm import LLMSDK
49
+ from anyscale.organization_invitation import OrganizationInvitationSDK
50
+ from anyscale.project import ProjectSDK
51
+ from anyscale.resource_quota import ResourceQuotaSDK
44
52
  from anyscale.schedule import ScheduleSDK
45
53
  from anyscale.sdk.anyscale_client.sdk import AnyscaleSDK
46
54
  from anyscale.service import ServiceSDK
@@ -58,6 +66,10 @@ for attr, _ in inspect.getmembers(ClientBuilder, inspect.isfunction):
58
66
  if attr.startswith("_"):
59
67
  continue
60
68
 
69
+ # This is exposed in anyscale/cloud/__init__.py since anyscale.cloud is used as the SDK module too.
70
+ if attr == "cloud":
71
+ continue
72
+
61
73
  def _new_builder(attr: str) -> Any:
62
74
  target = getattr(ClientBuilder, attr)
63
75
 
@@ -107,9 +119,15 @@ class Anyscale:
107
119
  self._job_sdk = JobSDK(client=self._anyscale_client)
108
120
  self._service_sdk = ServiceSDK(client=self._anyscale_client)
109
121
  self._compute_config_sdk = ComputeConfigSDK(client=self._anyscale_client)
122
+ self._cloud_sdk = CloudSDK(client=self._anyscale_client)
110
123
  self._schedule_sdk = ScheduleSDK(client=self._anyscale_client)
111
124
  self._image_sdk = ImageSDK(client=self._anyscale_client)
112
125
  self._llm_sdk = LLMSDK(client=self._anyscale_client)
126
+ self._organization_invitation_sdk = OrganizationInvitationSDK(
127
+ client=self._anyscale_client
128
+ )
129
+ self._project_sdk = ProjectSDK(client=self._anyscale_client)
130
+ self._resource_quota_sdk = ResourceQuotaSDK(client=self._anyscale_client)
113
131
  self._user_sdk = UserSDK(client=self._anyscale_client)
114
132
  self._workspace_sdk = WorkspaceSDK(client=self._anyscale_client)
115
133
 
@@ -131,13 +149,25 @@ class Anyscale:
131
149
  obj._compute_config_sdk = ComputeConfigSDK( # noqa: SLF001
132
150
  client=client, logger=logger, timer=timer
133
151
  )
152
+ obj._cloud_sdk = CloudSDK( # noqa: SLF001
153
+ client=client, logger=logger, timer=timer
154
+ )
134
155
  obj._schedule_sdk = ScheduleSDK( # noqa: SLF001
135
156
  client=client, logger=logger, timer=timer,
136
157
  )
137
158
  obj._image_sdk = ImageSDK(client=client, logger=logger) # noqa: SLF001
159
+ obj._organization_invitation_sdk = OrganizationInvitationSDK( # noqa: SLF001
160
+ client=client, logger=logger
161
+ )
162
+ obj._project_sdk = ProjectSDK( # noqa: SLF001
163
+ client=client, logger=logger, timer=timer
164
+ )
138
165
  obj._user_sdk = UserSDK( # noqa: SLF001
139
166
  client=client, logger=logger, timer=timer
140
167
  )
168
+ obj._resource_quota_sdk = ResourceQuotaSDK( # noqa: SLF001
169
+ client=client, logger=logger, timer=timer
170
+ )
141
171
  obj._workspace_sdk = WorkspaceSDK( # noqa: SLF001
142
172
  client=client, logger=logger, timer=timer,
143
173
  )
@@ -159,6 +189,10 @@ class Anyscale:
159
189
  def compute_config(self) -> ComputeConfigSDK: # noqa: F811
160
190
  return self._compute_config_sdk
161
191
 
192
+ @property
193
+ def cloud(self) -> CloudSDK: # noqa: F811
194
+ return self._cloud_sdk
195
+
162
196
  @property
163
197
  def schedule(self) -> ScheduleSDK: # noqa: F811
164
198
  return self._schedule_sdk
@@ -171,6 +205,18 @@ class Anyscale:
171
205
  def llm(self) -> LLMSDK: # noqa: F811
172
206
  return self._llm_sdk
173
207
 
208
+ @property
209
+ def organization_invitation(self) -> OrganizationInvitationSDK: # noqa: F811
210
+ return self._organization_invitation_sdk
211
+
212
+ @property
213
+ def project(self) -> ProjectSDK: # noqa: F811
214
+ return self._project_sdk
215
+
216
+ @property
217
+ def resource_quota(self) -> ResourceQuotaSDK: # noqa: F811
218
+ return self._resource_quota_sdk
219
+
174
220
  @property
175
221
  def user(self) -> UserSDK: # noqa: F811
176
222
  return self._user_sdk
@@ -47,10 +47,14 @@ from anyscale.client.openapi_client.models import (
47
47
  ComputeTemplate,
48
48
  ComputeTemplateConfig,
49
49
  ComputeTemplateQuery,
50
+ CreateCloudCollaborator,
50
51
  CreateComputeTemplate,
51
52
  CreateDataset,
52
53
  CreateExperimentalWorkspace,
53
54
  CreateInternalProductionJob,
55
+ CreateOrganizationInvitation,
56
+ CreateResourceQuota,
57
+ CreateUserProjectCollaborator,
54
58
  Dataset as InternalDataset,
55
59
  DatasetUpload,
56
60
  DecoratedComputeTemplate,
@@ -60,6 +64,11 @@ from anyscale.client.openapi_client.models import (
60
64
  FineTunedModel,
61
65
  FinetunedmodelListResponse,
62
66
  InternalProductionJob,
67
+ ListResourceQuotasQuery,
68
+ OrganizationCollaborator,
69
+ OrganizationInvitation,
70
+ ResourceQuota,
71
+ ResourceQuotaStatus,
63
72
  SessionSshKey,
64
73
  StartSessionOptions,
65
74
  StopSessionOptions,
@@ -544,6 +553,14 @@ class AnyscaleClient(AnyscaleClientInterface):
544
553
 
545
554
  raise e from None
546
555
 
556
+ @handle_api_exceptions
557
+ def add_cloud_collaborators(
558
+ self, cloud_id: str, collaborators: List[CreateCloudCollaborator]
559
+ ) -> None:
560
+ self._internal_api_client.batch_create_cloud_collaborators_api_v2_clouds_cloud_id_collaborators_users_batch_create_post(
561
+ cloud_id, collaborators
562
+ )
563
+
547
564
  @handle_api_exceptions
548
565
  def create_compute_config(
549
566
  self, config: ComputeTemplateConfig, *, name: Optional[str] = None
@@ -986,6 +1003,14 @@ class AnyscaleClient(AnyscaleClientInterface):
986
1003
  project_id
987
1004
  ).result
988
1005
 
1006
+ @handle_api_exceptions
1007
+ def add_project_collaborators(
1008
+ self, project_id: str, collaborators: List[CreateUserProjectCollaborator]
1009
+ ) -> None:
1010
+ self._internal_api_client.batch_create_project_collaborators_api_v2_projects_project_id_collaborators_users_batch_create_post(
1011
+ project_id, collaborators
1012
+ )
1013
+
989
1014
  @handle_api_exceptions
990
1015
  def get_job(
991
1016
  self,
@@ -1122,39 +1147,26 @@ class AnyscaleClient(AnyscaleClientInterface):
1122
1147
  # If the presigned URL scheme is SMART_OPEN, upload to cloud storage using the provided bucket name, path, & environment, and the smart_open library.
1123
1148
  bucket_name = info.bucket_name
1124
1149
  bucket_path = info.bucket_path
1125
- file_uri = info.file_uri
1126
1150
 
1127
- if file_uri and file_uri.startswith("azure"):
1128
-
1129
- from anyscale.utils.imports.azure import (
1130
- try_import_azure_storage_blob_BlobServiceClient,
1131
- )
1132
-
1133
- # Smartopen needs transport_params to be passed in for Azure.
1134
- blob_service_client = (
1135
- try_import_azure_storage_blob_BlobServiceClient()
1136
- )
1137
-
1138
- transport_params = {
1139
- "client": blob_service_client.from_connection_string(info.url),
1140
- }
1141
- with smart_open.open(
1142
- f"{file_uri}", "wb", transport_params=transport_params,
1143
- ) as fout:
1144
- fout.write(zip_file_bytes)
1145
- else:
1146
- env_vars: Dict[str, str] = {
1147
- "AWS_ENDPOINT_URL": info.url,
1148
- }
1149
- with set_env(**env_vars), smart_open.open(
1150
- f"{bucket_name}/{bucket_path}", "wb",
1151
- ) as fout:
1152
- fout.write(zip_file_bytes)
1151
+ env_vars: Dict[str, str] = {
1152
+ "AWS_ENDPOINT_URL": info.url,
1153
+ }
1154
+ with set_env(**env_vars), smart_open.open(
1155
+ f"{bucket_name}/{bucket_path}", "wb",
1156
+ ) as fout:
1157
+ fout.write(zip_file_bytes)
1153
1158
 
1154
1159
  else:
1155
1160
  # Default to HTTP PUT.
1156
1161
  internal_logger.debug(f"Uploading file '{file_name}' to cloud storage.")
1157
- requests.put(info.url, data=zip_file_bytes).raise_for_status()
1162
+ headers = (
1163
+ {"x-ms-blob-type": "BlockBlob"}
1164
+ if info.file_uri.startswith("azure")
1165
+ else None
1166
+ )
1167
+ requests.put(
1168
+ info.url, data=zip_file_bytes, headers=headers
1169
+ ).raise_for_status()
1158
1170
 
1159
1171
  return info.file_uri
1160
1172
 
@@ -1855,3 +1867,111 @@ class AnyscaleClient(AnyscaleClientInterface):
1855
1867
  return self._internal_api_client.admin_batch_create_users_api_v2_users_admin_batch_create_post(
1856
1868
  admin_create_users
1857
1869
  ).results
1870
+
1871
+ @handle_api_exceptions
1872
+ def create_organization_invitations(
1873
+ self, emails: List[str]
1874
+ ) -> Tuple[List[str], List[str]]:
1875
+ results = self._internal_api_client.batch_create_invitations_api_v2_organization_invitations_batch_create_post(
1876
+ [CreateOrganizationInvitation(email=email) for email in emails]
1877
+ ).results
1878
+
1879
+ success_emails = []
1880
+ error_messages = []
1881
+
1882
+ for idx, result in enumerate(results):
1883
+ if result.data:
1884
+ success_emails.append(emails[idx])
1885
+ else:
1886
+ error_messages.append(result.error.detail)
1887
+
1888
+ return success_emails, error_messages
1889
+
1890
+ @handle_api_exceptions
1891
+ def list_organization_invitations(self) -> List[OrganizationInvitation]:
1892
+ results = (
1893
+ self._internal_api_client.list_invitations_api_v2_organization_invitations_get().results
1894
+ )
1895
+
1896
+ return results
1897
+
1898
+ @handle_api_exceptions
1899
+ def delete_organization_invitation(self, email: str) -> OrganizationInvitation:
1900
+ invitation = self._internal_api_client.list_invitations_api_v2_organization_invitations_get(
1901
+ email=email
1902
+ ).results
1903
+
1904
+ if len(invitation) == 0:
1905
+ raise ValueError(f"Invitation for email '{email}' not found.")
1906
+ elif len(invitation) > 1:
1907
+ raise ValueError(
1908
+ f"Multiple invitations found for email '{email}'. Please contact Anyscale support."
1909
+ )
1910
+
1911
+ invitation_id = invitation[0].id
1912
+
1913
+ return self._internal_api_client.invalidate_invitation_api_v2_organization_invitations_invitation_id_invalidate_post(
1914
+ invitation_id
1915
+ ).result
1916
+
1917
+ @handle_api_exceptions
1918
+ def get_organization_collaborator(self, email: str) -> OrganizationCollaborator:
1919
+ results = self._internal_api_client.list_organization_collaborators_api_v2_organization_collaborators_get(
1920
+ email=email
1921
+ ).results
1922
+
1923
+ if len(results) == 0:
1924
+ raise ValueError(f"User with email '{email}' not found.")
1925
+
1926
+ if len(results) > 1:
1927
+ raise ValueError(
1928
+ f"Multiple users found for email '{email}'. Please contact Anyscale support."
1929
+ )
1930
+
1931
+ return results[0]
1932
+
1933
+ @handle_api_exceptions
1934
+ def create_resource_quota(
1935
+ self, create_resource_quota: CreateResourceQuota
1936
+ ) -> ResourceQuota:
1937
+ return self._internal_api_client.create_resource_quota_api_v2_resource_quotas_post(
1938
+ create_resource_quota
1939
+ ).result
1940
+
1941
+ @handle_api_exceptions
1942
+ def list_resource_quotas(
1943
+ self,
1944
+ name: Optional[str] = None,
1945
+ cloud_id: Optional[str] = None,
1946
+ creator_id: Optional[str] = None,
1947
+ is_enabled: Optional[bool] = None,
1948
+ max_items: int = 20,
1949
+ ) -> List[ResourceQuota]:
1950
+
1951
+ query = ListResourceQuotasQuery(
1952
+ name=TextQuery(equals=name) if name else None,
1953
+ cloud_id=cloud_id,
1954
+ creator_id=creator_id,
1955
+ is_enabled=is_enabled,
1956
+ paging=PageQuery(count=max_items),
1957
+ )
1958
+
1959
+ resource_quotas = self._internal_api_client.search_resource_quotas_api_v2_resource_quotas_search_post(
1960
+ query
1961
+ ).results
1962
+
1963
+ return resource_quotas
1964
+
1965
+ @handle_api_exceptions
1966
+ def delete_resource_quota(self, resource_quota_id: str) -> None:
1967
+ self._internal_api_client.delete_resource_quota_api_v2_resource_quotas_resource_quota_id_delete(
1968
+ resource_quota_id
1969
+ )
1970
+
1971
+ @handle_api_exceptions
1972
+ def set_resource_quota_status(
1973
+ self, resource_quota_id: str, is_enabled: bool
1974
+ ) -> None:
1975
+ self._internal_api_client.set_resource_quota_status_api_v2_resource_quotas_resource_quota_id_status_patch(
1976
+ resource_quota_id, ResourceQuotaStatus(is_enabled=is_enabled)
1977
+ ).result
@@ -8,13 +8,19 @@ from anyscale.client.openapi_client.models import (
8
8
  AdminCreateUser,
9
9
  Cloud,
10
10
  ComputeTemplateConfig,
11
+ CreateCloudCollaborator,
11
12
  CreateExperimentalWorkspace,
12
13
  CreateInternalProductionJob,
14
+ CreateResourceQuota,
15
+ CreateUserProjectCollaborator,
13
16
  DecoratedComputeTemplate,
14
17
  DeletedPlatformFineTunedModel,
15
18
  FineTunedModel,
16
19
  InternalProductionJob,
20
+ OrganizationCollaborator,
21
+ OrganizationInvitation,
17
22
  Project,
23
+ ResourceQuota,
18
24
  WorkspaceDataplaneProxiedArtifacts,
19
25
  )
20
26
  from anyscale.client.openapi_client.models.create_schedule import CreateSchedule
@@ -142,6 +148,13 @@ class AnyscaleClientInterface(ABC):
142
148
  """
143
149
  raise NotImplementedError
144
150
 
151
+ @abstractmethod
152
+ def add_cloud_collaborators(
153
+ self, cloud_id: str, collaborators: List[CreateCloudCollaborator]
154
+ ) -> None:
155
+ """Batch add collaborators to a cloud."""
156
+ raise NotImplementedError
157
+
145
158
  @abstractmethod
146
159
  def create_compute_config(
147
160
  self, config: ComputeTemplateConfig, *, name: Optional[str] = None
@@ -285,6 +298,13 @@ class AnyscaleClientInterface(ABC):
285
298
  """
286
299
  raise NotImplementedError
287
300
 
301
+ @abstractmethod
302
+ def add_project_collaborators(
303
+ self, project_id: str, collaborators: List[CreateUserProjectCollaborator]
304
+ ) -> None:
305
+ """Batch add collaborators to a project."""
306
+ raise NotImplementedError
307
+
288
308
  @abstractmethod
289
309
  def get_job(
290
310
  self,
@@ -598,5 +618,58 @@ class AnyscaleClientInterface(ABC):
598
618
  def admin_batch_create_users(
599
619
  self, admin_create_users: List[AdminCreateUser]
600
620
  ) -> List[AdminCreatedUser]:
601
- """Batch create users without email verification as an admin."""
621
+ """Batch create, as an admin, users without email verification."""
622
+ raise NotImplementedError
623
+
624
+ @abstractmethod
625
+ def create_organization_invitations(
626
+ self, emails: List[str]
627
+ ) -> Tuple[List[str], List[str]]:
628
+ """Create organization invitations."""
629
+ raise NotImplementedError
630
+
631
+ @abstractmethod
632
+ def list_organization_invitations(self) -> List[OrganizationInvitation]:
633
+ """List organization invitations."""
634
+ raise NotImplementedError
635
+
636
+ @abstractmethod
637
+ def delete_organization_invitation(self, email: str) -> OrganizationInvitation:
638
+ """Delete organization invitation."""
639
+ raise NotImplementedError
640
+
641
+ @abstractmethod
642
+ def get_organization_collaborator(self, email: str) -> OrganizationCollaborator:
643
+ """Get organization collaborator."""
644
+ raise NotImplementedError
645
+
646
+ @abstractmethod
647
+ def create_resource_quota(
648
+ self, create_resource_quota: CreateResourceQuota
649
+ ) -> ResourceQuota:
650
+ """Create a resource quota."""
651
+ raise NotImplementedError
652
+
653
+ @abstractmethod
654
+ def list_resource_quotas(
655
+ self,
656
+ name: Optional[str] = None,
657
+ cloud_id: Optional[str] = None,
658
+ creator_id: Optional[str] = None,
659
+ is_enabled: Optional[bool] = None,
660
+ max_items: int = 20,
661
+ ) -> List[ResourceQuota]:
662
+ """List resource quotas."""
663
+ raise NotImplementedError
664
+
665
+ @abstractmethod
666
+ def delete_resource_quota(self, resource_quota_id: str) -> None:
667
+ """Delete a resource quota."""
668
+ raise NotImplementedError
669
+
670
+ @abstractmethod
671
+ def set_resource_quota_status(
672
+ self, resource_quota_id: str, is_enabled: bool
673
+ ) -> None:
674
+ """Set the status of a resource quota."""
602
675
  raise NotImplementedError
@@ -17,9 +17,13 @@ from anyscale.client.openapi_client.models import (
17
17
  AdminCreatedUser,
18
18
  AdminCreateUser,
19
19
  Cloud,
20
+ CloudProviders,
20
21
  ComputeTemplateConfig,
22
+ CreateCloudCollaborator,
21
23
  CreateExperimentalWorkspace,
22
24
  CreateInternalProductionJob,
25
+ CreateResourceQuota,
26
+ CreateUserProjectCollaborator,
23
27
  DecoratedComputeTemplate,
24
28
  DeletedPlatformFineTunedModel,
25
29
  ExperimentalWorkspace,
@@ -28,9 +32,15 @@ from anyscale.client.openapi_client.models import (
28
32
  HaJobGoalStates,
29
33
  HaJobStates,
30
34
  InternalProductionJob,
35
+ MiniCloud,
36
+ MiniUser,
37
+ OrganizationCollaborator,
38
+ OrganizationInvitation,
39
+ OrganizationPermissionLevel,
31
40
  ProductionJob,
32
41
  ProductionJobStateTransition,
33
42
  Project,
43
+ ResourceQuota,
34
44
  WorkspaceDataplaneProxiedArtifacts,
35
45
  )
36
46
  from anyscale.client.openapi_client.models.create_schedule import CreateSchedule
@@ -135,6 +145,7 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
135
145
  self._jobs: Dict[str, ProductionJob] = {}
136
146
  self._job_runs: Dict[str, List[APIJobRun]] = defaultdict(list)
137
147
  self._project_to_id: Dict[Optional[str] : Dict[Optional[str], str]] = {}
148
+ self._project_collaborators: Dict[str, List[CreateUserProjectCollaborator]] = {}
138
149
  self._rolled_out_model: Optional[ApplyServiceModel] = None
139
150
  self._sent_workspace_notifications: List[WorkspaceNotification] = []
140
151
  self._rolled_back_service: Optional[Tuple[str, Optional[int]]] = None
@@ -154,6 +165,8 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
154
165
  self._workspaces_env_vars: Dict[str, Dict[str, str]] = {}
155
166
  self._clusters_headnode_ip: Dict[str, str] = {}
156
167
  self._clusters_ssh_key: Dict[str, SessionSshKey] = {}
168
+ self._organization_invitations: Dict[str, OrganizationInvitation] = {}
169
+ self._resource_quotas: Dict[str, ResourceQuota] = {}
157
170
 
158
171
  # Cloud ID -> Cloud.
159
172
  self._clouds: Dict[str, Cloud] = {
@@ -164,6 +177,9 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
164
177
  ),
165
178
  }
166
179
 
180
+ # Cloud ID -> collaborators
181
+ self._cloud_collaborators: Dict[str, List[CreateCloudCollaborator]] = {}
182
+
167
183
  # Cloud ID -> default ClusterCompute.
168
184
  compute_config = ClusterCompute(
169
185
  id=self.DEFAULT_CLUSTER_COMPUTE_ID,
@@ -347,6 +363,25 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
347
363
  def get_cloud(self, *, cloud_id: str) -> Optional[Cloud]:
348
364
  return self._clouds.get(cloud_id, None)
349
365
 
366
+ def add_cloud_collaborators(
367
+ self, cloud_id: str, collaborators: List[CreateCloudCollaborator]
368
+ ) -> None:
369
+ existing_collaborators = [
370
+ collaborator.email
371
+ for collaborator in self._cloud_collaborators.get(cloud_id, [])
372
+ ]
373
+
374
+ for collaborator in collaborators:
375
+ if collaborator.email in existing_collaborators:
376
+ raise ValueError(
377
+ f"Collaborator with email '{collaborator.email}' already exists in cloud '{cloud_id}'."
378
+ )
379
+
380
+ if cloud_id not in self._cloud_collaborators:
381
+ self._cloud_collaborators[cloud_id] = collaborators
382
+ else:
383
+ self._cloud_collaborators[cloud_id].extend(collaborators)
384
+
350
385
  def add_compute_config(self, compute_config: DecoratedComputeTemplate) -> int:
351
386
  compute_config.version = (
352
387
  len(self._compute_config_name_to_ids[compute_config.name]) + 1
@@ -579,6 +614,28 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
579
614
  )
580
615
  return None
581
616
 
617
+ def add_project_collaborators(
618
+ self, project_id: str, collaborators: List[CreateUserProjectCollaborator]
619
+ ):
620
+ existing_collaborators = [
621
+ collaborator.value.email if collaborator.value else None
622
+ for collaborator in self._project_collaborators.get(project_id, [])
623
+ ]
624
+
625
+ for collaborator in collaborators:
626
+ if (
627
+ collaborator.value
628
+ and collaborator.value.email in existing_collaborators
629
+ ):
630
+ raise ValueError(
631
+ f"Collaborator with email '{collaborator.value.email}' already exists in project '{project_id}'."
632
+ )
633
+
634
+ if project_id not in self._project_collaborators:
635
+ self._project_collaborators[project_id] = collaborators
636
+ else:
637
+ self._project_collaborators[project_id].extend(collaborators)
638
+
582
639
  def get_job(
583
640
  self,
584
641
  *,
@@ -908,7 +965,7 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
908
965
  compute_config_id=model.compute_config_id,
909
966
  environment_id=model.cluster_environment_build_id,
910
967
  cloud_id=model.cloud_id or self.get_cloud_id(),
911
- created_at=datetime.now(),
968
+ created_at=datetime.utcnow(),
912
969
  creator_id=self.DEFAULT_USER_ID,
913
970
  creator_email=self.DEFAULT_USER_EMAIL,
914
971
  organization_id=self.DEFAULT_ORGANIZATION_ID,
@@ -1106,3 +1163,110 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
1106
1163
  self._users[admin_create_user.email] = created_user
1107
1164
 
1108
1165
  return created_users
1166
+
1167
+ def create_organization_invitations(
1168
+ self, emails: List[str]
1169
+ ) -> Tuple[List[str], List[str]]:
1170
+ success_emails, error_messages = [], []
1171
+ for email in emails:
1172
+ if email in self._users:
1173
+ error_messages.append(
1174
+ f"User with email {email} is already a member of your organization."
1175
+ )
1176
+ else:
1177
+ orginv_id = f"orginv_{uuid.uuid4()!s}"
1178
+ self._organization_invitations[email] = OrganizationInvitation(
1179
+ email=email,
1180
+ id=orginv_id,
1181
+ organization_id="org_1",
1182
+ created_at=datetime.utcnow(),
1183
+ expires_at=datetime.utcnow(),
1184
+ )
1185
+ success_emails.append(email)
1186
+
1187
+ return success_emails, error_messages
1188
+
1189
+ def list_organization_invitations(self) -> List[OrganizationInvitation]:
1190
+ return list(self._organization_invitations.values())
1191
+
1192
+ def delete_organization_invitation(self, email: str) -> OrganizationInvitation:
1193
+ if email not in self._organization_invitations:
1194
+ raise ValueError(f"Invitation for email '{email}' not found.")
1195
+
1196
+ return self._organization_invitations.pop(email)
1197
+
1198
+ def get_organization_collaborator(self, email):
1199
+ return OrganizationCollaborator(
1200
+ id="identity_1",
1201
+ permission_level=OrganizationPermissionLevel.COLLABORATOR,
1202
+ name="Test User",
1203
+ created_at=datetime.utcnow(),
1204
+ email=email,
1205
+ user_id="usr_1",
1206
+ )
1207
+
1208
+ def create_resource_quota(
1209
+ self, create_resource_quota: CreateResourceQuota
1210
+ ) -> ResourceQuota:
1211
+ resource_quota_id = f"rq_{uuid.uuid4()!s}"
1212
+ resource_quota = ResourceQuota(
1213
+ id=resource_quota_id,
1214
+ name=create_resource_quota.name,
1215
+ cloud_id=create_resource_quota.cloud_id,
1216
+ project_id=create_resource_quota.project_id,
1217
+ quota=create_resource_quota.quota,
1218
+ is_enabled=True,
1219
+ created_at=datetime.utcnow(),
1220
+ creator=MiniUser(
1221
+ id="user_1",
1222
+ email="test@anyscale.com",
1223
+ name="Test User",
1224
+ username="testuser",
1225
+ ),
1226
+ cloud=MiniCloud(
1227
+ id=create_resource_quota.cloud_id,
1228
+ name="Test Cloud",
1229
+ provider=CloudProviders.AWS,
1230
+ ),
1231
+ )
1232
+
1233
+ self._resource_quotas[resource_quota_id] = resource_quota
1234
+
1235
+ return self._resource_quotas[resource_quota_id]
1236
+
1237
+ def list_resource_quotas(
1238
+ self,
1239
+ name: Optional[str] = None,
1240
+ cloud_id: Optional[str] = None,
1241
+ creator_id: Optional[str] = None,
1242
+ is_enabled: Optional[bool] = None,
1243
+ max_items: int = 20,
1244
+ ) -> List[ResourceQuota]:
1245
+ results = []
1246
+ for resource_quota in self._resource_quotas.values():
1247
+ if name and resource_quota.name != name:
1248
+ continue
1249
+ if cloud_id and resource_quota.cloud_id != cloud_id:
1250
+ continue
1251
+ if creator_id and resource_quota.creator.id != creator_id:
1252
+ continue
1253
+ if is_enabled is not None and resource_quota.is_enabled != is_enabled:
1254
+ continue
1255
+ results.append(resource_quota)
1256
+
1257
+ return results[:max_items]
1258
+
1259
+ def delete_resource_quota(self, resource_quota_id: str) -> None:
1260
+ if resource_quota_id not in self._resource_quotas:
1261
+ raise ValueError(f"Resource Quota with id '{resource_quota_id}' not found.")
1262
+
1263
+ self._resource_quotas.pop(resource_quota_id)
1264
+
1265
+ def set_resource_quota_status(
1266
+ self, resource_quota_id: str, is_enabled: bool
1267
+ ) -> None:
1268
+ resource_quota = self._resource_quotas.get(resource_quota_id)
1269
+ if resource_quota is None:
1270
+ raise ValueError(f"Resource Quota with id '{resource_quota_id}' not found.")
1271
+
1272
+ resource_quota.is_enabled = is_enabled
@@ -1,4 +1,4 @@
1
- Tool to autogenerate documentation for the Anyscale SDK/CLI that can be embedded in a [Docusarus](https://docusaurus.io/) site.
1
+ Tool to autogenerate documentation for the Anyscale SDK/CLI that can be embedded in a [Docusarus](https://docusaurus.io/) site. If you're adding a new API reference in anyscale/product, follow this guide to see what changes you need to make: https://www.notion.so/anyscale-hq/How-to-document-new-anyscale-API-references-173027c809cb8080b8e3e53fc244d70b.
2
2
 
3
3
  This is used by: https://github.com/anyscale/docs/tree/master/docs/reference.
4
4