anyscale 0.26.29__py3-none-any.whl → 0.26.31__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 (39) hide show
  1. anyscale/__init__.py +10 -0
  2. anyscale/_private/anyscale_client/anyscale_client.py +76 -60
  3. anyscale/_private/anyscale_client/common.py +39 -1
  4. anyscale/_private/anyscale_client/fake_anyscale_client.py +11 -0
  5. anyscale/_private/docgen/__main__.py +4 -0
  6. anyscale/_private/docgen/models.md +2 -2
  7. anyscale/client/README.md +2 -0
  8. anyscale/client/openapi_client/__init__.py +1 -0
  9. anyscale/client/openapi_client/api/default_api.py +118 -0
  10. anyscale/client/openapi_client/models/__init__.py +1 -0
  11. anyscale/client/openapi_client/models/baseimagesenum.py +68 -1
  12. anyscale/client/openapi_client/models/get_or_create_build_from_image_uri_request.py +207 -0
  13. anyscale/client/openapi_client/models/supportedbaseimagesenum.py +68 -1
  14. anyscale/cluster_compute.py +3 -8
  15. anyscale/commands/command_examples.py +10 -0
  16. anyscale/commands/job_queue_commands.py +295 -104
  17. anyscale/commands/list_util.py +14 -1
  18. anyscale/commands/machine_pool_commands.py +14 -2
  19. anyscale/commands/service_commands.py +6 -12
  20. anyscale/commands/workspace_commands_v2.py +462 -25
  21. anyscale/controllers/compute_config_controller.py +3 -19
  22. anyscale/controllers/job_controller.py +5 -210
  23. anyscale/job_queue/__init__.py +89 -0
  24. anyscale/job_queue/_private/job_queue_sdk.py +158 -0
  25. anyscale/job_queue/commands.py +130 -0
  26. anyscale/job_queue/models.py +284 -0
  27. anyscale/scripts.py +1 -1
  28. anyscale/sdk/anyscale_client/models/baseimagesenum.py +68 -1
  29. anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +68 -1
  30. anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
  31. anyscale/utils/ssh_websocket_proxy.py +178 -0
  32. anyscale/version.py +1 -1
  33. {anyscale-0.26.29.dist-info → anyscale-0.26.31.dist-info}/METADATA +3 -1
  34. {anyscale-0.26.29.dist-info → anyscale-0.26.31.dist-info}/RECORD +39 -33
  35. {anyscale-0.26.29.dist-info → anyscale-0.26.31.dist-info}/LICENSE +0 -0
  36. {anyscale-0.26.29.dist-info → anyscale-0.26.31.dist-info}/NOTICE +0 -0
  37. {anyscale-0.26.29.dist-info → anyscale-0.26.31.dist-info}/WHEEL +0 -0
  38. {anyscale-0.26.29.dist-info → anyscale-0.26.31.dist-info}/entry_points.txt +0 -0
  39. {anyscale-0.26.29.dist-info → anyscale-0.26.31.dist-info}/top_level.txt +0 -0
anyscale/__init__.py CHANGED
@@ -25,6 +25,7 @@ from anyscale import (
25
25
  image,
26
26
  integrations,
27
27
  job,
28
+ job_queue,
28
29
  llm,
29
30
  organization_invitation,
30
31
  project,
@@ -46,6 +47,7 @@ from anyscale.compute_config import ComputeConfigSDK
46
47
  from anyscale.connect import ClientBuilder
47
48
  from anyscale.image import ImageSDK
48
49
  from anyscale.job import JobSDK
50
+ from anyscale.job_queue import JobQueueSDK
49
51
  from anyscale.llm import LLMSDK
50
52
  from anyscale.organization_invitation import OrganizationInvitationSDK
51
53
  from anyscale.project import ProjectSDK
@@ -119,6 +121,7 @@ class Anyscale:
119
121
  client=self._anyscale_client
120
122
  )
121
123
  self._job_sdk = JobSDK(client=self._anyscale_client)
124
+ self._job_queue_sdk = JobQueueSDK(client=self._anyscale_client)
122
125
  self._service_sdk = ServiceSDK(client=self._anyscale_client)
123
126
  self._compute_config_sdk = ComputeConfigSDK(client=self._anyscale_client)
124
127
  self._cloud_sdk = CloudSDK(client=self._anyscale_client)
@@ -146,6 +149,9 @@ class Anyscale:
146
149
  client=client, logger=logger, timer=timer
147
150
  )
148
151
  obj._job_sdk = JobSDK(client=client, logger=logger, timer=timer) # noqa: SLF001
152
+ obj._job_queue_sdk = JobQueueSDK( # noqa: SLF001
153
+ client=client, logger=logger, timer=timer
154
+ )
149
155
  obj._service_sdk = ServiceSDK( # noqa: SLF001
150
156
  client=client, logger=logger, timer=timer
151
157
  )
@@ -187,6 +193,10 @@ class Anyscale:
187
193
  def job(self) -> JobSDK: # noqa: F811
188
194
  return self._job_sdk
189
195
 
196
+ @property
197
+ def job_queue(self) -> JobQueueSDK: # noqa: F811
198
+ return self._job_queue_sdk
199
+
190
200
  @property
191
201
  def service(self) -> ServiceSDK: # noqa: F811
192
202
  return self._service_sdk
@@ -60,6 +60,7 @@ from anyscale.client.openapi_client.models import (
60
60
  Dataset as InternalDataset,
61
61
  DatasetUpload,
62
62
  DecoratedComputeTemplate,
63
+ DecoratedjobqueueListResponse,
63
64
  DecoratedlistserviceapimodelListResponse,
64
65
  DecoratedProductionServiceV2APIModel,
65
66
  DecoratedSession,
@@ -67,7 +68,10 @@ from anyscale.client.openapi_client.models import (
67
68
  ExperimentalWorkspace,
68
69
  FineTunedModel,
69
70
  FinetunedmodelListResponse,
71
+ GetOrCreateBuildFromImageUriRequest,
70
72
  InternalProductionJob,
73
+ JobQueueSortDirective,
74
+ JobQueuesQuery,
71
75
  ListResourceQuotasQuery,
72
76
  OrganizationCollaborator,
73
77
  OrganizationInvitation,
@@ -75,13 +79,18 @@ from anyscale.client.openapi_client.models import (
75
79
  ResourceQuotaStatus,
76
80
  ServerSessionToken,
77
81
  SessionSshKey,
82
+ SessionState,
78
83
  StartSessionOptions,
79
84
  StopSessionOptions,
80
85
  WorkspaceDataplaneProxiedArtifacts,
81
86
  )
82
87
  from anyscale.client.openapi_client.models.create_schedule import CreateSchedule
88
+ from anyscale.client.openapi_client.models.decorated_job_queue import DecoratedJobQueue
83
89
  from anyscale.client.openapi_client.models.decorated_schedule import DecoratedSchedule
84
90
  from anyscale.client.openapi_client.models.production_job import ProductionJob
91
+ from anyscale.client.openapi_client.models.update_job_queue_request import (
92
+ UpdateJobQueueRequest,
93
+ )
85
94
  from anyscale.client.openapi_client.rest import ApiException as InternalApiException
86
95
  from anyscale.cluster_compute import parse_cluster_compute_name_version
87
96
  from anyscale.feature_flags import FLAG_DEFAULT_WORKING_DIR_FOR_PROJ
@@ -938,69 +947,15 @@ class AnyscaleClient(AnyscaleClientInterface):
938
947
  ray_version: Optional[str] = None,
939
948
  name: Optional[str] = None,
940
949
  ) -> str:
941
- if image_uri.is_cluster_env_image():
942
- identifier = image_uri.to_cluster_env_identifier()
943
- try:
944
- build = self._external_api_client.find_cluster_environment_build_by_identifier(
945
- identifier=identifier
946
- ).result
947
- if build.status == ClusterEnvironmentBuildStatus.SUCCEEDED:
948
- return build.id
949
- else:
950
- raise RuntimeError(
951
- f"Legacy cluster environment build '{identifier}' is not a successful build."
952
- )
953
- except ExternalApiException as e:
954
- if e.status == 404:
955
- raise RuntimeError(
956
- f"Legacy cluster environment '{identifier}' is not found."
957
- )
958
- elif image_uri.is_default_image():
959
- # Default image
960
- cluster_envs = self._internal_api_client.list_application_templates_api_v2_application_templates_get(
961
- image_name_contains=image_uri.image_uri
962
- ).results
963
- for cluster_env in cluster_envs:
964
- if (
965
- cluster_env.latest_build is not None
966
- and cluster_env.latest_build.docker_image_name
967
- == image_uri.image_uri
968
- ):
969
- return cluster_env.latest_build.id
970
- raise RuntimeError(f"Default image '{image_uri.image_uri}' is not found.")
971
-
972
- # BYOD image
973
- cluster_env_name = name if name else image_uri.to_cluster_env_name()
974
- image_uri_str = str(image_uri)
975
- cluster_env = self._find_or_create_cluster_env(
976
- cluster_env_name,
977
- anonymous=not name,
978
- image_uri=image_uri_str,
979
- registry_login_secret=registry_login_secret,
980
- ray_version=ray_version,
981
- )
982
- for build in self.list_cluster_env_builds(cluster_env.id):
983
- if (
984
- # NOTE: Ignore ray version mismatch for now. We plan to eventually remove ray version from the API model.
985
- build.docker_image_name == image_uri_str
986
- and build.registry_login_secret == registry_login_secret
987
- and build.status == ClusterEnvironmentBuildStatus.SUCCEEDED
988
- ):
989
- return build.id
990
-
991
- # Still create a new build if the cluster env already exists but the build does not match the image_uri.
992
- result = self._external_api_client.create_cluster_environment_build(
993
- CreateClusterEnvironmentBuild(
994
- # For historical reasons, we have to use docker_image_name instead of image_uri; but it is just a URI to the image.
995
- cluster_environment_id=cluster_env.id,
996
- docker_image_name=image_uri_str,
950
+ build = self._internal_api_client.get_or_create_build_from_image_uri_api_v2_builds_get_or_create_build_from_image_uri_post(
951
+ GetOrCreateBuildFromImageUriRequest(
952
+ image_uri=str(image_uri),
997
953
  registry_login_secret=registry_login_secret,
998
- ray_version=ray_version if ray_version else LATEST_RAY_VERSION,
954
+ ray_version=ray_version,
955
+ cluster_env_name=name,
999
956
  )
1000
957
  ).result
1001
-
1002
- assert result.completed
1003
- return result.cluster_environment_build_id
958
+ return build.id
1004
959
 
1005
960
  @handle_api_exceptions
1006
961
  def send_workspace_notification(
@@ -1154,6 +1109,67 @@ class AnyscaleClient(AnyscaleClientInterface):
1154
1109
  )
1155
1110
  return job_runs
1156
1111
 
1112
+ @handle_api_exceptions
1113
+ def get_job_queue(self, job_queue_id: str) -> Optional[DecoratedJobQueue]:
1114
+ try:
1115
+ return self._internal_api_client.get_job_queue_api_v2_job_queues_job_queue_id_get(
1116
+ job_queue_id
1117
+ ).result
1118
+ except InternalApiException as e:
1119
+ if e.status == 404:
1120
+ return None
1121
+
1122
+ raise e from None
1123
+
1124
+ @handle_api_exceptions
1125
+ def update_job_queue(
1126
+ self,
1127
+ job_queue_id: str,
1128
+ max_concurrency: Optional[int] = None,
1129
+ idle_timeout_s: Optional[int] = None,
1130
+ ) -> DecoratedJobQueue:
1131
+ if max_concurrency is None and idle_timeout_s is None:
1132
+ raise ValueError("No fields to update")
1133
+
1134
+ return self._internal_api_client.update_job_queue_api_v2_job_queues_job_queue_id_put(
1135
+ job_queue_id,
1136
+ update_job_queue_request=UpdateJobQueueRequest(
1137
+ max_concurrency=max_concurrency, idle_timeout_sec=idle_timeout_s,
1138
+ ),
1139
+ ).result
1140
+
1141
+ @handle_api_exceptions
1142
+ def list_job_queues(
1143
+ self,
1144
+ *,
1145
+ name: Optional[str] = None,
1146
+ creator_id: Optional[str] = None,
1147
+ cluster_status: Optional[SessionState] = None,
1148
+ project: Optional[str] = None,
1149
+ cloud: Optional[str] = None,
1150
+ count: Optional[int] = None,
1151
+ paging_token: Optional[str] = None,
1152
+ sorting_directives: Optional[List[JobQueueSortDirective]] = None,
1153
+ ) -> DecoratedjobqueueListResponse:
1154
+ cloud_id = self.get_cloud_id(cloud_name=cloud) if cloud else None
1155
+ project_id = (
1156
+ self.get_project_id(parent_cloud_id=cloud_id, name=project)
1157
+ if project
1158
+ else None
1159
+ )
1160
+
1161
+ return self._internal_api_client.list_job_queues_api_v2_job_queues_post(
1162
+ job_queues_query=JobQueuesQuery(
1163
+ name=name,
1164
+ creator_id=creator_id,
1165
+ cluster_status=cluster_status,
1166
+ project_id=project_id,
1167
+ cloud_id=cloud_id,
1168
+ paging=PageQuery(count=count, paging_token=paging_token),
1169
+ sorting_directives=sorting_directives,
1170
+ ),
1171
+ )
1172
+
1157
1173
  @handle_api_exceptions
1158
1174
  def rollout_service(
1159
1175
  self, model: ApplyProductionServiceV2Model
@@ -15,19 +15,23 @@ from anyscale.client.openapi_client.models import (
15
15
  CreateResourceQuota,
16
16
  CreateUserProjectCollaborator,
17
17
  DecoratedComputeTemplate,
18
+ DecoratedjobqueueListResponse,
18
19
  DecoratedlistserviceapimodelListResponse,
19
20
  DecoratedProductionServiceV2APIModel,
20
21
  DeletedPlatformFineTunedModel,
21
22
  FineTunedModel,
22
23
  InternalProductionJob,
24
+ JobQueueSortDirective,
23
25
  OrganizationCollaborator,
24
26
  OrganizationInvitation,
25
27
  Project,
26
28
  ResourceQuota,
27
29
  ServerSessionToken,
30
+ SessionState,
28
31
  WorkspaceDataplaneProxiedArtifacts,
29
32
  )
30
33
  from anyscale.client.openapi_client.models.create_schedule import CreateSchedule
34
+ from anyscale.client.openapi_client.models.decorated_job_queue import DecoratedJobQueue
31
35
  from anyscale.client.openapi_client.models.decorated_schedule import DecoratedSchedule
32
36
  from anyscale.client.openapi_client.models.decorated_session import DecoratedSession
33
37
  from anyscale.client.openapi_client.models.production_job import ProductionJob
@@ -50,7 +54,7 @@ from anyscale.utils.workspace_notification import WorkspaceNotification
50
54
  # Maybe just make it part of the release process to update it, or fetch the
51
55
  # default builds and get the latest one. The best thing to do is probably
52
56
  # to populate this in the backend.
53
- DEFAULT_RAY_VERSION = "2.46.0" # RAY_RELEASE_UPDATE: update to latest version.
57
+ DEFAULT_RAY_VERSION = "2.47.0" # RAY_RELEASE_UPDATE: update to latest version.
54
58
  DEFAULT_PYTHON_VERSION = "py311"
55
59
  RUNTIME_ENV_PACKAGE_FORMAT = "pkg_{content_hash}.zip"
56
60
 
@@ -366,6 +370,24 @@ class AnyscaleClientInterface(ABC):
366
370
  """
367
371
  raise NotImplementedError
368
372
 
373
+ @abstractmethod
374
+ def get_job_queue(self, job_queue_id: str) -> Optional[DecoratedJobQueue]:
375
+ """Get a job queue by id.
376
+
377
+ Returns None if not found.
378
+ """
379
+ raise NotImplementedError
380
+
381
+ @abstractmethod
382
+ def update_job_queue(
383
+ self,
384
+ job_queue_id: str,
385
+ max_concurrency: Optional[int] = None,
386
+ idle_timeout_s: Optional[int] = None,
387
+ ) -> DecoratedJobQueue:
388
+ """Update a job queue."""
389
+ raise NotImplementedError
390
+
369
391
  @abstractmethod
370
392
  def get_job_runs(self, job_id: str) -> List[APIJobRun]:
371
393
  """Returns all job runs for a given job id.
@@ -374,6 +396,22 @@ class AnyscaleClientInterface(ABC):
374
396
  """
375
397
  raise NotImplementedError
376
398
 
399
+ @abstractmethod
400
+ def list_job_queues(
401
+ self,
402
+ *,
403
+ name: Optional[str] = None,
404
+ creator_id: Optional[str] = None,
405
+ cluster_status: Optional[SessionState] = None,
406
+ project: Optional[str] = None,
407
+ cloud: Optional[str] = None,
408
+ count: Optional[int] = None,
409
+ paging_token: Optional[str] = None,
410
+ sorting_directives: Optional[List[JobQueueSortDirective]] = None,
411
+ ) -> DecoratedjobqueueListResponse:
412
+ """List job queues."""
413
+ raise NotImplementedError
414
+
377
415
  @abstractmethod
378
416
  def rollout_service(
379
417
  self, model: ApplyProductionServiceV2Model
@@ -49,6 +49,7 @@ from anyscale.client.openapi_client.models import (
49
49
  WorkspaceDataplaneProxiedArtifacts,
50
50
  )
51
51
  from anyscale.client.openapi_client.models.create_schedule import CreateSchedule
52
+ from anyscale.client.openapi_client.models.decorated_job_queue import DecoratedJobQueue
52
53
  from anyscale.client.openapi_client.models.decorated_schedule import DecoratedSchedule
53
54
  from anyscale.client.openapi_client.models.decorated_session import DecoratedSession
54
55
  from anyscale.client.openapi_client.models.session_ssh_key import SessionSshKey
@@ -150,6 +151,7 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
150
151
  self._deleted_services: Dict[str, DecoratedProductionServiceV2APIModel] = {}
151
152
  self._jobs: Dict[str, ProductionJob] = {}
152
153
  self._job_runs: Dict[str, List[APIJobRun]] = defaultdict(list)
154
+ self._job_queues: Dict[str, DecoratedJobQueue] = {}
153
155
  self._project_to_id: Dict[Optional[str] : Dict[Optional[str], str]] = {}
154
156
  self._project_collaborators: Dict[str, List[CreateUserProjectCollaborator]] = {}
155
157
  self._rolled_out_model: Optional[ApplyProductionServiceV2Model] = None
@@ -702,6 +704,15 @@ class FakeAnyscaleClient(AnyscaleClientInterface):
702
704
  def update_job_run(self, prod_job_id: str, model: APIJobRun):
703
705
  self._job_runs[prod_job_id].append(model)
704
706
 
707
+ def list_job_queues(self) -> List[DecoratedJobQueue]:
708
+ return list(self._job_queues.values())
709
+
710
+ def get_job_queue(self, job_queue_id: str) -> Optional[DecoratedJobQueue]:
711
+ return self._job_queues.get(job_queue_id, None)
712
+
713
+ def update_job_queue(self, model: DecoratedJobQueue):
714
+ self._job_queues[model.id] = model
715
+
705
716
  def register_project_by_name(
706
717
  self,
707
718
  name: str,
@@ -527,8 +527,12 @@ ALL_MODULES = [
527
527
  cli_prefix="anyscale machine-pool",
528
528
  cli_commands=[
529
529
  machine_pool_commands.create_machine_pool,
530
+ machine_pool_commands.update_machine_pool,
530
531
  machine_pool_commands.delete_machine_pool,
532
+ machine_pool_commands.attach_machine_pool_to_cloud,
533
+ machine_pool_commands.detach_machine_pool_from_cloud,
531
534
  machine_pool_commands.list_machine_pools,
535
+ machine_pool_commands.describe,
532
536
  ],
533
537
  sdk_prefix="anyscale.machine_pool",
534
538
  sdk_commands=[],