anyscale 0.26.17__py3-none-any.whl → 0.26.20__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 (61) hide show
  1. anyscale/_private/docgen/models.md +3 -3
  2. anyscale/anyscale-cloud-setup.yaml +0 -4
  3. anyscale/client/README.md +18 -38
  4. anyscale/client/openapi_client/__init__.py +14 -20
  5. anyscale/client/openapi_client/api/default_api.py +460 -2097
  6. anyscale/client/openapi_client/models/__init__.py +14 -20
  7. anyscale/client/openapi_client/models/aws_config.py +402 -0
  8. anyscale/client/openapi_client/models/baseimagesenum.py +68 -1
  9. anyscale/client/openapi_client/models/cloud_deployment.py +397 -0
  10. anyscale/client/openapi_client/models/{webterminal_list_response.py → clouddeployment_list_response.py} +15 -15
  11. anyscale/client/openapi_client/models/decorated_production_job_state_transition.py +2 -2
  12. anyscale/client/openapi_client/models/file_storage.py +206 -0
  13. anyscale/client/openapi_client/models/gcp_config.py +402 -0
  14. anyscale/client/openapi_client/models/{session_details.py → job_queue_sort_directive.py} +39 -39
  15. anyscale/client/openapi_client/models/{sessiondescribe_response.py → job_queue_sort_field.py} +20 -34
  16. anyscale/client/openapi_client/models/job_queues_query.py +31 -3
  17. anyscale/client/openapi_client/models/kubernetes_config.py +150 -0
  18. anyscale/client/openapi_client/models/{monitor_logs_extension.py → networking_mode.py} +7 -7
  19. anyscale/client/openapi_client/models/object_storage.py +178 -0
  20. anyscale/client/openapi_client/models/{sessiondetails_response.py → pcp_config.py} +23 -22
  21. anyscale/client/openapi_client/models/production_job_state_transition.py +2 -2
  22. anyscale/client/openapi_client/models/supportedbaseimagesenum.py +68 -1
  23. anyscale/client/openapi_client/models/{external_service_status.py → update_job_queue_request.py} +39 -36
  24. anyscale/client/openapi_client/models/workspace_template_readme.py +181 -0
  25. anyscale/client/openapi_client/models/{archivedlogsinfo_response.py → workspacetemplatereadme_response.py} +11 -11
  26. anyscale/commands/cloud_commands.py +55 -7
  27. anyscale/commands/command_examples.py +58 -0
  28. anyscale/commands/job_commands.py +2 -2
  29. anyscale/commands/job_queue_commands.py +172 -0
  30. anyscale/connect_utils/prepare_cluster.py +19 -14
  31. anyscale/controllers/cloud_controller.py +60 -3
  32. anyscale/controllers/job_controller.py +215 -3
  33. anyscale/scripts.py +3 -0
  34. anyscale/sdk/anyscale_client/models/baseimagesenum.py +68 -1
  35. anyscale/sdk/anyscale_client/models/production_job_state_transition.py +2 -2
  36. anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +68 -1
  37. anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
  38. anyscale/util.py +3 -1
  39. anyscale/utils/connect_helpers.py +34 -0
  40. anyscale/version.py +1 -1
  41. anyscale/workspace/_private/workspace_sdk.py +19 -6
  42. {anyscale-0.26.17.dist-info → anyscale-0.26.20.dist-info}/METADATA +1 -1
  43. {anyscale-0.26.17.dist-info → anyscale-0.26.20.dist-info}/RECORD +48 -53
  44. anyscale/client/openapi_client/models/archived_logs_info.py +0 -164
  45. anyscale/client/openapi_client/models/create_experimental_workspace_from_job.py +0 -123
  46. anyscale/client/openapi_client/models/create_session_from_snapshot_options.py +0 -538
  47. anyscale/client/openapi_client/models/create_session_in_db.py +0 -434
  48. anyscale/client/openapi_client/models/create_session_response.py +0 -174
  49. anyscale/client/openapi_client/models/createsessionresponse_response.py +0 -121
  50. anyscale/client/openapi_client/models/external_service_status_response.py +0 -250
  51. anyscale/client/openapi_client/models/externalservicestatusresponse_response.py +0 -121
  52. anyscale/client/openapi_client/models/session_describe.py +0 -175
  53. anyscale/client/openapi_client/models/session_history_item.py +0 -146
  54. anyscale/client/openapi_client/models/sessionhistoryitem_list_response.py +0 -147
  55. anyscale/client/openapi_client/models/update_compute_template.py +0 -146
  56. anyscale/client/openapi_client/models/update_compute_template_config.py +0 -464
  57. {anyscale-0.26.17.dist-info → anyscale-0.26.20.dist-info}/LICENSE +0 -0
  58. {anyscale-0.26.17.dist-info → anyscale-0.26.20.dist-info}/NOTICE +0 -0
  59. {anyscale-0.26.17.dist-info → anyscale-0.26.20.dist-info}/WHEEL +0 -0
  60. {anyscale-0.26.17.dist-info → anyscale-0.26.20.dist-info}/entry_points.txt +0 -0
  61. {anyscale-0.26.17.dist-info → anyscale-0.26.20.dist-info}/top_level.txt +0 -0
@@ -1388,6 +1388,43 @@ class CloudController(BaseController):
1388
1388
  cloud_id, CloudProviders.AWS, functions_to_verify, yes,
1389
1389
  )
1390
1390
 
1391
+ def get_cloud_deployments(self, cloud_id: str, cloud_name: str) -> Dict[str, Any]:
1392
+ cloud = self.api_client.get_cloud_api_v2_clouds_cloud_id_get(
1393
+ cloud_id=cloud_id,
1394
+ ).result
1395
+
1396
+ if cloud.is_aioa:
1397
+ raise ValueError(
1398
+ "Listing cloud deployments is only supported for customer-hosted clouds."
1399
+ )
1400
+
1401
+ try:
1402
+ deployments = self.api_client.get_cloud_deployments_api_v2_clouds_cloud_id_deployments_get(
1403
+ cloud_id=cloud_id,
1404
+ ).results
1405
+ except Exception as e: # noqa: BLE001
1406
+ raise ClickException(
1407
+ f"Failed to get cloud deployments for cloud {cloud_name} ({cloud_id}). Error: {e}"
1408
+ )
1409
+
1410
+ # Avoid displaying fields with empty values (since the values for optional fields default to None).
1411
+ def remove_empty_values(d):
1412
+ if isinstance(d, dict):
1413
+ return {
1414
+ k: remove_empty_values(v)
1415
+ for k, v in d.items()
1416
+ if remove_empty_values(v)
1417
+ }
1418
+ return d
1419
+
1420
+ return {
1421
+ "id": cloud_id,
1422
+ "name": cloud_name,
1423
+ "deployments": [
1424
+ remove_empty_values(deployment.to_dict()) for deployment in deployments
1425
+ ],
1426
+ }
1427
+
1391
1428
  def get_cloud_config(
1392
1429
  self, cloud_name: Optional[str] = None, cloud_id: Optional[str] = None,
1393
1430
  ) -> CloudDeploymentConfig:
@@ -3650,7 +3687,9 @@ class CloudController(BaseController):
3650
3687
 
3651
3688
  ### End of edit cloud ###
3652
3689
 
3653
- def generate_jobs_report(self, cloud_id: str, csv: bool, out_path: str) -> None:
3690
+ def generate_jobs_report(
3691
+ self, cloud_id: str, csv: bool, out_path: str, sort: str, sort_order_asc: bool
3692
+ ) -> None:
3654
3693
  end_time = datetime.now()
3655
3694
  start_time = end_time - timedelta(days=7)
3656
3695
 
@@ -3690,7 +3729,25 @@ class CloudController(BaseController):
3690
3729
  for job in full_results
3691
3730
  if job.job_state in TERMINAL_HA_JOB_STATES and job.job_report is not None
3692
3731
  ]
3693
- filtered_results.sort(key=lambda x: x.created_at)
3732
+ if sort == "created_at":
3733
+ filtered_results.sort(
3734
+ key=lambda x: x.created_at, reverse=not sort_order_asc
3735
+ )
3736
+ elif sort == "gpu":
3737
+ filtered_results.sort(
3738
+ key=lambda x: x.job_report.unused_gpu_hours or 0,
3739
+ reverse=not sort_order_asc,
3740
+ )
3741
+ elif sort == "cpu":
3742
+ filtered_results.sort(
3743
+ key=lambda x: x.job_report.unused_cpu_hours or 0,
3744
+ reverse=not sort_order_asc,
3745
+ )
3746
+ elif sort == "instances":
3747
+ filtered_results.sort(
3748
+ key=lambda x: x.job_report.max_instances_launched or 0,
3749
+ reverse=not sort_order_asc,
3750
+ )
3694
3751
 
3695
3752
  with open(out_path, "w") as out_file:
3696
3753
  if csv:
@@ -3710,7 +3767,7 @@ class CloudController(BaseController):
3710
3767
  max_instances_launched = job.job_report.max_instances_launched or ""
3711
3768
 
3712
3769
  out_file.write(
3713
- f"{job.job_id},{job.job_name},{job_state},{str(job.created_at)},{finished_at},{duration},{unused_cpu_hours},{unused_gpu_hours},{max_instances_launched}\n"
3770
+ f'"{job.job_id}","{job.job_name}","{job_state}","{str(job.created_at)}","{finished_at}","{duration}","{unused_cpu_hours}","{unused_gpu_hours}","{max_instances_launched}"\n'
3714
3771
  )
3715
3772
  else:
3716
3773
  out_file.write(
@@ -1,9 +1,13 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
4
+ from collections import defaultdict
5
+ from enum import Enum
2
6
  import os
3
7
  import random
4
8
  import string
5
9
  import time
6
- from typing import Any, cast, Dict, List, Optional
10
+ from typing import Any, Callable, cast, Dict, Iterable, List, Optional
7
11
 
8
12
  import click
9
13
  import tabulate
@@ -23,10 +27,21 @@ from anyscale.client.openapi_client.models.create_internal_production_job import
23
27
  from anyscale.client.openapi_client.models.create_job_queue_config import (
24
28
  CreateJobQueueConfig,
25
29
  )
30
+ from anyscale.client.openapi_client.models.decorated_job_queue import DecoratedJobQueue
26
31
  from anyscale.client.openapi_client.models.decorated_production_job import (
27
32
  DecoratedProductionJob,
28
33
  )
34
+ from anyscale.client.openapi_client.models.decoratedjobqueue_response import (
35
+ DecoratedjobqueueResponse,
36
+ )
29
37
  from anyscale.client.openapi_client.models.ha_job_states import HaJobStates
38
+ from anyscale.client.openapi_client.models.job_queue_sort_directive import (
39
+ JobQueueSortDirective,
40
+ )
41
+ from anyscale.client.openapi_client.models.job_queues_query import JobQueuesQuery
42
+ from anyscale.client.openapi_client.models.update_job_queue_request import (
43
+ UpdateJobQueueRequest,
44
+ )
30
45
  from anyscale.controllers.base_controller import BaseController
31
46
  from anyscale.models.job_model import JobConfig
32
47
  from anyscale.project_utils import infer_project_id
@@ -45,7 +60,7 @@ from anyscale.util import (
45
60
  populate_unspecified_cluster_configs_from_current_workspace,
46
61
  validate_job_config_dict,
47
62
  )
48
- from anyscale.utils.connect_helpers import search_entities
63
+ from anyscale.utils.connect_helpers import paginate, search_entities
49
64
  from anyscale.utils.runtime_env import override_runtime_env_config
50
65
  from anyscale.utils.workload_types import Workload
51
66
 
@@ -294,7 +309,7 @@ class JobController(BaseController):
294
309
  project_id: Optional[str],
295
310
  include_archived: bool,
296
311
  max_items: int,
297
- states: List[str],
312
+ states: List[HaJobStates],
298
313
  ) -> None:
299
314
  """
300
315
  This function will list jobs.
@@ -338,6 +353,7 @@ class JobController(BaseController):
338
353
  )
339
354
  else:
340
355
  creator_id = None
356
+
341
357
  resp = self.api_client.list_decorated_jobs_api_v2_decorated_ha_jobs_get(
342
358
  project_id=project_id,
343
359
  name=name,
@@ -647,3 +663,199 @@ class JobController(BaseController):
647
663
  ),
648
664
  )
649
665
  return job_runs
666
+
667
+ def update_job_queue(
668
+ self,
669
+ job_queue_id: str,
670
+ job_queue_name: str,
671
+ max_concurrency: Optional[int] = None,
672
+ idle_timeout_s: Optional[int] = None,
673
+ ):
674
+ job_queue: DecoratedJobQueue = _resolve_object(
675
+ fetch_by_id=cast(
676
+ Callable[[str], DecoratedjobqueueResponse],
677
+ self.api_client.get_job_queue_api_v2_job_queues_job_queue_id_get,
678
+ ),
679
+ fetch_by_id_param=job_queue_id,
680
+ fetch_by_name=cast(
681
+ Callable[[str], DecoratedjobqueueResponse],
682
+ self.api_client.list_job_queues_api_v2_job_queues_post,
683
+ ),
684
+ fetch_by_name_query={
685
+ "job_queues_query": {"name": {"equals": job_queue_name,}}
686
+ },
687
+ object_type_description="job queue",
688
+ )
689
+
690
+ queue: DecoratedJobQueue = self.api_client.update_job_queue_api_v2_job_queues_job_queue_id_put(
691
+ job_queue_id=job_queue.id,
692
+ update_job_queue_request=UpdateJobQueueRequest(
693
+ max_concurrency=max_concurrency, idle_timeout_sec=idle_timeout_s,
694
+ ),
695
+ ).result
696
+
697
+ _print_job_queue_vertical(queue, JobQueueView.ALL)
698
+
699
+ def get_job_queue(self, job_queue_id: str):
700
+ queue: DecoratedJobQueue = self.api_client.get_job_queue_api_v2_job_queues_job_queue_id_get(
701
+ job_queue_id=job_queue_id
702
+ ).result
703
+
704
+ _print_job_queue_vertical(queue, JobQueueView.ALL)
705
+
706
+ def list_job_queues(
707
+ self,
708
+ include_all_users: bool,
709
+ view: JobQueueView,
710
+ page_size: int,
711
+ max_items: Optional[int],
712
+ sorting_directives: List[JobQueueSortDirective],
713
+ interactive: bool,
714
+ ):
715
+ creator_id = (
716
+ None
717
+ if include_all_users
718
+ else self.api_client.get_user_info_api_v2_userinfo_get().result.id
719
+ )
720
+
721
+ def build_query(paging_token: Optional[str], count: int) -> Dict:
722
+ return {
723
+ "job_queues_query": JobQueuesQuery(
724
+ creator_id=creator_id,
725
+ paging=PageQuery(paging_token=paging_token, count=count),
726
+ sorting_directives=sorting_directives,
727
+ )
728
+ }
729
+
730
+ for batch in paginate(
731
+ search_function=self.api_client.list_job_queues_api_v2_job_queues_post,
732
+ query_builder=build_query,
733
+ interactive=interactive,
734
+ page_size=page_size,
735
+ max_items=max_items,
736
+ ):
737
+ _render_job_queues(batch, view)
738
+
739
+
740
+ def _render_jobs(jobs):
741
+ jobs_table = [
742
+ [
743
+ job.name,
744
+ job.id,
745
+ job.project.name,
746
+ job.last_job_run.cluster.name
747
+ if job.last_job_run and job.last_job_run.cluster
748
+ else None,
749
+ job.state.current_state,
750
+ job.creator.email,
751
+ job.config.entrypoint
752
+ if len(job.config.entrypoint) < 50
753
+ else job.config.entrypoint[:50] + " ...",
754
+ ]
755
+ for job in jobs
756
+ ]
757
+
758
+ table = tabulate.tabulate(
759
+ jobs_table,
760
+ headers=[
761
+ "NAME",
762
+ "ID",
763
+ "PROJECT NAME",
764
+ "CLUSTER NAME",
765
+ "CURRENT STATE",
766
+ "CREATOR",
767
+ "ENTRYPOINT",
768
+ ],
769
+ tablefmt="plain",
770
+ )
771
+ click.echo(f"{table}")
772
+
773
+
774
+ class JobQueueView(Enum):
775
+ ALL = DecoratedJobQueue.attribute_map.keys()
776
+ STATS = [
777
+ "id",
778
+ "name",
779
+ "total_jobs",
780
+ "active_jobs",
781
+ "successful_jobs",
782
+ "failed_jobs",
783
+ ]
784
+ DEFAULT = [
785
+ "id",
786
+ "name",
787
+ "cluster_id",
788
+ "creator_id",
789
+ "max_concurrency",
790
+ "idle_timeout_sec",
791
+ "current_cluster_state",
792
+ "created_at",
793
+ ]
794
+
795
+
796
+ def _format_job_queue(queue: DecoratedJobQueue, view: JobQueueView) -> List[str]:
797
+ formatters: Dict[str, Callable[[Any], Any]] = defaultdict(lambda: (lambda v: v))
798
+ formatters["created_at"] = lambda k: k.strftime("%Y-%m-%d %H:%M:%S")
799
+
800
+ return [formatters[field](getattr(queue, field, "")) or "" for field in view.value]
801
+
802
+
803
+ def _render_job_queues(queues: Iterable[DecoratedJobQueue], view: JobQueueView):
804
+ if not queues:
805
+ click.echo("No job queues found!")
806
+ return
807
+ table = tabulate.tabulate(
808
+ [
809
+ _format_job_queue(queue, view)
810
+ for queue in cast(Iterable[DecoratedJobQueue], queues)
811
+ ],
812
+ headers=[field.replace("_", " ").upper() for field in view.value],
813
+ tablefmt="plain",
814
+ maxcolwidths=30, # type: ignore
815
+ numalign="center",
816
+ stralign="center",
817
+ )
818
+ click.echo(table)
819
+
820
+
821
+ def _print_job_queue_vertical(queue: DecoratedJobQueue, job_queue_view: JobQueueView):
822
+ """
823
+ Print single job queue with headers as a vertical table
824
+ """
825
+ for header, value in zip(
826
+ [field.replace("_", " ").upper() for field in job_queue_view.value],
827
+ _format_job_queue(queue, job_queue_view),
828
+ ):
829
+ print(f"{header:<{30}}: {value}")
830
+
831
+
832
+ def _resolve_object(
833
+ fetch_by_id: Optional[Callable[[str], object]],
834
+ fetch_by_id_param: Optional[str],
835
+ fetch_by_name,
836
+ fetch_by_name_query,
837
+ object_type_description: str,
838
+ ) -> Any:
839
+ """Given job_id or job_name, retrieve decorated ha job spec"""
840
+ if fetch_by_id_param is None and fetch_by_name_query is None:
841
+ raise click.ClickException(
842
+ "Either `--id` or `--name` must be passed in for object."
843
+ )
844
+ if fetch_by_id_param:
845
+ try:
846
+ return fetch_by_id(fetch_by_id_param).result # type: ignore
847
+ except Exception as e: # noqa: BLE001
848
+ raise click.ClickException(
849
+ f"Could not fetch {object_type_description} by id: {e}"
850
+ )
851
+
852
+ object_list_resp: List[Any] = fetch_by_name(**fetch_by_name_query).results
853
+ if len(object_list_resp) == 0:
854
+ raise click.ClickException(
855
+ f"No {object_type_description} found with the provided name"
856
+ )
857
+ if len(object_list_resp) > 1:
858
+ raise click.ClickException(
859
+ f"Multiple {object_type_description}s found with the provided name"
860
+ )
861
+ return object_list_resp[0]
anyscale/scripts.py CHANGED
@@ -22,6 +22,7 @@ from anyscale.commands.experimental_integrations_commands import (
22
22
  )
23
23
  from anyscale.commands.image_commands import image_cli
24
24
  from anyscale.commands.job_commands import job_cli
25
+ from anyscale.commands.job_queue_commands import job_queue_cli
25
26
  from anyscale.commands.list_commands import list_cli
26
27
  from anyscale.commands.llm.group import llm_cli
27
28
  from anyscale.commands.login_commands import anyscale_login, anyscale_logout
@@ -120,6 +121,7 @@ cli.add_command(version_cli)
120
121
  cli.add_command(list_cli)
121
122
  cli.add_command(cluster_env_cli)
122
123
  cli.add_command(job_cli)
124
+ # cli.add_command(job_queue_cli) # TODO will be enabled later
123
125
  cli.add_command(schedule_cli)
124
126
  cli.add_command(service_cli)
125
127
  cli.add_command(cluster_cli)
@@ -153,6 +155,7 @@ ALIASES = {
153
155
  "h": anyscale_help,
154
156
  "schedules": schedule_cli,
155
157
  "jobs": job_cli,
158
+ "jq": job_queue_cli,
156
159
  "services": service_cli,
157
160
  "cluster-compute": compute_config_cli,
158
161
  "images": image_cli,