anyscale 0.25.1__py3-none-any.whl → 0.25.3__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 (50) hide show
  1. anyscale/__init__.py +10 -0
  2. anyscale/_private/anyscale_client/anyscale_client.py +40 -9
  3. anyscale/_private/anyscale_client/common.py +37 -3
  4. anyscale/_private/anyscale_client/fake_anyscale_client.py +80 -6
  5. anyscale/_private/docgen/__main__.py +9 -2
  6. anyscale/_private/docgen/api.md +13 -0
  7. anyscale/_private/docgen/models.md +3 -3
  8. anyscale/_private/workload/workload_config.py +20 -6
  9. anyscale/client/README.md +6 -0
  10. anyscale/client/openapi_client/__init__.py +3 -0
  11. anyscale/client/openapi_client/api/default_api.py +391 -2
  12. anyscale/client/openapi_client/models/__init__.py +3 -0
  13. anyscale/client/openapi_client/models/baseimagesenum.py +43 -1
  14. anyscale/client/openapi_client/models/cluster_event_source.py +105 -0
  15. anyscale/client/openapi_client/models/clusterevent_list_response.py +147 -0
  16. anyscale/client/openapi_client/models/jobs_sort_field.py +1 -2
  17. anyscale/client/openapi_client/models/sessions_sort_field.py +1 -2
  18. anyscale/client/openapi_client/models/supportedbaseimagesenum.py +43 -1
  19. anyscale/client/openapi_client/models/update_cloud_collaborator.py +121 -0
  20. anyscale/commands/cloud_commands.py +49 -12
  21. anyscale/commands/command_examples.py +4 -0
  22. anyscale/commands/service_account_commands.py +65 -8
  23. anyscale/commands/service_commands.py +60 -0
  24. anyscale/controllers/cloud_controller.py +58 -25
  25. anyscale/controllers/cluster_controller.py +1 -9
  26. anyscale/controllers/job_controller.py +0 -3
  27. anyscale/resource_quota/_private/resource_quota_sdk.py +15 -6
  28. anyscale/sdk/anyscale_client/api/default_api.py +119 -0
  29. anyscale/sdk/anyscale_client/models/baseimagesenum.py +43 -1
  30. anyscale/sdk/anyscale_client/models/jobs_sort_field.py +1 -2
  31. anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +43 -1
  32. anyscale/service/__init__.py +21 -0
  33. anyscale/service/_private/service_sdk.py +13 -0
  34. anyscale/service/commands.py +35 -0
  35. anyscale/service_account/__init__.py +88 -0
  36. anyscale/service_account/_private/service_account_sdk.py +101 -0
  37. anyscale/service_account/commands.py +147 -0
  38. anyscale/service_account/models.py +66 -0
  39. anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
  40. anyscale/shared_anyscale_utils/utils/id_gen.py +2 -0
  41. anyscale/util.py +8 -0
  42. anyscale/version.py +1 -1
  43. {anyscale-0.25.1.dist-info → anyscale-0.25.3.dist-info}/METADATA +1 -1
  44. {anyscale-0.25.1.dist-info → anyscale-0.25.3.dist-info}/RECORD +49 -43
  45. anyscale/controllers/service_account_controller.py +0 -168
  46. {anyscale-0.25.1.dist-info → anyscale-0.25.3.dist-info}/LICENSE +0 -0
  47. {anyscale-0.25.1.dist-info → anyscale-0.25.3.dist-info}/NOTICE +0 -0
  48. {anyscale-0.25.1.dist-info → anyscale-0.25.3.dist-info}/WHEEL +0 -0
  49. {anyscale-0.25.1.dist-info → anyscale-0.25.3.dist-info}/entry_points.txt +0 -0
  50. {anyscale-0.25.1.dist-info → anyscale-0.25.3.dist-info}/top_level.txt +0 -0
anyscale/__init__.py CHANGED
@@ -31,6 +31,7 @@ from anyscale import (
31
31
  resource_quota,
32
32
  schedule,
33
33
  service,
34
+ service_account,
34
35
  user,
35
36
  )
36
37
  from anyscale._private.anyscale_client import AnyscaleClient, AnyscaleClientInterface
@@ -52,6 +53,7 @@ from anyscale.resource_quota import ResourceQuotaSDK
52
53
  from anyscale.schedule import ScheduleSDK
53
54
  from anyscale.sdk.anyscale_client.sdk import AnyscaleSDK
54
55
  from anyscale.service import ServiceSDK
56
+ from anyscale.service_account import ServiceAccountSDK
55
57
  from anyscale.user import UserSDK
56
58
  from anyscale.workspace import WorkspaceSDK
57
59
 
@@ -128,6 +130,7 @@ class Anyscale:
128
130
  )
129
131
  self._project_sdk = ProjectSDK(client=self._anyscale_client)
130
132
  self._resource_quota_sdk = ResourceQuotaSDK(client=self._anyscale_client)
133
+ self._service_account_sdk = ServiceAccountSDK(client=self._anyscale_client)
131
134
  self._user_sdk = UserSDK(client=self._anyscale_client)
132
135
  self._workspace_sdk = WorkspaceSDK(client=self._anyscale_client)
133
136
 
@@ -162,6 +165,9 @@ class Anyscale:
162
165
  obj._project_sdk = ProjectSDK( # noqa: SLF001
163
166
  client=client, logger=logger, timer=timer
164
167
  )
168
+ obj._service_account_sdk = ServiceAccountSDK( # noqa: SLF001
169
+ client=client, logger=logger, timer=timer
170
+ )
165
171
  obj._user_sdk = UserSDK( # noqa: SLF001
166
172
  client=client, logger=logger, timer=timer
167
173
  )
@@ -217,6 +223,10 @@ class Anyscale:
217
223
  def resource_quota(self) -> ResourceQuotaSDK: # noqa: F811
218
224
  return self._resource_quota_sdk
219
225
 
226
+ @property
227
+ def service_account(self) -> ServiceAccountSDK: # noqa: F811
228
+ return self._service_account_sdk
229
+
220
230
  @property
221
231
  def user(self) -> UserSDK: # noqa: F811
222
232
  return self._user_sdk
@@ -36,6 +36,8 @@ from anyscale.client.openapi_client.api.default_api import DefaultApi as Interna
36
36
  from anyscale.client.openapi_client.models import (
37
37
  AdminCreatedUser,
38
38
  AdminCreateUser,
39
+ AnyscaleServiceAccount,
40
+ ApiKeyParameters,
39
41
  ArchiveStatus,
40
42
  Cloud,
41
43
  CloudDataBucketAccessMode,
@@ -69,6 +71,7 @@ from anyscale.client.openapi_client.models import (
69
71
  OrganizationInvitation,
70
72
  ResourceQuota,
71
73
  ResourceQuotaStatus,
74
+ ServerSessionToken,
72
75
  SessionSshKey,
73
76
  StartSessionOptions,
74
77
  StopSessionOptions,
@@ -1092,6 +1095,11 @@ class AnyscaleClient(AnyscaleClientInterface):
1092
1095
  result: ServiceModel = self._external_api_client.terminate_service(service_id)
1093
1096
  return result
1094
1097
 
1098
+ @handle_api_exceptions
1099
+ def archive_service(self, service_id: str) -> ServiceModel:
1100
+ result: ServiceModel = self._external_api_client.archive_service(service_id)
1101
+ return result
1102
+
1095
1103
  @handle_api_exceptions
1096
1104
  def submit_job(self, model: CreateInternalProductionJob) -> InternalProductionJob:
1097
1105
  job: InternalProductionJob = self._internal_api_client.create_job_api_v2_decorated_ha_jobs_create_post(
@@ -1860,6 +1868,20 @@ class AnyscaleClient(AnyscaleClientInterface):
1860
1868
 
1861
1869
  return filepath
1862
1870
 
1871
+ @handle_api_exceptions
1872
+ def create_api_key(
1873
+ self, duration: float, user_id: Optional[str]
1874
+ ) -> ServerSessionToken:
1875
+ return self._internal_api_client.create_api_key_api_v2_users_create_api_key_post(
1876
+ ApiKeyParameters(user_id=user_id, duration=duration)
1877
+ ).result
1878
+
1879
+ @handle_api_exceptions
1880
+ def rotate_api_key(self, user_id: str) -> None:
1881
+ self._internal_api_client.rotate_api_key_for_user_api_v2_organization_collaborators_rotate_api_key_for_user_user_id_post(
1882
+ user_id
1883
+ )
1884
+
1863
1885
  @handle_api_exceptions
1864
1886
  def admin_batch_create_users(
1865
1887
  self, admin_create_users: List[AdminCreateUser]
@@ -1915,20 +1937,29 @@ class AnyscaleClient(AnyscaleClientInterface):
1915
1937
  ).result
1916
1938
 
1917
1939
  @handle_api_exceptions
1918
- def get_organization_collaborator(self, email: str) -> OrganizationCollaborator:
1940
+ def get_organization_collaborators(
1941
+ self,
1942
+ email: Optional[str] = None,
1943
+ name: Optional[str] = None,
1944
+ is_service_account: Optional[bool] = None,
1945
+ ) -> List[OrganizationCollaborator]:
1919
1946
  results = self._internal_api_client.list_organization_collaborators_api_v2_organization_collaborators_get(
1920
- email=email
1947
+ email=email, name=name, is_service_account=is_service_account
1921
1948
  ).results
1922
1949
 
1923
- if len(results) == 0:
1924
- raise ValueError(f"User with email '{email}' not found.")
1950
+ return results
1925
1951
 
1926
- if len(results) > 1:
1927
- raise ValueError(
1928
- f"Multiple users found for email '{email}'. Please contact Anyscale support."
1929
- )
1952
+ @handle_api_exceptions
1953
+ def delete_organization_collaborator(self, identity_id: str) -> None:
1954
+ self._internal_api_client.remove_organization_collaborator_api_v2_organization_collaborators_identity_id_delete(
1955
+ identity_id
1956
+ )
1930
1957
 
1931
- return results[0]
1958
+ @handle_api_exceptions
1959
+ def create_service_account(self, name: str) -> AnyscaleServiceAccount:
1960
+ return self._internal_api_client.create_service_account_api_v2_users_service_accounts_post(
1961
+ name
1962
+ ).result
1932
1963
 
1933
1964
  @handle_api_exceptions
1934
1965
  def create_resource_quota(
@@ -6,6 +6,7 @@ from anyscale._private.models.image_uri import ImageURI
6
6
  from anyscale.client.openapi_client.models import (
7
7
  AdminCreatedUser,
8
8
  AdminCreateUser,
9
+ AnyscaleServiceAccount,
9
10
  Cloud,
10
11
  ComputeTemplateConfig,
11
12
  CreateCloudCollaborator,
@@ -21,6 +22,7 @@ from anyscale.client.openapi_client.models import (
21
22
  OrganizationInvitation,
22
23
  Project,
23
24
  ResourceQuota,
25
+ ServerSessionToken,
24
26
  WorkspaceDataplaneProxiedArtifacts,
25
27
  )
26
28
  from anyscale.client.openapi_client.models.create_schedule import CreateSchedule
@@ -47,7 +49,7 @@ from anyscale.utils.workspace_notification import WorkspaceNotification
47
49
  # Maybe just make it part of the release process to update it, or fetch the
48
50
  # default builds and get the latest one. The best thing to do is probably
49
51
  # to populate this in the backend.
50
- DEFAULT_RAY_VERSION = "2.40.0" # RAY_RELEASE_UPDATE: update to latest version.
52
+ DEFAULT_RAY_VERSION = "2.41.0" # RAY_RELEASE_UPDATE: update to latest version.
51
53
  DEFAULT_PYTHON_VERSION = "py311"
52
54
  RUNTIME_ENV_PACKAGE_FORMAT = "pkg_{content_hash}.zip"
53
55
 
@@ -351,6 +353,11 @@ class AnyscaleClientInterface(ABC):
351
353
  """Mark the service to be terminated asynchronously."""
352
354
  raise NotImplementedError
353
355
 
356
+ @abstractmethod
357
+ def archive_service(self, service_id: str):
358
+ """Mark the service to be archived asynchronously."""
359
+ raise NotImplementedError
360
+
354
361
  @abstractmethod
355
362
  def submit_job(self, model: CreateInternalProductionJob) -> InternalProductionJob:
356
363
  """Submit the job to run."""
@@ -614,6 +621,18 @@ class AnyscaleClientInterface(ABC):
614
621
  """Download the aggregated instance usage csv."""
615
622
  raise NotImplementedError
616
623
 
624
+ @abstractmethod
625
+ def create_api_key(
626
+ self, duration: float, user_id: Optional[str]
627
+ ) -> ServerSessionToken:
628
+ """Create a new API key."""
629
+ raise NotImplementedError
630
+
631
+ @abstractmethod
632
+ def rotate_api_key(self, user_id: str) -> None:
633
+ """Rotate the API key for user."""
634
+ raise NotImplementedError
635
+
617
636
  @abstractmethod
618
637
  def admin_batch_create_users(
619
638
  self, admin_create_users: List[AdminCreateUser]
@@ -639,8 +658,23 @@ class AnyscaleClientInterface(ABC):
639
658
  raise NotImplementedError
640
659
 
641
660
  @abstractmethod
642
- def get_organization_collaborator(self, email: str) -> OrganizationCollaborator:
643
- """Get organization collaborator."""
661
+ def get_organization_collaborators(
662
+ self,
663
+ email: Optional[str] = None,
664
+ name: Optional[str] = None,
665
+ is_service_account: Optional[bool] = None,
666
+ ) -> List[OrganizationCollaborator]:
667
+ """Get organization collaborators."""
668
+ raise NotImplementedError
669
+
670
+ @abstractmethod
671
+ def delete_organization_collaborator(self, identity_id: str) -> None:
672
+ """Delete organization collaborator."""
673
+ raise NotImplementedError
674
+
675
+ @abstractmethod
676
+ def create_service_account(self, name: str) -> AnyscaleServiceAccount:
677
+ """Create a service account."""
644
678
  raise NotImplementedError
645
679
 
646
680
  @abstractmethod
@@ -16,6 +16,7 @@ from anyscale.cli_logger import BlockLogger
16
16
  from anyscale.client.openapi_client.models import (
17
17
  AdminCreatedUser,
18
18
  AdminCreateUser,
19
+ AnyscaleServiceAccount,
19
20
  Cloud,
20
21
  CloudProviders,
21
22
  ComputeTemplateConfig,
@@ -41,6 +42,7 @@ from anyscale.client.openapi_client.models import (
41
42
  ProductionJobStateTransition,
42
43
  Project,
43
44
  ResourceQuota,
45
+ ServerSessionToken,
44
46
  WorkspaceDataplaneProxiedArtifacts,
45
47
  )
46
48
  from anyscale.client.openapi_client.models.create_schedule import CreateSchedule
@@ -142,6 +144,7 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
142
144
  self._workspace_cluster: Optional[Cluster] = None
143
145
  self._workspace_dependency_tracking_enabled: bool = False
144
146
  self._services: Dict[str, ServiceModel] = {}
147
+ self._archived_services: Dict[str, ServiceModel] = {}
145
148
  self._jobs: Dict[str, ProductionJob] = {}
146
149
  self._job_runs: Dict[str, List[APIJobRun]] = defaultdict(list)
147
150
  self._project_to_id: Dict[Optional[str] : Dict[Optional[str], str]] = {}
@@ -165,6 +168,8 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
165
168
  self._workspaces_env_vars: Dict[str, Dict[str, str]] = {}
166
169
  self._clusters_headnode_ip: Dict[str, str] = {}
167
170
  self._clusters_ssh_key: Dict[str, SessionSshKey] = {}
171
+ self._api_keys: Dict[str, List[str]] = defaultdict(list)
172
+ self._organization_collaborators: List[OrganizationCollaborator] = []
168
173
  self._organization_invitations: Dict[str, OrganizationInvitation] = {}
169
174
  self._resource_quotas: Dict[str, ResourceQuota] = {}
170
175
 
@@ -759,6 +764,16 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
759
764
  service_id
760
765
  ].primary_version.current_state = ServiceVersionState.TERMINATED
761
766
 
767
+ @property
768
+ def archived_services(self) -> Dict[str, ServiceModel]:
769
+ return self._archived_services
770
+
771
+ def archive_service(self, service_id: str):
772
+ self._archived_services[service_id] = self._services.pop(service_id)
773
+
774
+ def is_archived_service(self, service_id: str) -> bool:
775
+ return service_id in self._archived_services
776
+
762
777
  @property
763
778
  def submitted_job(self) -> Optional[CreateInternalProductionJob]:
764
779
  return self._submitted_job
@@ -1139,6 +1154,20 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
1139
1154
 
1140
1155
  return filepath
1141
1156
 
1157
+ def create_api_key(
1158
+ self, duration: float, user_id: Optional[str] # noqa: ARG002
1159
+ ) -> ServerSessionToken:
1160
+ api_key = f"{uuid.uuid4()!s}"
1161
+ api_keys = self._api_keys.get(user_id or "usr_1", [])
1162
+ self._api_keys[user_id or "usr_1"] = api_keys + [api_key]
1163
+
1164
+ return ServerSessionToken(server_session_id=api_key)
1165
+
1166
+ def rotate_api_key(self, user_id: str) -> None:
1167
+ if user_id not in self._api_keys:
1168
+ raise ValueError(f"User '{user_id}' not found.")
1169
+ self._api_keys[user_id] = [f"{uuid.uuid4()!s}"]
1170
+
1142
1171
  def admin_batch_create_users(
1143
1172
  self, admin_create_users: List[AdminCreateUser]
1144
1173
  ) -> List[AdminCreatedUser]:
@@ -1195,14 +1224,59 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
1195
1224
 
1196
1225
  return self._organization_invitations.pop(email)
1197
1226
 
1198
- def get_organization_collaborator(self, email):
1199
- return OrganizationCollaborator(
1200
- id="identity_1",
1227
+ def get_organization_collaborators(
1228
+ self,
1229
+ email: Optional[str] = None,
1230
+ name: Optional[str] = None,
1231
+ is_service_account: Optional[bool] = None, # noqa: ARG002
1232
+ ) -> List[OrganizationCollaborator]:
1233
+ # Since organization collaborator doesn't include whether it's a service account or not, we'll just return all
1234
+ results = []
1235
+ for organization_collaborator in self._organization_collaborators:
1236
+ if email and organization_collaborator.email != email:
1237
+ continue
1238
+ if name and organization_collaborator.name != name:
1239
+ continue
1240
+
1241
+ results.append(organization_collaborator)
1242
+
1243
+ return results
1244
+
1245
+ def delete_organization_collaborator(self, identity_id: str) -> None:
1246
+ for organization_collaborator in self._organization_collaborators:
1247
+ if organization_collaborator.id == identity_id:
1248
+ self._organization_collaborators.remove(organization_collaborator)
1249
+ return
1250
+
1251
+ raise ValueError(
1252
+ f"Organization collaborator with id '{identity_id}' not found."
1253
+ )
1254
+
1255
+ def create_service_account(self, name) -> AnyscaleServiceAccount:
1256
+ for organization_collaborator in self._organization_collaborators:
1257
+ if organization_collaborator.name == name:
1258
+ raise ValueError(f"Service account with name '{name}' already exists.")
1259
+
1260
+ identity_id = f"id_{uuid.uuid4()!s}"
1261
+ user_id = f"usr_{uuid.uuid4()!s}"
1262
+ organization_collaborator = OrganizationCollaborator(
1201
1263
  permission_level=OrganizationPermissionLevel.COLLABORATOR,
1202
- name="Test User",
1264
+ id=identity_id,
1265
+ name=name,
1266
+ email=f"{name}@service-account.com",
1267
+ user_id=user_id,
1203
1268
  created_at=datetime.utcnow(),
1204
- email=email,
1205
- user_id="usr_1",
1269
+ )
1270
+
1271
+ self._organization_collaborators.append((organization_collaborator))
1272
+
1273
+ return AnyscaleServiceAccount(
1274
+ user_id=organization_collaborator.user_id,
1275
+ name=organization_collaborator.name,
1276
+ organization_id="org_1",
1277
+ email=organization_collaborator.email,
1278
+ permission_level=organization_collaborator.permission_level,
1279
+ created_at=organization_collaborator.created_at,
1206
1280
  )
1207
1281
 
1208
1282
  def create_resource_quota(
@@ -76,6 +76,7 @@ from anyscale.service.models import (
76
76
  ServiceVersionStatus,
77
77
  TracingConfig,
78
78
  )
79
+ from anyscale.service_account.models import OrganizationPermissionLevel, ServiceAccount
79
80
  from anyscale.user.models import AdminCreatedUser, AdminCreateUser
80
81
  from anyscale.workspace.models import WorkspaceConfig
81
82
 
@@ -356,8 +357,14 @@ ALL_MODULES = [
356
357
  service_account_commands.rotate_api_keys,
357
358
  ],
358
359
  sdk_prefix="anyscale.service_account",
359
- sdk_commands=[],
360
- models=[],
360
+ sdk_commands=[
361
+ anyscale.service_account.create,
362
+ anyscale.service_account.create_api_key,
363
+ anyscale.service_account.list,
364
+ anyscale.service_account.delete,
365
+ anyscale.service_account.rotate_api_keys,
366
+ ],
367
+ models=[ServiceAccount, OrganizationPermissionLevel],
361
368
  ),
362
369
  Module(
363
370
  title="Image",
@@ -949,6 +949,18 @@ Returns [ProductionjobResponse](./models.md#productionjobresponse)
949
949
 
950
950
  ## Services
951
951
 
952
+ ### archive_service
953
+
954
+ Archives a Service. It is a no-op if already archived.
955
+
956
+ Parameters
957
+
958
+ Name | Type | Description | Notes
959
+ ------------- | ------------- | ------------- | -------------
960
+ `service_id` | str| | Defaults to null
961
+
962
+ Returns void (empty response body)
963
+
952
964
  ### get_service
953
965
 
954
966
  Get a Service
@@ -972,6 +984,7 @@ Name | Type | Description | Notes
972
984
  `project_id` | optional str| project_id to filter by | Defaults to null
973
985
  `name` | optional str| name to filter by | Defaults to null
974
986
  `state_filter` | [List[ServiceEventCurrentState]](./models.md#serviceeventcurrentstate)| A list of Service states to filter by | Defaults to []
987
+ `archive_status` | [ArchiveStatus](./models.md#archivestatus)| The archive status to filter by. Defaults to unarchived. | Defaults to null
975
988
  `creator_id` | optional str| creator_id to filter by | Defaults to null
976
989
  `cloud_id` | optional str| cloud_id to filter by | Defaults to null
977
990
  `sort_field` | [ServiceSortField](./models.md#servicesortfield)| If absent, the default sorting order is 1. status (active first).2. Last updated at (desc). 3. Name (asc). | Defaults to null