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.
- anyscale/_private/anyscale_client/README.md +1 -1
- anyscale/_private/anyscale_client/anyscale_client.py +178 -46
- anyscale/_private/anyscale_client/common.py +61 -2
- anyscale/_private/anyscale_client/fake_anyscale_client.py +145 -8
- anyscale/_private/docgen/__main__.py +34 -23
- anyscale/_private/docgen/generator.py +15 -18
- anyscale/_private/docgen/models.md +4 -2
- anyscale/_private/workload/workload_sdk.py +103 -8
- anyscale/client/README.md +3 -0
- anyscale/client/openapi_client/__init__.py +1 -0
- anyscale/client/openapi_client/api/default_api.py +249 -0
- anyscale/client/openapi_client/models/__init__.py +1 -0
- anyscale/client/openapi_client/models/baseimagesenum.py +83 -1
- anyscale/client/openapi_client/models/cloud_resource.py +59 -3
- anyscale/client/openapi_client/models/cloud_resource_gcp.py +59 -3
- anyscale/client/openapi_client/models/clouddeployment_response.py +121 -0
- anyscale/client/openapi_client/models/create_cloud_resource.py +59 -3
- anyscale/client/openapi_client/models/create_cloud_resource_gcp.py +59 -3
- anyscale/client/openapi_client/models/object_storage.py +2 -2
- anyscale/client/openapi_client/models/ray_runtime_env_config.py +57 -1
- anyscale/client/openapi_client/models/supportedbaseimagesenum.py +80 -1
- anyscale/cloud/models.py +1 -1
- anyscale/commands/cloud_commands.py +73 -70
- anyscale/commands/command_examples.py +28 -40
- anyscale/commands/project_commands.py +377 -106
- anyscale/controllers/cloud_controller.py +81 -86
- anyscale/job/_private/job_sdk.py +38 -20
- anyscale/project/__init__.py +101 -1
- anyscale/project/_private/project_sdk.py +90 -2
- anyscale/project/commands.py +188 -1
- anyscale/project/models.py +198 -2
- anyscale/sdk/anyscale_client/models/baseimagesenum.py +83 -1
- anyscale/sdk/anyscale_client/models/ray_runtime_env_config.py +57 -1
- anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +80 -1
- anyscale/service/_private/service_sdk.py +2 -1
- anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
- anyscale/util.py +3 -0
- anyscale/utils/runtime_env.py +3 -1
- anyscale/version.py +1 -1
- {anyscale-0.26.51.dist-info → anyscale-0.26.52.dist-info}/METADATA +1 -1
- {anyscale-0.26.51.dist-info → anyscale-0.26.52.dist-info}/RECORD +46 -45
- {anyscale-0.26.51.dist-info → anyscale-0.26.52.dist-info}/WHEEL +0 -0
- {anyscale-0.26.51.dist-info → anyscale-0.26.52.dist-info}/entry_points.txt +0 -0
- {anyscale-0.26.51.dist-info → anyscale-0.26.52.dist-info}/licenses/LICENSE +0 -0
- {anyscale-0.26.51.dist-info → anyscale-0.26.52.dist-info}/licenses/NOTICE +0 -0
- {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
|
1744
|
+
def update_cloud_deployments( # noqa: PLR0912, C901
|
1780
1745
|
self,
|
1781
|
-
|
1782
|
-
|
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(
|
1757
|
+
path = pathlib.Path(resources_file)
|
1788
1758
|
if not path.exists():
|
1789
|
-
raise ClickException(f"{
|
1759
|
+
raise ClickException(f"{resources_file} does not exist.")
|
1790
1760
|
if not path.is_file():
|
1791
|
-
raise ClickException(f"{
|
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
|
-
|
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
|
-
"
|
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
|
1774
|
+
"Please use `anyscale cloud deployment create` to add cloud deployments."
|
1816
1775
|
)
|
1817
1776
|
|
1818
|
-
# Diff the existing and new
|
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
|
-
|
1828
|
-
|
1829
|
-
|
1830
|
-
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
1835
|
-
|
1836
|
-
|
1837
|
-
|
1838
|
-
|
1839
|
-
|
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
|
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
|
-
|
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.
|
1850
|
-
cloud_id=cloud_id, cloud_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
|
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.
|
anyscale/job/_private/job_sdk.py
CHANGED
@@ -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
|
-
|
86
|
-
|
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
|
124
|
+
def _get_compute_config_cloud_deployments(
|
110
125
|
self, compute_config: Union[ComputeConfigType, str, None], cloud: Optional[str]
|
111
|
-
) ->
|
126
|
+
) -> List[Optional[str]]:
|
112
127
|
if isinstance(compute_config, ComputeConfig):
|
113
128
|
# single-deployment compute config
|
114
|
-
return
|
129
|
+
return [compute_config.cloud_deployment]
|
115
130
|
|
116
131
|
if isinstance(compute_config, MultiDeploymentComputeConfig):
|
117
|
-
return
|
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
|
-
|
129
|
-
|
130
|
-
|
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.
|
anyscale/project/__init__.py
CHANGED
@@ -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
|
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())
|