anyscale 0.24.88__py3-none-any.whl → 0.25.5__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 (148) hide show
  1. anyscale/__init__.py +56 -0
  2. anyscale/_private/anyscale_client/anyscale_client.py +179 -28
  3. anyscale/_private/anyscale_client/common.py +109 -2
  4. anyscale/_private/anyscale_client/fake_anyscale_client.py +239 -1
  5. anyscale/_private/docgen/README.md +1 -1
  6. anyscale/_private/docgen/__main__.py +71 -21
  7. anyscale/_private/docgen/api.md +13 -20
  8. anyscale/_private/docgen/generator.py +3 -2
  9. anyscale/_private/docgen/models.md +4 -49
  10. anyscale/_private/workload/workload_config.py +21 -7
  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 +25 -22
  15. anyscale/client/openapi_client/__init__.py +16 -14
  16. anyscale/client/openapi_client/api/default_api.py +1139 -959
  17. anyscale/client/openapi_client/models/__init__.py +16 -14
  18. anyscale/client/openapi_client/models/baseimagesenum.py +43 -1
  19. anyscale/client/openapi_client/models/{session_event_types.py → cloud_deployment_config.py} +35 -24
  20. anyscale/client/openapi_client/models/{platformfinetuningjob_response.py → clouddeploymentconfig_response.py} +11 -11
  21. anyscale/client/openapi_client/models/{log_level_types.py → cluster_event_source.py} +12 -7
  22. anyscale/client/openapi_client/models/{company_size.py → cluster_size.py} +10 -10
  23. anyscale/client/openapi_client/models/cluster_status_details.py +2 -1
  24. anyscale/client/openapi_client/models/{sessionevent_list_response.py → clusterevent_list_response.py} +15 -15
  25. anyscale/client/openapi_client/models/create_experimental_workspace.py +29 -1
  26. anyscale/client/openapi_client/models/create_notification_channel_record.py +29 -3
  27. anyscale/client/openapi_client/models/decorated_interactive_session.py +1 -57
  28. anyscale/client/openapi_client/models/decorated_job.py +1 -57
  29. anyscale/client/openapi_client/models/decorated_job_submission.py +1 -29
  30. anyscale/client/openapi_client/models/decorated_production_job.py +1 -29
  31. anyscale/client/openapi_client/models/decorated_session.py +1 -57
  32. anyscale/client/openapi_client/models/decorated_unified_job.py +1 -30
  33. anyscale/client/openapi_client/models/{resubmit_ft_job_request.py → describe_machine_pool_request.py} +21 -20
  34. anyscale/client/openapi_client/models/describe_machine_pool_response.py +123 -0
  35. anyscale/client/openapi_client/models/describemachinepoolresponse_response.py +121 -0
  36. anyscale/client/openapi_client/models/ha_jobs_sort_field.py +1 -2
  37. anyscale/client/openapi_client/models/internal_production_job.py +1 -29
  38. anyscale/client/openapi_client/models/jobs_sort_field.py +1 -2
  39. anyscale/client/openapi_client/models/machine_allocation_state.py +3 -1
  40. anyscale/client/openapi_client/models/machine_state_info.py +326 -0
  41. anyscale/client/openapi_client/models/{fine_tuning_job_status.py → notification_channel_slack_config.py} +34 -16
  42. anyscale/client/openapi_client/models/organization_marketing_questions.py +80 -54
  43. anyscale/client/openapi_client/models/request_state_info.py +210 -0
  44. anyscale/client/openapi_client/models/{platformfinetuningjob_list_response.py → scheduler_info.py} +43 -38
  45. anyscale/client/openapi_client/models/serve_deployment_fast_api_docs_status.py +123 -0
  46. anyscale/client/openapi_client/models/serve_deployment_state.py +2 -1
  47. anyscale/client/openapi_client/models/servedeploymentfastapidocsstatus_response.py +121 -0
  48. anyscale/client/openapi_client/models/sessions_sort_field.py +1 -2
  49. anyscale/client/openapi_client/models/supportedbaseimagesenum.py +43 -1
  50. anyscale/client/openapi_client/models/unified_job_sort_field.py +1 -2
  51. anyscale/client/openapi_client/models/update_cloud_collaborator.py +121 -0
  52. anyscale/client/openapi_client/models/usage_by_cluster.py +28 -1
  53. anyscale/client/openapi_client/models/usage_by_user.py +30 -3
  54. anyscale/client/openapi_client/models/workload_info.py +210 -0
  55. anyscale/cloud/__init__.py +83 -0
  56. anyscale/cloud/_private/cloud_sdk.py +25 -0
  57. anyscale/cloud/commands.py +45 -0
  58. anyscale/cloud/models.py +91 -0
  59. anyscale/cluster_compute.py +1 -1
  60. anyscale/commands/aggregated_instance_usage_commands.py +4 -4
  61. anyscale/commands/cloud_commands.py +87 -14
  62. anyscale/commands/command_examples.py +65 -0
  63. anyscale/commands/job_commands.py +15 -3
  64. anyscale/commands/machine_pool_commands.py +113 -1
  65. anyscale/commands/organization_invitation_commands.py +98 -0
  66. anyscale/commands/project_commands.py +52 -2
  67. anyscale/commands/resource_quota_commands.py +98 -11
  68. anyscale/commands/service_account_commands.py +65 -8
  69. anyscale/commands/service_commands.py +61 -1
  70. anyscale/commands/session_commands_hidden.py +5 -1
  71. anyscale/commands/user_commands.py +1 -1
  72. anyscale/commands/util.py +2 -2
  73. anyscale/commands/workspace_commands.py +1 -1
  74. anyscale/connect.py +1 -1
  75. anyscale/connect_utils/project.py +7 -4
  76. anyscale/controllers/cloud_controller.py +63 -30
  77. anyscale/controllers/cloud_functional_verification_controller.py +1 -1
  78. anyscale/controllers/cluster_controller.py +3 -11
  79. anyscale/controllers/compute_config_controller.py +1 -1
  80. anyscale/controllers/experimental_integrations_controller.py +1 -1
  81. anyscale/controllers/job_controller.py +8 -6
  82. anyscale/controllers/list_controller.py +2 -2
  83. anyscale/controllers/machine_pool_controller.py +12 -1
  84. anyscale/controllers/project_controller.py +4 -3
  85. anyscale/controllers/schedule_controller.py +1 -1
  86. anyscale/controllers/service_controller.py +1 -1
  87. anyscale/controllers/workspace_controller.py +1 -1
  88. anyscale/models/job_model.py +1 -1
  89. anyscale/organization_invitation/__init__.py +61 -0
  90. anyscale/organization_invitation/_private/organization_invitation_sdk.py +24 -0
  91. anyscale/organization_invitation/commands.py +84 -0
  92. anyscale/organization_invitation/models.py +45 -0
  93. anyscale/project/__init__.py +35 -0
  94. anyscale/project/_private/project_sdk.py +27 -0
  95. anyscale/project/commands.py +56 -0
  96. anyscale/project/models.py +91 -0
  97. anyscale/{project.py → project_utils.py} +3 -4
  98. anyscale/resource_quota/__init__.py +99 -0
  99. anyscale/resource_quota/_private/resource_quota_sdk.py +120 -0
  100. anyscale/resource_quota/commands.py +150 -0
  101. anyscale/resource_quota/models.py +303 -0
  102. anyscale/scripts.py +4 -0
  103. anyscale/sdk/anyscale_client/__init__.py +0 -5
  104. anyscale/sdk/anyscale_client/api/default_api.py +119 -150
  105. anyscale/sdk/anyscale_client/models/__init__.py +0 -5
  106. anyscale/sdk/anyscale_client/models/baseimagesenum.py +43 -1
  107. anyscale/sdk/anyscale_client/models/cluster_status_details.py +2 -1
  108. anyscale/sdk/anyscale_client/models/jobs_sort_field.py +1 -2
  109. anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +43 -1
  110. anyscale/sdk/anyscale_client/sdk.py +1 -1
  111. anyscale/service/__init__.py +21 -0
  112. anyscale/service/_private/service_sdk.py +13 -0
  113. anyscale/service/commands.py +35 -0
  114. anyscale/service_account/__init__.py +88 -0
  115. anyscale/service_account/_private/service_account_sdk.py +101 -0
  116. anyscale/service_account/commands.py +147 -0
  117. anyscale/service_account/models.py +66 -0
  118. anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
  119. anyscale/shared_anyscale_utils/utils/id_gen.py +2 -0
  120. anyscale/user/__init__.py +1 -1
  121. anyscale/user/commands.py +1 -1
  122. anyscale/user/models.py +25 -15
  123. anyscale/util.py +23 -0
  124. anyscale/utils/cloud_utils.py +1 -1
  125. anyscale/version.py +1 -1
  126. anyscale/workspace_utils.py +1 -1
  127. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/METADATA +1 -5
  128. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/RECORD +134 -119
  129. anyscale/client/openapi_client/models/create_fine_tuning_hyperparameters.py +0 -156
  130. anyscale/client/openapi_client/models/create_fine_tuning_job_product_request.py +0 -353
  131. anyscale/client/openapi_client/models/finish_ft_job_request.py +0 -204
  132. anyscale/client/openapi_client/models/platform_fine_tuning_job.py +0 -577
  133. anyscale/client/openapi_client/models/session_event.py +0 -267
  134. anyscale/client/openapi_client/models/session_event_cause.py +0 -150
  135. anyscale/controllers/resource_quota_controller.py +0 -183
  136. anyscale/controllers/service_account_controller.py +0 -168
  137. anyscale/sdk/anyscale_client/models/log_level_types.py +0 -100
  138. anyscale/sdk/anyscale_client/models/session_event.py +0 -267
  139. anyscale/sdk/anyscale_client/models/session_event_cause.py +0 -150
  140. anyscale/sdk/anyscale_client/models/session_event_types.py +0 -111
  141. anyscale/sdk/anyscale_client/models/sessionevent_list_response.py +0 -147
  142. anyscale/utils/imports/azure.py +0 -14
  143. /anyscale/{cloud.py → cloud_utils.py} +0 -0
  144. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/LICENSE +0 -0
  145. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/NOTICE +0 -0
  146. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/WHEEL +0 -0
  147. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/entry_points.txt +0 -0
  148. {anyscale-0.24.88.dist-info → anyscale-0.25.5.dist-info}/top_level.txt +0 -0
@@ -217,6 +217,10 @@ $ anyscale machine-pool update --name can-testing --spec-file spec.yaml
217
217
  Updated machine pool 'can-testing'.
218
218
  """
219
219
 
220
+ MACHINE_POOL_DESCRIBE_EXAMPLE = """\
221
+ $ anyscale machine-pool describe --name can-testing --mode machines
222
+ """
223
+
220
224
  MACHINE_POOL_DELETE_EXAMPLE = """\
221
225
  $ anyscale machine-pool delete --name can-testing
222
226
  Deleted machine pool 'can-testing'.
@@ -498,4 +502,65 @@ USER_BATCH_CREATE_EXAMPLE = """\
498
502
  $ anyscale user batch-create --users-file users.yaml
499
503
  (anyscale +0.5s) Creating users...
500
504
  (anyscale +0.8s) 2 users created.
505
+
506
+ $ cat users_file.yaml
507
+ create_users:
508
+ - name: name1
509
+ email: test1@anyscale.com
510
+ password: ''
511
+ is_sso_user: false
512
+ lastname: lastname1
513
+ title: title1
514
+ - name: name2
515
+ email: test2@anyscale.com
516
+ password: ''
517
+ is_sso_user: false
518
+ lastname: lastname2
519
+ title: title2
520
+ """
521
+
522
+ ORGANIZATION_INVITATION_CREATE_EXAMPLE = """\
523
+ $ anyscale organization-invitation create --emails test1@anyscale.com,test2@anyscale.com
524
+ (anyscale +0.5s) Creating organization invitations...
525
+ (anyscale +1.7s) Organization invitations sent to: test1@anyscale.com, test2@anyscale.com
526
+ """
527
+
528
+ ORGANIZATION_INVITATION_LIST_EXAMPLE = """\
529
+ $ anyscale organization-invitation list
530
+ ID Email Created At Expires At
531
+ ------------- ----------------- ------------------- -------------------
532
+ orginv_abcedf test@anyscale.com 11/25/2024 10:24 PM 12/02/2024 10:24 PM
533
+ """
534
+
535
+ ORGANIZATION_INVITATION_DELETE_EXAMPLE = """\
536
+ $ anyscale organization-invitation delete --email test@anyscale.com
537
+ (anyscale +0.6s) Organization invitation for test@anyscale.com deleted.
538
+ """
539
+
540
+ PROJECT_ADD_COLLABORATORS_EXAMPLE = """\
541
+ $ anyscale project add-collaborators --cloud cloud_name --project project_name --users-file collaborators.yaml
542
+ (anyscale +1.3s) Successfully added 3 collaborators to project project_name.
543
+ $ cat collaborators.yaml
544
+ collaborators:
545
+ - email: "test1@anyscale.com"
546
+ permission_level: "write"
547
+ - email: "test2@anyscale.com"
548
+ permission_level: "readonly"
549
+ - email: "test3@anyscale.com"
550
+ permission_level: "owner"
551
+ """
552
+
553
+ CLOUD_ADD_COLLABORATORS_EXAMPLE = """\
554
+ $ anyscale cloud add-collaborators --cloud cloud_name --users-file collaborators.yaml
555
+ (anyscale +1.3s) Successfully added 2 collaborators to cloud cloud_name.
556
+ $ cat collaborators.yaml
557
+ collaborators:
558
+ - email: "test1@anyscale.com"
559
+ permission_level: "write"
560
+ - email: "test2@anyscale.com"
561
+ permission_level: "readonly"
562
+ """
563
+
564
+ SERVICE_ARCHIVE_EXAMPLE = """\
565
+ $ anyscale service archive --name my_service
501
566
  """
@@ -2,7 +2,7 @@ from io import StringIO
2
2
  from json import dumps as json_dumps
3
3
  import pathlib
4
4
  from subprocess import list2cmdline
5
- from typing import Optional, Tuple
5
+ from typing import List, Optional, Tuple
6
6
 
7
7
  import click
8
8
  import yaml
@@ -10,6 +10,7 @@ import yaml
10
10
  import anyscale
11
11
  from anyscale._private.models.image_uri import ImageURI
12
12
  from anyscale.cli_logger import BlockLogger
13
+ from anyscale.client.openapi_client.models.ha_job_states import HaJobStates
13
14
  from anyscale.commands import command_examples
14
15
  from anyscale.commands.util import (
15
16
  AnyscaleCommand,
@@ -18,7 +19,7 @@ from anyscale.commands.util import (
18
19
  )
19
20
  from anyscale.controllers.job_controller import JobController
20
21
  from anyscale.job.models import JobConfig, JobLogMode, JobState, JobStatus
21
- from anyscale.util import validate_non_negative_arg
22
+ from anyscale.util import validate_list_jobs_state_filter, validate_non_negative_arg
22
23
 
23
24
 
24
25
  log = BlockLogger() # CLI Logger
@@ -118,7 +119,7 @@ def job_cli() -> None:
118
119
  required=False,
119
120
  default=None,
120
121
  type=str,
121
- help="Path to a requirements.txt file containing dependencies for the job. These will be installed on top of the image. When running in a workspace, this defaults to the workspace dependencies.",
122
+ help="Path to a requirements.txt file containing dependencies for the job. Anyscale installs these dependencies on top of the image. If you run a job from a workspace, the default is to use the workspace dependencies, but specifying this option overrides them.",
122
123
  )
123
124
  @click.option(
124
125
  "--py-module",
@@ -386,6 +387,15 @@ and override the entrypoint with `python main.py`.
386
387
  help="Max items to show in list.",
387
388
  callback=validate_non_negative_arg,
388
389
  )
390
+ @click.option(
391
+ "--state",
392
+ "-s",
393
+ "states",
394
+ required=False,
395
+ multiple=True,
396
+ help=f"Filter jobs by state. Accepts one or more states. Allowed states: {', '.join(HaJobStates.allowable_values)}",
397
+ callback=validate_list_jobs_state_filter,
398
+ )
389
399
  def list( # noqa: A001
390
400
  name: Optional[str],
391
401
  id: Optional[str], # noqa: A002
@@ -393,6 +403,7 @@ def list( # noqa: A001
393
403
  include_all_users: bool,
394
404
  include_archived: bool,
395
405
  max_items: int,
406
+ states: List[str],
396
407
  ) -> None:
397
408
  job_controller = JobController()
398
409
  job_controller.list(
@@ -402,6 +413,7 @@ def list( # noqa: A001
402
413
  include_all_users=include_all_users,
403
414
  include_archived=include_archived,
404
415
  max_items=max_items,
416
+ states=states,
405
417
  )
406
418
 
407
419
 
@@ -2,10 +2,16 @@
2
2
  This file holds all of the CLI commands for the "anyscale machine-pool" path.
3
3
  """
4
4
 
5
+ import json
6
+
5
7
  import click
6
8
  import tabulate
7
9
  import yaml
8
10
 
11
+ from anyscale.client.openapi_client.models import (
12
+ DescribeMachinePoolResponse,
13
+ SchedulerInfo,
14
+ )
9
15
  from anyscale.commands import command_examples
10
16
  from anyscale.commands.util import AnyscaleCommand
11
17
  from anyscale.controllers.machine_pool_controller import MachinePoolController
@@ -71,6 +77,110 @@ def update_machine_pool(name: str, spec_file: str) -> None:
71
77
  print(f"Updated machine pool '{name}'.")
72
78
 
73
79
 
80
+ @machine_pool_cli.command(
81
+ name="describe",
82
+ help="Describe a machine pool in Anyscale.",
83
+ cls=AnyscaleCommand,
84
+ example=command_examples.MACHINE_POOL_DESCRIBE_EXAMPLE,
85
+ is_beta=True,
86
+ )
87
+ @click.option("--name", type=str, required=True, help="Provide a machine pool name.")
88
+ @click.option(
89
+ "--format",
90
+ "format_",
91
+ type=str,
92
+ default="table",
93
+ required=False,
94
+ help="Output format (table, json).",
95
+ )
96
+ def describe(name: str, format_: str) -> None:
97
+ machine_pool_controller = MachinePoolController()
98
+ response: DescribeMachinePoolResponse = machine_pool_controller.describe_machine_pool(
99
+ machine_pool_name=name
100
+ )
101
+ scheduler_info: SchedulerInfo = response.scheduler_info # type: ignore
102
+ if format_ == "json":
103
+ print(json.dumps(scheduler_info.to_dict(), default=str))
104
+ elif format_ == "table":
105
+ machines_table = []
106
+ columns = [
107
+ "MACHINE ID",
108
+ "TYPE",
109
+ "PARTITION",
110
+ "STATE",
111
+ "WORKLOAD DETAILS",
112
+ "WORKLOAD SCORE",
113
+ "WORKLOAD START TIME",
114
+ "CLOUD INSTANCE ID",
115
+ ]
116
+ for row in scheduler_info.machines:
117
+ machines_table.append(
118
+ [
119
+ row.machine_id,
120
+ row.machine_type,
121
+ row.partition,
122
+ row.allocation_state,
123
+ f"{row.workload_info.workload_type}/{row.workload_info.workload_name}/{row.workload_info.workload_cloud}"
124
+ if row.workload_info.workload_name
125
+ else "",
126
+ row.workload_score,
127
+ str(
128
+ row.workload_info.workload_start_time.astimezone().strftime(
129
+ "%m/%d/%Y %I:%M:%S %p %Z"
130
+ )
131
+ )
132
+ if row.workload_info.workload_name
133
+ else "",
134
+ row.cloud_instance_id,
135
+ ]
136
+ )
137
+
138
+ # Sort by (type, partition, state, workload start time)
139
+ machines_table.sort(key=lambda x: (x[1], x[2], x[3], x[6]))
140
+
141
+ print("Machines:")
142
+ print(
143
+ tabulate.tabulate(
144
+ machines_table, tablefmt="outline", headers=columns, stralign="left"
145
+ )
146
+ )
147
+
148
+ requests_table = []
149
+ columns = [
150
+ "SIZE",
151
+ "MACHINE TYPE",
152
+ "WORKLOAD DETAILS",
153
+ "WORKLOAD START TIME",
154
+ "WORKLOAD CLOUD",
155
+ "PARTITION SCORES",
156
+ ]
157
+ for row in scheduler_info.requests:
158
+ requests_table.append(
159
+ [
160
+ row.size,
161
+ row.machine_type,
162
+ f"{row.workload_info.workload_type}/{row.workload_info.workload_name}",
163
+ str(
164
+ row.workload_info.workload_start_time.astimezone().strftime(
165
+ "%m/%d/%Y %I:%M:%S %p %Z"
166
+ )
167
+ )
168
+ if row.workload_info.workload_name
169
+ else "",
170
+ row.workload_info.workload_cloud,
171
+ row.partition_scores,
172
+ ]
173
+ )
174
+
175
+ # Sort by (machine type, workload start time, size)
176
+ print("Requests:")
177
+ print(
178
+ tabulate.tabulate(
179
+ requests_table, tablefmt="outline", headers=columns, stralign="left"
180
+ )
181
+ )
182
+
183
+
74
184
  @machine_pool_cli.command(
75
185
  name="delete",
76
186
  help="Delete a machine pool in Anyscale.",
@@ -122,7 +232,9 @@ def list_machine_pools(format_: str) -> None:
122
232
  ]
123
233
  )
124
234
  print(
125
- tabulate.tabulate(table, tablefmt="plain", headers=columns, stralign="left")
235
+ tabulate.tabulate(
236
+ table, tablefmt="simple_grid", headers=columns, stralign="left"
237
+ )
126
238
  )
127
239
  elif format_ == "yaml":
128
240
  rows = []
@@ -0,0 +1,98 @@
1
+ import click
2
+ from dateutil import tz
3
+ from rich import print as rprint
4
+ import tabulate
5
+
6
+ import anyscale
7
+ from anyscale.cli_logger import BlockLogger
8
+ from anyscale.commands import command_examples
9
+ from anyscale.commands.util import AnyscaleCommand
10
+
11
+
12
+ log = BlockLogger() # CLI Logger
13
+
14
+
15
+ @click.group("organization-invitation", help="Manage organization invitations.")
16
+ def organization_invitation_cli() -> None:
17
+ pass
18
+
19
+
20
+ @organization_invitation_cli.command(
21
+ name="create",
22
+ cls=AnyscaleCommand,
23
+ example=command_examples.ORGANIZATION_INVITATION_CREATE_EXAMPLE,
24
+ )
25
+ @click.option(
26
+ "--emails",
27
+ required=True,
28
+ type=str,
29
+ help="The emails to send the organization invitations to. Delimited by commas.",
30
+ )
31
+ def create(emails: str,) -> None:
32
+ """
33
+ Creates organization invitations for the provided emails.
34
+ """
35
+ log.info("Creating organization invitations...")
36
+
37
+ success_emails, error_messages = anyscale.organization_invitation.create(
38
+ emails.split(",")
39
+ )
40
+
41
+ if success_emails:
42
+ log.info(f"Organization invitations sent to: {', '.join(success_emails)}")
43
+
44
+ if error_messages:
45
+ for error_message in error_messages:
46
+ log.error(
47
+ f"Failed to send organization invitations with the following errors: {error_message}"
48
+ )
49
+
50
+
51
+ @organization_invitation_cli.command(
52
+ name="list",
53
+ cls=AnyscaleCommand,
54
+ example=command_examples.ORGANIZATION_INVITATION_LIST_EXAMPLE,
55
+ )
56
+ def list() -> None: # noqa: A001
57
+ """
58
+ Lists organization invitations.
59
+ """
60
+ organization_invitations = anyscale.organization_invitation.list()
61
+
62
+ table = tabulate.tabulate(
63
+ [
64
+ (
65
+ i.id,
66
+ i.email,
67
+ i.created_at.astimezone(tz=tz.tzlocal()).strftime("%m/%d/%Y %I:%M %p"),
68
+ i.expires_at.astimezone(tz=tz.tzlocal()).strftime("%m/%d/%Y %I:%M %p"),
69
+ )
70
+ for i in organization_invitations
71
+ ],
72
+ headers=["ID", "Email", "Created At", "Expires At"],
73
+ )
74
+ rprint(table)
75
+
76
+
77
+ @organization_invitation_cli.command(
78
+ name="delete",
79
+ cls=AnyscaleCommand,
80
+ example=command_examples.ORGANIZATION_INVITATION_DELETE_EXAMPLE,
81
+ )
82
+ @click.option(
83
+ "--email",
84
+ required=True,
85
+ type=str,
86
+ help="The email of the organization invitation to delete.",
87
+ )
88
+ def delete(email: str,) -> None:
89
+ """
90
+ Deletes an organization invitation.
91
+ """
92
+ try:
93
+ organization_invitation_email = anyscale.organization_invitation.delete(email)
94
+ except ValueError as e:
95
+ log.error(f"Failed to delete organization invitation: {e}")
96
+ return
97
+
98
+ log.info(f"Organization invitation for {organization_invitation_email} deleted.")
@@ -2,10 +2,16 @@ from typing import Optional
2
2
 
3
3
  import click
4
4
 
5
+ import anyscale
5
6
  from anyscale.cli_logger import BlockLogger
6
- from anyscale.commands.util import LegacyAnyscaleCommand, NotRequiredIf
7
+ from anyscale.commands import command_examples
8
+ from anyscale.commands.util import AnyscaleCommand, LegacyAnyscaleCommand, NotRequiredIf
7
9
  from anyscale.controllers.project_controller import ProjectController
8
- from anyscale.project import validate_project_name
10
+ from anyscale.project.models import (
11
+ CreateProjectCollaborator,
12
+ CreateProjectCollaborators,
13
+ )
14
+ from anyscale.project_utils import validate_project_name
9
15
  from anyscale.util import validate_non_negative_arg
10
16
 
11
17
 
@@ -201,3 +207,47 @@ def init(project_id: Optional[str], name: Optional[str],) -> None:
201
207
  def create(name: str, parent_cloud_id: str) -> None:
202
208
  project_controller = ProjectController()
203
209
  project_controller.create(name, parent_cloud_id)
210
+
211
+
212
+ @project_cli.command(
213
+ name="add-collaborators",
214
+ help="Add collaborators to the project.",
215
+ cls=AnyscaleCommand,
216
+ example=command_examples.PROJECT_ADD_COLLABORATORS_EXAMPLE,
217
+ )
218
+ @click.option(
219
+ "--cloud",
220
+ "-c",
221
+ help="Name of the cloud that the project belongs to.",
222
+ required=True,
223
+ )
224
+ @click.option(
225
+ "--project",
226
+ "-p",
227
+ help="Name of the project to add collaborators to.",
228
+ required=True,
229
+ )
230
+ @click.option(
231
+ "--users-file",
232
+ help="Path to a YAML file containing a list of users to add to the project.",
233
+ required=True,
234
+ )
235
+ def add_collaborators(cloud: str, project: str, users_file: str,) -> None:
236
+ collaborators = CreateProjectCollaborators.from_yaml(users_file)
237
+
238
+ try:
239
+ anyscale.project.add_collaborators(
240
+ cloud=cloud,
241
+ project=project,
242
+ collaborators=[
243
+ CreateProjectCollaborator(**collaborator)
244
+ for collaborator in collaborators.collaborators
245
+ ],
246
+ )
247
+ except ValueError as e:
248
+ log.error(f"Error adding collaborators to project: {e}")
249
+ return
250
+
251
+ log.info(
252
+ f"Successfully added {len(collaborators.collaborators)} collaborators to project {project}."
253
+ )
@@ -1,11 +1,14 @@
1
1
  from typing import List, Optional, Tuple
2
2
 
3
3
  import click
4
+ from rich import print as rprint
5
+ import tabulate
4
6
 
7
+ import anyscale
5
8
  from anyscale.cli_logger import BlockLogger
6
9
  from anyscale.commands import command_examples
7
10
  from anyscale.commands.util import AnyscaleCommand
8
- from anyscale.controllers.resource_quota_controller import ResourceQuotaController
11
+ from anyscale.resource_quota.models import CreateResourceQuota, ResourceQuota
9
12
  from anyscale.util import validate_non_negative_arg
10
13
 
11
14
 
@@ -17,6 +20,43 @@ def resource_quota_cli() -> None:
17
20
  pass
18
21
 
19
22
 
23
+ def _format_resource_quotas(resource_quotas: List[ResourceQuota]) -> str:
24
+ table_rows = []
25
+ for resource_quota in resource_quotas:
26
+ table_rows.append(
27
+ [
28
+ resource_quota.id,
29
+ resource_quota.name,
30
+ resource_quota.cloud_id,
31
+ resource_quota.project_id,
32
+ resource_quota.user_id,
33
+ resource_quota.is_enabled,
34
+ resource_quota.created_at.strftime("%m/%d/%Y"),
35
+ resource_quota.deleted_at.strftime("%m/%d/%Y")
36
+ if resource_quota.deleted_at
37
+ else None,
38
+ resource_quota.quota,
39
+ ]
40
+ )
41
+ table = tabulate.tabulate(
42
+ table_rows,
43
+ headers=[
44
+ "ID",
45
+ "NAME",
46
+ "CLOUD ID",
47
+ "PROJECT ID",
48
+ "USER ID",
49
+ "IS ENABLED",
50
+ "CREATED AT",
51
+ "DELETED AT",
52
+ "QUOTA",
53
+ ],
54
+ tablefmt="plain",
55
+ )
56
+
57
+ return f"Resource quotas:\n{table}"
58
+
59
+
20
60
  @resource_quota_cli.command(
21
61
  name="create",
22
62
  help="Create a resource quota.",
@@ -84,8 +124,7 @@ def create( # noqa: PLR0913
84
124
 
85
125
  `$ anyscale resource-quota create -n my-resource-quota --cloud my-cloud --project my-project --user-email test@myorg.com --num-cpus 10 --num-instances 10 --num-gpus 10 --num-accelerators L4 5 --num-accelerators T4 10`
86
126
  """
87
- resource_quota_controller = ResourceQuotaController()
88
- resource_quota_controller.create(
127
+ create_resource_quota = CreateResourceQuota(
89
128
  name=name,
90
129
  cloud=cloud,
91
130
  project=project,
@@ -96,6 +135,35 @@ def create( # noqa: PLR0913
96
135
  num_accelerators=dict(num_accelerators),
97
136
  )
98
137
 
138
+ try:
139
+ with log.spinner("Creating resource quota..."):
140
+ resource_quota = anyscale.resource_quota.create(create_resource_quota)
141
+
142
+ create_resource_quota_message = [f"Name: {name}\nCloud name: {cloud}"]
143
+ if project:
144
+ create_resource_quota_message.append(f"Project name: {project}")
145
+ if user_email:
146
+ create_resource_quota_message.append(f"User email: {user_email}")
147
+ if num_cpus:
148
+ create_resource_quota_message.append(f"Number of CPUs: {num_cpus}")
149
+ if num_instances:
150
+ create_resource_quota_message.append(
151
+ f"Number of instances: {num_instances}"
152
+ )
153
+ if num_gpus:
154
+ create_resource_quota_message.append(f"Number of GPUs: {num_gpus}")
155
+ if num_accelerators:
156
+ create_resource_quota_message.append(
157
+ f"Number of accelerators: {dict(num_accelerators)}"
158
+ )
159
+
160
+ log.info("\n".join(create_resource_quota_message))
161
+ log.info(f"Resource quota created successfully ID: {resource_quota.id}")
162
+
163
+ except ValueError as e:
164
+ log.error(f"Error creating resource quota: {e}")
165
+ return
166
+
99
167
 
100
168
  @resource_quota_cli.command(
101
169
  name="list",
@@ -141,8 +209,7 @@ def list_resource_quotas(
141
209
 
142
210
  `$ anyscale resource-quota list -n my-resource-quota --cloud my-cloud`
143
211
  """
144
- resource_quota_controller = ResourceQuotaController()
145
- resource_quota_controller.list_resource_quotas(
212
+ resource_quotas = anyscale.resource_quota.list(
146
213
  name=name,
147
214
  cloud=cloud,
148
215
  creator_id=creator_id,
@@ -150,6 +217,8 @@ def list_resource_quotas(
150
217
  max_items=max_items,
151
218
  )
152
219
 
220
+ rprint(_format_resource_quotas(resource_quotas))
221
+
153
222
 
154
223
  @resource_quota_cli.command(
155
224
  name="delete",
@@ -168,8 +237,14 @@ def delete(id: str) -> None: # noqa: A002
168
237
 
169
238
  `$ anyscale resource-quota delete --id rsq_123`
170
239
  """
171
- resource_quota_controller = ResourceQuotaController()
172
- resource_quota_controller.delete(id)
240
+ try:
241
+ with log.spinner("Deleting resource quota..."):
242
+ anyscale.resource_quota.delete(resource_quota_id=id)
243
+ except ValueError as e:
244
+ log.error(f"Error deleting resource quota: {e}")
245
+ return
246
+
247
+ log.info(f"Resource quota with ID {id} deleted successfully.")
173
248
 
174
249
 
175
250
  @resource_quota_cli.command(
@@ -189,8 +264,14 @@ def enable(id: str) -> None: # noqa: A002
189
264
 
190
265
  `$ anyscale resource-quota enable --id rsq_123`
191
266
  """
192
- resource_quota_controller = ResourceQuotaController()
193
- resource_quota_controller.set_status(id, is_enabled=True)
267
+ try:
268
+ with log.spinner("Setting resource quota status..."):
269
+ anyscale.resource_quota.enable(resource_quota_id=id)
270
+ except ValueError as e:
271
+ log.error(f"Error enabling resource quota: {e}")
272
+ return
273
+
274
+ log.info(f"Enabled resource quota with ID {id} successfully.")
194
275
 
195
276
 
196
277
  @resource_quota_cli.command(
@@ -210,5 +291,11 @@ def disable(id: str) -> None: # noqa: A002
210
291
 
211
292
  `$ anyscale resource-quota disable --id rsq_123`
212
293
  """
213
- resource_quota_controller = ResourceQuotaController()
214
- resource_quota_controller.set_status(id, is_enabled=False)
294
+ try:
295
+ with log.spinner("Setting resource quota status..."):
296
+ anyscale.resource_quota.disable(resource_quota_id=id)
297
+ except ValueError as e:
298
+ log.error(f"Error disabling resource quota: {e}")
299
+ return
300
+
301
+ log.info(f"Disabled resource quota with ID {id} successfully.")