anyscale 0.26.51__py3-none-any.whl → 0.26.52__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 (46) 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 +34 -23
  6. anyscale/_private/docgen/generator.py +15 -18
  7. anyscale/_private/docgen/models.md +4 -2
  8. anyscale/_private/workload/workload_sdk.py +103 -8
  9. anyscale/client/README.md +3 -0
  10. anyscale/client/openapi_client/__init__.py +1 -0
  11. anyscale/client/openapi_client/api/default_api.py +249 -0
  12. anyscale/client/openapi_client/models/__init__.py +1 -0
  13. anyscale/client/openapi_client/models/baseimagesenum.py +83 -1
  14. anyscale/client/openapi_client/models/cloud_resource.py +59 -3
  15. anyscale/client/openapi_client/models/cloud_resource_gcp.py +59 -3
  16. anyscale/client/openapi_client/models/clouddeployment_response.py +121 -0
  17. anyscale/client/openapi_client/models/create_cloud_resource.py +59 -3
  18. anyscale/client/openapi_client/models/create_cloud_resource_gcp.py +59 -3
  19. anyscale/client/openapi_client/models/object_storage.py +2 -2
  20. anyscale/client/openapi_client/models/ray_runtime_env_config.py +57 -1
  21. anyscale/client/openapi_client/models/supportedbaseimagesenum.py +80 -1
  22. anyscale/cloud/models.py +1 -1
  23. anyscale/commands/cloud_commands.py +73 -70
  24. anyscale/commands/command_examples.py +28 -40
  25. anyscale/commands/project_commands.py +377 -106
  26. anyscale/controllers/cloud_controller.py +81 -86
  27. anyscale/job/_private/job_sdk.py +38 -20
  28. anyscale/project/__init__.py +101 -1
  29. anyscale/project/_private/project_sdk.py +90 -2
  30. anyscale/project/commands.py +188 -1
  31. anyscale/project/models.py +198 -2
  32. anyscale/sdk/anyscale_client/models/baseimagesenum.py +83 -1
  33. anyscale/sdk/anyscale_client/models/ray_runtime_env_config.py +57 -1
  34. anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +80 -1
  35. anyscale/service/_private/service_sdk.py +2 -1
  36. anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
  37. anyscale/util.py +3 -0
  38. anyscale/utils/runtime_env.py +3 -1
  39. anyscale/version.py +1 -1
  40. {anyscale-0.26.51.dist-info → anyscale-0.26.52.dist-info}/METADATA +1 -1
  41. {anyscale-0.26.51.dist-info → anyscale-0.26.52.dist-info}/RECORD +46 -45
  42. {anyscale-0.26.51.dist-info → anyscale-0.26.52.dist-info}/WHEEL +0 -0
  43. {anyscale-0.26.51.dist-info → anyscale-0.26.52.dist-info}/entry_points.txt +0 -0
  44. {anyscale-0.26.51.dist-info → anyscale-0.26.52.dist-info}/licenses/LICENSE +0 -0
  45. {anyscale-0.26.51.dist-info → anyscale-0.26.52.dist-info}/licenses/NOTICE +0 -0
  46. {anyscale-0.26.51.dist-info → anyscale-0.26.52.dist-info}/top_level.txt +0 -0
@@ -1425,18 +1425,6 @@ class CloudController(BaseController):
1425
1425
  cloud_id, CloudProviders.AWS, functions_to_verify, yes,
1426
1426
  )
1427
1427
 
1428
- def get_cloud_deployment(
1429
- self, cloud_id: str, cloud_deployment_id: str
1430
- ) -> CloudDeployment:
1431
- try:
1432
- return self.api_client.get_cloud_deployment_api_v2_clouds_cloud_id_deployment_get(
1433
- cloud_id=cloud_id, cloud_deployment_id=cloud_deployment_id,
1434
- ).result
1435
- except Exception as e: # noqa: BLE001
1436
- raise ClickException(
1437
- f"Failed to get cloud deployment {cloud_deployment_id} for cloud {cloud_id}. Error: {e}"
1438
- )
1439
-
1440
1428
  # Avoid displaying fields with empty values (since the values for optional fields default to None).
1441
1429
  def _remove_empty_values(self, d):
1442
1430
  if isinstance(d, dict):
@@ -1477,29 +1465,6 @@ class CloudController(BaseController):
1477
1465
  ],
1478
1466
  }
1479
1467
 
1480
- def get_cloud_deployment_dict_by_name(
1481
- self, cloud_name: str, cloud_deployment_name: Optional[str]
1482
- ) -> Dict[str, Any]:
1483
- cloud_id, _ = get_cloud_id_and_name(self.api_client, cloud_name=cloud_name)
1484
-
1485
- result = self.get_cloud_deployments(cloud_id)
1486
- deployments = result.get("deployments", [])
1487
- if len(deployments) == 0:
1488
- raise ClickException(f"Cloud {cloud_name} has no cloud deployments.")
1489
-
1490
- if cloud_deployment_name is None:
1491
- if len(deployments) > 1:
1492
- self.log.warning(
1493
- f"Cloud {cloud_name} has {len(deployments)} deployments, only the primary deployment will be returned."
1494
- )
1495
- return deployments[0]
1496
-
1497
- for deployment in deployments:
1498
- if deployment.get("name") == cloud_deployment_name:
1499
- return deployment
1500
-
1501
- raise ClickException(f"Cloud deployment {cloud_deployment_name} not found.")
1502
-
1503
1468
  def update_aws_anyscale_iam_role(
1504
1469
  self,
1505
1470
  cloud_id: str,
@@ -1776,85 +1741,103 @@ class CloudController(BaseController):
1776
1741
  f"Successfully created cloud deployment{' ' + new_deployment.name if new_deployment.name else ''} in cloud {existing_spec['name']}!"
1777
1742
  )
1778
1743
 
1779
- def update_cloud_deployment( # noqa: PLR0912
1744
+ def update_cloud_deployments( # noqa: PLR0912, C901
1780
1745
  self,
1781
- cloud: str,
1782
- spec_file: str,
1746
+ cloud_name: Optional[str],
1747
+ cloud_id: Optional[str],
1748
+ resources_file: str,
1783
1749
  skip_verification: bool = False,
1784
1750
  yes: bool = False,
1785
1751
  ):
1752
+ if not cloud_id:
1753
+ cloud_id, _ = get_cloud_id_and_name(self.api_client, cloud_name=cloud_name)
1754
+ assert cloud_id
1755
+
1786
1756
  # Read the spec file.
1787
- path = pathlib.Path(spec_file)
1757
+ path = pathlib.Path(resources_file)
1788
1758
  if not path.exists():
1789
- raise ClickException(f"{spec_file} does not exist.")
1759
+ raise ClickException(f"{resources_file} does not exist.")
1790
1760
  if not path.is_file():
1791
- raise ClickException(f"{spec_file} is not a file.")
1761
+ raise ClickException(f"{resources_file} is not a file.")
1792
1762
 
1793
1763
  spec = yaml.safe_load(path.read_text())
1794
- try:
1795
- updated_deployment = CloudDeployment(**spec)
1796
- except Exception as e: # noqa: BLE001
1797
- raise ClickException(f"Failed to parse cloud deployment: {e}")
1798
1764
 
1799
- if not updated_deployment.cloud_deployment_id:
1765
+ # Get the existing spec.
1766
+ existing_spec = self.get_cloud_deployments(cloud_id=cloud_id)
1767
+
1768
+ if len(existing_spec["deployments"]) > len(spec):
1800
1769
  raise ClickException(
1801
- "The cloud deployment must include a cloud_deployment_id."
1770
+ "Please use `anyscale cloud deployment delete` to remove cloud deployments."
1802
1771
  )
1803
-
1804
- # Get the existing cloud deployment.
1805
- cloud_id, _ = get_cloud_id_and_name(self.api_client, cloud_name=cloud)
1806
- existing_deployment = self.get_cloud_deployment(
1807
- cloud_id=cloud_id,
1808
- cloud_deployment_id=updated_deployment.cloud_deployment_id,
1809
- )
1810
- if (
1811
- updated_deployment.provider == CloudProviders.PCP
1812
- or existing_deployment.provider == CloudProviders.PCP
1813
- ):
1772
+ if len(existing_spec["deployments"]) < len(spec):
1814
1773
  raise ClickException(
1815
- "Please use the `anyscale machine-pool` CLI to update machine pools."
1774
+ "Please use `anyscale cloud deployment create` to add cloud deployments."
1816
1775
  )
1817
1776
 
1818
- # Diff the existing and new cloud deployments.
1819
- diff = self._generate_diff(
1820
- self._remove_empty_values(existing_deployment.to_dict()),
1821
- self._remove_empty_values(updated_deployment.to_dict()),
1822
- )
1777
+ # Diff the existing and new specs
1778
+ diff = self._generate_diff(existing_spec["deployments"], spec)
1823
1779
  if not diff:
1824
1780
  self.log.info("No changes detected.")
1825
1781
  return
1826
1782
 
1827
- # Preprocess the deployment if necessary.
1828
- if updated_deployment.provider == CloudProviders.AWS:
1829
- self._preprocess_aws(cloud_id=cloud_id, deployment=updated_deployment)
1830
- elif updated_deployment.provider == CloudProviders.GCP:
1831
- self._preprocess_gcp(deployment=updated_deployment)
1832
- # Skip verification for Kubernetes stacks or if explicitly requested
1833
- if updated_deployment.compute_stack == ComputeStack.K8S:
1834
- self.log.info("Skipping verification for Kubernetes compute stack.")
1835
- elif not skip_verification and not self.verify_cloud_deployment(
1836
- cloud_id=cloud_id, cloud_deployment=updated_deployment
1837
- ):
1838
- raise ClickException(
1839
- f"Verification failed for cloud deployment {updated_deployment.name}."
1840
- )
1783
+ existing_deployments = {
1784
+ deployment["cloud_deployment_id"]: CloudDeployment(**deployment)
1785
+ for deployment in existing_spec["deployments"]
1786
+ }
1787
+
1788
+ updated_deployments: List[CloudDeployment] = []
1789
+ for d in spec:
1790
+ try:
1791
+ deployment = CloudDeployment(**d)
1792
+ except Exception as e: # noqa: BLE001
1793
+ raise ClickException(f"Failed to parse deployment: {e}")
1794
+
1795
+ if not deployment.cloud_deployment_id:
1796
+ raise ClickException(
1797
+ "All cloud deployments must include a cloud_deployment_id."
1798
+ )
1799
+ if deployment.cloud_deployment_id not in existing_deployments:
1800
+ raise ClickException(
1801
+ f"Cloud deployment {deployment.cloud_deployment_id} not found."
1802
+ )
1803
+ if deployment.provider == CloudProviders.PCP:
1804
+ raise ClickException(
1805
+ "Please use the `anyscale machine-pool` CLI to update machine pools."
1806
+ )
1807
+ if deployment != existing_deployments[deployment.cloud_deployment_id]:
1808
+ updated_deployments.append(deployment)
1841
1809
 
1842
1810
  # Log the diff and confirm.
1843
1811
  self.log.info(f"Detected the following changes:\n{diff}")
1844
1812
 
1845
- confirm("Would you like to proceed with updating this cloud deployment?", yes)
1813
+ confirm("Would you like to proceed with updating this cloud?", yes)
1814
+
1815
+ # Preprocess the deployments if necessary.
1816
+ for deployment in updated_deployments:
1817
+ if deployment.provider == CloudProviders.AWS:
1818
+ self._preprocess_aws(cloud_id=cloud_id, deployment=deployment)
1819
+ elif deployment.provider == CloudProviders.GCP:
1820
+ self._preprocess_gcp(deployment=deployment)
1846
1821
 
1847
- # Update the deployment.
1822
+ # Skip verification for Kubernetes stacks or if explicitly requested
1823
+ if deployment.compute_stack == ComputeStack.K8S:
1824
+ self.log.info("Skipping verification for Kubernetes compute stack.")
1825
+ elif not skip_verification and not self.verify_cloud_deployment(
1826
+ cloud_id=cloud_id, cloud_deployment=deployment
1827
+ ):
1828
+ raise ClickException(
1829
+ f"Verification failed for cloud deployment {deployment.name or deployment.cloud_deployment_id}."
1830
+ )
1831
+
1832
+ # Update the deployments.
1848
1833
  try:
1849
- self.api_client.update_cloud_deployment_api_v2_clouds_cloud_id_update_deployment_put(
1850
- cloud_id=cloud_id, cloud_deployment=updated_deployment,
1834
+ self.api_client.update_cloud_deployments_api_v2_clouds_cloud_id_deployments_put(
1835
+ cloud_id=cloud_id, cloud_deployment=updated_deployments,
1851
1836
  )
1852
1837
  except Exception as e: # noqa: BLE001
1853
- raise ClickException(f"Failed to update cloud deployment: {e}")
1838
+ raise ClickException(f"Failed to update cloud deployments: {e}")
1854
1839
 
1855
- self.log.info(
1856
- f"Successfully updated cloud deployment {updated_deployment.name or updated_deployment.cloud_deployment_id} in cloud {cloud}."
1857
- )
1840
+ self.log.info(f"Successfully updated cloud {cloud_name or cloud_id}.")
1858
1841
 
1859
1842
  def remove_cloud_deployment(
1860
1843
  self, cloud_name: str, deployment_name: str, yes: bool,
@@ -2517,6 +2500,8 @@ class CloudController(BaseController):
2517
2500
  cloud_storage_bucket_region: Optional[str] = None,
2518
2501
  nfs_mount_targets: Optional[List[str]] = None,
2519
2502
  nfs_mount_path: Optional[str] = None,
2503
+ persistent_volume_claim: Optional[str] = None,
2504
+ csi_ephemeral_volume_driver: Optional[str] = None,
2520
2505
  kubernetes_zones: Optional[List[str]] = None,
2521
2506
  anyscale_operator_iam_identity: Optional[str] = None,
2522
2507
  ) -> None:
@@ -2588,6 +2573,8 @@ class CloudController(BaseController):
2588
2573
  or region,
2589
2574
  nfs_mount_targets=mount_targets,
2590
2575
  nfs_mount_path=nfs_mount_path,
2576
+ persistent_volume_claim=persistent_volume_claim,
2577
+ csi_ephemeral_volume_driver=csi_ephemeral_volume_driver,
2591
2578
  ),
2592
2579
  ),
2593
2580
  )
@@ -2661,6 +2648,8 @@ class CloudController(BaseController):
2661
2648
  compute_stack: ComputeStack = ComputeStack.VM,
2662
2649
  kubernetes_zones: Optional[List[str]] = None,
2663
2650
  anyscale_operator_iam_identity: Optional[str] = None,
2651
+ persistent_volume_claim: Optional[str] = None,
2652
+ csi_ephemeral_volume_driver: Optional[str] = None,
2664
2653
  ):
2665
2654
  functions_to_verify = self._validate_functional_verification_args(
2666
2655
  functional_verify
@@ -2824,6 +2813,8 @@ class CloudController(BaseController):
2824
2813
  kubernetes_zones=kubernetes_zones,
2825
2814
  kubernetes_dataplane_identity=anyscale_operator_iam_identity,
2826
2815
  cloud_storage_bucket_name=cloud_storage_bucket_name,
2816
+ persistent_volume_claim=persistent_volume_claim,
2817
+ csi_ephemeral_volume_driver=csi_ephemeral_volume_driver,
2827
2818
  )
2828
2819
 
2829
2820
  # Verification is only performed for VM compute stack.
@@ -3187,6 +3178,8 @@ class CloudController(BaseController):
3187
3178
  compute_stack: ComputeStack = ComputeStack.VM,
3188
3179
  kubernetes_zones: Optional[List[str]] = None,
3189
3180
  anyscale_operator_iam_identity: Optional[str] = None,
3181
+ persistent_volume_claim: Optional[str] = None,
3182
+ csi_ephemeral_volume_driver: Optional[str] = None,
3190
3183
  ):
3191
3184
  functions_to_verify = self._validate_functional_verification_args(
3192
3185
  functional_verify
@@ -3336,6 +3329,8 @@ class CloudController(BaseController):
3336
3329
  kubernetes_zones=kubernetes_zones,
3337
3330
  kubernetes_dataplane_identity=anyscale_operator_iam_identity,
3338
3331
  cloud_storage_bucket_name=cloud_storage_bucket_name,
3332
+ persistent_volume_claim=persistent_volume_claim,
3333
+ csi_ephemeral_volume_driver=csi_ephemeral_volume_driver,
3339
3334
  )
3340
3335
 
3341
3336
  # Verification is only performed for VM compute stack.
@@ -82,19 +82,34 @@ class PrivateJobSDK(WorkloadSDK):
82
82
  - 'working_dir' will be set to '.'.
83
83
  - 'pip' will be set to the workspace-managed requirements file.
84
84
  """
85
- runtime_env: Dict[str, Any] = {}
86
- [runtime_env] = self.override_and_upload_local_dirs(
87
- [runtime_env],
88
- working_dir_override=config.working_dir,
89
- excludes_override=config.excludes,
90
- cloud_id=cloud_id,
91
- autopopulate_in_workspace=autopopulate_in_workspace,
92
- additional_py_modules=config.py_modules,
93
- py_executable_override=config.py_executable,
94
- has_multiple_cloud_deployments=self._compute_config_has_multiple_cloud_deployments(
95
- compute_config=config.compute_config, cloud=config.cloud
96
- ),
85
+ cloud_deployments = self._get_compute_config_cloud_deployments(
86
+ compute_config=config.compute_config, cloud=config.cloud
97
87
  )
88
+ assert len(cloud_deployments) > 0
89
+
90
+ runtime_env: Dict[str, Any] = {}
91
+ if len(cloud_deployments) == 1:
92
+ [runtime_env] = self.override_and_upload_local_dirs_single_deployment(
93
+ [runtime_env],
94
+ working_dir_override=config.working_dir,
95
+ excludes_override=config.excludes,
96
+ cloud_id=cloud_id,
97
+ autopopulate_in_workspace=autopopulate_in_workspace,
98
+ additional_py_modules=config.py_modules,
99
+ py_executable_override=config.py_executable,
100
+ cloud_deployment=cloud_deployments[0],
101
+ )
102
+ else:
103
+ [runtime_env] = self.override_and_upload_local_dirs_multi_deployment(
104
+ [runtime_env],
105
+ working_dir_override=config.working_dir,
106
+ excludes_override=config.excludes,
107
+ cloud_id=cloud_id,
108
+ autopopulate_in_workspace=autopopulate_in_workspace,
109
+ additional_py_modules=config.py_modules,
110
+ py_executable_override=config.py_executable,
111
+ cloud_deployments=cloud_deployments,
112
+ )
98
113
  [runtime_env] = self.override_and_load_requirements_files(
99
114
  [runtime_env],
100
115
  requirements_override=config.requirements,
@@ -106,15 +121,15 @@ class PrivateJobSDK(WorkloadSDK):
106
121
 
107
122
  return runtime_env or None
108
123
 
109
- def _compute_config_has_multiple_cloud_deployments(
124
+ def _get_compute_config_cloud_deployments(
110
125
  self, compute_config: Union[ComputeConfigType, str, None], cloud: Optional[str]
111
- ) -> bool:
126
+ ) -> List[Optional[str]]:
112
127
  if isinstance(compute_config, ComputeConfig):
113
128
  # single-deployment compute config
114
- return False
129
+ return [compute_config.cloud_deployment]
115
130
 
116
131
  if isinstance(compute_config, MultiDeploymentComputeConfig):
117
- return len(compute_config.configs) > 1
132
+ return [config.cloud_deployment for config in compute_config.configs]
118
133
 
119
134
  compute_config_id = self._resolve_compute_config_id(
120
135
  compute_config=compute_config, cloud=cloud
@@ -125,10 +140,13 @@ class PrivateJobSDK(WorkloadSDK):
125
140
  f"The compute config '{compute_config_id}' does not exist."
126
141
  )
127
142
 
128
- return (
129
- compute_template.config.deployment_configs
130
- and len(compute_template.config.deployment_configs) > 1
131
- )
143
+ if compute_template.config.deployment_configs is None:
144
+ return [None]
145
+
146
+ return [
147
+ config.cloud_deployment
148
+ for config in compute_template.config.deployment_configs
149
+ ]
132
150
 
133
151
  def get_default_name(self) -> str:
134
152
  """Get a default name for the job.
@@ -1,6 +1,7 @@
1
1
  from typing import List, Optional
2
2
 
3
3
  from anyscale._private.anyscale_client import AnyscaleClientInterface
4
+ from anyscale._private.models.model_base import ResultIterator
4
5
  from anyscale._private.sdk import sdk_docs
5
6
  from anyscale._private.sdk.base_sdk import Timer
6
7
  from anyscale.cli_logger import BlockLogger
@@ -8,9 +9,30 @@ from anyscale.project._private.project_sdk import PrivateProjectSDK
8
9
  from anyscale.project.commands import (
9
10
  _ADD_COLLABORATORS_DOCSTRINGS,
10
11
  _ADD_COLLABORATORS_EXAMPLE,
12
+ _CREATE_PROJECT_DOCSTRINGS,
13
+ _CREATE_PROJECT_EXAMPLE,
14
+ _DELETE_PROJECT_DOCSTRINGS,
15
+ _DELETE_PROJECT_EXAMPLE,
16
+ _GET_DEFAULT_PROJECT_DOCSTRINGS,
17
+ _GET_DEFAULT_PROJECT_EXAMPLE,
18
+ _GET_PROJECT_DOCSTRINGS,
19
+ _GET_PROJECT_EXAMPLE,
20
+ _LIST_PROJECTS_DOCSTRINGS,
21
+ _LIST_PROJECTS_EXAMPLE,
11
22
  add_collaborators,
23
+ create,
24
+ delete,
25
+ get,
26
+ get_default,
27
+ list,
28
+ )
29
+ from anyscale.project.models import (
30
+ CreateProjectCollaborator,
31
+ Project,
32
+ ProjectPermissionLevel,
33
+ ProjectSortField,
34
+ ProjectSortOrder,
12
35
  )
13
- from anyscale.project.models import CreateProjectCollaborator
14
36
 
15
37
 
16
38
  class ProjectSDK:
@@ -33,3 +55,81 @@ class ProjectSDK:
33
55
  """Batch add collaborators to a project.
34
56
  """
35
57
  self._private_sdk.add_collaborators(cloud, project, collaborators)
58
+
59
+ @sdk_docs(
60
+ doc_py_example=_GET_PROJECT_EXAMPLE, arg_docstrings=_GET_PROJECT_DOCSTRINGS,
61
+ )
62
+ def get(self, project_id: str) -> Project: # noqa: F811
63
+ """Get details of a project."""
64
+ return self._private_sdk.get(project_id)
65
+
66
+ @sdk_docs(
67
+ doc_py_example=_LIST_PROJECTS_EXAMPLE, arg_docstrings=_LIST_PROJECTS_DOCSTRINGS,
68
+ )
69
+ def list( # noqa: F811
70
+ self,
71
+ *,
72
+ name_contains: Optional[str] = None,
73
+ creator_id: Optional[str] = None,
74
+ parent_cloud_id: Optional[str] = None,
75
+ include_defaults: bool = True,
76
+ max_items: Optional[int] = None,
77
+ page_size: Optional[int] = None,
78
+ sort_field: Optional[ProjectSortField] = None,
79
+ sort_order: Optional[ProjectSortOrder] = None,
80
+ ) -> ResultIterator[Project]:
81
+ """List all projects with optional filters.
82
+
83
+ Returns
84
+ -------
85
+ ResultIterator[Project]
86
+ An iterator yielding Project objects. Fetches pages
87
+ lazily as needed. Use a `for` loop to iterate or `list()`
88
+ to consume all at once.
89
+ """
90
+ return self._private_sdk.list(
91
+ name_contains=name_contains,
92
+ creator_id=creator_id,
93
+ parent_cloud_id=parent_cloud_id,
94
+ include_defaults=include_defaults,
95
+ max_items=max_items,
96
+ page_size=page_size,
97
+ sort_field=sort_field,
98
+ sort_order=sort_order,
99
+ )
100
+
101
+ @sdk_docs(
102
+ doc_py_example=_CREATE_PROJECT_EXAMPLE,
103
+ arg_docstrings=_CREATE_PROJECT_DOCSTRINGS,
104
+ )
105
+ def create( # noqa: F811
106
+ self,
107
+ name: str,
108
+ parent_cloud_id: str,
109
+ *,
110
+ description: Optional[str] = None,
111
+ initial_cluster_config: Optional[str] = None,
112
+ ) -> str:
113
+ """Create a project."""
114
+ return self._private_sdk.create(
115
+ name,
116
+ description or "",
117
+ parent_cloud_id,
118
+ initial_cluster_config=initial_cluster_config,
119
+ )
120
+
121
+ @sdk_docs(
122
+ doc_py_example=_DELETE_PROJECT_EXAMPLE,
123
+ arg_docstrings=_DELETE_PROJECT_DOCSTRINGS,
124
+ )
125
+ def delete(self, project_id: str): # noqa: F811
126
+ """Delete a project."""
127
+ self._private_sdk.delete(project_id)
128
+
129
+ @sdk_docs(
130
+ doc_py_example=_GET_DEFAULT_PROJECT_EXAMPLE,
131
+ arg_docstrings=_GET_DEFAULT_PROJECT_DOCSTRINGS,
132
+ )
133
+ def get_default(self, parent_cloud_id: str) -> Project: # noqa: F811
134
+ """Get the default project for a cloud."""
135
+ return self._private_sdk.get_default(parent_cloud_id)
@@ -1,11 +1,23 @@
1
- from typing import List
1
+ from typing import List, Optional
2
2
 
3
+ from anyscale._private.models.model_base import ResultIterator
3
4
  from anyscale._private.sdk.base_sdk import BaseSDK
4
5
  from anyscale.client.openapi_client import (
5
6
  CreateUserProjectCollaborator,
6
7
  CreateUserProjectCollaboratorValue,
8
+ Project as OpenAPIProject,
9
+ ProjectListResponse,
10
+ WriteProject,
7
11
  )
8
- from anyscale.project.models import CreateProjectCollaborator
12
+ from anyscale.project.models import (
13
+ CreateProjectCollaborator,
14
+ Project,
15
+ ProjectSortField,
16
+ ProjectSortOrder,
17
+ )
18
+
19
+
20
+ MAX_PAGE_SIZE = 50
9
21
 
10
22
 
11
23
  class PrivateProjectSDK(BaseSDK):
@@ -25,3 +37,79 @@ class PrivateProjectSDK(BaseSDK):
25
37
  for collaborator in collaborators
26
38
  ],
27
39
  )
40
+
41
+ def get(self, project_id: str) -> Optional[Project]:
42
+ project: OpenAPIProject = self.client.get_project(project_id)
43
+ return Project.from_dict(project.to_dict()) if project else None
44
+
45
+ def list(
46
+ self,
47
+ *,
48
+ # filters
49
+ name_contains: Optional[str] = None,
50
+ creator_id: Optional[str] = None,
51
+ parent_cloud_id: Optional[str] = None,
52
+ include_defaults: bool = True,
53
+ # pagination
54
+ max_items: Optional[int] = None,
55
+ page_size: Optional[int] = None,
56
+ # sorting
57
+ sort_field: Optional[ProjectSortField] = None,
58
+ sort_order: Optional[ProjectSortOrder] = None,
59
+ ) -> ResultIterator[Project]:
60
+ if max_items is not None and (max_items <= 0):
61
+ raise ValueError("'max_items' must be greater than 0.")
62
+
63
+ if page_size is not None and (page_size <= 0 or page_size > MAX_PAGE_SIZE):
64
+ raise ValueError(
65
+ f"'page_size' must be between 1 and {MAX_PAGE_SIZE}, inclusive."
66
+ )
67
+
68
+ def _fetch_page(token: Optional[str],) -> ProjectListResponse:
69
+ return self.client.list_projects(
70
+ name_contains=name_contains,
71
+ creator_id=creator_id,
72
+ parent_cloud_id=parent_cloud_id,
73
+ include_defaults=include_defaults,
74
+ sort_field=str(sort_field) if sort_field else None,
75
+ sort_order=str(sort_order) if sort_order else None,
76
+ paging_token=token,
77
+ count=page_size,
78
+ )
79
+
80
+ def _openapi_project_to_sdk_project(openapi_project: OpenAPIProject) -> Project:
81
+ return Project.from_dict(openapi_project.to_dict())
82
+
83
+ return ResultIterator(
84
+ page_token=None,
85
+ max_items=max_items,
86
+ fetch_page=_fetch_page,
87
+ parse_fn=_openapi_project_to_sdk_project,
88
+ )
89
+
90
+ def create(
91
+ self,
92
+ name: str,
93
+ description: str,
94
+ parent_cloud_id: str,
95
+ *,
96
+ initial_cluster_config: Optional[str] = None,
97
+ ) -> str:
98
+ project = self.client.create_project(
99
+ project=WriteProject(
100
+ name=name,
101
+ description=description,
102
+ parent_cloud_id=parent_cloud_id,
103
+ initial_cluster_config=initial_cluster_config,
104
+ )
105
+ )
106
+ return project.id # type: ignore
107
+
108
+ def delete(
109
+ self, project_id: str,
110
+ ):
111
+ self.client.delete_project(project_id)
112
+
113
+ def get_default(self, parent_cloud_id: str,) -> Project:
114
+ project: OpenAPIProject = self.client.get_default_project(parent_cloud_id)
115
+ return Project.from_dict(project.to_dict())