anyscale 0.26.70__py3-none-any.whl → 0.26.71__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/anyscale_client.py +63 -6
- anyscale/_private/anyscale_client/common.py +33 -3
- anyscale/_private/anyscale_client/fake_anyscale_client.py +27 -2
- anyscale/client/README.md +29 -0
- anyscale/client/openapi_client/__init__.py +19 -0
- anyscale/client/openapi_client/api/default_api.py +1307 -4
- anyscale/client/openapi_client/models/__init__.py +19 -0
- anyscale/client/openapi_client/models/apply_multi_version_update_weights_update_model.py +152 -0
- anyscale/client/openapi_client/models/apply_version_weight_update_model.py +181 -0
- anyscale/client/openapi_client/models/backend_server_api_product_models_catalog_client_models_table_metadata.py +546 -0
- anyscale/client/openapi_client/models/backend_server_api_product_models_data_catalogs_table_metadata.py +178 -0
- anyscale/client/openapi_client/models/baseimagesenum.py +70 -1
- anyscale/client/openapi_client/models/catalog_metadata.py +150 -0
- anyscale/client/openapi_client/models/column_info.py +265 -0
- anyscale/client/openapi_client/models/compute_node_type.py +29 -1
- anyscale/client/openapi_client/models/connection_metadata.py +206 -0
- anyscale/client/openapi_client/models/create_workspace_template_version.py +31 -3
- anyscale/client/openapi_client/models/data_catalog.py +45 -31
- anyscale/client/openapi_client/models/data_catalog_connection.py +74 -58
- anyscale/client/openapi_client/models/data_catalog_object_type.py +100 -0
- anyscale/client/openapi_client/models/data_catalog_schema.py +324 -0
- anyscale/client/openapi_client/models/data_catalog_table.py +437 -0
- anyscale/client/openapi_client/models/data_catalog_volume.py +437 -0
- anyscale/client/openapi_client/models/datacatalogschema_list_response.py +147 -0
- anyscale/client/openapi_client/models/datacatalogtable_list_response.py +147 -0
- anyscale/client/openapi_client/models/datacatalogvolume_list_response.py +147 -0
- anyscale/client/openapi_client/models/decorated_serve_deployment.py +27 -1
- anyscale/client/openapi_client/models/decoratedproductionservicev2_versionapimodel_response.py +121 -0
- anyscale/client/openapi_client/models/describe_machine_pool_machines_filters.py +2 -2
- anyscale/client/openapi_client/models/describe_machine_pool_requests_filters.py +33 -5
- anyscale/client/openapi_client/models/describe_machine_pool_workloads_filters.py +2 -2
- anyscale/client/openapi_client/models/physical_resources.py +178 -0
- anyscale/client/openapi_client/models/schema_metadata.py +150 -0
- anyscale/client/openapi_client/models/sso_config.py +18 -18
- anyscale/client/openapi_client/models/supportedbaseimagesenum.py +70 -1
- anyscale/client/openapi_client/models/table_data_preview.py +209 -0
- anyscale/client/openapi_client/models/volume_metadata.py +150 -0
- anyscale/client/openapi_client/models/worker_node_type.py +29 -1
- anyscale/client/openapi_client/models/workspace_template_version.py +29 -1
- anyscale/client/openapi_client/models/workspace_template_version_data_object.py +29 -1
- anyscale/commands/job_commands.py +120 -0
- anyscale/commands/job_queue_commands.py +99 -2
- anyscale/commands/service_commands.py +139 -2
- anyscale/commands/util.py +104 -1
- anyscale/commands/workspace_commands.py +123 -5
- anyscale/commands/workspace_commands_v2.py +17 -1
- anyscale/compute_config/_private/compute_config_sdk.py +25 -12
- anyscale/compute_config/models.py +15 -0
- anyscale/controllers/job_controller.py +12 -0
- anyscale/controllers/workspace_controller.py +67 -5
- anyscale/job/_private/job_sdk.py +3 -1
- anyscale/job/models.py +16 -0
- anyscale/job_queue/__init__.py +37 -1
- anyscale/job_queue/_private/job_queue_sdk.py +28 -1
- anyscale/job_queue/commands.py +61 -1
- anyscale/sdk/anyscale_client/__init__.py +1 -0
- anyscale/sdk/anyscale_client/api/default_api.py +12 -2
- anyscale/sdk/anyscale_client/models/__init__.py +1 -0
- anyscale/sdk/anyscale_client/models/baseimagesenum.py +70 -1
- anyscale/sdk/anyscale_client/models/compute_node_type.py +29 -1
- anyscale/sdk/anyscale_client/models/physical_resources.py +178 -0
- anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +70 -1
- anyscale/sdk/anyscale_client/models/worker_node_type.py +29 -1
- anyscale/service/__init__.py +40 -0
- anyscale/service/_private/service_sdk.py +121 -24
- anyscale/service/commands.py +75 -1
- anyscale/service/models.py +46 -2
- anyscale/shared_anyscale_utils/latest_ray_version.py +1 -1
- anyscale/version.py +1 -1
- anyscale/workspace/_private/workspace_sdk.py +1 -0
- anyscale/workspace/models.py +19 -0
- {anyscale-0.26.70.dist-info → anyscale-0.26.71.dist-info}/METADATA +1 -1
- {anyscale-0.26.70.dist-info → anyscale-0.26.71.dist-info}/RECORD +78 -58
- {anyscale-0.26.70.dist-info → anyscale-0.26.71.dist-info}/WHEEL +0 -0
- {anyscale-0.26.70.dist-info → anyscale-0.26.71.dist-info}/entry_points.txt +0 -0
- {anyscale-0.26.70.dist-info → anyscale-0.26.71.dist-info}/licenses/LICENSE +0 -0
- {anyscale-0.26.70.dist-info → anyscale-0.26.71.dist-info}/licenses/NOTICE +0 -0
- {anyscale-0.26.70.dist-info → anyscale-0.26.71.dist-info}/top_level.txt +0 -0
|
@@ -36,6 +36,7 @@ class WorkerNodeType(object):
|
|
|
36
36
|
'name': 'str',
|
|
37
37
|
'instance_type': 'str',
|
|
38
38
|
'resources': 'Resources',
|
|
39
|
+
'physical_resources': 'PhysicalResources',
|
|
39
40
|
'labels': 'dict(str, str)',
|
|
40
41
|
'aws_advanced_configurations_json': 'object',
|
|
41
42
|
'gcp_advanced_configurations_json': 'object',
|
|
@@ -51,6 +52,7 @@ class WorkerNodeType(object):
|
|
|
51
52
|
'name': 'name',
|
|
52
53
|
'instance_type': 'instance_type',
|
|
53
54
|
'resources': 'resources',
|
|
55
|
+
'physical_resources': 'physical_resources',
|
|
54
56
|
'labels': 'labels',
|
|
55
57
|
'aws_advanced_configurations_json': 'aws_advanced_configurations_json',
|
|
56
58
|
'gcp_advanced_configurations_json': 'gcp_advanced_configurations_json',
|
|
@@ -62,7 +64,7 @@ class WorkerNodeType(object):
|
|
|
62
64
|
'fallback_to_ondemand': 'fallback_to_ondemand'
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
def __init__(self, name=None, instance_type=None, resources=None, labels=None, aws_advanced_configurations_json=None, gcp_advanced_configurations_json=None, advanced_configurations_json=None, flags=None, min_workers=None, max_workers=None, use_spot=False, fallback_to_ondemand=False, local_vars_configuration=None): # noqa: E501
|
|
67
|
+
def __init__(self, name=None, instance_type=None, resources=None, physical_resources=None, labels=None, aws_advanced_configurations_json=None, gcp_advanced_configurations_json=None, advanced_configurations_json=None, flags=None, min_workers=None, max_workers=None, use_spot=False, fallback_to_ondemand=False, local_vars_configuration=None): # noqa: E501
|
|
66
68
|
"""WorkerNodeType - a model defined in OpenAPI""" # noqa: E501
|
|
67
69
|
if local_vars_configuration is None:
|
|
68
70
|
local_vars_configuration = Configuration()
|
|
@@ -71,6 +73,7 @@ class WorkerNodeType(object):
|
|
|
71
73
|
self._name = None
|
|
72
74
|
self._instance_type = None
|
|
73
75
|
self._resources = None
|
|
76
|
+
self._physical_resources = None
|
|
74
77
|
self._labels = None
|
|
75
78
|
self._aws_advanced_configurations_json = None
|
|
76
79
|
self._gcp_advanced_configurations_json = None
|
|
@@ -86,6 +89,8 @@ class WorkerNodeType(object):
|
|
|
86
89
|
self.instance_type = instance_type
|
|
87
90
|
if resources is not None:
|
|
88
91
|
self.resources = resources
|
|
92
|
+
if physical_resources is not None:
|
|
93
|
+
self.physical_resources = physical_resources
|
|
89
94
|
if labels is not None:
|
|
90
95
|
self.labels = labels
|
|
91
96
|
if aws_advanced_configurations_json is not None:
|
|
@@ -178,6 +183,29 @@ class WorkerNodeType(object):
|
|
|
178
183
|
|
|
179
184
|
self._resources = resources
|
|
180
185
|
|
|
186
|
+
@property
|
|
187
|
+
def physical_resources(self):
|
|
188
|
+
"""Gets the physical_resources of this WorkerNodeType. # noqa: E501
|
|
189
|
+
|
|
190
|
+
Physical resources for compute node type which specifies the actual CPU, memory, and GPU resources that should be allocated for this node type. # noqa: E501
|
|
191
|
+
|
|
192
|
+
:return: The physical_resources of this WorkerNodeType. # noqa: E501
|
|
193
|
+
:rtype: PhysicalResources
|
|
194
|
+
"""
|
|
195
|
+
return self._physical_resources
|
|
196
|
+
|
|
197
|
+
@physical_resources.setter
|
|
198
|
+
def physical_resources(self, physical_resources):
|
|
199
|
+
"""Sets the physical_resources of this WorkerNodeType.
|
|
200
|
+
|
|
201
|
+
Physical resources for compute node type which specifies the actual CPU, memory, and GPU resources that should be allocated for this node type. # noqa: E501
|
|
202
|
+
|
|
203
|
+
:param physical_resources: The physical_resources of this WorkerNodeType. # noqa: E501
|
|
204
|
+
:type: PhysicalResources
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
self._physical_resources = physical_resources
|
|
208
|
+
|
|
181
209
|
@property
|
|
182
210
|
def labels(self):
|
|
183
211
|
"""Gets the labels of this WorkerNodeType. # noqa: E501
|
anyscale/service/__init__.py
CHANGED
|
@@ -22,14 +22,20 @@ from anyscale.service.commands import (
|
|
|
22
22
|
_ROLLBACK_EXAMPLE,
|
|
23
23
|
_STATUS_ARG_DOCSTRINGS,
|
|
24
24
|
_STATUS_EXAMPLE,
|
|
25
|
+
_TAGS_ADD_ARG_DOCSTRINGS,
|
|
26
|
+
_TAGS_ADD_EXAMPLE,
|
|
27
|
+
_TAGS_REMOVE_ARG_DOCSTRINGS,
|
|
28
|
+
_TAGS_REMOVE_EXAMPLE,
|
|
25
29
|
_TERMINATE_ARG_DOCSTRINGS,
|
|
26
30
|
_TERMINATE_EXAMPLE,
|
|
27
31
|
_WAIT_ARG_DOCSTRINGS,
|
|
28
32
|
_WAIT_EXAMPLE,
|
|
33
|
+
add_tags,
|
|
29
34
|
archive,
|
|
30
35
|
delete,
|
|
31
36
|
deploy,
|
|
32
37
|
list,
|
|
38
|
+
remove_tags,
|
|
33
39
|
rollback,
|
|
34
40
|
status,
|
|
35
41
|
terminate,
|
|
@@ -181,6 +187,7 @@ class ServiceSDK:
|
|
|
181
187
|
# Filters
|
|
182
188
|
name: Optional[str] = None,
|
|
183
189
|
state_filter: Optional[Union[List[ServiceState], List[str]]] = None,
|
|
190
|
+
tags_filter: Optional[Dict[str, List[str]]] = None,
|
|
184
191
|
creator_id: Optional[str] = None,
|
|
185
192
|
cloud: Optional[str] = None,
|
|
186
193
|
project: Optional[str] = None,
|
|
@@ -205,6 +212,7 @@ class ServiceSDK:
|
|
|
205
212
|
service_id=service_id,
|
|
206
213
|
name=name,
|
|
207
214
|
state_filter=state_filter,
|
|
215
|
+
tags_filter=tags_filter,
|
|
208
216
|
creator_id=creator_id,
|
|
209
217
|
cloud=cloud,
|
|
210
218
|
project=project,
|
|
@@ -270,3 +278,35 @@ class ServiceSDK:
|
|
|
270
278
|
mode=mode,
|
|
271
279
|
max_lines=max_lines,
|
|
272
280
|
)
|
|
281
|
+
|
|
282
|
+
@sdk_docs(doc_py_example=_TAGS_ADD_EXAMPLE, arg_docstrings=_TAGS_ADD_ARG_DOCSTRINGS)
|
|
283
|
+
def add_tags( # noqa: F811
|
|
284
|
+
self,
|
|
285
|
+
*,
|
|
286
|
+
id: Optional[str] = None, # noqa: A002
|
|
287
|
+
name: Optional[str] = None,
|
|
288
|
+
cloud: Optional[str] = None,
|
|
289
|
+
project: Optional[str] = None,
|
|
290
|
+
tags: Dict[str, str],
|
|
291
|
+
) -> None:
|
|
292
|
+
"""Upsert (add/update) tag key/value pairs for a service."""
|
|
293
|
+
return self._private_sdk.add_tags(
|
|
294
|
+
id=id, name=name, cloud=cloud, project=project, tags=tags
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
@sdk_docs(
|
|
298
|
+
doc_py_example=_TAGS_REMOVE_EXAMPLE, arg_docstrings=_TAGS_REMOVE_ARG_DOCSTRINGS
|
|
299
|
+
)
|
|
300
|
+
def remove_tags( # noqa: F811
|
|
301
|
+
self,
|
|
302
|
+
*,
|
|
303
|
+
id: Optional[str] = None, # noqa: A002
|
|
304
|
+
name: Optional[str] = None,
|
|
305
|
+
cloud: Optional[str] = None,
|
|
306
|
+
project: Optional[str] = None,
|
|
307
|
+
keys: List[str],
|
|
308
|
+
) -> None:
|
|
309
|
+
"""Remove tags by key from a service."""
|
|
310
|
+
return self._private_sdk.remove_tags(
|
|
311
|
+
id=id, name=name, cloud=cloud, project=project, keys=keys
|
|
312
|
+
)
|
|
@@ -20,6 +20,9 @@ from anyscale.client.openapi_client.models.decoratedlistserviceapimodel_list_res
|
|
|
20
20
|
from anyscale.client.openapi_client.models.list_response_metadata import (
|
|
21
21
|
ListResponseMetadata,
|
|
22
22
|
)
|
|
23
|
+
from anyscale.client.openapi_client.models.resource_tag_resource_type import (
|
|
24
|
+
ResourceTagResourceType,
|
|
25
|
+
)
|
|
23
26
|
from anyscale.compute_config.models import ComputeConfig
|
|
24
27
|
from anyscale.sdk.anyscale_client.models import (
|
|
25
28
|
AccessConfig,
|
|
@@ -296,6 +299,7 @@ class PrivateServiceSDK(WorkloadSDK):
|
|
|
296
299
|
),
|
|
297
300
|
ray_gcs_external_storage_config=existing_config.ray_gcs_external_storage_config,
|
|
298
301
|
tracing_config=existing_config.tracing_config,
|
|
302
|
+
tags=getattr(config, "tags", None),
|
|
299
303
|
)
|
|
300
304
|
|
|
301
305
|
def _build_apply_service_model_for_rollout( # noqa: PLR0912
|
|
@@ -431,6 +435,7 @@ class PrivateServiceSDK(WorkloadSDK):
|
|
|
431
435
|
ray_gcs_external_storage_config=ray_gcs_external_storage_config,
|
|
432
436
|
tracing_config=tracing_config,
|
|
433
437
|
traffic_percent=traffic_percent,
|
|
438
|
+
tags=getattr(config, "tags", None),
|
|
434
439
|
)
|
|
435
440
|
|
|
436
441
|
def _build_apply_service_model_for_multi_version(
|
|
@@ -454,13 +459,20 @@ class PrivateServiceSDK(WorkloadSDK):
|
|
|
454
459
|
if existing_service is not None:
|
|
455
460
|
existing_versions = {
|
|
456
461
|
v.version: v
|
|
457
|
-
for v in self.client.get_service_versions(
|
|
462
|
+
for v in self.client.get_service_versions(
|
|
463
|
+
existing_service.id, read_all_versions=True
|
|
464
|
+
)
|
|
458
465
|
}
|
|
459
466
|
|
|
460
467
|
service_versions: List[ApplyProductionServiceV2Model] = []
|
|
461
468
|
for _, (config, version) in version_service_map.items():
|
|
462
469
|
version_name = version["name"]
|
|
463
470
|
traffic_percent = version["traffic_percent"]
|
|
471
|
+
if traffic_percent == 0:
|
|
472
|
+
self.logger.warning(
|
|
473
|
+
f"Setting traffic for version {version_name} to 0 will terminate it."
|
|
474
|
+
)
|
|
475
|
+
|
|
464
476
|
# If config is None, it means the version is already deployed, and the user is likely to be only updating the traffic/capacity percent without serve config update.
|
|
465
477
|
if config is None:
|
|
466
478
|
if version_name not in existing_versions:
|
|
@@ -945,14 +957,18 @@ class PrivateServiceSDK(WorkloadSDK):
|
|
|
945
957
|
sampling_ratio=model.tracing_config.sampling_ratio,
|
|
946
958
|
)
|
|
947
959
|
|
|
960
|
+
version_name = model.version or self._get_user_facing_service_version_id(model)
|
|
961
|
+
|
|
948
962
|
return ServiceVersionStatus(
|
|
949
963
|
id=self._get_user_facing_service_version_id(model),
|
|
964
|
+
name=version_name,
|
|
950
965
|
created_at=model.created_at,
|
|
951
966
|
state=model.current_state,
|
|
952
967
|
# NOTE(edoakes): there is also a "current_weight" field but it does not match the UI.
|
|
953
968
|
weight=model.weight,
|
|
954
969
|
config=ServiceConfig(
|
|
955
970
|
name=service_name,
|
|
971
|
+
version_name=version_name,
|
|
956
972
|
applications=model.ray_serve_config["applications"],
|
|
957
973
|
image_uri=str(image_uri),
|
|
958
974
|
compute_config=compute_config,
|
|
@@ -996,35 +1012,63 @@ class PrivateServiceSDK(WorkloadSDK):
|
|
|
996
1012
|
# which means that the per-version `query_auth_token_enabled` field will lie if
|
|
997
1013
|
# it's changed.
|
|
998
1014
|
query_auth_token_enabled = model.auth_token is not None
|
|
1015
|
+
all_versions = None
|
|
1016
|
+
primary_version = None
|
|
1017
|
+
canary_version = None
|
|
1018
|
+
project_name = None
|
|
999
1019
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1020
|
+
if model.is_multi_version:
|
|
1021
|
+
# For multi-version services, format all versions
|
|
1022
|
+
version_tasks = [
|
|
1023
|
+
asyncio.create_task(
|
|
1024
|
+
self._service_version_model_to_status_async(
|
|
1025
|
+
version,
|
|
1026
|
+
service_name=model.name,
|
|
1027
|
+
project_id=model.project_id,
|
|
1028
|
+
query_auth_token_enabled=query_auth_token_enabled,
|
|
1029
|
+
)
|
|
1008
1030
|
)
|
|
1009
|
-
|
|
1031
|
+
for version in model.versions
|
|
1032
|
+
]
|
|
1033
|
+
all_versions = await asyncio.gather(*version_tasks)
|
|
1034
|
+
|
|
1035
|
+
if (
|
|
1036
|
+
all_versions
|
|
1037
|
+
and all_versions[0]
|
|
1038
|
+
and isinstance(all_versions[0].config, ServiceConfig)
|
|
1039
|
+
):
|
|
1040
|
+
project_name = all_versions[0].config.project
|
|
1010
1041
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1042
|
+
else:
|
|
1043
|
+
primary_version_task = None
|
|
1044
|
+
if model.primary_version is not None:
|
|
1045
|
+
primary_version_task = asyncio.create_task(
|
|
1046
|
+
self._service_version_model_to_status_async(
|
|
1047
|
+
model.primary_version,
|
|
1048
|
+
service_name=model.name,
|
|
1049
|
+
project_id=model.project_id,
|
|
1050
|
+
query_auth_token_enabled=query_auth_token_enabled,
|
|
1051
|
+
)
|
|
1052
|
+
)
|
|
1053
|
+
|
|
1054
|
+
canary_version_task = None
|
|
1055
|
+
if model.canary_version is not None:
|
|
1056
|
+
canary_version_task = asyncio.create_task(
|
|
1057
|
+
self._service_version_model_to_status_async(
|
|
1058
|
+
model.canary_version,
|
|
1059
|
+
service_name=model.name,
|
|
1060
|
+
project_id=model.project_id,
|
|
1061
|
+
query_auth_token_enabled=query_auth_token_enabled,
|
|
1062
|
+
)
|
|
1019
1063
|
)
|
|
1020
|
-
)
|
|
1021
1064
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1065
|
+
primary_version = (
|
|
1066
|
+
await primary_version_task if primary_version_task else None
|
|
1067
|
+
)
|
|
1068
|
+
canary_version = await canary_version_task if canary_version_task else None
|
|
1024
1069
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
project_name = primary_version.config.project
|
|
1070
|
+
if primary_version and isinstance(primary_version.config, ServiceConfig):
|
|
1071
|
+
project_name = primary_version.config.project
|
|
1028
1072
|
|
|
1029
1073
|
return ServiceStatus(
|
|
1030
1074
|
id=model.id,
|
|
@@ -1036,6 +1080,7 @@ class PrivateServiceSDK(WorkloadSDK):
|
|
|
1036
1080
|
primary_version=primary_version,
|
|
1037
1081
|
canary_version=canary_version,
|
|
1038
1082
|
project=project_name,
|
|
1083
|
+
versions=all_versions,
|
|
1039
1084
|
)
|
|
1040
1085
|
|
|
1041
1086
|
def status(
|
|
@@ -1056,6 +1101,7 @@ class PrivateServiceSDK(WorkloadSDK):
|
|
|
1056
1101
|
cloud: Optional[str] = None,
|
|
1057
1102
|
project: Optional[str] = None,
|
|
1058
1103
|
include_archived: bool = False,
|
|
1104
|
+
tags_filter: Optional[Dict[str, List[str]]] = None,
|
|
1059
1105
|
# Paging
|
|
1060
1106
|
max_items: Optional[int] = None, # Controls total items yielded by iterator
|
|
1061
1107
|
page_size: Optional[int] = None, # Controls items fetched per API call
|
|
@@ -1096,6 +1142,18 @@ class PrivateServiceSDK(WorkloadSDK):
|
|
|
1096
1142
|
|
|
1097
1143
|
normalised_states = _normalize_state_filter(state_filter)
|
|
1098
1144
|
|
|
1145
|
+
# Convert tags dict into backend tag_filter list[str]
|
|
1146
|
+
backend_tag_filter: Optional[List[str]] = None
|
|
1147
|
+
if tags_filter:
|
|
1148
|
+
flattened: List[str] = []
|
|
1149
|
+
for key, values in tags_filter.items():
|
|
1150
|
+
if not values:
|
|
1151
|
+
continue
|
|
1152
|
+
for value in values:
|
|
1153
|
+
if key and value:
|
|
1154
|
+
flattened.append(f"{key}:{value}")
|
|
1155
|
+
backend_tag_filter = flattened if len(flattened) > 0 else None
|
|
1156
|
+
|
|
1099
1157
|
def _fetch_page(
|
|
1100
1158
|
token: Optional[str],
|
|
1101
1159
|
) -> DecoratedlistserviceapimodelListResponse:
|
|
@@ -1106,6 +1164,7 @@ class PrivateServiceSDK(WorkloadSDK):
|
|
|
1106
1164
|
cloud=cloud,
|
|
1107
1165
|
project=project,
|
|
1108
1166
|
include_archived=include_archived,
|
|
1167
|
+
tag_filter=backend_tag_filter,
|
|
1109
1168
|
count=page_size,
|
|
1110
1169
|
paging_token=token,
|
|
1111
1170
|
sort_field=sort_field,
|
|
@@ -1199,6 +1258,44 @@ class PrivateServiceSDK(WorkloadSDK):
|
|
|
1199
1258
|
model.primary_version, head, max_lines
|
|
1200
1259
|
)
|
|
1201
1260
|
|
|
1261
|
+
def add_tags(
|
|
1262
|
+
self,
|
|
1263
|
+
*,
|
|
1264
|
+
id: Optional[str] = None, # noqa: A002
|
|
1265
|
+
name: Optional[str] = None,
|
|
1266
|
+
cloud: Optional[str] = None,
|
|
1267
|
+
project: Optional[str] = None,
|
|
1268
|
+
tags: Dict[str, str],
|
|
1269
|
+
) -> None:
|
|
1270
|
+
if id is None:
|
|
1271
|
+
model = self._resolve_to_service_model(
|
|
1272
|
+
name=name, cloud=cloud, project=project
|
|
1273
|
+
)
|
|
1274
|
+
resource_id = model.id
|
|
1275
|
+
assert id is not None
|
|
1276
|
+
self.client.upsert_resource_tags(
|
|
1277
|
+
ResourceTagResourceType.SERVICE, resource_id, tags
|
|
1278
|
+
)
|
|
1279
|
+
|
|
1280
|
+
def remove_tags(
|
|
1281
|
+
self,
|
|
1282
|
+
*,
|
|
1283
|
+
id: Optional[str] = None, # noqa: A002
|
|
1284
|
+
name: Optional[str] = None,
|
|
1285
|
+
cloud: Optional[str] = None,
|
|
1286
|
+
project: Optional[str] = None,
|
|
1287
|
+
keys: List[str],
|
|
1288
|
+
) -> None:
|
|
1289
|
+
if id is None:
|
|
1290
|
+
model = self._resolve_to_service_model(
|
|
1291
|
+
name=name, cloud=cloud, project=project
|
|
1292
|
+
)
|
|
1293
|
+
resource_id = model.id
|
|
1294
|
+
assert id is not None
|
|
1295
|
+
self.client.delete_resource_tags(
|
|
1296
|
+
ResourceTagResourceType.SERVICE, resource_id, keys
|
|
1297
|
+
)
|
|
1298
|
+
|
|
1202
1299
|
|
|
1203
1300
|
def _normalize_state_filter(
|
|
1204
1301
|
states: Optional[Union[List[ServiceState], List[str]]],
|
anyscale/service/commands.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List, Optional, Union
|
|
1
|
+
from typing import Dict, List, Optional, Union
|
|
2
2
|
|
|
3
3
|
from anyscale._private.models.model_base import ResultIterator
|
|
4
4
|
from anyscale._private.sdk import sdk_command
|
|
@@ -323,6 +323,77 @@ _CONTROLLER_LOGS_ARG_DOCSTRINGS = {
|
|
|
323
323
|
"max_lines": "The number of log lines to be fetched. If not provided, 1000 lines will be fetched.",
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
# SDK docs for Service tag operations
|
|
327
|
+
_TAGS_ADD_EXAMPLE = """
|
|
328
|
+
import anyscale
|
|
329
|
+
|
|
330
|
+
anyscale.service.add_tags(id="svc_123", tags={"team": "mlops", "env": "prod"})
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
_TAGS_ADD_ARG_DOCSTRINGS = {
|
|
334
|
+
"id": "ID of the service. Provide either id or name.",
|
|
335
|
+
"name": "Name of the service. Provide either id or name.",
|
|
336
|
+
"cloud": "Cloud name (used when resolving by name).",
|
|
337
|
+
"project": "Project name (used when resolving by name).",
|
|
338
|
+
"tags": "Key/value tags to upsert as a map {key: value}.",
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
_TAGS_REMOVE_EXAMPLE = """
|
|
342
|
+
import anyscale
|
|
343
|
+
|
|
344
|
+
anyscale.service.remove_tags(id="svc_123", keys=["team", "env"])
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
_TAGS_REMOVE_ARG_DOCSTRINGS = {
|
|
348
|
+
"id": "ID of the service. Provide either id or name.",
|
|
349
|
+
"name": "Name of the service. Provide either id or name.",
|
|
350
|
+
"cloud": "Cloud name (used when resolving by name).",
|
|
351
|
+
"project": "Project name (used when resolving by name).",
|
|
352
|
+
"keys": "List of tag keys to remove.",
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
@sdk_command(
|
|
357
|
+
_SERVICE_SDK_SINGLETON_KEY,
|
|
358
|
+
PrivateServiceSDK,
|
|
359
|
+
doc_py_example=_TAGS_ADD_EXAMPLE,
|
|
360
|
+
arg_docstrings=_TAGS_ADD_ARG_DOCSTRINGS,
|
|
361
|
+
)
|
|
362
|
+
def add_tags(
|
|
363
|
+
*,
|
|
364
|
+
id: Optional[str] = None, # noqa: A002
|
|
365
|
+
name: Optional[str] = None,
|
|
366
|
+
cloud: Optional[str] = None,
|
|
367
|
+
project: Optional[str] = None,
|
|
368
|
+
tags: Dict[str, str],
|
|
369
|
+
_private_sdk: Optional[PrivateServiceSDK] = None,
|
|
370
|
+
) -> None:
|
|
371
|
+
"""Upsert (add/update) tag key/value pairs for a service."""
|
|
372
|
+
return _private_sdk.add_tags( # type: ignore
|
|
373
|
+
id=id, name=name, cloud=cloud, project=project, tags=tags
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
@sdk_command(
|
|
378
|
+
_SERVICE_SDK_SINGLETON_KEY,
|
|
379
|
+
PrivateServiceSDK,
|
|
380
|
+
doc_py_example=_TAGS_REMOVE_EXAMPLE,
|
|
381
|
+
arg_docstrings=_TAGS_REMOVE_ARG_DOCSTRINGS,
|
|
382
|
+
)
|
|
383
|
+
def remove_tags(
|
|
384
|
+
*,
|
|
385
|
+
id: Optional[str] = None, # noqa: A002
|
|
386
|
+
name: Optional[str] = None,
|
|
387
|
+
cloud: Optional[str] = None,
|
|
388
|
+
project: Optional[str] = None,
|
|
389
|
+
keys: List[str],
|
|
390
|
+
_private_sdk: Optional[PrivateServiceSDK] = None,
|
|
391
|
+
) -> None:
|
|
392
|
+
"""Remove tags by key from a service."""
|
|
393
|
+
return _private_sdk.remove_tags( # type: ignore
|
|
394
|
+
id=id, name=name, cloud=cloud, project=project, keys=keys
|
|
395
|
+
)
|
|
396
|
+
|
|
326
397
|
|
|
327
398
|
# This is a private command that is not exposed to the user.
|
|
328
399
|
@sdk_command(
|
|
@@ -375,6 +446,7 @@ _LIST_ARG_DOCSTRINGS = {
|
|
|
375
446
|
"cloud": "Name of the Anyscale Cloud to search in.",
|
|
376
447
|
"project": "Name of the Anyscale Project to search in.",
|
|
377
448
|
"include_archived": "Include archived services (default: False).",
|
|
449
|
+
"tags_filter": "Filter services by tags as a dict: {key: [values...]}. Values with the same key are ORed; keys are ANDed.",
|
|
378
450
|
# Paging
|
|
379
451
|
"max_items": "Maximum **total** number of items to yield (default: iterate all).",
|
|
380
452
|
"page_size": "Number of items to fetch per API request (default: API default).",
|
|
@@ -402,6 +474,7 @@ def list( # noqa: A001
|
|
|
402
474
|
cloud: Optional[str] = None,
|
|
403
475
|
project: Optional[str] = None,
|
|
404
476
|
include_archived: bool = False,
|
|
477
|
+
tags_filter: Optional[Dict[str, List[str]]] = None,
|
|
405
478
|
# Paging
|
|
406
479
|
max_items: Optional[int] = None,
|
|
407
480
|
page_size: Optional[int] = None,
|
|
@@ -420,6 +493,7 @@ def list( # noqa: A001
|
|
|
420
493
|
cloud=cloud,
|
|
421
494
|
project=project,
|
|
422
495
|
include_archived=include_archived,
|
|
496
|
+
tags_filter=tags_filter,
|
|
423
497
|
max_items=max_items,
|
|
424
498
|
page_size=page_size,
|
|
425
499
|
sort_field=sort_field,
|
anyscale/service/models.py
CHANGED
|
@@ -213,6 +213,9 @@ tracing_config: # (Optional) Configuration options for tracing.
|
|
|
213
213
|
enabled: true
|
|
214
214
|
exporter_import_path: my_module:custom_tracing_exporter
|
|
215
215
|
sampling_ratio: 1.0
|
|
216
|
+
tags:
|
|
217
|
+
team: serving
|
|
218
|
+
cost-center: eng
|
|
216
219
|
"""
|
|
217
220
|
|
|
218
221
|
# Override the `name` field from `WorkloadConfig` so we can document it separately for jobs and services.
|
|
@@ -239,8 +242,6 @@ tracing_config: # (Optional) Configuration options for tracing.
|
|
|
239
242
|
# Allow None if optional; enforce non-empty string if required
|
|
240
243
|
if version_name is None:
|
|
241
244
|
return None
|
|
242
|
-
if not isinstance(version_name, str) or not version_name.strip():
|
|
243
|
-
raise ValueError("version_name must be a non-empty string")
|
|
244
245
|
return version_name
|
|
245
246
|
|
|
246
247
|
def _validate_applications(self, applications: List[Dict[str, Any]]):
|
|
@@ -402,6 +403,19 @@ tracing_config: # (Optional) Configuration options for tracing.
|
|
|
402
403
|
|
|
403
404
|
return tracing_config
|
|
404
405
|
|
|
406
|
+
tags: Optional[Dict[str, str]] = field(
|
|
407
|
+
default=None, metadata={"docstring": "Tags to associate with the service."},
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
def _validate_tags(self, tags: Optional[Dict[str, str]]):
|
|
411
|
+
if tags is None:
|
|
412
|
+
return
|
|
413
|
+
if not isinstance(tags, dict):
|
|
414
|
+
raise TypeError("'tags' must be a Dict[str, str].")
|
|
415
|
+
for k, v in tags.items():
|
|
416
|
+
if not isinstance(k, str) or not isinstance(v, str):
|
|
417
|
+
raise TypeError("'tags' must be a Dict[str, str].")
|
|
418
|
+
|
|
405
419
|
|
|
406
420
|
class ServiceState(ModelEnum):
|
|
407
421
|
"""Current state of a service."""
|
|
@@ -499,6 +513,7 @@ id: service2_uz6l8yhy2as5wrer3shzj6kh67
|
|
|
499
513
|
state: RUNNING
|
|
500
514
|
primary_version:
|
|
501
515
|
id: 601bd56c4b
|
|
516
|
+
name: v1
|
|
502
517
|
state: RUNNING
|
|
503
518
|
weight: 100
|
|
504
519
|
created_at: 2025-04-18 17:21:28.323174+00:00
|
|
@@ -514,6 +529,15 @@ primary_version:
|
|
|
514
529
|
if not isinstance(id, str):
|
|
515
530
|
raise TypeError("'id' must be a string.")
|
|
516
531
|
|
|
532
|
+
name: str = field(
|
|
533
|
+
metadata={"docstring": "Human-readable name of the service version."}
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
def _validate_name(self, name: str):
|
|
537
|
+
if name is None:
|
|
538
|
+
return None
|
|
539
|
+
return name
|
|
540
|
+
|
|
517
541
|
state: Union[ServiceVersionState, str] = field(
|
|
518
542
|
metadata={"docstring": "Current state of the service version."}
|
|
519
543
|
)
|
|
@@ -700,6 +724,26 @@ primary_version:
|
|
|
700
724
|
if project is not None and not isinstance(project, str):
|
|
701
725
|
raise TypeError("project must be a string.")
|
|
702
726
|
|
|
727
|
+
versions: Optional[List[ServiceVersionStatus]] = field(
|
|
728
|
+
default=None,
|
|
729
|
+
repr=False,
|
|
730
|
+
metadata={
|
|
731
|
+
"docstring": "All active versions of the service. For multi-version services, this contains all versions with their traffic weights."
|
|
732
|
+
},
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
def _validate_versions(
|
|
736
|
+
self, versions: Optional[List[ServiceVersionStatus]]
|
|
737
|
+
) -> Optional[List[ServiceVersionStatus]]:
|
|
738
|
+
if versions is None:
|
|
739
|
+
return None
|
|
740
|
+
if not isinstance(versions, list):
|
|
741
|
+
raise TypeError("'versions' must be a list.")
|
|
742
|
+
for v in versions:
|
|
743
|
+
if not isinstance(v, ServiceVersionStatus):
|
|
744
|
+
raise TypeError("'versions' must be a list of ServiceVersionStatus.")
|
|
745
|
+
return versions
|
|
746
|
+
|
|
703
747
|
|
|
704
748
|
class ServiceSortField(ModelEnum):
|
|
705
749
|
"""Fields available for sorting services."""
|
anyscale/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.26.
|
|
1
|
+
__version__ = "0.26.71"
|
anyscale/workspace/models.py
CHANGED
|
@@ -53,6 +53,9 @@ idle_termination_minutes: 220
|
|
|
53
53
|
env_vars:
|
|
54
54
|
key: value
|
|
55
55
|
requirements: /tmp/requirements.txt
|
|
56
|
+
tags:
|
|
57
|
+
team: mlops
|
|
58
|
+
env: prod
|
|
56
59
|
"""
|
|
57
60
|
|
|
58
61
|
name: Optional[str] = field(
|
|
@@ -88,6 +91,19 @@ requirements: /tmp/requirements.txt
|
|
|
88
91
|
if containerfile is not None and not isinstance(containerfile, str):
|
|
89
92
|
raise TypeError("'containerfile' must be a string.")
|
|
90
93
|
|
|
94
|
+
tags: Optional[Dict[str, str]] = field(
|
|
95
|
+
default=None, metadata={"docstring": "Tags to associate with the workspace."},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def _validate_tags(self, tags: Optional[Dict[str, str]]):
|
|
99
|
+
if tags is None:
|
|
100
|
+
return
|
|
101
|
+
if not isinstance(tags, dict):
|
|
102
|
+
raise TypeError("'tags' must be a Dict[str, str].")
|
|
103
|
+
for k, v in tags.items():
|
|
104
|
+
if not isinstance(k, str) or not isinstance(v, str):
|
|
105
|
+
raise TypeError("'tags' must be a Dict[str, str].")
|
|
106
|
+
|
|
91
107
|
compute_config: Union[ComputeConfigType, Dict, str, None] = field(
|
|
92
108
|
default=None,
|
|
93
109
|
metadata={
|
|
@@ -215,6 +231,9 @@ idle_termination_minutes: 220
|
|
|
215
231
|
env_vars:
|
|
216
232
|
key: value
|
|
217
233
|
requirements: /tmp/requirements.txt
|
|
234
|
+
tags:
|
|
235
|
+
team: mlops
|
|
236
|
+
env: prod
|
|
218
237
|
"""
|
|
219
238
|
|
|
220
239
|
project: Optional[str] = field(
|