anyscale 0.26.51__py3-none-any.whl → 0.26.53__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 (75) hide show
  1. anyscale/_private/anyscale_client/README.md +1 -1
  2. anyscale/_private/anyscale_client/anyscale_client.py +178 -46
  3. anyscale/_private/anyscale_client/common.py +61 -2
  4. anyscale/_private/anyscale_client/fake_anyscale_client.py +145 -8
  5. anyscale/_private/docgen/__main__.py +42 -31
  6. anyscale/_private/docgen/generator.py +63 -28
  7. anyscale/_private/docgen/models.md +4 -2
  8. anyscale/_private/sdk/__init__.py +124 -1
  9. anyscale/_private/workload/workload_config.py +4 -6
  10. anyscale/_private/workload/workload_sdk.py +105 -12
  11. anyscale/client/README.md +13 -11
  12. anyscale/client/openapi_client/__init__.py +3 -3
  13. anyscale/client/openapi_client/api/default_api.py +512 -316
  14. anyscale/client/openapi_client/models/__init__.py +3 -3
  15. anyscale/client/openapi_client/models/aws_config.py +2 -2
  16. anyscale/client/openapi_client/models/baseimagesenum.py +158 -1
  17. anyscale/client/openapi_client/models/cloud_data_bucket_presigned_url_request.py +31 -3
  18. anyscale/client/openapi_client/models/cloud_deployment.py +37 -36
  19. anyscale/client/openapi_client/models/cloud_resource.py +59 -3
  20. anyscale/client/openapi_client/models/cloud_resource_gcp.py +59 -3
  21. anyscale/client/openapi_client/models/create_cloud_resource.py +59 -3
  22. anyscale/client/openapi_client/models/create_cloud_resource_gcp.py +59 -3
  23. anyscale/client/openapi_client/models/create_resource_notification.py +31 -3
  24. anyscale/client/openapi_client/models/{decorated_cloud_deployment.py → decorated_cloud_resource.py} +124 -96
  25. anyscale/client/openapi_client/models/{clouddeployment_list_response.py → decoratedcloudresource_list_response.py} +15 -15
  26. anyscale/client/openapi_client/models/{decoratedclouddeployment_response.py → decoratedcloudresource_response.py} +11 -11
  27. anyscale/client/openapi_client/models/file_storage.py +4 -4
  28. anyscale/client/openapi_client/models/gcp_config.py +2 -2
  29. anyscale/client/openapi_client/models/ha_job_error_types.py +9 -2
  30. anyscale/client/openapi_client/models/object_storage.py +4 -4
  31. anyscale/client/openapi_client/models/ray_runtime_env_config.py +57 -1
  32. anyscale/client/openapi_client/models/resource_alert_event_type.py +2 -1
  33. anyscale/client/openapi_client/models/resource_notification.py +29 -1
  34. anyscale/client/openapi_client/models/supportedbaseimagesenum.py +155 -1
  35. anyscale/client/openapi_client/models/workload_info.py +31 -3
  36. anyscale/client/openapi_client/models/workload_state_info.py +29 -1
  37. anyscale/cloud/models.py +40 -43
  38. anyscale/commands/cloud_commands.py +93 -88
  39. anyscale/commands/command_examples.py +37 -49
  40. anyscale/commands/exec_commands.py +12 -1
  41. anyscale/commands/list_commands.py +42 -12
  42. anyscale/commands/project_commands.py +399 -115
  43. anyscale/commands/schedule_commands.py +22 -11
  44. anyscale/commands/service_commands.py +11 -6
  45. anyscale/commands/util.py +94 -1
  46. anyscale/commands/workspace_commands.py +92 -38
  47. anyscale/compute_config/__init__.py +1 -1
  48. anyscale/compute_config/_private/compute_config_sdk.py +8 -11
  49. anyscale/compute_config/commands.py +3 -3
  50. anyscale/compute_config/models.py +30 -30
  51. anyscale/controllers/cloud_controller.py +361 -360
  52. anyscale/controllers/kubernetes_verifier.py +1 -1
  53. anyscale/job/_private/job_sdk.py +41 -23
  54. anyscale/job/models.py +1 -1
  55. anyscale/project/__init__.py +101 -1
  56. anyscale/project/_private/project_sdk.py +90 -2
  57. anyscale/project/commands.py +188 -1
  58. anyscale/project/models.py +198 -2
  59. anyscale/sdk/anyscale_client/models/baseimagesenum.py +158 -1
  60. anyscale/sdk/anyscale_client/models/ray_runtime_env_config.py +57 -1
  61. anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +155 -1
  62. anyscale/service/_private/service_sdk.py +2 -1
  63. anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
  64. anyscale/util.py +3 -0
  65. anyscale/utils/runtime_env.py +3 -1
  66. anyscale/version.py +1 -1
  67. anyscale/workspace/commands.py +114 -23
  68. anyscale/workspace/models.py +3 -5
  69. {anyscale-0.26.51.dist-info → anyscale-0.26.53.dist-info}/METADATA +1 -1
  70. {anyscale-0.26.51.dist-info → anyscale-0.26.53.dist-info}/RECORD +75 -75
  71. {anyscale-0.26.51.dist-info → anyscale-0.26.53.dist-info}/WHEEL +0 -0
  72. {anyscale-0.26.51.dist-info → anyscale-0.26.53.dist-info}/entry_points.txt +0 -0
  73. {anyscale-0.26.51.dist-info → anyscale-0.26.53.dist-info}/licenses/LICENSE +0 -0
  74. {anyscale-0.26.51.dist-info → anyscale-0.26.53.dist-info}/licenses/NOTICE +0 -0
  75. {anyscale-0.26.51.dist-info → anyscale-0.26.53.dist-info}/top_level.txt +0 -0
@@ -8,6 +8,7 @@ from typing import Any, Optional
8
8
  import click
9
9
 
10
10
  from anyscale.cli_logger import BlockLogger
11
+ from anyscale.commands.util import DeprecatedAnyscaleCommand
11
12
  from anyscale.controllers.list_controller import ListController
12
13
 
13
14
 
@@ -25,26 +26,40 @@ def list_cli() -> None:
25
26
  name="clouds",
26
27
  help="[DEPRECATED] List the clouds currently available in your account.",
27
28
  hidden=True,
29
+ cls=DeprecatedAnyscaleCommand,
30
+ removal_date="2025-10-01",
31
+ deprecation_message="`anyscale list clouds` has been deprecated",
32
+ alternative="use `anyscale cloud list` instead",
28
33
  )
29
34
  @click.option("--json", "show_json", help="Return the results in json", is_flag=True)
30
35
  def list_clouds(show_json: bool) -> None:
31
- log.warning(
32
- "`anyscale list clouds` has been deprecated. Please use `anyscale cloud list` instead."
33
- )
36
+ """List the clouds currently available in your account.
37
+
38
+ DEPRECATED: This command will be removed on 2025-10-01.
39
+ Use 'anyscale cloud list' instead.
40
+ """
34
41
  list_controller = ListController()
35
42
  output = list_controller.list_clouds(json_format=show_json)
36
43
  print(output)
37
44
 
38
45
 
39
46
  @list_cli.command(
40
- name="projects", help="[DEPRECATED] List all accessible projects.", hidden=True
47
+ name="projects",
48
+ help="[DEPRECATED] List all accessible projects.",
49
+ hidden=True,
50
+ cls=DeprecatedAnyscaleCommand,
51
+ removal_date="2025-10-01",
52
+ deprecation_message="`anyscale list projects` has been deprecated",
53
+ alternative="use `anyscale project list` instead",
41
54
  )
42
55
  @click.option("--json", "show_json", help="Return the results in json", is_flag=True)
43
56
  @click.pass_context
44
57
  def project_list(ctx: Any, show_json: bool) -> None: # noqa: ARG001
45
- log.warning(
46
- "`anyscale list projects` has been deprecated. Please use `anyscale project list` instead."
47
- )
58
+ """List all accessible projects.
59
+
60
+ DEPRECATED: This command will be removed on 2025-10-01.
61
+ Use 'anyscale project list' instead.
62
+ """
48
63
  list_controller = ListController()
49
64
  output = list_controller.list_projects(json_format=show_json)
50
65
  print(output)
@@ -54,6 +69,10 @@ def project_list(ctx: Any, show_json: bool) -> None: # noqa: ARG001
54
69
  name="sessions",
55
70
  help="[DEPRECATED] List all clusters within the current project.",
56
71
  hidden=True,
72
+ cls=DeprecatedAnyscaleCommand,
73
+ removal_date="2025-10-01",
74
+ deprecation_message="`anyscale list sessions` has been deprecated",
75
+ alternative="use `anyscale cluster list` instead",
57
76
  )
58
77
  @click.option(
59
78
  "--name",
@@ -65,9 +84,11 @@ def project_list(ctx: Any, show_json: bool) -> None: # noqa: ARG001
65
84
  @click.option("--all", help="List all clusters, including inactive ones.", is_flag=True)
66
85
  @click.option("--json", "show_json", help="Return the results in json", is_flag=True)
67
86
  def session_list(name: Optional[str], all: bool, show_json: bool) -> None: # noqa: A002
68
- log.warning(
69
- "`anyscale list sessions` has been deprecated. Please use `anyscale cluster list` instead."
70
- )
87
+ """List all clusters within the current project.
88
+
89
+ DEPRECATED: This command will be removed on 2025-10-01.
90
+ Use 'anyscale cluster list' instead.
91
+ """
71
92
  list_controller = ListController()
72
93
  output = list_controller.list_sessions(
73
94
  name=name, show_all=all, json_format=show_json
@@ -79,7 +100,16 @@ def session_list(name: Optional[str], all: bool, show_json: bool) -> None: # no
79
100
  name="ips",
80
101
  help="[DEPRECATED] List IP addresses of head and worker nodes.",
81
102
  context_settings={"ignore_unknown_options": True, "allow_extra_args": True,},
103
+ cls=DeprecatedAnyscaleCommand,
104
+ removal_date="2025-10-01",
105
+ deprecation_message="Listing IPs is not supported on Anyscale",
106
+ alternative="use the Anyscale console to view cluster node information",
82
107
  )
83
108
  def list_ips() -> None:
84
- """List IP addresses of head and worker nodes."""
85
- raise click.ClickException("Listing IPs is not supported on Anyscale V2")
109
+ """List IP addresses of head and worker nodes.
110
+
111
+ DEPRECATED: This command will be removed on 2025-10-01.
112
+ Listing IPs is not supported on Anyscale.
113
+ Use the Anyscale console to view cluster node information.
114
+ """
115
+ raise click.ClickException("Listing IPs is not supported on Anyscale")
@@ -1,22 +1,103 @@
1
- from typing import Optional
1
+ from io import StringIO
2
+ from json import dumps as json_dumps
3
+ import sys
4
+ from typing import Dict, Optional, Tuple
2
5
 
3
6
  import click
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+ import yaml
4
10
 
5
11
  import anyscale
6
12
  from anyscale.cli_logger import BlockLogger
7
13
  from anyscale.commands import command_examples
8
- from anyscale.commands.util import AnyscaleCommand, LegacyAnyscaleCommand, NotRequiredIf
14
+ from anyscale.commands.list_util import display_list
15
+ from anyscale.commands.util import (
16
+ AnyscaleCommand,
17
+ DeprecatedAnyscaleCommand,
18
+ NotRequiredIf,
19
+ )
9
20
  from anyscale.controllers.project_controller import ProjectController
10
21
  from anyscale.project.models import (
11
22
  CreateProjectCollaborator,
12
23
  CreateProjectCollaborators,
24
+ Project,
25
+ ProjectMinimal,
26
+ ProjectSortField,
27
+ ProjectSortOrder,
13
28
  )
14
29
  from anyscale.project_utils import validate_project_name
15
- from anyscale.util import validate_non_negative_arg
30
+ from anyscale.util import (
31
+ AnyscaleJSONEncoder,
32
+ get_endpoint,
33
+ )
16
34
 
17
35
 
18
36
  log = BlockLogger()
19
37
 
38
+ MAX_PAGE_SIZE = 50
39
+ NON_INTERACTIVE_DEFAULT_MAX_ITEMS = 10
40
+
41
+
42
+ def _create_project_list_table(show_header: bool) -> Table:
43
+ table = Table(show_header=show_header, expand=True)
44
+ # NAME and ID: larger ratios, can wrap but never truncate
45
+ table.add_column(
46
+ "NAME", no_wrap=False, overflow="fold", ratio=3, min_width=15,
47
+ )
48
+ table.add_column(
49
+ "ID", no_wrap=False, overflow="fold", ratio=2, min_width=12,
50
+ )
51
+ # all other columns will wrap as needed
52
+ for heading in (
53
+ "DESCRIPTION",
54
+ "CREATED AT",
55
+ "CREATOR",
56
+ "PARENT CLOUD ID",
57
+ ):
58
+ table.add_column(
59
+ heading, no_wrap=False, overflow="fold", ratio=1, min_width=8,
60
+ )
61
+ return table
62
+
63
+
64
+ def _format_project_output_data(project: Project) -> Dict[str, str]:
65
+ return {
66
+ "name": project.name,
67
+ "id": project.id,
68
+ "description": project.description,
69
+ "created_at": project.created_at,
70
+ "creator": str(project.creator_id or ""),
71
+ "parent_cloud_id": str(project.parent_cloud_id or ""),
72
+ }
73
+
74
+
75
+ def _parse_sort_option(
76
+ sort: Optional[str],
77
+ ) -> Tuple[Optional[ProjectSortField], ProjectSortOrder]:
78
+ if not sort:
79
+ return None, ProjectSortOrder.ASC
80
+
81
+ # build case-insensitive map of allowed fields
82
+ allowed = {f.value.lower(): f.value for f in ProjectSortField.__members__.values()}
83
+
84
+ # detect leading '-' for descending
85
+ if sort.startswith("-"):
86
+ raw = sort[1:]
87
+ order = ProjectSortOrder.DESC
88
+ else:
89
+ raw = sort
90
+ order = ProjectSortOrder.ASC
91
+
92
+ key = raw.lower()
93
+ if key not in allowed:
94
+ allowed_names = ", ".join(sorted(allowed.values()))
95
+ raise click.BadParameter(
96
+ f"Invalid sort field '{raw}'. Allowed fields: {allowed_names}"
97
+ )
98
+
99
+ return ProjectSortField(allowed[key]), order
100
+
20
101
 
21
102
  @click.group(
22
103
  "project",
@@ -28,49 +109,316 @@ def project_cli() -> None:
28
109
 
29
110
 
30
111
  @project_cli.command(
31
- name="list",
32
- short_help="List projects for which you have access.",
33
- help="List projects for which you have access. By default, only projects created by you are listed.",
34
- cls=LegacyAnyscaleCommand,
35
- is_limited_support=True,
36
- legacy_prefix="anyscale project",
112
+ name="get",
113
+ help="Get details of a project.",
114
+ cls=AnyscaleCommand,
115
+ example=command_examples.PROJECT_GET_EXAMPLE,
37
116
  )
38
117
  @click.option(
39
- "--name", "-n", help="List information for a particular project.", type=str
118
+ "--id", "-i", type=str, required=True, help="ID of the project.",
40
119
  )
41
- @click.option("--json", help="Format output as JSON.", is_flag=True)
42
120
  @click.option(
43
- "--any-creator",
44
- "-a",
45
- help="[Deprecated] List projects created by any user.",
121
+ "--json",
122
+ "-j",
46
123
  is_flag=True,
47
- default=None,
48
- hidden=True,
124
+ default=False,
125
+ help="Output the details in a structured JSON format.",
126
+ )
127
+ def get(id: str, json: bool = False): # noqa: A002
128
+ try:
129
+ project: Project = anyscale.project.get(project_id=id)
130
+ except ValueError as e:
131
+ log.error(f"Error getting project details: {e}")
132
+ sys.exit(1)
133
+
134
+ console = Console()
135
+ if json:
136
+ json_str = json_dumps(project.to_dict(), indent=2, cls=AnyscaleJSONEncoder)
137
+ console.print_json(json=json_str)
138
+ else:
139
+ stream = StringIO()
140
+ yaml.dump(project.to_dict(), stream, sort_keys=False)
141
+ console.print(stream.getvalue(), end="")
142
+
143
+
144
+ @project_cli.command(
145
+ name="list",
146
+ help="List all projects with optional filters.",
147
+ cls=AnyscaleCommand,
148
+ example=command_examples.PROJECT_LIST_EXAMPLE,
49
149
  )
50
- @click.option("--created-by-me", help="List projects created by me only.", is_flag=True)
51
150
  @click.option(
52
- "--max-items",
53
- required=False,
54
- default=20,
151
+ "--name", "-n", type=str, help="A string to filter projects by name.",
152
+ )
153
+ @click.option(
154
+ "--creator", "-u", type=str, help="The ID of a creator to filter projects.",
155
+ )
156
+ @click.option(
157
+ "--cloud", "-c", type=str, help="The ID of a parent cloud to filter projects.",
158
+ )
159
+ @click.option(
160
+ "--include-defaults/--exclude-defaults",
161
+ default=True,
162
+ show_default=True,
163
+ help="Whether to include default projects.",
164
+ )
165
+ @click.option(
166
+ "--max-items", type=int, help="The maximum number of projects to return.",
167
+ )
168
+ @click.option(
169
+ "--page-size",
55
170
  type=int,
56
- help="Max items to show in list.",
57
- callback=validate_non_negative_arg,
171
+ default=10,
172
+ show_default=True,
173
+ help="The number of projects to return per page.",
174
+ )
175
+ @click.option(
176
+ "--sort",
177
+ help=(
178
+ "Sort by FIELD (prefix with '-' for desc). "
179
+ f"Allowed: {', '.join(ProjectSortField.__members__.values())}"
180
+ ),
181
+ )
182
+ @click.option(
183
+ "--interactive/--no-interactive",
184
+ default=True,
185
+ show_default=True,
186
+ help="Use interactive paging.",
187
+ )
188
+ @click.option(
189
+ "--json",
190
+ "-j",
191
+ is_flag=True,
192
+ default=False,
193
+ help="Output the list in a structured JSON format.",
58
194
  )
59
195
  def list( # noqa: A001
196
+ *,
197
+ name: Optional[str] = None,
198
+ creator: Optional[str] = None,
199
+ cloud: Optional[str] = None,
200
+ include_defaults: bool = True,
201
+ max_items: Optional[int] = None,
202
+ page_size: Optional[int] = None,
203
+ sort: Optional[str] = None,
204
+ interactive: bool = True,
205
+ json: bool = False,
206
+ ):
207
+
208
+ if max_items is not None and interactive:
209
+ raise click.UsageError("--max-items only allowed with --no-interactive")
210
+
211
+ sort_field, sort_order = _parse_sort_option(sort)
212
+
213
+ # normalize max_items
214
+ effective_max = max_items
215
+ if not interactive and effective_max is None:
216
+ stderr = Console(stderr=True)
217
+ stderr.print(
218
+ f"Defaulting to {NON_INTERACTIVE_DEFAULT_MAX_ITEMS} items in batch mode; "
219
+ "use --max-items to override."
220
+ )
221
+ effective_max = NON_INTERACTIVE_DEFAULT_MAX_ITEMS
222
+
223
+ console = Console()
224
+ stderr = Console(stderr=True)
225
+
226
+ # diagnostics
227
+ stderr.print("[bold]Listing projects with:[/]")
228
+ stderr.print(f"• name_contains = {name or '<any>'}")
229
+ stderr.print(f"• creator_id = {creator or '<any>'}")
230
+ stderr.print(f"• parent_cloud_id = {cloud or '<any>'}")
231
+ stderr.print(f"• include defaults = {include_defaults}")
232
+ stderr.print(f"• sort-field = {sort_field or '<none>'}")
233
+ stderr.print(f"• sort-order = {sort_order or '<none>'}")
234
+ stderr.print(f"• mode = {'interactive' if interactive else 'batch'}")
235
+ stderr.print(f"• per-page limit = {page_size}")
236
+ stderr.print(f"• max-items total = {effective_max or 'all'}")
237
+ stderr.print(f"\nView your Projects in the UI at {get_endpoint('/projects')}\n")
238
+
239
+ # choose formatter
240
+ if json:
241
+
242
+ def formatter(project):
243
+ return ProjectMinimal.from_dict(project.to_dict()).to_dict()
244
+
245
+ else:
246
+ formatter = _format_project_output_data
247
+
248
+ total = 0
249
+ try:
250
+ iterator = anyscale.project.list(
251
+ name_contains=name,
252
+ creator_id=creator,
253
+ parent_cloud_id=cloud,
254
+ include_defaults=include_defaults,
255
+ max_items=effective_max,
256
+ page_size=page_size,
257
+ sort_field=sort_field,
258
+ sort_order=sort_order,
259
+ )
260
+ total = display_list(
261
+ iterator=iter(iterator),
262
+ item_formatter=formatter,
263
+ table_creator=_create_project_list_table,
264
+ json_output=json,
265
+ page_size=page_size or MAX_PAGE_SIZE,
266
+ interactive=interactive,
267
+ max_items=effective_max,
268
+ console=console,
269
+ )
270
+
271
+ if not json:
272
+ if total > 0:
273
+ stderr.print(f"\nFetched {total} projects.")
274
+ else:
275
+ stderr.print("\nNo projects found.")
276
+ except Exception as e: # noqa: BLE001
277
+ log.error(f"Failed to list projects: {e}")
278
+ sys.exit(1)
279
+
280
+
281
+ @project_cli.command(
282
+ name="create",
283
+ help="Create a new project.",
284
+ cls=AnyscaleCommand,
285
+ example=command_examples.PROJECT_CREATE_EXAMPLE,
286
+ )
287
+ @click.option(
288
+ "--name", "-n", type=str, required=True, help="Name of the project.",
289
+ )
290
+ @click.option(
291
+ "--cloud", "-c", type=str, required=True, help="Parent cloud ID for the project.",
292
+ )
293
+ @click.option(
294
+ "--description", "-d", type=str, help="Description of the project.",
295
+ )
296
+ @click.option(
297
+ "--initial-cluster-config",
298
+ "-f",
299
+ type=str,
300
+ help="Initial cluster config for the project.",
301
+ )
302
+ def create(
60
303
  name: str,
61
- json: bool,
62
- created_by_me: bool,
63
- any_creator: Optional[bool],
64
- max_items: int,
304
+ cloud: str,
305
+ *,
306
+ description: Optional[str] = None,
307
+ initial_cluster_config: Optional[str] = None,
65
308
  ) -> None:
66
- if any_creator is not None:
67
- log.warning(
68
- "`--any-creator` and `-a` flags have been deprecated. "
69
- "`anyscale project list` now shows projects created by any user by default. "
70
- "If you would like to show projects created by you only, you can pass the --created-by-me flag"
309
+ try:
310
+ project_id: str = anyscale.project.create(
311
+ name,
312
+ cloud,
313
+ description=description or "",
314
+ initial_cluster_config=initial_cluster_config,
71
315
  )
72
- project_controller = ProjectController()
73
- project_controller.list(name, json, created_by_me, max_items)
316
+ except ValueError as e:
317
+ log.error(f"Error creating project: {e}")
318
+ sys.exit(1)
319
+
320
+ log.info(f"Created project '{name}' with ID: {project_id}")
321
+
322
+
323
+ @project_cli.command(
324
+ name="delete",
325
+ help="Delete a project.",
326
+ cls=AnyscaleCommand,
327
+ example=command_examples.PROJECT_DELETE_EXAMPLE,
328
+ )
329
+ @click.option(
330
+ "--id", "-i", type=str, required=True, help="ID of the project to delete.",
331
+ )
332
+ def delete(id: str): # noqa: A002
333
+ try:
334
+ anyscale.project.delete(id)
335
+ except ValueError as e:
336
+ log.error(f"Error deleting project: {e}")
337
+ sys.exit(1)
338
+
339
+ log.info(f"Deleted project '{id}'")
340
+
341
+
342
+ @project_cli.command(
343
+ name="get-default",
344
+ help="Get the default project for a cloud.",
345
+ cls=AnyscaleCommand,
346
+ example=command_examples.PROJECT_GET_DEFAULT_EXAMPLE,
347
+ )
348
+ @click.option(
349
+ "--cloud", "-c", type=str, required=True, help="Parent cloud ID for the project.",
350
+ )
351
+ @click.option(
352
+ "--json",
353
+ "-j",
354
+ is_flag=True,
355
+ default=False,
356
+ help="Output the project in a structured JSON format.",
357
+ )
358
+ def get_default(cloud: str, json: bool = False):
359
+ try:
360
+ project: Project = anyscale.project.get_default(cloud)
361
+ except ValueError as e:
362
+ log.error(f"Error getting default project for cloud '{cloud}': {e}")
363
+ sys.exit(1)
364
+
365
+ console = Console()
366
+ if json:
367
+ json_str = json_dumps(project.to_dict(), indent=2, cls=AnyscaleJSONEncoder)
368
+ console.print_json(json=json_str)
369
+ else:
370
+ stream = StringIO()
371
+ yaml.dump(project.to_dict(), stream, sort_keys=False)
372
+ console.print(stream.getvalue(), end="")
373
+
374
+
375
+ @project_cli.command(
376
+ name="add-collaborators",
377
+ help="Add collaborators to the project.",
378
+ cls=AnyscaleCommand,
379
+ example=command_examples.PROJECT_ADD_COLLABORATORS_EXAMPLE,
380
+ )
381
+ @click.option(
382
+ "--cloud",
383
+ "-c",
384
+ help="Name of the cloud that the project belongs to.",
385
+ required=True,
386
+ )
387
+ @click.option(
388
+ "--project",
389
+ "-p",
390
+ help="Name of the project to add collaborators to.",
391
+ required=True,
392
+ )
393
+ @click.option(
394
+ "--users-file",
395
+ help="Path to a YAML file containing a list of users to add to the project.",
396
+ required=True,
397
+ )
398
+ def add_collaborators(cloud: str, project: str, users_file: str) -> None:
399
+ collaborators = CreateProjectCollaborators.from_yaml(users_file)
400
+
401
+ try:
402
+ anyscale.project.add_collaborators(
403
+ cloud=cloud,
404
+ project=project,
405
+ collaborators=[
406
+ CreateProjectCollaborator(**collaborator)
407
+ for collaborator in collaborators.collaborators
408
+ ],
409
+ )
410
+ except ValueError as e:
411
+ log.error(f"Error adding collaborators to project: {e}")
412
+ return
413
+
414
+ log.info(
415
+ f"Successfully added {len(collaborators.collaborators)} collaborators to project {project}."
416
+ )
417
+
418
+
419
+ # ================================================
420
+ # LEGACY CODE
421
+ # ================================================
74
422
 
75
423
 
76
424
  def _validate_project_name(ctx, param, value) -> str: # noqa: ARG001
@@ -97,6 +445,10 @@ def _default_project_name() -> str:
97
445
  "[DEPRECATED] Create a new project or attach this directory to an existing project."
98
446
  ),
99
447
  hidden=True,
448
+ cls=DeprecatedAnyscaleCommand,
449
+ removal_date="2025-10-01",
450
+ deprecation_message="`anyscale init` has been deprecated",
451
+ alternative="use `anyscale project create` to create a new project",
100
452
  )
101
453
  @click.option(
102
454
  "--project-id",
@@ -130,10 +482,11 @@ def anyscale_init(
130
482
  config: Optional[str],
131
483
  requirements: Optional[str],
132
484
  ) -> None:
133
- log.warning(
134
- "`anyscale init` has been deprecated. Please use `anyscale project init` "
135
- "to create or attach to a project from this directory."
136
- )
485
+ """Create a new project or attach this directory to an existing project.
486
+
487
+ DEPRECATED: This command will be removed on 2025-10-01.
488
+ Use 'anyscale project create' to create a new project.
489
+ """
137
490
  if (project_id and name) or not (project_id or name):
138
491
  raise click.BadArgumentUsage(
139
492
  "Only one of project_id and name must be provided."
@@ -147,6 +500,10 @@ def anyscale_init(
147
500
  name="init",
148
501
  help="[DEPRECATED] Create a new project or attach this directory to an existing project.",
149
502
  hidden=True,
503
+ cls=DeprecatedAnyscaleCommand,
504
+ removal_date="2025-10-01",
505
+ deprecation_message="`anyscale project init` has been deprecated",
506
+ alternative="use `anyscale project create` to create a new project and specify a project id or name for the other Anyscale CLI commands",
150
507
  )
151
508
  @click.option(
152
509
  "--project-id",
@@ -166,11 +523,11 @@ def anyscale_init(
166
523
  default=_default_project_name(),
167
524
  )
168
525
  def init(project_id: Optional[str], name: Optional[str],) -> None:
169
- log.warning(
170
- "`anyscale project init` has been deprecated and will be removed in "
171
- "April 2022. Please use `anyscale project create` to create a new project "
172
- "and specify a project id or name for the other Anyscale CLI commands."
173
- )
526
+ """Create a new project or attach this directory to an existing project.
527
+
528
+ DEPRECATED: This command will be removed on 2025-10-01.
529
+ Use 'anyscale project create' to create a new project.
530
+ """
174
531
  if (project_id and name) or not (project_id or name):
175
532
  raise click.BadArgumentUsage(
176
533
  "Only one of --project-id and --name must be provided."
@@ -178,76 +535,3 @@ def init(project_id: Optional[str], name: Optional[str],) -> None:
178
535
 
179
536
  project_controller = ProjectController()
180
537
  project_controller.init(project_id, name, None, None)
181
-
182
-
183
- @project_cli.command(
184
- name="create",
185
- help="Create a new project.",
186
- cls=LegacyAnyscaleCommand,
187
- is_limited_support=True,
188
- legacy_prefix="anyscale project",
189
- )
190
- @click.option(
191
- "--name",
192
- "-n",
193
- help="Project name.",
194
- callback=_validate_project_name,
195
- prompt=True,
196
- default=_default_project_name(),
197
- )
198
- @click.option(
199
- "--parent-cloud-id",
200
- required=False,
201
- default=None,
202
- help=(
203
- "Cloud id that this project is associated with. This argument "
204
- "is only relevant if cloud isolation is enabled."
205
- ),
206
- )
207
- def create(name: str, parent_cloud_id: str) -> None:
208
- project_controller = ProjectController()
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
- )