From 01c51e5b91fe8f9f96d643630715b3d61d87b5d3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 16:31:04 +0000 Subject: [PATCH 01/10] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index efb5f168..b4c9ba78 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 645 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-2f258036cad399055647446f0aae41c40f29bf6b486de68ed565653c10adb569.yml -openapi_spec_hash: 319b97727a2c05125aa27228db52053c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-a1732e101d79775c87c8d81eb591cb63394c4502dd148628473d3cec10ef518c.yml +openapi_spec_hash: a411bda4ae7c7da72e9fd574442b5d08 config_hash: 332323cce99008ceec46e04aeb672e0a From cdfe3544b44f065624b5d7054873b291a54da836 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:45:13 +0000 Subject: [PATCH 02/10] chore(internal): make `test_proxy_environment_variables` more resilient to env --- tests/test_client.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index c04ade3d..9a14904e 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -965,8 +965,14 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") - # Delete in case our environment has this set + # Delete in case our environment has any proxy env vars set monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultHttpxClient() @@ -1889,8 +1895,14 @@ async def test_get_platform(self) -> None: async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") - # Delete in case our environment has this set + # Delete in case our environment has any proxy env vars set monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultAsyncHttpxClient() From 14512b9907e7201e6b3f6a934dd84d0f277fc55c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:54:23 +0000 Subject: [PATCH 03/10] feat(cloud)!: update gpu baremetal endpoints to latest versions --- .stats.yml | 4 +- src/gcore/resources/cloud/api.md | 4 +- .../cloud/gpu_baremetal/clusters/clusters.py | 265 ++---------------- .../cloud/gpu_baremetal/clusters/servers.py | 128 ++++++++- .../types/cloud/gpu_baremetal/__init__.py | 4 +- .../gpu_baremetal/cluster_rebuild_params.py | 28 -- .../cluster_update_servers_settings_params.py | 41 +++ .../gpu_baremetal/clusters/test_servers.py | 116 ++++++++ .../cloud/gpu_baremetal/test_clusters.py | 196 +++++++++---- 9 files changed, 451 insertions(+), 335 deletions(-) delete mode 100644 src/gcore/types/cloud/gpu_baremetal/cluster_rebuild_params.py create mode 100644 src/gcore/types/cloud/gpu_baremetal/cluster_update_servers_settings_params.py diff --git a/.stats.yml b/.stats.yml index b4c9ba78..4be6d4f8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 645 +configured_endpoints: 647 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-a1732e101d79775c87c8d81eb591cb63394c4502dd148628473d3cec10ef518c.yml openapi_spec_hash: a411bda4ae7c7da72e9fd574442b5d08 -config_hash: 332323cce99008ceec46e04aeb672e0a +config_hash: ee9fe3677a3591bb2fcc219ef6448eb2 diff --git a/src/gcore/resources/cloud/api.md b/src/gcore/resources/cloud/api.md index 79c3958d..0028620e 100644 --- a/src/gcore/resources/cloud/api.md +++ b/src/gcore/resources/cloud/api.md @@ -792,8 +792,9 @@ Methods: - client.cloud.gpu_baremetal.clusters.get(cluster_id, \*, project_id, region_id) -> GPUBaremetalCluster - client.cloud.gpu_baremetal.clusters.powercycle_all_servers(cluster_id, \*, project_id, region_id) -> GPUBaremetalClusterServerV1List - client.cloud.gpu_baremetal.clusters.reboot_all_servers(cluster_id, \*, project_id, region_id) -> GPUBaremetalClusterServerV1List -- client.cloud.gpu_baremetal.clusters.rebuild(cluster_id, \*, project_id, region_id, \*\*params) -> TaskIDList +- client.cloud.gpu_baremetal.clusters.rebuild(cluster_id, \*, project_id, region_id) -> TaskIDList - client.cloud.gpu_baremetal.clusters.resize(cluster_id, \*, project_id, region_id, \*\*params) -> TaskIDList +- client.cloud.gpu_baremetal.clusters.update_servers_settings(cluster_id, \*, project_id, region_id, \*\*params) -> GPUBaremetalCluster #### Interfaces @@ -822,6 +823,7 @@ Methods: - client.cloud.gpu_baremetal.clusters.servers.get_console(instance_id, \*, project_id, region_id) -> Console - client.cloud.gpu_baremetal.clusters.servers.powercycle(instance_id, \*, project_id, region_id) -> GPUBaremetalClusterServerV1 - client.cloud.gpu_baremetal.clusters.servers.reboot(instance_id, \*, project_id, region_id) -> GPUBaremetalClusterServerV1 +- client.cloud.gpu_baremetal.clusters.servers.rebuild(server_id, \*, project_id, region_id, cluster_id) -> TaskIDList #### Flavors diff --git a/src/gcore/resources/cloud/gpu_baremetal/clusters/clusters.py b/src/gcore/resources/cloud/gpu_baremetal/clusters/clusters.py index 700297ba..b52e7f83 100644 --- a/src/gcore/resources/cloud/gpu_baremetal/clusters/clusters.py +++ b/src/gcore/resources/cloud/gpu_baremetal/clusters/clusters.py @@ -58,7 +58,7 @@ cluster_create_params, cluster_delete_params, cluster_resize_params, - cluster_rebuild_params, + cluster_update_servers_settings_params, ) from .....types.cloud.tag_update_map_param import TagUpdateMapParam from .....types.cloud.gpu_baremetal.gpu_baremetal_cluster import GPUBaremetalCluster @@ -522,9 +522,6 @@ def rebuild( *, project_id: int | None = None, region_id: int | None = None, - nodes: SequenceNotStr[str], - image_id: Optional[str] | Omit = omit, - user_data: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -532,19 +529,21 @@ def rebuild( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TaskIDList: - """Rebuild one or more nodes in a GPU cluster. + """Perform a rebuild operation on a bare metal GPU cluster. - All cluster nodes must be specified - to update the cluster image. + During the rebuild + process, the servers in cluster receive a new image, SSH key, and user data. + Important: Before triggering a rebuild, the cluster must have updated server + settings to apply. These cluster settings must be patched using the following + endpoint: PATCH + '/v3/gpu/baremetal/{`project_id`}/{`region_id`}/clusters/{`cluster_id`}/servers_settings' Args: - nodes: List of nodes uuids to be rebuild + project_id: Project ID - image_id: AI GPU image ID + region_id: Region ID - user_data: - String in base64 format.Examples of the `user_data`: - https://cloudinit.readthedocs.io/en/latest/topics/examples.html + cluster_id: Cluster unique identifier extra_headers: Send extra headers @@ -561,15 +560,7 @@ def rebuild( if not cluster_id: raise ValueError(f"Expected a non-empty value for `cluster_id` but received {cluster_id!r}") return self._post( - f"/cloud/v1/ai/clusters/gpu/{project_id}/{region_id}/{cluster_id}/rebuild", - body=maybe_transform( - { - "nodes": nodes, - "image_id": image_id, - "user_data": user_data, - }, - cluster_rebuild_params.ClusterRebuildParams, - ), + f"/cloud/v3/gpu/baremetal/{project_id}/{region_id}/clusters/{cluster_id}/rebuild", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -1239,9 +1230,6 @@ async def rebuild( *, project_id: int | None = None, region_id: int | None = None, - nodes: SequenceNotStr[str], - image_id: Optional[str] | Omit = omit, - user_data: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -1249,19 +1237,21 @@ async def rebuild( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TaskIDList: - """Rebuild one or more nodes in a GPU cluster. + """Perform a rebuild operation on a bare metal GPU cluster. - All cluster nodes must be specified - to update the cluster image. + During the rebuild + process, the servers in cluster receive a new image, SSH key, and user data. + Important: Before triggering a rebuild, the cluster must have updated server + settings to apply. These cluster settings must be patched using the following + endpoint: PATCH + '/v3/gpu/baremetal/{`project_id`}/{`region_id`}/clusters/{`cluster_id`}/servers_settings' Args: - nodes: List of nodes uuids to be rebuild + project_id: Project ID - image_id: AI GPU image ID + region_id: Region ID - user_data: - String in base64 format.Examples of the `user_data`: - https://cloudinit.readthedocs.io/en/latest/topics/examples.html + cluster_id: Cluster unique identifier extra_headers: Send extra headers @@ -1278,15 +1268,7 @@ async def rebuild( if not cluster_id: raise ValueError(f"Expected a non-empty value for `cluster_id` but received {cluster_id!r}") return await self._post( - f"/cloud/v1/ai/clusters/gpu/{project_id}/{region_id}/{cluster_id}/rebuild", - body=await async_maybe_transform( - { - "nodes": nodes, - "image_id": image_id, - "user_data": user_data, - }, - cluster_rebuild_params.ClusterRebuildParams, - ), + f"/cloud/v3/gpu/baremetal/{project_id}/{region_id}/clusters/{cluster_id}/rebuild", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -1340,167 +1322,6 @@ async def resize( cast_to=TaskIDList, ) - async def create_and_poll( - self, - *, - project_id: int | None = None, - region_id: int | None = None, - flavor: str, - image_id: str, - name: str, - servers_count: int, - servers_settings: cluster_create_params.ServersSettings, - tags: Dict[str, str] | Omit = omit, - polling_interval_seconds: int | Omit = omit, - polling_timeout_seconds: int | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> GPUBaremetalCluster: - """ - Create a bare metal GPU cluster and wait for it to be ready. - """ - response = await self.create( - project_id=project_id, - region_id=region_id, - flavor=flavor, - image_id=image_id, - name=name, - servers_count=servers_count, - servers_settings=servers_settings, - tags=tags, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - if not response.tasks or len(response.tasks) != 1: - raise ValueError(f"Expected exactly one task to be created") - task = await self._client.cloud.tasks.poll( - response.tasks[0], - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - polling_interval_seconds=polling_interval_seconds, - polling_timeout_seconds=polling_timeout_seconds, - ) - if not task.created_resources or not task.created_resources.clusters: - raise ValueError("No cluster was created") - cluster_id = task.created_resources.clusters[0] - return await self.get( # pyright: ignore[reportDeprecated] - cluster_id=cluster_id, - project_id=project_id, - region_id=region_id, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - - async def rebuild_and_poll( - self, - cluster_id: str, - *, - project_id: int | None = None, - region_id: int | None = None, - nodes: List[str], - image_id: Optional[str] | Omit = omit, - user_data: Optional[str] | Omit = omit, - polling_interval_seconds: int | Omit = omit, - polling_timeout_seconds: int | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> GPUBaremetalCluster: - """ - Rebuild a bare metal GPU cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. - """ - response = await self.rebuild( - cluster_id=cluster_id, - project_id=project_id, - region_id=region_id, - nodes=nodes, - image_id=image_id, - user_data=user_data, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - if not response.tasks: - raise ValueError("Expected at least one task to be created") - await self._client.cloud.tasks.poll( - response.tasks[0], - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - polling_interval_seconds=polling_interval_seconds, - polling_timeout_seconds=polling_timeout_seconds, - ) - return await self.get( # pyright: ignore[reportDeprecated] - cluster_id=cluster_id, - project_id=project_id, - region_id=region_id, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - - async def resize_and_poll( - self, - cluster_id: str, - *, - project_id: int | None = None, - region_id: int | None = None, - instances_count: int, - polling_interval_seconds: int | Omit = omit, - polling_timeout_seconds: int | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> GPUBaremetalCluster: - """ - Resize a bare metal GPU cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. - """ - response = await self.resize( - cluster_id=cluster_id, - project_id=project_id, - region_id=region_id, - instances_count=instances_count, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - if not response.tasks: - raise ValueError("Expected at least one task to be created") - await self._client.cloud.tasks.poll( - response.tasks[0], - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - polling_interval_seconds=polling_interval_seconds, - polling_timeout_seconds=polling_timeout_seconds, - ) - return await self.get( # pyright: ignore[reportDeprecated] - cluster_id=cluster_id, - project_id=project_id, - region_id=region_id, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) class ClustersResourceWithRawResponse: @@ -1534,15 +1355,7 @@ def __init__(self, clusters: ClustersResource) -> None: self.resize = to_raw_response_wrapper( clusters.resize, ) - self.create_and_poll = to_raw_response_wrapper( - clusters.create_and_poll, - ) - self.rebuild_and_poll = to_raw_response_wrapper( - clusters.rebuild_and_poll, - ) - self.resize_and_poll = to_raw_response_wrapper( - clusters.resize_and_poll, - ) + @cached_property def interfaces(self) -> InterfacesResourceWithRawResponse: @@ -1592,15 +1405,7 @@ def __init__(self, clusters: AsyncClustersResource) -> None: self.resize = async_to_raw_response_wrapper( clusters.resize, ) - self.create_and_poll = async_to_raw_response_wrapper( - clusters.create_and_poll, - ) - self.rebuild_and_poll = async_to_raw_response_wrapper( - clusters.rebuild_and_poll, - ) - self.resize_and_poll = async_to_raw_response_wrapper( - clusters.resize_and_poll, - ) + @cached_property def interfaces(self) -> AsyncInterfacesResourceWithRawResponse: @@ -1650,15 +1455,7 @@ def __init__(self, clusters: ClustersResource) -> None: self.resize = to_streamed_response_wrapper( clusters.resize, ) - self.create_and_poll = to_streamed_response_wrapper( - clusters.create_and_poll, - ) - self.rebuild_and_poll = to_streamed_response_wrapper( - clusters.rebuild_and_poll, - ) - self.resize_and_poll = to_streamed_response_wrapper( - clusters.resize_and_poll, - ) + @cached_property def interfaces(self) -> InterfacesResourceWithStreamingResponse: @@ -1708,15 +1505,7 @@ def __init__(self, clusters: AsyncClustersResource) -> None: self.resize = async_to_streamed_response_wrapper( clusters.resize, ) - self.create_and_poll = async_to_streamed_response_wrapper( - clusters.create_and_poll, - ) - self.rebuild_and_poll = async_to_streamed_response_wrapper( - clusters.rebuild_and_poll, - ) - self.resize_and_poll = async_to_streamed_response_wrapper( - clusters.resize_and_poll, - ) + @cached_property def interfaces(self) -> AsyncInterfacesResourceWithStreamingResponse: diff --git a/src/gcore/resources/cloud/gpu_baremetal/clusters/servers.py b/src/gcore/resources/cloud/gpu_baremetal/clusters/servers.py index c4d6ad91..76384f49 100644 --- a/src/gcore/resources/cloud/gpu_baremetal/clusters/servers.py +++ b/src/gcore/resources/cloud/gpu_baremetal/clusters/servers.py @@ -376,6 +376,62 @@ def reboot( cast_to=GPUBaremetalClusterServerV1, ) + def rebuild( + self, + server_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + cluster_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TaskIDList: + """Perform a rebuild operation on a bare metal GPU cluster server. + + During the + rebuild process, the server receive a new image, SSH key, and user data. + Important: Before triggering a rebuild, the cluster must have updated server + settings to apply. These cluster settings must be patched using the following + endpoint: PATCH + '/v3/gpu/baremetal/{`project_id`}/{`region_id`}/clusters/{`cluster_id`}/servers_settings' + + Args: + project_id: Project ID + + region_id: Region ID + + cluster_id: Cluster unique identifier + + server_id: Server unique identifier + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if project_id is None: + project_id = self._client._get_cloud_project_id_path_param() + if region_id is None: + region_id = self._client._get_cloud_region_id_path_param() + if not cluster_id: + raise ValueError(f"Expected a non-empty value for `cluster_id` but received {cluster_id!r}") + if not server_id: + raise ValueError(f"Expected a non-empty value for `server_id` but received {server_id!r}") + return self._post( + f"/cloud/v3/gpu/baremetal/{project_id}/{region_id}/clusters/{cluster_id}/servers/{server_id}/rebuild", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TaskIDList, + ) + class AsyncServersResource(AsyncAPIResource): @cached_property @@ -726,6 +782,62 @@ async def reboot( cast_to=GPUBaremetalClusterServerV1, ) + async def rebuild( + self, + server_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + cluster_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TaskIDList: + """Perform a rebuild operation on a bare metal GPU cluster server. + + During the + rebuild process, the server receive a new image, SSH key, and user data. + Important: Before triggering a rebuild, the cluster must have updated server + settings to apply. These cluster settings must be patched using the following + endpoint: PATCH + '/v3/gpu/baremetal/{`project_id`}/{`region_id`}/clusters/{`cluster_id`}/servers_settings' + + Args: + project_id: Project ID + + region_id: Region ID + + cluster_id: Cluster unique identifier + + server_id: Server unique identifier + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if project_id is None: + project_id = self._client._get_cloud_project_id_path_param() + if region_id is None: + region_id = self._client._get_cloud_region_id_path_param() + if not cluster_id: + raise ValueError(f"Expected a non-empty value for `cluster_id` but received {cluster_id!r}") + if not server_id: + raise ValueError(f"Expected a non-empty value for `server_id` but received {server_id!r}") + return await self._post( + f"/cloud/v3/gpu/baremetal/{project_id}/{region_id}/clusters/{cluster_id}/servers/{server_id}/rebuild", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TaskIDList, + ) + class ServersResourceWithRawResponse: def __init__(self, servers: ServersResource) -> None: @@ -746,8 +858,8 @@ def __init__(self, servers: ServersResource) -> None: self.reboot = to_raw_response_wrapper( servers.reboot, ) - self.delete_and_poll = to_raw_response_wrapper( - servers.delete_and_poll, + self.rebuild = to_raw_response_wrapper( + servers.rebuild, ) @@ -770,9 +882,7 @@ def __init__(self, servers: AsyncServersResource) -> None: self.reboot = async_to_raw_response_wrapper( servers.reboot, ) - self.delete_and_poll = async_to_raw_response_wrapper( - servers.delete_and_poll, - ) + class ServersResourceWithStreamingResponse: @@ -794,9 +904,7 @@ def __init__(self, servers: ServersResource) -> None: self.reboot = to_streamed_response_wrapper( servers.reboot, ) - self.delete_and_poll = to_streamed_response_wrapper( - servers.delete_and_poll, - ) + class AsyncServersResourceWithStreamingResponse: @@ -818,6 +926,4 @@ def __init__(self, servers: AsyncServersResource) -> None: self.reboot = async_to_streamed_response_wrapper( servers.reboot, ) - self.delete_and_poll = async_to_streamed_response_wrapper( - servers.delete_and_poll, - ) + diff --git a/src/gcore/types/cloud/gpu_baremetal/__init__.py b/src/gcore/types/cloud/gpu_baremetal/__init__.py index baff756f..ccd8f847 100644 --- a/src/gcore/types/cloud/gpu_baremetal/__init__.py +++ b/src/gcore/types/cloud/gpu_baremetal/__init__.py @@ -8,4 +8,6 @@ from .cluster_delete_params import ClusterDeleteParams as ClusterDeleteParams from .cluster_resize_params import ClusterResizeParams as ClusterResizeParams from .gpu_baremetal_cluster import GPUBaremetalCluster as GPUBaremetalCluster -from .cluster_rebuild_params import ClusterRebuildParams as ClusterRebuildParams +from .cluster_update_servers_settings_params import ( + ClusterUpdateServersSettingsParams as ClusterUpdateServersSettingsParams, +) diff --git a/src/gcore/types/cloud/gpu_baremetal/cluster_rebuild_params.py b/src/gcore/types/cloud/gpu_baremetal/cluster_rebuild_params.py deleted file mode 100644 index f9fe6f9d..00000000 --- a/src/gcore/types/cloud/gpu_baremetal/cluster_rebuild_params.py +++ /dev/null @@ -1,28 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Optional -from typing_extensions import Required, TypedDict - -from ...._types import SequenceNotStr - -__all__ = ["ClusterRebuildParams"] - - -class ClusterRebuildParams(TypedDict, total=False): - project_id: int - - region_id: int - - nodes: Required[SequenceNotStr[str]] - """List of nodes uuids to be rebuild""" - - image_id: Optional[str] - """AI GPU image ID""" - - user_data: Optional[str] - """ - String in base64 format.Examples of the `user_data`: - https://cloudinit.readthedocs.io/en/latest/topics/examples.html - """ diff --git a/src/gcore/types/cloud/gpu_baremetal/cluster_update_servers_settings_params.py b/src/gcore/types/cloud/gpu_baremetal/cluster_update_servers_settings_params.py new file mode 100644 index 00000000..60977f28 --- /dev/null +++ b/src/gcore/types/cloud/gpu_baremetal/cluster_update_servers_settings_params.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ClusterUpdateServersSettingsParams", "ServersSettings", "ServersSettingsCredentials"] + + +class ClusterUpdateServersSettingsParams(TypedDict, total=False): + project_id: int + """Project ID""" + + region_id: int + """Region ID""" + + image_id: str + """System image ID""" + + servers_settings: ServersSettings + """Configuration settings for the servers in the cluster""" + + +class ServersSettingsCredentials(TypedDict, total=False): + """Optional server access credentials""" + + ssh_key_name: str + """ + Specifies the name of the SSH keypair, created via the + [/v1/`ssh_keys` endpoint](/docs/api-reference/cloud/ssh-keys/add-or-generate-ssh-key). + """ + + +class ServersSettings(TypedDict, total=False): + """Configuration settings for the servers in the cluster""" + + credentials: ServersSettingsCredentials + """Optional server access credentials""" + + user_data: str + """Optional custom user data (Base64-encoded)""" diff --git a/tests/api_resources/cloud/gpu_baremetal/clusters/test_servers.py b/tests/api_resources/cloud/gpu_baremetal/clusters/test_servers.py index d468d7c4..d51f5379 100644 --- a/tests/api_resources/cloud/gpu_baremetal/clusters/test_servers.py +++ b/tests/api_resources/cloud/gpu_baremetal/clusters/test_servers.py @@ -294,6 +294,64 @@ def test_path_params_reboot(self, client: Gcore) -> None: region_id=0, ) + @parametrize + def test_method_rebuild(self, client: Gcore) -> None: + server = client.cloud.gpu_baremetal.clusters.servers.rebuild( + server_id="f1c1eeb6-1834-48c9-a7b0-daafce64872b", + project_id=1, + region_id=7, + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + ) + assert_matches_type(TaskIDList, server, path=["response"]) + + @parametrize + def test_raw_response_rebuild(self, client: Gcore) -> None: + response = client.cloud.gpu_baremetal.clusters.servers.with_raw_response.rebuild( + server_id="f1c1eeb6-1834-48c9-a7b0-daafce64872b", + project_id=1, + region_id=7, + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + server = response.parse() + assert_matches_type(TaskIDList, server, path=["response"]) + + @parametrize + def test_streaming_response_rebuild(self, client: Gcore) -> None: + with client.cloud.gpu_baremetal.clusters.servers.with_streaming_response.rebuild( + server_id="f1c1eeb6-1834-48c9-a7b0-daafce64872b", + project_id=1, + region_id=7, + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + server = response.parse() + assert_matches_type(TaskIDList, server, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_rebuild(self, client: Gcore) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `cluster_id` but received ''"): + client.cloud.gpu_baremetal.clusters.servers.with_raw_response.rebuild( + server_id="f1c1eeb6-1834-48c9-a7b0-daafce64872b", + project_id=1, + region_id=7, + cluster_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `server_id` but received ''"): + client.cloud.gpu_baremetal.clusters.servers.with_raw_response.rebuild( + server_id="", + project_id=1, + region_id=7, + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + ) + class TestAsyncServers: parametrize = pytest.mark.parametrize( @@ -570,3 +628,61 @@ async def test_path_params_reboot(self, async_client: AsyncGcore) -> None: project_id=0, region_id=0, ) + + @parametrize + async def test_method_rebuild(self, async_client: AsyncGcore) -> None: + server = await async_client.cloud.gpu_baremetal.clusters.servers.rebuild( + server_id="f1c1eeb6-1834-48c9-a7b0-daafce64872b", + project_id=1, + region_id=7, + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + ) + assert_matches_type(TaskIDList, server, path=["response"]) + + @parametrize + async def test_raw_response_rebuild(self, async_client: AsyncGcore) -> None: + response = await async_client.cloud.gpu_baremetal.clusters.servers.with_raw_response.rebuild( + server_id="f1c1eeb6-1834-48c9-a7b0-daafce64872b", + project_id=1, + region_id=7, + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + server = await response.parse() + assert_matches_type(TaskIDList, server, path=["response"]) + + @parametrize + async def test_streaming_response_rebuild(self, async_client: AsyncGcore) -> None: + async with async_client.cloud.gpu_baremetal.clusters.servers.with_streaming_response.rebuild( + server_id="f1c1eeb6-1834-48c9-a7b0-daafce64872b", + project_id=1, + region_id=7, + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + server = await response.parse() + assert_matches_type(TaskIDList, server, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_rebuild(self, async_client: AsyncGcore) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `cluster_id` but received ''"): + await async_client.cloud.gpu_baremetal.clusters.servers.with_raw_response.rebuild( + server_id="f1c1eeb6-1834-48c9-a7b0-daafce64872b", + project_id=1, + region_id=7, + cluster_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `server_id` but received ''"): + await async_client.cloud.gpu_baremetal.clusters.servers.with_raw_response.rebuild( + server_id="", + project_id=1, + region_id=7, + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + ) diff --git a/tests/api_resources/cloud/gpu_baremetal/test_clusters.py b/tests/api_resources/cloud/gpu_baremetal/test_clusters.py index a46257df..a1522ef3 100644 --- a/tests/api_resources/cloud/gpu_baremetal/test_clusters.py +++ b/tests/api_resources/cloud/gpu_baremetal/test_clusters.py @@ -405,32 +405,18 @@ def test_path_params_reboot_all_servers(self, client: Gcore) -> None: @parametrize def test_method_rebuild(self, client: Gcore) -> None: cluster = client.cloud.gpu_baremetal.clusters.rebuild( - cluster_id="cluster_id", - project_id=0, - region_id=0, - nodes=["string"], - ) - assert_matches_type(TaskIDList, cluster, path=["response"]) - - @parametrize - def test_method_rebuild_with_all_params(self, client: Gcore) -> None: - cluster = client.cloud.gpu_baremetal.clusters.rebuild( - cluster_id="cluster_id", - project_id=0, - region_id=0, - nodes=["string"], - image_id="f01fd9a0-9548-48ba-82dc-a8c8b2d6f2f1", - user_data="user_data", + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + project_id=1, + region_id=7, ) assert_matches_type(TaskIDList, cluster, path=["response"]) @parametrize def test_raw_response_rebuild(self, client: Gcore) -> None: response = client.cloud.gpu_baremetal.clusters.with_raw_response.rebuild( - cluster_id="cluster_id", - project_id=0, - region_id=0, - nodes=["string"], + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + project_id=1, + region_id=7, ) assert response.is_closed is True @@ -441,10 +427,9 @@ def test_raw_response_rebuild(self, client: Gcore) -> None: @parametrize def test_streaming_response_rebuild(self, client: Gcore) -> None: with client.cloud.gpu_baremetal.clusters.with_streaming_response.rebuild( - cluster_id="cluster_id", - project_id=0, - region_id=0, - nodes=["string"], + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + project_id=1, + region_id=7, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -459,9 +444,8 @@ def test_path_params_rebuild(self, client: Gcore) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `cluster_id` but received ''"): client.cloud.gpu_baremetal.clusters.with_raw_response.rebuild( cluster_id="", - project_id=0, - region_id=0, - nodes=["string"], + project_id=1, + region_id=7, ) @parametrize @@ -514,6 +498,66 @@ def test_path_params_resize(self, client: Gcore) -> None: instances_count=1, ) + @parametrize + def test_method_update_servers_settings(self, client: Gcore) -> None: + cluster = client.cloud.gpu_baremetal.clusters.update_servers_settings( + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + project_id=1, + region_id=7, + ) + assert_matches_type(GPUBaremetalCluster, cluster, path=["response"]) + + @parametrize + def test_method_update_servers_settings_with_all_params(self, client: Gcore) -> None: + cluster = client.cloud.gpu_baremetal.clusters.update_servers_settings( + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + project_id=1, + region_id=7, + image_id="3793c250-0b3b-4678-bab3-e11afbc29657", + servers_settings={ + "credentials": {"ssh_key_name": "my-ssh-key"}, + "user_data": "eyJ0ZXN0IjogImRhdGEifQ==", + }, + ) + assert_matches_type(GPUBaremetalCluster, cluster, path=["response"]) + + @parametrize + def test_raw_response_update_servers_settings(self, client: Gcore) -> None: + response = client.cloud.gpu_baremetal.clusters.with_raw_response.update_servers_settings( + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + project_id=1, + region_id=7, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + cluster = response.parse() + assert_matches_type(GPUBaremetalCluster, cluster, path=["response"]) + + @parametrize + def test_streaming_response_update_servers_settings(self, client: Gcore) -> None: + with client.cloud.gpu_baremetal.clusters.with_streaming_response.update_servers_settings( + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + project_id=1, + region_id=7, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + cluster = response.parse() + assert_matches_type(GPUBaremetalCluster, cluster, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update_servers_settings(self, client: Gcore) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `cluster_id` but received ''"): + client.cloud.gpu_baremetal.clusters.with_raw_response.update_servers_settings( + cluster_id="", + project_id=1, + region_id=7, + ) + class TestAsyncClusters: parametrize = pytest.mark.parametrize( @@ -903,32 +947,18 @@ async def test_path_params_reboot_all_servers(self, async_client: AsyncGcore) -> @parametrize async def test_method_rebuild(self, async_client: AsyncGcore) -> None: cluster = await async_client.cloud.gpu_baremetal.clusters.rebuild( - cluster_id="cluster_id", - project_id=0, - region_id=0, - nodes=["string"], - ) - assert_matches_type(TaskIDList, cluster, path=["response"]) - - @parametrize - async def test_method_rebuild_with_all_params(self, async_client: AsyncGcore) -> None: - cluster = await async_client.cloud.gpu_baremetal.clusters.rebuild( - cluster_id="cluster_id", - project_id=0, - region_id=0, - nodes=["string"], - image_id="f01fd9a0-9548-48ba-82dc-a8c8b2d6f2f1", - user_data="user_data", + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + project_id=1, + region_id=7, ) assert_matches_type(TaskIDList, cluster, path=["response"]) @parametrize async def test_raw_response_rebuild(self, async_client: AsyncGcore) -> None: response = await async_client.cloud.gpu_baremetal.clusters.with_raw_response.rebuild( - cluster_id="cluster_id", - project_id=0, - region_id=0, - nodes=["string"], + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + project_id=1, + region_id=7, ) assert response.is_closed is True @@ -939,10 +969,9 @@ async def test_raw_response_rebuild(self, async_client: AsyncGcore) -> None: @parametrize async def test_streaming_response_rebuild(self, async_client: AsyncGcore) -> None: async with async_client.cloud.gpu_baremetal.clusters.with_streaming_response.rebuild( - cluster_id="cluster_id", - project_id=0, - region_id=0, - nodes=["string"], + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + project_id=1, + region_id=7, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -957,9 +986,8 @@ async def test_path_params_rebuild(self, async_client: AsyncGcore) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `cluster_id` but received ''"): await async_client.cloud.gpu_baremetal.clusters.with_raw_response.rebuild( cluster_id="", - project_id=0, - region_id=0, - nodes=["string"], + project_id=1, + region_id=7, ) @parametrize @@ -1011,3 +1039,63 @@ async def test_path_params_resize(self, async_client: AsyncGcore) -> None: region_id=0, instances_count=1, ) + + @parametrize + async def test_method_update_servers_settings(self, async_client: AsyncGcore) -> None: + cluster = await async_client.cloud.gpu_baremetal.clusters.update_servers_settings( + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + project_id=1, + region_id=7, + ) + assert_matches_type(GPUBaremetalCluster, cluster, path=["response"]) + + @parametrize + async def test_method_update_servers_settings_with_all_params(self, async_client: AsyncGcore) -> None: + cluster = await async_client.cloud.gpu_baremetal.clusters.update_servers_settings( + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + project_id=1, + region_id=7, + image_id="3793c250-0b3b-4678-bab3-e11afbc29657", + servers_settings={ + "credentials": {"ssh_key_name": "my-ssh-key"}, + "user_data": "eyJ0ZXN0IjogImRhdGEifQ==", + }, + ) + assert_matches_type(GPUBaremetalCluster, cluster, path=["response"]) + + @parametrize + async def test_raw_response_update_servers_settings(self, async_client: AsyncGcore) -> None: + response = await async_client.cloud.gpu_baremetal.clusters.with_raw_response.update_servers_settings( + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + project_id=1, + region_id=7, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + cluster = await response.parse() + assert_matches_type(GPUBaremetalCluster, cluster, path=["response"]) + + @parametrize + async def test_streaming_response_update_servers_settings(self, async_client: AsyncGcore) -> None: + async with async_client.cloud.gpu_baremetal.clusters.with_streaming_response.update_servers_settings( + cluster_id="1aaaab48-10d0-46d9-80cc-85209284ceb4", + project_id=1, + region_id=7, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + cluster = await response.parse() + assert_matches_type(GPUBaremetalCluster, cluster, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update_servers_settings(self, async_client: AsyncGcore) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `cluster_id` but received ''"): + await async_client.cloud.gpu_baremetal.clusters.with_raw_response.update_servers_settings( + cluster_id="", + project_id=1, + region_id=7, + ) From 044afb025fd06341eb71ebd75b34861c93bc0452 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 08:23:18 +0000 Subject: [PATCH 04/10] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4be6d4f8..f12cc938 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 647 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-a1732e101d79775c87c8d81eb591cb63394c4502dd148628473d3cec10ef518c.yml -openapi_spec_hash: a411bda4ae7c7da72e9fd574442b5d08 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-54e83d6c4fc5836205a11053586d5807f995b50f8fccb30cad37b23ce686f57c.yml +openapi_spec_hash: 7f285992f05eaf44903826c94370c5c8 config_hash: ee9fe3677a3591bb2fcc219ef6448eb2 From 3321a2f287f4a94aa88c738f09c5677e2c670ca9 Mon Sep 17 00:00:00 2001 From: Pedro Oliveira <8281907+pedrodeoliveira@users.noreply.github.com> Date: Wed, 25 Feb 2026 12:00:56 +0000 Subject: [PATCH 05/10] fix(cloud): restore custom polling methods and missing wrappers for gpu baremetal The merge in 14512b99 dropped custom *_and_poll methods and removed rebuild/update_servers_settings wrappers from gpu baremetal resources. - Restore sync and async create_and_poll, rebuild_and_poll, resize_and_poll on clusters - Fix rebuild_and_poll to match v3 rebuild() signature (no nodes/image_id/user_data) - Add update_servers_settings to all Raw/Streaming wrapper classes - Add *_and_poll to all Raw/Streaming wrapper classes - Restore rebuild wrappers and add delete_and_poll wrappers on servers - Move custom polling methods to end of class definitions to reduce future merge conflicts --- .../cloud/gpu_baremetal/clusters/clusters.py | 347 +++++++++++++++++- .../cloud/gpu_baremetal/clusters/servers.py | 196 +++++----- 2 files changed, 438 insertions(+), 105 deletions(-) diff --git a/src/gcore/resources/cloud/gpu_baremetal/clusters/clusters.py b/src/gcore/resources/cloud/gpu_baremetal/clusters/clusters.py index b52e7f83..0ea1d06c 100644 --- a/src/gcore/resources/cloud/gpu_baremetal/clusters/clusters.py +++ b/src/gcore/resources/cloud/gpu_baremetal/clusters/clusters.py @@ -31,7 +31,7 @@ ServersResourceWithStreamingResponse, AsyncServersResourceWithStreamingResponse, ) -from ....._types import NOT_GIVEN, Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ....._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given from ....._utils import maybe_transform, async_maybe_transform from .interfaces import ( InterfacesResource, @@ -612,6 +612,69 @@ def resize( cast_to=TaskIDList, ) + def update_servers_settings( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + image_id: str | Omit = omit, + servers_settings: cluster_update_servers_settings_params.ServersSettings | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUBaremetalCluster: + """ + This operation only modifies cluster settings such as SSH key, image, and user + data. **It does NOT modify or rebuild any existing servers in the cluster.** + + To apply these configuration changes to running servers, use the + `/cloud/v3/gpu/baremetal/{project_id}/{region_id}/clusters/{cluster_id}/rebuild` + endpoint. + + Args: + project_id: Project ID + + region_id: Region ID + + cluster_id: Cluster unique identifier + + image_id: System image ID + + servers_settings: Configuration settings for the servers in the cluster + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if project_id is None: + project_id = self._client._get_cloud_project_id_path_param() + if region_id is None: + region_id = self._client._get_cloud_region_id_path_param() + if not cluster_id: + raise ValueError(f"Expected a non-empty value for `cluster_id` but received {cluster_id!r}") + return self._patch( + f"/cloud/v3/gpu/baremetal/{project_id}/{region_id}/clusters/{cluster_id}/servers_settings", + body=maybe_transform( + { + "image_id": image_id, + "servers_settings": servers_settings, + }, + cluster_update_servers_settings_params.ClusterUpdateServersSettingsParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=GPUBaremetalCluster, + ) + def create_and_poll( self, *, @@ -630,7 +693,7 @@ def create_and_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> GPUBaremetalCluster: """ Create a bare metal GPU cluster and wait for it to be ready. @@ -678,9 +741,6 @@ def rebuild_and_poll( *, project_id: int | None = None, region_id: int | None = None, - nodes: List[str], - image_id: Optional[str] | Omit = omit, - user_data: Optional[str] | Omit = omit, polling_interval_seconds: int | Omit = omit, polling_timeout_seconds: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -688,7 +748,7 @@ def rebuild_and_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> GPUBaremetalCluster: """ Rebuild a bare metal GPU cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. @@ -697,9 +757,6 @@ def rebuild_and_poll( cluster_id=cluster_id, project_id=project_id, region_id=region_id, - nodes=nodes, - image_id=image_id, - user_data=user_data, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, @@ -739,7 +796,7 @@ def resize_and_poll( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> GPUBaremetalCluster: """ Resize a bare metal GPU cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. @@ -1322,6 +1379,224 @@ async def resize( cast_to=TaskIDList, ) + async def update_servers_settings( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + image_id: str | Omit = omit, + servers_settings: cluster_update_servers_settings_params.ServersSettings | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUBaremetalCluster: + """ + This operation only modifies cluster settings such as SSH key, image, and user + data. **It does NOT modify or rebuild any existing servers in the cluster.** + + To apply these configuration changes to running servers, use the + `/cloud/v3/gpu/baremetal/{project_id}/{region_id}/clusters/{cluster_id}/rebuild` + endpoint. + + Args: + project_id: Project ID + + region_id: Region ID + + cluster_id: Cluster unique identifier + + image_id: System image ID + + servers_settings: Configuration settings for the servers in the cluster + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if project_id is None: + project_id = self._client._get_cloud_project_id_path_param() + if region_id is None: + region_id = self._client._get_cloud_region_id_path_param() + if not cluster_id: + raise ValueError(f"Expected a non-empty value for `cluster_id` but received {cluster_id!r}") + return await self._patch( + f"/cloud/v3/gpu/baremetal/{project_id}/{region_id}/clusters/{cluster_id}/servers_settings", + body=await async_maybe_transform( + { + "image_id": image_id, + "servers_settings": servers_settings, + }, + cluster_update_servers_settings_params.ClusterUpdateServersSettingsParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=GPUBaremetalCluster, + ) + + async def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + flavor: str, + image_id: str, + name: str, + servers_count: int, + servers_settings: cluster_create_params.ServersSettings, + tags: Dict[str, str] | Omit = omit, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUBaremetalCluster: + """ + Create a bare metal GPU cluster and wait for it to be ready. + """ + response = await self.create( + project_id=project_id, + region_id=region_id, + flavor=flavor, + image_id=image_id, + name=name, + servers_count=servers_count, + servers_settings=servers_settings, + tags=tags, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks or len(response.tasks) != 1: + raise ValueError(f"Expected exactly one task to be created") + task = await self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + if not task.created_resources or not task.created_resources.clusters: + raise ValueError("No cluster was created") + cluster_id = task.created_resources.clusters[0] + return await self.get( # pyright: ignore[reportDeprecated] + cluster_id=cluster_id, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + async def rebuild_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUBaremetalCluster: + """ + Rebuild a bare metal GPU cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + """ + response = await self.rebuild( + cluster_id=cluster_id, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + await self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + return await self.get( # pyright: ignore[reportDeprecated] + cluster_id=cluster_id, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + async def resize_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + instances_count: int, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUBaremetalCluster: + """ + Resize a bare metal GPU cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + """ + response = await self.resize( + cluster_id=cluster_id, + project_id=project_id, + region_id=region_id, + instances_count=instances_count, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + await self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + return await self.get( # pyright: ignore[reportDeprecated] + cluster_id=cluster_id, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) class ClustersResourceWithRawResponse: @@ -1355,7 +1630,18 @@ def __init__(self, clusters: ClustersResource) -> None: self.resize = to_raw_response_wrapper( clusters.resize, ) - + self.update_servers_settings = to_raw_response_wrapper( + clusters.update_servers_settings, + ) + self.create_and_poll = to_raw_response_wrapper( + clusters.create_and_poll, + ) + self.rebuild_and_poll = to_raw_response_wrapper( + clusters.rebuild_and_poll, + ) + self.resize_and_poll = to_raw_response_wrapper( + clusters.resize_and_poll, + ) @cached_property def interfaces(self) -> InterfacesResourceWithRawResponse: @@ -1405,7 +1691,18 @@ def __init__(self, clusters: AsyncClustersResource) -> None: self.resize = async_to_raw_response_wrapper( clusters.resize, ) - + self.update_servers_settings = async_to_raw_response_wrapper( + clusters.update_servers_settings, + ) + self.create_and_poll = async_to_raw_response_wrapper( + clusters.create_and_poll, + ) + self.rebuild_and_poll = async_to_raw_response_wrapper( + clusters.rebuild_and_poll, + ) + self.resize_and_poll = async_to_raw_response_wrapper( + clusters.resize_and_poll, + ) @cached_property def interfaces(self) -> AsyncInterfacesResourceWithRawResponse: @@ -1455,7 +1752,18 @@ def __init__(self, clusters: ClustersResource) -> None: self.resize = to_streamed_response_wrapper( clusters.resize, ) - + self.update_servers_settings = to_streamed_response_wrapper( + clusters.update_servers_settings, + ) + self.create_and_poll = to_streamed_response_wrapper( + clusters.create_and_poll, + ) + self.rebuild_and_poll = to_streamed_response_wrapper( + clusters.rebuild_and_poll, + ) + self.resize_and_poll = to_streamed_response_wrapper( + clusters.resize_and_poll, + ) @cached_property def interfaces(self) -> InterfacesResourceWithStreamingResponse: @@ -1505,7 +1813,18 @@ def __init__(self, clusters: AsyncClustersResource) -> None: self.resize = async_to_streamed_response_wrapper( clusters.resize, ) - + self.update_servers_settings = async_to_streamed_response_wrapper( + clusters.update_servers_settings, + ) + self.create_and_poll = async_to_streamed_response_wrapper( + clusters.create_and_poll, + ) + self.rebuild_and_poll = async_to_streamed_response_wrapper( + clusters.rebuild_and_poll, + ) + self.resize_and_poll = async_to_streamed_response_wrapper( + clusters.resize_and_poll, + ) @cached_property def interfaces(self) -> AsyncInterfacesResourceWithStreamingResponse: diff --git a/src/gcore/resources/cloud/gpu_baremetal/clusters/servers.py b/src/gcore/resources/cloud/gpu_baremetal/clusters/servers.py index 76384f49..23ecc80b 100644 --- a/src/gcore/resources/cloud/gpu_baremetal/clusters/servers.py +++ b/src/gcore/resources/cloud/gpu_baremetal/clusters/servers.py @@ -215,50 +215,6 @@ def delete( cast_to=TaskIDList, ) - - def delete_and_poll( - self, - instance_id: str, - *, - project_id: int | None = None, - region_id: int | None = None, - cluster_id: str, - delete_floatings: bool | Omit = omit, - polling_interval_seconds: int | Omit = omit, - polling_timeout_seconds: int | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> None: - """ - Delete a bare metal GPU server from cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. - """ - response = self.delete( - instance_id=instance_id, - project_id=project_id, - region_id=region_id, - cluster_id=cluster_id, - delete_floatings=delete_floatings, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - if not response.tasks or len(response.tasks) < 1: - raise ValueError("Expected at least one task to be created") - self._client.cloud.tasks.poll( - response.tasks[0], - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - polling_interval_seconds=polling_interval_seconds, - polling_timeout_seconds=polling_timeout_seconds, - ) - - def get_console( self, instance_id: str, @@ -432,6 +388,48 @@ def rebuild( cast_to=TaskIDList, ) + def delete_and_poll( + self, + instance_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + cluster_id: str, + delete_floatings: bool | Omit = omit, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete a bare metal GPU server from cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + """ + response = self.delete( + instance_id=instance_id, + project_id=project_id, + region_id=region_id, + cluster_id=cluster_id, + delete_floatings=delete_floatings, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks or len(response.tasks) < 1: + raise ValueError("Expected at least one task to be created") + self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + class AsyncServersResource(AsyncAPIResource): @cached_property @@ -621,50 +619,6 @@ async def delete( cast_to=TaskIDList, ) - - async def delete_and_poll( - self, - instance_id: str, - *, - project_id: int | None = None, - region_id: int | None = None, - cluster_id: str, - delete_floatings: bool | Omit = omit, - polling_interval_seconds: int | Omit = omit, - polling_timeout_seconds: int | Omit = omit, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> None: - """ - Delete a bare metal GPU server from cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. - """ - response = await self.delete( - instance_id=instance_id, - project_id=project_id, - region_id=region_id, - cluster_id=cluster_id, - delete_floatings=delete_floatings, - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ) - if not response.tasks or len(response.tasks) < 1: - raise ValueError("Expected at least one task to be created") - await self._client.cloud.tasks.poll( - response.tasks[0], - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - polling_interval_seconds=polling_interval_seconds, - polling_timeout_seconds=polling_timeout_seconds, - ) - - async def get_console( self, instance_id: str, @@ -838,6 +792,48 @@ async def rebuild( cast_to=TaskIDList, ) + async def delete_and_poll( + self, + instance_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + cluster_id: str, + delete_floatings: bool | Omit = omit, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete a bare metal GPU server from cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + """ + response = await self.delete( + instance_id=instance_id, + project_id=project_id, + region_id=region_id, + cluster_id=cluster_id, + delete_floatings=delete_floatings, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks or len(response.tasks) < 1: + raise ValueError("Expected at least one task to be created") + await self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + class ServersResourceWithRawResponse: def __init__(self, servers: ServersResource) -> None: @@ -861,6 +857,9 @@ def __init__(self, servers: ServersResource) -> None: self.rebuild = to_raw_response_wrapper( servers.rebuild, ) + self.delete_and_poll = to_raw_response_wrapper( + servers.delete_and_poll, + ) class AsyncServersResourceWithRawResponse: @@ -882,7 +881,12 @@ def __init__(self, servers: AsyncServersResource) -> None: self.reboot = async_to_raw_response_wrapper( servers.reboot, ) - + self.rebuild = async_to_raw_response_wrapper( + servers.rebuild, + ) + self.delete_and_poll = async_to_raw_response_wrapper( + servers.delete_and_poll, + ) class ServersResourceWithStreamingResponse: @@ -904,7 +908,12 @@ def __init__(self, servers: ServersResource) -> None: self.reboot = to_streamed_response_wrapper( servers.reboot, ) - + self.rebuild = to_streamed_response_wrapper( + servers.rebuild, + ) + self.delete_and_poll = to_streamed_response_wrapper( + servers.delete_and_poll, + ) class AsyncServersResourceWithStreamingResponse: @@ -926,4 +935,9 @@ def __init__(self, servers: AsyncServersResource) -> None: self.reboot = async_to_streamed_response_wrapper( servers.reboot, ) - + self.rebuild = async_to_streamed_response_wrapper( + servers.rebuild, + ) + self.delete_and_poll = async_to_streamed_response_wrapper( + servers.delete_and_poll, + ) From 0e36db93402657340c5c9f38263662ba0c905593 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 12:24:32 +0000 Subject: [PATCH 06/10] feat(api): aggregated API specs update --- .stats.yml | 4 +- src/gcore/resources/security/bgp_announces.py | 64 ++++++++++++------- .../security/bgp_announce_list_params.py | 11 +++- .../security/bgp_announce_toggle_params.py | 3 + .../security/test_bgp_announces.py | 14 ++-- 5 files changed, 65 insertions(+), 31 deletions(-) diff --git a/.stats.yml b/.stats.yml index f12cc938..eb11f878 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 647 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-54e83d6c4fc5836205a11053586d5807f995b50f8fccb30cad37b23ce686f57c.yml -openapi_spec_hash: 7f285992f05eaf44903826c94370c5c8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-e1435565c5da22e3a924f1f8474ba980ea06cc89fe02011b4f29a3f4df4ec48e.yml +openapi_spec_hash: 4e551b8de20de612de780784d15c3eb5 config_hash: ee9fe3677a3591bb2fcc219ef6448eb2 diff --git a/src/gcore/resources/security/bgp_announces.py b/src/gcore/resources/security/bgp_announces.py index 1908f05a..a8f7cd47 100644 --- a/src/gcore/resources/security/bgp_announces.py +++ b/src/gcore/resources/security/bgp_announces.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional +from typing import List, Optional from typing_extensions import Literal import httpx @@ -48,7 +48,10 @@ def list( self, *, announced: Optional[bool] | Omit = omit, - origin: Optional[Literal["STATIC", "DYNAMIC"]] | Omit = omit, + client_id: Optional[int] | Omit = omit, + limit: Optional[int] | Omit = omit, + offset: Optional[int] | Omit = omit, + origin: Optional[List[Literal["STATIC", "DYNAMIC", "IAAS", "PROTECTED_NETWORK", "EDGE_PROXY"]]] | Omit = omit, site: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -57,13 +60,13 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BgpAnnounceListResponse: - """Get BGP announces filtered by parameters. - - Shows announces in active profiles, - meaning that to get a non-empty response, the client must have at least one - active profile. + """ + List BGP announces with optional filtering by site, origin, announcement status, + and client. Args: + client_id: A positive integer ID + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -82,6 +85,9 @@ def list( query=maybe_transform( { "announced": announced, + "client_id": client_id, + "limit": limit, + "offset": offset, "origin": origin, "site": site, }, @@ -104,13 +110,16 @@ def toggle( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> object: - """Change BGP announces (it can be enabled or disabled, but not created or - updated). - - Can be applied to already existing announces in active profiles, - meaning that the client must have at least one active profile. + """ + Enable or disable BGP announces for a client. Args: + announce: IP network to announce + + enabled: Whether the announcement is enabled + + client_id: A positive integer ID + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -163,7 +172,10 @@ async def list( self, *, announced: Optional[bool] | Omit = omit, - origin: Optional[Literal["STATIC", "DYNAMIC"]] | Omit = omit, + client_id: Optional[int] | Omit = omit, + limit: Optional[int] | Omit = omit, + offset: Optional[int] | Omit = omit, + origin: Optional[List[Literal["STATIC", "DYNAMIC", "IAAS", "PROTECTED_NETWORK", "EDGE_PROXY"]]] | Omit = omit, site: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -172,13 +184,13 @@ async def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> BgpAnnounceListResponse: - """Get BGP announces filtered by parameters. - - Shows announces in active profiles, - meaning that to get a non-empty response, the client must have at least one - active profile. + """ + List BGP announces with optional filtering by site, origin, announcement status, + and client. Args: + client_id: A positive integer ID + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -197,6 +209,9 @@ async def list( query=await async_maybe_transform( { "announced": announced, + "client_id": client_id, + "limit": limit, + "offset": offset, "origin": origin, "site": site, }, @@ -219,13 +234,16 @@ async def toggle( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> object: - """Change BGP announces (it can be enabled or disabled, but not created or - updated). - - Can be applied to already existing announces in active profiles, - meaning that the client must have at least one active profile. + """ + Enable or disable BGP announces for a client. Args: + announce: IP network to announce + + enabled: Whether the announcement is enabled + + client_id: A positive integer ID + extra_headers: Send extra headers extra_query: Add additional query parameters to the request diff --git a/src/gcore/types/security/bgp_announce_list_params.py b/src/gcore/types/security/bgp_announce_list_params.py index 2bc6a994..64cf7ccf 100644 --- a/src/gcore/types/security/bgp_announce_list_params.py +++ b/src/gcore/types/security/bgp_announce_list_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional +from typing import List, Optional from typing_extensions import Literal, TypedDict __all__ = ["BgpAnnounceListParams"] @@ -11,6 +11,13 @@ class BgpAnnounceListParams(TypedDict, total=False): announced: Optional[bool] - origin: Optional[Literal["STATIC", "DYNAMIC"]] + client_id: Optional[int] + """A positive integer ID""" + + limit: Optional[int] + + offset: Optional[int] + + origin: Optional[List[Literal["STATIC", "DYNAMIC", "IAAS", "PROTECTED_NETWORK", "EDGE_PROXY"]]] site: Optional[str] diff --git a/src/gcore/types/security/bgp_announce_toggle_params.py b/src/gcore/types/security/bgp_announce_toggle_params.py index 4c5dd1b6..0dcd2a00 100644 --- a/src/gcore/types/security/bgp_announce_toggle_params.py +++ b/src/gcore/types/security/bgp_announce_toggle_params.py @@ -10,7 +10,10 @@ class BgpAnnounceToggleParams(TypedDict, total=False): announce: Required[str] + """IP network to announce""" enabled: Required[bool] + """Whether the announcement is enabled""" client_id: Optional[int] + """A positive integer ID""" diff --git a/tests/api_resources/security/test_bgp_announces.py b/tests/api_resources/security/test_bgp_announces.py index 8907ce2f..bac96db4 100644 --- a/tests/api_resources/security/test_bgp_announces.py +++ b/tests/api_resources/security/test_bgp_announces.py @@ -26,7 +26,10 @@ def test_method_list(self, client: Gcore) -> None: def test_method_list_with_all_params(self, client: Gcore) -> None: bgp_announce = client.security.bgp_announces.list( announced=True, - origin="STATIC", + client_id=1, + limit=1, + offset=0, + origin=["STATIC", "DYNAMIC"], site="x", ) assert_matches_type(BgpAnnounceListResponse, bgp_announce, path=["response"]) @@ -64,7 +67,7 @@ def test_method_toggle_with_all_params(self, client: Gcore) -> None: bgp_announce = client.security.bgp_announces.toggle( announce="192.9.9.1/32", enabled=True, - client_id=0, + client_id=1, ) assert_matches_type(object, bgp_announce, path=["response"]) @@ -109,7 +112,10 @@ async def test_method_list(self, async_client: AsyncGcore) -> None: async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> None: bgp_announce = await async_client.security.bgp_announces.list( announced=True, - origin="STATIC", + client_id=1, + limit=1, + offset=0, + origin=["STATIC", "DYNAMIC"], site="x", ) assert_matches_type(BgpAnnounceListResponse, bgp_announce, path=["response"]) @@ -147,7 +153,7 @@ async def test_method_toggle_with_all_params(self, async_client: AsyncGcore) -> bgp_announce = await async_client.security.bgp_announces.toggle( announce="192.9.9.1/32", enabled=True, - client_id=0, + client_id=1, ) assert_matches_type(object, bgp_announce, path=["response"]) From b484f9c9aa4d3e54b42b49fbb06805d290ec2264 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:29:18 +0000 Subject: [PATCH 07/10] feat(api): aggregated API specs update --- .stats.yml | 4 ++-- src/gcore/resources/cloud/volumes.py | 18 +++++++++--------- src/gcore/types/cloud/volume_create_params.py | 10 ++++------ tests/api_resources/cloud/test_volumes.py | 12 ++++++------ 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/.stats.yml b/.stats.yml index eb11f878..4584e149 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 647 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-e1435565c5da22e3a924f1f8474ba980ea06cc89fe02011b4f29a3f4df4ec48e.yml -openapi_spec_hash: 4e551b8de20de612de780784d15c3eb5 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-6be5ef30147395fa70c249990edbb9f6c42789b9df04701483026bf6318cd31c.yml +openapi_spec_hash: 9fc23b4b32709495a448fce244b5de62 config_hash: ee9fe3677a3591bb2fcc219ef6448eb2 diff --git a/src/gcore/resources/cloud/volumes.py b/src/gcore/resources/cloud/volumes.py index 28bd9ea0..f07ab83d 100644 --- a/src/gcore/resources/cloud/volumes.py +++ b/src/gcore/resources/cloud/volumes.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Iterable, Optional +from typing import Dict, Iterable, Optional from typing_extensions import Literal, overload import httpx @@ -69,7 +69,7 @@ def create( attachment_tag: str | Omit = omit, instance_id_to_attach_to: str | Omit = omit, lifecycle_policy_ids: Iterable[int] | Omit = omit, - tags: TagUpdateMapParam | Omit = omit, + tags: Dict[str, str] | Omit = omit, type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -138,7 +138,7 @@ def create( instance_id_to_attach_to: str | Omit = omit, lifecycle_policy_ids: Iterable[int] | Omit = omit, size: int | Omit = omit, - tags: TagUpdateMapParam | Omit = omit, + tags: Dict[str, str] | Omit = omit, type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -207,7 +207,7 @@ def create( attachment_tag: str | Omit = omit, instance_id_to_attach_to: str | Omit = omit, lifecycle_policy_ids: Iterable[int] | Omit = omit, - tags: TagUpdateMapParam | Omit = omit, + tags: Dict[str, str] | Omit = omit, type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -276,7 +276,7 @@ def create( attachment_tag: str | Omit = omit, instance_id_to_attach_to: str | Omit = omit, lifecycle_policy_ids: Iterable[int] | Omit = omit, - tags: TagUpdateMapParam | Omit = omit, + tags: Dict[str, str] | Omit = omit, type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] | Omit = omit, snapshot_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -1313,7 +1313,7 @@ async def create( attachment_tag: str | Omit = omit, instance_id_to_attach_to: str | Omit = omit, lifecycle_policy_ids: Iterable[int] | Omit = omit, - tags: TagUpdateMapParam | Omit = omit, + tags: Dict[str, str] | Omit = omit, type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -1382,7 +1382,7 @@ async def create( instance_id_to_attach_to: str | Omit = omit, lifecycle_policy_ids: Iterable[int] | Omit = omit, size: int | Omit = omit, - tags: TagUpdateMapParam | Omit = omit, + tags: Dict[str, str] | Omit = omit, type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -1451,7 +1451,7 @@ async def create( attachment_tag: str | Omit = omit, instance_id_to_attach_to: str | Omit = omit, lifecycle_policy_ids: Iterable[int] | Omit = omit, - tags: TagUpdateMapParam | Omit = omit, + tags: Dict[str, str] | Omit = omit, type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -1520,7 +1520,7 @@ async def create( attachment_tag: str | Omit = omit, instance_id_to_attach_to: str | Omit = omit, lifecycle_policy_ids: Iterable[int] | Omit = omit, - tags: TagUpdateMapParam | Omit = omit, + tags: Dict[str, str] | Omit = omit, type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] | Omit = omit, snapshot_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. diff --git a/src/gcore/types/cloud/volume_create_params.py b/src/gcore/types/cloud/volume_create_params.py index 2e2dd5a7..43b240c3 100644 --- a/src/gcore/types/cloud/volume_create_params.py +++ b/src/gcore/types/cloud/volume_create_params.py @@ -2,11 +2,9 @@ from __future__ import annotations -from typing import Union, Iterable +from typing import Dict, Union, Iterable from typing_extensions import Literal, Required, TypeAlias, TypedDict -from .tag_update_map_param import TagUpdateMapParam - __all__ = [ "VolumeCreateParams", "CreateVolumeFromImageSerializer", @@ -49,7 +47,7 @@ class CreateVolumeFromImageSerializer(TypedDict, total=False): volume """ - tags: TagUpdateMapParam + tags: Dict[str, str] """Key-value tags to associate with the resource. A tag is a key-value pair that can be associated with a resource, enabling @@ -104,7 +102,7 @@ class CreateVolumeFromSnapshotSerializer(TypedDict, total=False): If specified, value must be equal to respective snapshot size """ - tags: TagUpdateMapParam + tags: Dict[str, str] """Key-value tags to associate with the resource. A tag is a key-value pair that can be associated with a resource, enabling @@ -153,7 +151,7 @@ class CreateNewVolumeSerializer(TypedDict, total=False): volume """ - tags: TagUpdateMapParam + tags: Dict[str, str] """Key-value tags to associate with the resource. A tag is a key-value pair that can be associated with a resource, enabling diff --git a/tests/api_resources/cloud/test_volumes.py b/tests/api_resources/cloud/test_volumes.py index ffe1d3bc..1b58422a 100644 --- a/tests/api_resources/cloud/test_volumes.py +++ b/tests/api_resources/cloud/test_volumes.py @@ -45,7 +45,7 @@ def test_method_create_with_all_params_overload_1(self, client: Gcore) -> None: attachment_tag="device-tag", instance_id_to_attach_to="88f3e0bd-ca86-4cf7-be8b-dd2988e23c2d", lifecycle_policy_ids=[1, 2], - tags={"foo": "string"}, + tags={"my-tag": "my-tag-value"}, type_name="standard", ) assert_matches_type(TaskIDList, volume, path=["response"]) @@ -107,7 +107,7 @@ def test_method_create_with_all_params_overload_2(self, client: Gcore) -> None: instance_id_to_attach_to="88f3e0bd-ca86-4cf7-be8b-dd2988e23c2d", lifecycle_policy_ids=[1, 2], size=10, - tags={"foo": "string"}, + tags={"my-tag": "my-tag-value"}, type_name="standard", ) assert_matches_type(TaskIDList, volume, path=["response"]) @@ -166,7 +166,7 @@ def test_method_create_with_all_params_overload_3(self, client: Gcore) -> None: attachment_tag="device-tag", instance_id_to_attach_to="88f3e0bd-ca86-4cf7-be8b-dd2988e23c2d", lifecycle_policy_ids=[1, 2], - tags={"foo": "string"}, + tags={"my-tag": "my-tag-value"}, type_name="standard", ) assert_matches_type(TaskIDList, volume, path=["response"]) @@ -701,7 +701,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn attachment_tag="device-tag", instance_id_to_attach_to="88f3e0bd-ca86-4cf7-be8b-dd2988e23c2d", lifecycle_policy_ids=[1, 2], - tags={"foo": "string"}, + tags={"my-tag": "my-tag-value"}, type_name="standard", ) assert_matches_type(TaskIDList, volume, path=["response"]) @@ -763,7 +763,7 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn instance_id_to_attach_to="88f3e0bd-ca86-4cf7-be8b-dd2988e23c2d", lifecycle_policy_ids=[1, 2], size=10, - tags={"foo": "string"}, + tags={"my-tag": "my-tag-value"}, type_name="standard", ) assert_matches_type(TaskIDList, volume, path=["response"]) @@ -822,7 +822,7 @@ async def test_method_create_with_all_params_overload_3(self, async_client: Asyn attachment_tag="device-tag", instance_id_to_attach_to="88f3e0bd-ca86-4cf7-be8b-dd2988e23c2d", lifecycle_policy_ids=[1, 2], - tags={"foo": "string"}, + tags={"my-tag": "my-tag-value"}, type_name="standard", ) assert_matches_type(TaskIDList, volume, path=["response"]) From d776fc99bc06e5d0c72f2246d35ed4ab0933853b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 18:14:31 +0000 Subject: [PATCH 08/10] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4584e149..eed43293 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 647 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-6be5ef30147395fa70c249990edbb9f6c42789b9df04701483026bf6318cd31c.yml -openapi_spec_hash: 9fc23b4b32709495a448fce244b5de62 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-3cc106e393b7a5de098a164d2a52ff79530d085d29bdf62a3c5026ad2273f748.yml +openapi_spec_hash: ad410d0835dc26f01ea6aabfd3587f7f config_hash: ee9fe3677a3591bb2fcc219ef6448eb2 From d934f8f808c574f34082fad6131521b1bb65a1da Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 13:32:35 +0000 Subject: [PATCH 09/10] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index eed43293..67ea29d3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 647 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-3cc106e393b7a5de098a164d2a52ff79530d085d29bdf62a3c5026ad2273f748.yml -openapi_spec_hash: ad410d0835dc26f01ea6aabfd3587f7f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-0d3d2c1c10b7328afa9ca4d987f7ee955e7352886472a40e0bff276c155a981a.yml +openapi_spec_hash: 698f5847aae8e77acf7f0d5e556a3baa config_hash: ee9fe3677a3591bb2fcc219ef6448eb2 From 0d03998840b3ee27e85956e134277499c7e591ad Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 13:32:58 +0000 Subject: [PATCH 10/10] release: 0.36.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 24 ++++++++++++++++++++++++ pyproject.toml | 2 +- src/gcore/_version.py | 2 +- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ce5e5c7c..157f0355 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.35.0" + ".": "0.36.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e2a6eb33..8720e4f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## 0.36.0 (2026-02-26) + +Full Changelog: [v0.35.0...v0.36.0](https://github.com/G-Core/gcore-python/compare/v0.35.0...v0.36.0) + +### ⚠ BREAKING CHANGES + +* **cloud:** update gpu baremetal endpoints to latest versions + +### Features + +* **api:** aggregated API specs update ([b484f9c](https://github.com/G-Core/gcore-python/commit/b484f9c9aa4d3e54b42b49fbb06805d290ec2264)) +* **api:** aggregated API specs update ([0e36db9](https://github.com/G-Core/gcore-python/commit/0e36db93402657340c5c9f38263662ba0c905593)) +* **cloud:** update gpu baremetal endpoints to latest versions ([14512b9](https://github.com/G-Core/gcore-python/commit/14512b9907e7201e6b3f6a934dd84d0f277fc55c)) + + +### Bug Fixes + +* **cloud:** restore custom polling methods and missing wrappers for gpu baremetal ([3321a2f](https://github.com/G-Core/gcore-python/commit/3321a2f287f4a94aa88c738f09c5677e2c670ca9)) + + +### Chores + +* **internal:** make `test_proxy_environment_variables` more resilient to env ([cdfe354](https://github.com/G-Core/gcore-python/commit/cdfe3544b44f065624b5d7054873b291a54da836)) + ## 0.35.0 (2026-02-24) Full Changelog: [v0.34.0...v0.35.0](https://github.com/G-Core/gcore-python/compare/v0.34.0...v0.35.0) diff --git a/pyproject.toml b/pyproject.toml index 8d80f22d..045fe512 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "gcore" -version = "0.35.0" +version = "0.36.0" description = "The official Python library for the gcore API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/gcore/_version.py b/src/gcore/_version.py index 7bcbde4b..72b7e16a 100644 --- a/src/gcore/_version.py +++ b/src/gcore/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "gcore" -__version__ = "0.35.0" # x-release-please-version +__version__ = "0.36.0" # x-release-please-version